diff --git a/.github/tests/Unit/GamingToolkit.Tests.ps1 b/.github/tests/Unit/GamingToolkit.Tests.ps1 index 61f9623..ba52258 100644 --- a/.github/tests/Unit/GamingToolkit.Tests.ps1 +++ b/.github/tests/Unit/GamingToolkit.Tests.ps1 @@ -40,8 +40,9 @@ Describe 'GamingToolkit — Firma' { } It '-CountdownSeconds deve avere valore di default 30' { - $default = (Get-Command GamingToolkit).Parameters['CountdownSeconds'].DefaultValue - $default | Should -Be 30 + # Verifica leggendo il sorgente, poiché .DefaultValue non funziona con definizioni semplici + $source = Get-Content -Path $script:ToolPath -Raw + $source | Should -Match '\[int\]\$CountdownSeconds\s*=\s*30' } } diff --git a/.github/workflows/CI_UpdateWinToolkit_Dev.yml b/.github/workflows/CI_UpdateWinToolkit_Dev.yml index 8f9e04e..9311c88 100644 --- a/.github/workflows/CI_UpdateWinToolkit_Dev.yml +++ b/.github/workflows/CI_UpdateWinToolkit_Dev.yml @@ -455,6 +455,7 @@ jobs: $config.Run.Path = @( '.github/tests/WinToolkit.Tests.ps1', '.github/tests/Unit' + '.github/tests/Integration' ) $config.Run.PassThru = $true $config.Output.Verbosity = 'Detailed' @@ -474,7 +475,7 @@ jobs: } catch { Write-Host "⚠️ Pester execution encountered an error: $($_.Exception.Message)" -ForegroundColor Yellow Write-Host "Retrying without code coverage..." -ForegroundColor Yellow - + $config.CodeCoverage.Enabled = $false $result = Invoke-Pester -Configuration $config } diff --git a/WinToolkit-template.ps1 b/WinToolkit-template.ps1 index 60bc0aa..3381d76 100644 --- a/WinToolkit-template.ps1 +++ b/WinToolkit-template.ps1 @@ -930,6 +930,10 @@ function Invoke-ToolkitDownload { if ($totalBytes -gt 0 -and -not $Global:GuiSessionActive) { $percent = [Math]::Round(($totalRead / $totalBytes) * 100) if ($percent -ne $lastPercent) { + $filled = '█' * [Math]::Floor($percent * 30 / 100) + $empty = '░' * (30 - $filled.Length) + $bar = "[$filled$empty] {0,3}%" -f $percent + # Convertire bytes in KB/MB appropriato $currentDisplay = if ($totalRead -gt 1048576) { "$([Math]::Round($totalRead / 1048576, 1)) MB" @@ -943,8 +947,7 @@ function Invoke-ToolkitDownload { "$([Math]::Round($totalBytes / 1024, 1)) KB" } - Clear-ProgressLine - Show-ProgressBar -Activity "Download $Description" -Status "($currentDisplay / $totalDisplay)" -Percent $percent -Icon '📥' -Color 'Cyan' + Write-Host "`r⏳ Download $Description $bar ($currentDisplay / $totalDisplay)" -NoNewline -ForegroundColor Cyan $lastPercent = $percent } } @@ -959,7 +962,7 @@ function Invoke-ToolkitDownload { $handler.Dispose() if (Test-Path $OutputPath) { - if (-not $Global:GuiSessionActive) { Clear-ProgressLine; Write-Host "" } + if (-not $Global:GuiSessionActive) { Write-Host "" } Write-StyledMessage -Type 'Success' -Text "✅ Download completato: $Description." return $true } diff --git a/WinToolkit.ps1 b/WinToolkit.ps1 index 7b15691..c89b5ca 100644 --- a/WinToolkit.ps1 +++ b/WinToolkit.ps1 @@ -64,9 +64,10 @@ $AppConfig = @{ OfficeSetup = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/refs/heads/main/asset/Setup.exe" OfficeBasicConfig = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/refs/heads/main/asset/Basic.xml" GetHelpInstaller = "https://aka.ms/SaRA_EnterpriseVersionFiles" - AMDInstaller = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/refs/heads/main/asset/AMD-Autodetect.exe" + AMDInstaller = "https://drivers.amd.com/drivers/installer/26.10/whql/amd-software-adrenalin-edition-26.5.2-minimalsetup-260513_web.exe" NVCleanstall = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/refs/heads/main/asset/NVCleanstall_1.19.0.exe" DDUZip = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/refs/heads/main/asset/DDU.zip" + DriverOverridesJson = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/refs/heads/ENHANCEMENT-Upgrade-Video-Driver-Install-Script/asset/DriverOverrides.json" DirectXWebSetup = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/refs/heads/main/asset/dxwebsetup.exe" BattleNetInstaller = "https://downloader.battle.net/download/getInstallerForGame?os=win&gameProgram=BATTLENET_APP&version=Live" SevenZipOfficial = "https://www.7-zip.org/a/7zr.exe" @@ -117,8 +118,8 @@ $AppConfig = @{ 'winget', 'WindowsPackageManagerServer' ) } -$Global:Spinners = '⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'.ToCharArray() -$Global:MsgStyles = @{ +$Global:Spinners = '⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'.ToCharArray() +$Global:MsgStyles = @{ Success = @{ Icon = '✅'; Color = 'Green' } Warning = @{ Icon = '⚠️'; Color = 'Yellow' } Error = @{ Icon = '❌'; Color = 'Red' } @@ -126,7 +127,7 @@ $Global:MsgStyles = @{ Progress = @{ Icon = '🔄'; Color = 'Magenta' } Question = @{ Icon = '❓'; Color = 'Cyan' } } -$Global:ExecutionLog = @() +$Global:ExecutionLog = @() $Global:NeedsFinalReboot = $false function Clear-ProgressLine { if ($Host.Name -eq 'ConsoleHost') { @@ -164,8 +165,8 @@ function Show-ProgressBar { param([string]$Activity, [string]$Status, [int]$Percent, [string]$Icon = '⏳', [string]$Spinner = '', [string]$Color = 'Green') $safePercent = [math]::Max(0, [math]::Min(100, $Percent)) $filled = '█' * [math]::Floor($safePercent * 30 / 100) - $empty = '░' * (30 - $filled.Length) - $bar = "[$filled$empty] {0,3}%" -f $safePercent + $empty = '░' * (30 - $filled.Length) + $bar = "[$filled$empty] {0,3}%" -f $safePercent if (-not $Global:GuiSessionActive) { Write-Host "`r$Spinner $Icon $Activity $bar $Status" -NoNewline -ForegroundColor $Color if ($Percent -ge 100) { Write-Host '' } @@ -208,9 +209,9 @@ function Show-ConsoleTable { } $sep = '+' + (($Columns | ForEach-Object { '-' * ($widths[$_.Key] + 2) }) -join '+') + '+' if ($Title) { - $totalWidth = $sep.Length + $totalWidth = $sep.Length $paddedTitle = " $Title " - $pad = [Math]::Max(0, [Math]::Floor(($totalWidth - $paddedTitle.Length) / 2)) + $pad = [Math]::Max(0, [Math]::Floor(($totalWidth - $paddedTitle.Length) / 2)) Write-Host ('=' * $totalWidth) -ForegroundColor Cyan Write-Host ((' ' * $pad) + $paddedTitle) -ForegroundColor Cyan Write-Host ('=' * $totalWidth) -ForegroundColor Cyan @@ -223,16 +224,16 @@ function Show-ConsoleTable { foreach ($row in $Rows) { $line = '|' foreach ($col in $Columns) { - $val = if ($row -is [hashtable]) { "$($row[$col.Key])" } else { "$($row.$($col.Key))" } + $val = if ($row -is [hashtable]) { "$($row[$col.Key])" } else { "$($row.$($col.Key))" } $line += ' ' + $val.PadRight($widths[$col.Key]) + ' |' } - $rowColor = 'White' + $rowColor = 'White' $statusKey = ($Columns | Where-Object { $_.Key -eq 'Status' -or $_.Key -eq 'Stato' } | Select-Object -First 1)?.Key if ($statusKey) { $statusVal = if ($row -is [hashtable]) { "$($row[$statusKey])" } else { "$($row.$statusKey)" } - if ($statusVal -match '✅|OK|Successo|Completato') { $rowColor = 'Green' } - elseif ($statusVal -match '⚠️|Warning|Parziale') { $rowColor = 'Yellow' } - elseif ($statusVal -match '❌|Errore|Fallito') { $rowColor = 'Red' } + if ($statusVal -match '✅|OK|Successo|Completato') { $rowColor = 'Green' } + elseif ($statusVal -match '⚠️|Warning|Parziale') { $rowColor = 'Yellow' } + elseif ($statusVal -match '❌|Errore|Fallito') { $rowColor = 'Red' } } Write-Host $line -ForegroundColor $rowColor } @@ -243,20 +244,20 @@ function Start-ToolkitLog { $Global:CurrentToolName = $ToolName try { Stop-Transcript -ErrorAction SilentlyContinue } catch {} $dateTime = Get-Date -Format "yyyy-MM-dd_HH-mm-ss" - $logdir = $AppConfig.Paths.Logs + $logdir = $AppConfig.Paths.Logs if (-not (Test-Path $logdir)) { New-Item -Path $logdir -ItemType Directory -Force | Out-Null } - $Global:CurrentLogFile = "$logdir\${ToolName}_$dateTime.log" + $Global:CurrentLogFile = "$logdir\${ToolName}_$dateTime.log" $Global:CurrentCorrelationId = [guid]::NewGuid().ToString() - $os = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue - $psVer = $PSVersionTable.PSVersion.ToString() - $psEd = $PSVersionTable.PSEdition + $os = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue + $psVer = $PSVersionTable.PSVersion.ToString() + $psEd = $PSVersionTable.PSEdition $psCompat = ($PSVersionTable.PSCompatibleVersions | ForEach-Object { $_.ToString() }) -join ', ' - $gitId = if ($PSVersionTable.GitCommitId) { $PSVersionTable.GitCommitId } else { 'N/A' } - $wsManVer = if ($PSVersionTable.WSManStackVersion) { $PSVersionTable.WSManStackVersion.ToString() } else { 'N/A' } + $gitId = if ($PSVersionTable.GitCommitId) { $PSVersionTable.GitCommitId } else { 'N/A' } + $wsManVer = if ($PSVersionTable.WSManStackVersion) { $PSVersionTable.WSManStackVersion.ToString() } else { 'N/A' } $remoteVer = if ($PSVersionTable.PSRemotingProtocolVersion) { $PSVersionTable.PSRemotingProtocolVersion.ToString() } else { 'N/A' } - $serVer = if ($PSVersionTable.SerializationVersion) { $PSVersionTable.SerializationVersion.ToString() } else { 'N/A' } - $build = [int]$os.BuildNumber - $verMap = @{26100 = '24H2'; 22631 = '23H2'; 22621 = '22H2'; 22000 = '21H2'; 19045 = '22H2'; 19044 = '21H2' } + $serVer = if ($PSVersionTable.SerializationVersion) { $PSVersionTable.SerializationVersion.ToString() } else { 'N/A' } + $build = [int]$os.BuildNumber + $verMap = @{26100 = '24H2'; 22631 = '23H2'; 22621 = '22H2'; 22000 = '21H2'; 19045 = '22H2'; 19044 = '21H2' } $dispVer = 'N/A' foreach ($k in ($verMap.Keys | Sort-Object -Descending)) { if ($build -ge $k) { $dispVer = $verMap[$k]; break } } $header = @" @@ -287,16 +288,16 @@ function Write-ToolkitLog { [hashtable]$Context = @{} ) if (-not $Global:CurrentLogFile) { return } - $ts = Get-Date -Format "HH:mm:ss" + $ts = Get-Date -Format "HH:mm:ss" $clean = $Message -replace '^\s+', '' $clean = $clean -replace '\x1B\[[0-9;]*[a-zA-Z]', '' $clean = $clean -replace '[⌀-⏿☀-➿\uD800-\uDFFF]', '' - $line = "[$ts] [$Level] $clean" + $line = "[$ts] [$Level] $clean" if ($Context.Count -gt 0) { try { $line += " | Context: " + ($Context | ConvertTo-Json -Compress -Depth 3) } catch {} } try { - $mutex = New-Object System.Threading.Mutex($false, "Global\WinToolkitLogMutex") + $mutex = New-Object System.Threading.Mutex($false, "Global\WinToolkitLogMutex") $hasHandle = $false try { $hasHandle = $mutex.WaitOne(5000) @@ -325,17 +326,17 @@ function Write-ToolkitError { function Get-SystemInfo { if ($Global:SystemInfoCache) { return $Global:SystemInfoCache } try { - $osInfo = Get-CimInstance Win32_OperatingSystem + $osInfo = Get-CimInstance Win32_OperatingSystem $computerInfo = Get-CimInstance Win32_ComputerSystem - $diskInfo = Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='C:'" - $versionMap = @{ + $diskInfo = Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='C:'" + $versionMap = @{ 28000 = "26H1"; 26200 = "25H2"; 26100 = "24H2"; 22631 = "23H2"; 22621 = "22H2"; 22000 = "21H2" 19045 = "22H2"; 19044 = "21H2"; 19043 = "21H1"; 19042 = "20H2"; 19041 = "2004"; 18363 = "1909" 18362 = "1903"; 17763 = "1809"; 17134 = "1803"; 16299 = "1709"; 15063 = "1703"; 14393 = "1607" 10586 = "1511"; 10240 = "1507" } $build = [int]$osInfo.BuildNumber - $ver = "N/A" + $ver = "N/A" foreach ($k in ($versionMap.Keys | Sort-Object -Descending)) { if ($build -ge $k) { $ver = $versionMap[$k]; break } } $Global:SystemInfoCache = @{ ProductName = $osInfo.Caption -replace 'Microsoft ', '' @@ -362,7 +363,7 @@ function Get-BitlockerStatus { } function Get-LocalUserProfiles { return Get-ChildItem "C:\Users" -Directory -ErrorAction SilentlyContinue | - Where-Object { $_.Name -notmatch '^(Public|Default|Default User|All Users)$' } + Where-Object { $_.Name -notmatch '^(Public|Default|Default User|All Users)$' } } function Initialize-ToolkitPaths { foreach ($path in $AppConfig.Paths.Values) { @@ -376,9 +377,9 @@ function Initialize-ToolkitPaths { } function Update-EnvironmentPath { $machinePath = [Environment]::GetEnvironmentVariable('Path', 'Machine') - $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') - $newPath = ($machinePath, $userPath | Where-Object { $_ }) -join ';' - $env:Path = $newPath + $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') + $newPath = ($machinePath, $userPath | Where-Object { $_ }) -join ';' + $env:Path = $newPath [System.Environment]::SetEnvironmentVariable('Path', $newPath, 'Process') } function Set-RegistryValue { @@ -391,8 +392,8 @@ function Stop-ToolkitProcesses { Write-StyledMessage -Type Info -Text "Chiusura processi interferenti..." foreach ($procName in $ProcessNames) { Get-Process -Name $procName -ErrorAction SilentlyContinue | - Where-Object { $_.Id -ne $PID } | - Stop-Process -Force -ErrorAction SilentlyContinue + Where-Object { $_.Id -ne $PID } | + Stop-Process -Force -ErrorAction SilentlyContinue } Start-Sleep -Seconds 2 } @@ -400,14 +401,14 @@ function Invoke-ExternalCommandWithLog { [CmdletBinding()] param( [Parameter(Mandatory = $true)][string]$Command, - [string[]]$Arguments = @(), + [string[]]$Arguments = @(), [string]$WorkingDirectory, - [int]$TimeoutSeconds = 0, - [string]$LogContextKey = '', - [string]$Activity = '', - [int]$UpdateInterval = 500, - [string]$Tool = $Global:CurrentToolName, - [string]$Step = 'ExternalCommand' + [int]$TimeoutSeconds = 0, + [string]$LogContextKey = '', + [string]$Activity = '', + [int]$UpdateInterval = 500, + [string]$Tool = $Global:CurrentToolName, + [string]$Step = 'ExternalCommand' ) $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() $startTime = Get-Date @@ -417,16 +418,16 @@ function Invoke-ExternalCommandWithLog { Tool = $Tool; Step = $Step; WorkingDir = $WorkingDirectory; ContextKey = $LogContextKey } $psi = New-Object System.Diagnostics.ProcessStartInfo - $psi.FileName = $Command - $psi.Arguments = $argString + $psi.FileName = $Command + $psi.Arguments = $argString if ($WorkingDirectory) { $psi.WorkingDirectory = $WorkingDirectory } - $psi.UseShellExecute = $false + $psi.UseShellExecute = $false $psi.RedirectStandardOutput = $true - $psi.RedirectStandardError = $true - $psi.CreateNoWindow = $true - $proc = [System.Diagnostics.Process]::new() + $psi.RedirectStandardError = $true + $psi.CreateNoWindow = $true + $proc = [System.Diagnostics.Process]::new() $proc.StartInfo = $psi - $outText = ""; $errText = ""; $success = $false; $exitCode = $null; $timedOut = $false + $outText = ""; $errText = ""; $success = $false; $exitCode = $null; $timedOut = $false try { if (-not $proc.Start()) { throw "Impossibile avviare il processo esterno." } $outTask = $proc.StandardOutput.ReadToEndAsync() @@ -461,7 +462,7 @@ function Invoke-ExternalCommandWithLog { if ($outTask.Status -eq 'RanToCompletion') { $outText = $outTask.Result } if ($errTask.Status -eq 'RanToCompletion') { $errText = $errTask.Result } $exitCode = $proc.ExitCode - $success = ($exitCode -eq 0) + $success = ($exitCode -eq 0) } catch { $exitCode = if ($exitCode -ne $null) { $exitCode } else { -1 } @@ -500,19 +501,19 @@ function Invoke-WithSpinner { param( [Parameter(Mandatory = $true)][string]$Activity, [scriptblock]$Action, - [int]$TimeoutSeconds = 300, - [int]$UpdateInterval = 500, + [int]$TimeoutSeconds = 300, + [int]$UpdateInterval = 500, [switch]$Process, [switch]$Job, [switch]$Timer, [scriptblock]$PercentUpdate, [string]$Command, - [string[]]$Arguments = @(), + [string[]]$Arguments = @(), [string]$LogContextKey = '' ) - $startTime = Get-Date + $startTime = Get-Date $spinnerIndex = 0 - $percent = 0 + $percent = 0 if ($Command) { return Invoke-ExternalCommandWithLog -Command $Command -Arguments $Arguments ` -TimeoutSeconds $TimeoutSeconds -Activity $Activity -UpdateInterval $UpdateInterval -LogContextKey $LogContextKey @@ -535,13 +536,13 @@ function Invoke-WithSpinner { $spinner = $Global:Spinners[$spinnerIndex++ % $Global:Spinners.Length] $elapsed = [math]::Round(((Get-Date) - $startTime).TotalSeconds, 1) $percent = if ($PercentUpdate) { & $PercentUpdate } elseif ($percent -lt 90) { $percent + (Get-Random -Minimum 1 -Maximum 3) } else { $percent } - if (-not $Global:GuiSessionActive) { Write-Host "`r" -NoNewline } + if (-not $Global:GuiSessionActive) { Clear-ProgressLine } Show-ProgressBar -Activity $Activity -Status "Esecuzione in corso... ($elapsed secondi)" -Percent $percent -Icon '⏳' -Spinner $spinner Start-Sleep -Milliseconds $UpdateInterval $result.Refresh() } if (-not $result.HasExited) { - if (-not $Global:GuiSessionActive) { Write-Host "" } + if (-not $Global:GuiSessionActive) { Clear-ProgressLine; Write-Host "" } Write-StyledMessage -Type 'Warning' -Text "Timeout raggiunto dopo $TimeoutSeconds secondi, terminazione processo..." $result.Kill(); Start-Sleep -Seconds 2 return @{ Success = $false; TimedOut = $true; ExitCode = -1 } @@ -583,9 +584,9 @@ function Start-InterruptibleCountdown { Write-StyledMessage -Type 'Warning' -Text '⏸️ Riavvio del sistema annullato.' return $false } - $percent = [Math]::Round((($Seconds - $i) / $Seconds) * 100) - $filled = [Math]::Floor($percent * 20 / 100) - $bar = "[$('█' * $filled)$('▒' * (20 - $filled))]" + $percent = [Math]::Round((($Seconds - $i) / $Seconds) * 100) + $filled = [Math]::Floor($percent * 20 / 100) + $bar = "[$('█' * $filled)$('▒' * (20 - $filled))]" Write-Host "`r⏰ $Message tra $i secondi $bar" -NoNewline -ForegroundColor Red Start-Sleep 1 } @@ -600,14 +601,15 @@ function Start-ToolkitSession { } function Invoke-ToolkitReboot { param( - [string]$Message = "Operazione completata", - [int]$Seconds = 30, + [string]$Message = "Operazione completata", + [int]$Seconds = 30, [switch]$SuppressIndividualReboot ) if ($SuppressIndividualReboot) { $Global:NeedsFinalReboot = $true Write-StyledMessage -Type 'Info' -Text "🚫 Riavvio individuale soppresso. Verrà gestito un riavvio finale." - } else { + } + else { if (Start-InterruptibleCountdown -Seconds $Seconds -Message $Message) { Restart-Computer -Force } @@ -622,33 +624,104 @@ function Remove-ItemSafely { Remove-Item @params *>$null Clear-ProgressLine return $true - } catch { return $false } + } + catch { return $false } } function Invoke-ToolkitDownload { param( [string]$Uri, [string]$OutputPath, [string]$Description = "file", - [int]$MaxRetries = 3 + [int]$MaxRetries = 3, + [switch]$NoSpinner ) - Write-StyledMessage -Type 'Info' -Text "📥 Download $Description." for ($attempt = 1; $attempt -le $MaxRetries; $attempt++) { try { - $wc = New-Object System.Net.WebClient - $wc.DownloadFile($Uri, $OutputPath) - $wc.Dispose() + Write-StyledMessage -Type 'Info' -Text "📥 Download $Description..." + $parentDir = Split-Path -Parent $OutputPath + if (-not (Test-Path $parentDir)) { + New-Item -Path $parentDir -ItemType Directory -Force | Out-Null + } + $handler = New-Object System.Net.Http.HttpClientHandler + $handler.AllowAutoRedirect = $true + $handler.AutomaticDecompression = [System.Net.DecompressionMethods]::GZip -bor [System.Net.DecompressionMethods]::Deflate + $httpClient = New-Object System.Net.Http.HttpClient($handler) + $httpClient.Timeout = [TimeSpan]::FromSeconds(300) + $httpClient.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") + if ($Uri -match 'drivers\.amd\.com|amd-software') { + $httpClient.DefaultRequestHeaders.Add("Referer", "https://www.amd.com") + } + $totalBytes = 0 + try { + $headRequest = New-Object System.Net.Http.HttpRequestMessage([System.Net.Http.HttpMethod]::Head, $Uri) + $headResponse = $httpClient.SendAsync($headRequest).Result + if ($headResponse.Content.Headers.ContentLength -gt 0) { + $totalBytes = $headResponse.Content.Headers.ContentLength + } + $headResponse.Dispose() + } + catch {} + $getRequest = New-Object System.Net.Http.HttpRequestMessage([System.Net.Http.HttpMethod]::Get, $Uri) + $getResponse = $httpClient.SendAsync($getRequest).Result + if (-not $getResponse.IsSuccessStatusCode) { + throw "HTTP Error $($getResponse.StatusCode): $($getResponse.ReasonPhrase)" + } + $contentStream = $getResponse.Content.ReadAsStreamAsync().Result + $fileStream = [System.IO.File]::Create($OutputPath) + $buffer = New-Object byte[] 8192 + $totalRead = 0 + $lastPercent = -1 + try { + while ($true) { + $read = $contentStream.Read($buffer, 0, $buffer.Length) + if ($read -eq 0) { break } + $fileStream.Write($buffer, 0, $read) + $totalRead += $read + if ($totalBytes -gt 0 -and -not $Global:GuiSessionActive) { + $percent = [Math]::Round(($totalRead / $totalBytes) * 100) + if ($percent -ne $lastPercent) { + $filled = '█' * [Math]::Floor($percent * 30 / 100) + $empty = '░' * (30 - $filled.Length) + $bar = "[$filled$empty] {0,3}%" -f $percent + $currentDisplay = if ($totalRead -gt 1048576) { + "$([Math]::Round($totalRead / 1048576, 1)) MB" + } else { + "$([Math]::Round($totalRead / 1024, 1)) KB" + } + $totalDisplay = if ($totalBytes -gt 1048576) { + "$([Math]::Round($totalBytes / 1048576, 1)) MB" + } else { + "$([Math]::Round($totalBytes / 1024, 1)) KB" + } + Write-Host "`r⏳ Download $Description $bar ($currentDisplay / $totalDisplay)" -NoNewline -ForegroundColor Cyan + $lastPercent = $percent + } + } + } + } + finally { + $fileStream.Dispose() + $contentStream.Dispose() + } + $httpClient.Dispose() + $handler.Dispose() if (Test-Path $OutputPath) { - Write-StyledMessage -Type 'Success' -Text "Download completato: $Description." + if (-not $Global:GuiSessionActive) { Write-Host "" } + Write-StyledMessage -Type 'Success' -Text "✅ Download completato: $Description." return $true } - } catch { + } + catch { + if (Test-Path $OutputPath) { + Remove-Item $OutputPath -Force -ErrorAction SilentlyContinue + } if ($attempt -lt $MaxRetries) { - Write-StyledMessage -Type 'Warning' -Text "Tentativo $attempt/$MaxRetries fallito. Riprovo..." + Write-StyledMessage -Type 'Warning' -Text "⚠️ Tentativo $attempt/$MaxRetries fallito: $($_.Exception.Message). Riprovo..." Start-Sleep -Seconds 2 } } } - Write-StyledMessage -Type 'Error' -Text "Download fallito dopo $MaxRetries tentativi: $Description." + Write-StyledMessage -Type 'Error' -Text "❌ Download fallito dopo $MaxRetries tentativi: $Description." return $false } function Restart-ServiceSafely { @@ -659,7 +732,8 @@ function Restart-ServiceSafely { Start-Service -Name $Name -ErrorAction Stop Write-StyledMessage -Type 'Success' -Text "Servizio riavviato: $Name." return $true - } catch { + } + catch { Write-StyledMessage -Type 'Warning' -Text "Impossibile riavviare '$Name': $($_.Exception.Message)." return $false } @@ -667,10 +741,10 @@ function Restart-ServiceSafely { function Get-WingetExecutable { $aliasPath = Join-Path $env:LOCALAPPDATA "Microsoft\WindowsApps\winget.exe" if (Test-Path $aliasPath) { return $aliasPath } - $arch = [Environment]::Is64BitOperatingSystem ? "x64" : "x86" + $arch = [Environment]::Is64BitOperatingSystem ? "x64" : "x86" $wingetDir = Get-ChildItem -Path "$env:ProgramFiles\WindowsApps" ` -Filter "Microsoft.DesktopAppInstaller_*_*${arch}__8wekyb3d8bbwe" -ErrorAction SilentlyContinue | - Sort-Object Name -Descending | Select-Object -First 1 + Sort-Object Name -Descending | Select-Object -First 1 if ($wingetDir) { $exe = Join-Path $wingetDir.FullName "winget.exe" if (Test-Path $exe) { return $exe } @@ -680,7 +754,7 @@ function Get-WingetExecutable { function Start-AppxSilentProcess { param( [string]$AppxPath, - [string]$Flags = '-ForceApplicationShutdown', + [string]$Flags = '-ForceApplicationShutdown', [string[]]$DependencyPaths = @() ) $pathParam = ($Flags -match '-Register') ? "" : "-Path '$($AppxPath -replace "'", "''")'" @@ -710,19 +784,19 @@ exit 0 "@ $encodedCmd = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($cmd)) $psi = New-Object System.Diagnostics.ProcessStartInfo - $psi.FileName = "powershell.exe" - $psi.Arguments = "-NoProfile -NonInteractive -EncodedCommand $encodedCmd" - $psi.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden - $psi.CreateNoWindow = $true - $psi.UseShellExecute = $false + $psi.FileName = "powershell.exe" + $psi.Arguments = "-NoProfile -NonInteractive -EncodedCommand $encodedCmd" + $psi.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden + $psi.CreateNoWindow = $true + $psi.UseShellExecute = $false $psi.RedirectStandardOutput = $true - $psi.RedirectStandardError = $true + $psi.RedirectStandardError = $true return [System.Diagnostics.Process]::Start($psi) } function Wait-WingetReady { param([int]$MaxWaitSeconds = 300, [int]$PollIntervalSeconds = 5) Write-StyledMessage -Type Info -Text "🔍 Validazione integrità Winget in corso (timeout: $MaxWaitSeconds s)..." - $wingetExe = Get-WingetExecutable + $wingetExe = Get-WingetExecutable $maxRetries = [Math]::Floor($MaxWaitSeconds / $PollIntervalSeconds) for ($i = 1; $i -le $maxRetries; $i++) { try { @@ -755,7 +829,7 @@ function Reset-Winget { $(if ($64BitOS) { 'WOW6432Node' } else { '' }), $(if ($64BitOS) { '64' } else { '86' }) ) - $major = (Get-ItemProperty -Path $registryPath -Name 'Major' -ErrorAction SilentlyContinue).Major + $major = (Get-ItemProperty -Path $registryPath -Name 'Major' -ErrorAction SilentlyContinue).Major $dllPath = [string]::Format('{0}\system32\concrt140.dll', $env:windir) return (Test-Path $registryPath) -and ($major -ge 14) -and (Test-Path $dllPath) } @@ -776,7 +850,7 @@ function Reset-Winget { param([string]$Match) try { $latest = Invoke-RestMethod -Uri "https://api.github.com/repos/microsoft/winget-cli/releases/latest" -UseBasicParsing -ErrorAction Stop - $asset = $latest.assets | Where-Object { $_.name -match $Match } | Select-Object -First 1 + $asset = $latest.assets | Where-Object { $_.name -match $Match } | Select-Object -First 1 return $asset ? $asset.browser_download_url : $null } catch { return $null } @@ -837,10 +911,10 @@ function Reset-Winget { param([string]$FolderPath) if (-not (Test-Path $FolderPath)) { return } try { - $sid = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-544') + $sid = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-544') $group = $sid.Translate([System.Security.Principal.NTAccount]) - $acl = Get-Acl -Path $FolderPath -ErrorAction Stop - $rule = New-Object System.Security.AccessControl.FileSystemAccessRule( + $acl = Get-Acl -Path $FolderPath -ErrorAction Stop + $rule = New-Object System.Security.AccessControl.FileSystemAccessRule( $group, 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow') $acl.SetAccessRule($rule) Set-Acl -Path $FolderPath -AclObject $acl -ErrorAction Stop @@ -851,10 +925,10 @@ function Reset-Winget { function _Set-WingetPathPermissions { $wingetFolderPath = $null try { - $arch = [Environment]::Is64BitOperatingSystem ? 'x64' : 'x86' + $arch = [Environment]::Is64BitOperatingSystem ? 'x64' : 'x86' $wingetDir = Get-ChildItem "$env:ProgramFiles\WindowsApps" ` -Filter "Microsoft.DesktopAppInstaller_*_*${arch}__8wekyb3d8bbwe" -ErrorAction SilentlyContinue | - Sort-Object Name -Descending | Select-Object -First 1 + Sort-Object Name -Descending | Select-Object -First 1 if ($wingetDir) { $wingetFolderPath = $wingetDir.FullName } } catch {} @@ -873,11 +947,11 @@ function Reset-Winget { if (Test-Path $cachePath) { Write-StyledMessage -Type Info -Text "Pulizia cache Winget." Get-ChildItem -Path $cachePath -Recurse -Force -ErrorAction SilentlyContinue | - Where-Object { $_.FullName -notmatch '\\lock\\|\\tmp\\' } | - ForEach-Object { try { Remove-Item $_.FullName -Force -Recurse -ErrorAction SilentlyContinue } catch {} } + Where-Object { $_.FullName -notmatch '\\lock\\|\\tmp\\' } | + ForEach-Object { try { Remove-Item $_.FullName -Force -Recurse -ErrorAction SilentlyContinue } catch {} } } @("$env:LOCALAPPDATA\WinGet\Data\USERTEMPLATE.json", - "$env:LOCALAPPDATA\WinGet\Data\DEFAULTUSER.json") | ForEach-Object { + "$env:LOCALAPPDATA\WinGet\Data\DEFAULTUSER.json") | ForEach-Object { if (Test-Path $_ -PathType Leaf) { Write-StyledMessage -Type Info -Text "Reset file stato: $_." Remove-Item $_ -Force -ErrorAction SilentlyContinue @@ -974,15 +1048,15 @@ function Reset-Winget { function _Test-WingetDeepValidation { Write-StyledMessage -Type Info -Text "🔍 Validazione profonda Winget (connettività + integrità database)." try { - $wingetExe = Get-WingetExecutable + $wingetExe = Get-WingetExecutable $searchResult = & $wingetExe search "Git.Git" --accept-source-agreements 2>&1 - $exitCode = $LASTEXITCODE + $exitCode = $LASTEXITCODE if ($exitCode -eq -1073741819 -or $exitCode -eq 3221225781) { Write-StyledMessage -Type Warning -Text "⚠️ Crash ACCESS_VIOLATION (ExitCode: $exitCode). Ripristino database." $null = _Repair-WingetDatabase Start-Sleep 3 $searchResult = & $wingetExe search "Git.Git" --accept-source-agreements 2>&1 - $exitCode = $LASTEXITCODE + $exitCode = $LASTEXITCODE if ($exitCode -eq -1073741819 -or $exitCode -eq 3221225781) { Write-StyledMessage -Type Warning -Text "⚠️ Crash persistente dopo ripristino database." return $false @@ -1013,8 +1087,8 @@ function Reset-Winget { Write-StyledMessage -Type Info -Text "⚡ Fase 1: Ripristino Core (VC++, dipendenze AppX, MSIXBundle)." if (-not (_Test-VCRedistInstalled) -or $Force) { Write-StyledMessage -Type Info -Text "Installazione Visual C++ Redistributable..." - $arch = [Environment]::Is64BitOperatingSystem ? "x64" : "x86" - $vcUrl = "https://aka.ms/vs/17/release/vc_redist.$arch.exe" + $arch = [Environment]::Is64BitOperatingSystem ? "x64" : "x86" + $vcUrl = "https://aka.ms/vs/17/release/vc_redist.$arch.exe" $vcFile = Join-Path $AppConfig.Paths.Temp "vc_redist.exe" if (-not (Test-Path $AppConfig.Paths.Temp)) { $null = New-Item $AppConfig.Paths.Temp -ItemType Directory -Force } Invoke-WebRequest -Uri $vcUrl -OutFile $vcFile -UseBasicParsing @@ -1028,11 +1102,11 @@ function Reset-Winget { $depDir = Join-Path $AppConfig.Paths.Temp "deps" Invoke-WebRequest -Uri $depUrl -OutFile $depZip -UseBasicParsing Expand-Archive -Path $depZip -DestinationPath $depDir -Force - $archPattern = [Environment]::Is64BitOperatingSystem ? "x64|ne" : "x86|ne" + $archPattern = [Environment]::Is64BitOperatingSystem ? "x64|ne" : "x86|ne" $script:WingetDependencies = @() Get-ChildItem $depDir -Recurse -Filter "*.appx" | - Where-Object { $_.Name -match $archPattern } | - ForEach-Object { Write-StyledMessage -Type Info -Text "Trovata dipendenza: $($_.Name)."; $script:WingetDependencies += $_.FullName } + Where-Object { $_.Name -match $archPattern } | + ForEach-Object { Write-StyledMessage -Type Info -Text "Trovata dipendenza: $($_.Name)."; $script:WingetDependencies += $_.FullName } Write-StyledMessage -Type Success -Text "Dipendenze caricate." } Write-StyledMessage -Type Info -Text "Installazione Winget MSIXBundle (con dipendenze)..." @@ -1085,7 +1159,7 @@ function Get-UserConfirmation { [switch]$DefaultYes, [ValidateSet('Info', 'Warning', 'Question')][string]$Level = 'Question' ) - $choices = if ($DefaultYes) { "[S/n]" } else { "[s/N]" } + $choices = if ($DefaultYes) { "[S/n]" } else { "[s/N]" } $fullPrompt = "$Prompt $choices" if ($Global:GuiSessionActive) { Write-StyledMessage -Type $Level -Text $fullPrompt @@ -1103,7 +1177,7 @@ function Read-ValidatedChoice { [int]$Min, [int]$Max, [switch]$AllowZero, - [string]$Prompt = "Seleziona un'opzione", + [string]$Prompt = "Seleziona un'opzione", [string]$RawInput ) $currentInput = if ($PSBoundParameters.ContainsKey('RawInput')) { $RawInput } else { $null } @@ -1145,9 +1219,9 @@ function WinOSCheck { $si = Get-SystemInfo if (-not $si) { Write-StyledMessage -Type 'Warning' -Text "Info sistema non disponibili."; return } Write-StyledMessage -Type 'Info' -Text "Sistema: $($si.ProductName) ($($si.DisplayVersion))" - if ($si.BuildNumber -ge 22000) { Write-StyledMessage -Type 'Success' -Text "Sistema compatibile (Win11/10 recente)." } + if ($si.BuildNumber -ge 22000) { Write-StyledMessage -Type 'Success' -Text "Sistema compatibile (Win11/10 recente)." } elseif ($si.BuildNumber -ge 17763) { Write-StyledMessage -Type 'Success' -Text "Sistema compatibile (Win10)." } - elseif ($si.BuildNumber -eq 9600) { Write-StyledMessage -Type 'Warning' -Text "Windows 8.1: Compatibilità parziale." } + elseif ($si.BuildNumber -eq 9600) { Write-StyledMessage -Type 'Warning' -Text "Windows 8.1: Compatibilità parziale." } else { Write-StyledMessage -Type 'Error' -Text "$(Center-Text '🤣 ERRORE CRITICO 🤣' 65)" Write-StyledMessage -Type 'Error' -Text "Davvero pensi che questo script possa fare qualcosa per questa versione?" @@ -1160,7 +1234,7 @@ function Test-WindowsUpdateStatus { try { if ($Global:GuiSessionActive) { return } Write-StyledMessage -Type 'Info' -Text "🔍 Controllo stato aggiornamenti Windows..." - $pendingReboot = $false + $pendingReboot = $false $installerRunning = $false if (Get-Module -ListAvailable -Name PSWindowsUpdate -ErrorAction SilentlyContinue) { Import-Module PSWindowsUpdate -ErrorAction SilentlyContinue @@ -1201,7 +1275,7 @@ function Test-WindowsUpdateStatus { Write-Host (Center-Text "⚠️ AVVISO IMPORTANTE ⚠️") -ForegroundColor Yellow Write-Host "" Write-Host " Sono stati rilevati aggiornamenti di sistema pendenti:" -ForegroundColor Yellow - if ($pendingReboot) { Write-Host " ✓ Riavvio del sistema richiesto per completare aggiornamenti" -ForegroundColor Yellow } + if ($pendingReboot) { Write-Host " ✓ Riavvio del sistema richiesto per completare aggiornamenti" -ForegroundColor Yellow } if ($installerRunning) { Write-Host " ✓ Servizio installazione aggiornamenti Windows in corso" -ForegroundColor Yellow } Write-Host "" Write-Host " Questo potrebbe causare malfunzionamenti, errori o comportamenti" -ForegroundColor Yellow @@ -1230,7 +1304,7 @@ function Invoke-OfficeSilentRemoval { } function Stop-OfficeProcesses { $processes = @('winword', 'excel', 'powerpnt', 'outlook', 'onenote', 'msaccess', 'visio', 'lync') - $closed = 0 + $closed = 0 Write-StyledMessage -Type 'Info' -Text "📋 Chiusura processi Office." foreach ($processName in $processes) { $running = Get-Process -Name $processName -ErrorAction SilentlyContinue @@ -1273,6 +1347,119 @@ function Set-OfficePostConfig { } Write-StyledMessage -Type 'Success' -Text "✅ Office ottimizzato: telemetria, privacy e task pianificati rimossi." } +function VcardAnalizer { + [CmdletBinding()] + param( + [string]$OverridesPath + ) + $assetCacheDir = Join-Path $AppConfig.Paths.Root 'asset' + if (-not (Test-Path $assetCacheDir)) { + $null = New-Item -Path $assetCacheDir -ItemType Directory -Force + } + $defaultLocalOverrides = Join-Path $assetCacheDir 'DriverOverrides.json' + $resolvedOverridesPath = if ($OverridesPath) { $OverridesPath } else { $defaultLocalOverrides } + $analysis = [pscustomobject]@{ + Cards = @() + Matches = @() + PrimaryManufacturer = 'Unknown' + OverridesLoaded = $false + OverridesSource = $resolvedOverridesPath + } + try { + $cards = Get-CimInstance Win32_VideoController -ErrorAction SilentlyContinue + foreach ($card in $cards) { + $name = [string]$card.Name + $caption = [string]$card.Caption + $pnpId = [string]$card.PNPDeviceID + $manufacturer = 'Unknown' + if ($name -match 'NVIDIA|GeForce|Quadro|Tesla' -or $caption -match 'NVIDIA') { $manufacturer = 'NVIDIA' } + elseif ($name -match 'AMD|Radeon|ATI' -or $caption -match 'AMD|ATI') { $manufacturer = 'AMD' } + elseif ($name -match 'Intel|Iris|UHD|HD Graphics' -or $caption -match 'Intel') { $manufacturer = 'Intel' } + $analysis.Cards += [pscustomobject]@{ + Name = $name + Caption = $caption + PnpDeviceID = $pnpId + Manufacturer = $manufacturer + } + } + } + catch { + Write-StyledMessage -Type 'Warning' -Text "Analisi GPU: errore durante lettura Win32_VideoController: $($_.Exception.Message)" + } + if ($analysis.Cards.Count -gt 0) { + $analysis.PrimaryManufacturer = ($analysis.Cards | Select-Object -First 1).Manufacturer + } + $overrides = @() + $remoteUrl = $AppConfig.URLs.DriverOverridesJson + if ([string]::IsNullOrWhiteSpace($remoteUrl)) { + $remoteUrl = "$($AppConfig.URLs.GitHubAssetBaseUrl)DriverOverrides.json" + } + try { + if (Invoke-ToolkitDownload -Uri $remoteUrl -OutputPath $defaultLocalOverrides -Description 'Driver Overrides JSON') { + $resolvedOverridesPath = $defaultLocalOverrides + $analysis.OverridesSource = $resolvedOverridesPath + } + } + catch { + Write-StyledMessage -Type 'Warning' -Text "Download DriverOverrides.json fallito, uso cache locale se disponibile." + } + if (Test-Path $resolvedOverridesPath) { + try { + $jsonRaw = Get-Content -Path $resolvedOverridesPath -Raw -Encoding UTF8 + $parsed = $jsonRaw | ConvertFrom-Json -ErrorAction Stop + if ($parsed -is [System.Array]) { $overrides = $parsed } + elseif ($parsed) { $overrides = @($parsed) } + $analysis.OverridesLoaded = $true + } + catch { + Write-StyledMessage -Type 'Warning' -Text "DriverOverrides.json non valido: $($_.Exception.Message)" + } + } + else { + Write-StyledMessage -Type 'Warning' -Text "DriverOverrides.json non trovato in $resolvedOverridesPath" + } + foreach ($gpu in $analysis.Cards) { + foreach ($ovr in $overrides) { + $namePattern = [string]$ovr.NamePattern + $pnpPattern = [string]$ovr.PnpIdPattern + $manufacturer = [string]$ovr.Manufacturer + $nameMatches = $false + $pnpMatches = $false + $mfrMatches = $false + if (-not [string]::IsNullOrWhiteSpace($namePattern) -and -not [string]::IsNullOrWhiteSpace($gpu.Name)) { + $nameMatches = $gpu.Name -match $namePattern + } + if (-not [string]::IsNullOrWhiteSpace($pnpPattern) -and -not [string]::IsNullOrWhiteSpace($gpu.PnpDeviceID)) { + $pnpMatches = $gpu.PnpDeviceID -like $pnpPattern + } + if (-not [string]::IsNullOrWhiteSpace($manufacturer) -and $gpu.Manufacturer -ne 'Unknown') { + $mfrMatches = $gpu.Manufacturer -eq $manufacturer + } + if (($nameMatches -or $pnpMatches) -and ($mfrMatches -or [string]::IsNullOrWhiteSpace($manufacturer))) { + $analysis.Matches += [pscustomobject]@{ + Key = [string]$ovr.Key + Manufacturer = [string]$ovr.Manufacturer + NamePattern = [string]$ovr.NamePattern + PnpIdPattern = [string]$ovr.PnpIdPattern + DownloadUrl = [string]$ovr.DownloadUrl + FileName = [string]$ovr.FileName + DisplayName = [string]$ovr.DisplayName + MatchedGpu = [string]$gpu.Name + MatchedPnpId = [string]$gpu.PnpDeviceID + } + } + } + } + if ($analysis.Matches.Count -gt 0) { + $analysis.Matches = @($analysis.Matches | Group-Object Key | ForEach-Object { $_.Group | Select-Object -First 1 }) + Write-StyledMessage -Type 'Success' -Text "Rilevate $($analysis.Matches.Count) corrispondenze driver stabili da DriverOverrides.json." + } + else { + Write-StyledMessage -Type 'Warning' -Text "Nessun driver stabile conosciuto trovato per le GPU rilevate." + } + $Global:VcardAnalysisResult = $analysis + return $analysis +} function WinRepairToolkit { [CmdletBinding()] param( @@ -2053,9 +2240,11 @@ function WinReinstallStore { return $false } try { - $null = Invoke-WithSpinner -Activity "Disinstallazione versioni precedenti UniGet UI" -Command $wingetExe -Arguments @('uninstall', '--exact', '--id', 'MartiCliment.UniGetUI', '--silent', '--disable-interactivity') -TimeoutSeconds 120 -LogContextKey "Store-UniGet-Uninstall" - Clear-ProgressLine - [Console]::Out.Flush() + foreach ($oldId in @('MartiCliment.UniGetUI', 'Devolutions.UniGetUI')) { + $null = Invoke-WithSpinner -Activity "Disinstallazione $oldId" -Command $wingetExe -Arguments @('uninstall', '--exact', '--id', $oldId, '--silent', '--disable-interactivity') -TimeoutSeconds 120 -LogContextKey "Store-UniGet-Uninstall" + Clear-ProgressLine + [Console]::Out.Flush() + } $processResult = Invoke-WithSpinner -Activity "Installazione UniGet UI" -Command $wingetExe -Arguments @('install', '--exact', '--id', 'Devolutions.UniGetUI', '--source', 'winget', '--accept-source-agreements', '--accept-package-agreements', '--silent', '--disable-interactivity', '--force') -TimeoutSeconds 600 -LogContextKey "Store-UniGet-Install" Clear-ProgressLine [Console]::Out.Flush() @@ -3299,7 +3488,9 @@ function Install-Office { } try { Write-StyledMessage -Type 'Info' -Text "🏢 Avvio installazione Office Basic." - if (-not (Test-Path $tempDir)) { $null = New-Item -ItemType Directory -Path $tempDir -Force } + if (-not (Test-Path $tempDir)) { + $null = New-Item -ItemType Directory -Path $tempDir -Force + } $setupPath = Join-Path $tempDir 'Setup.exe' $configPath = Join-Path $tempDir 'Basic.xml' foreach ($dl in @( @@ -3686,262 +3877,223 @@ function Uninstall-Office { Invoke-ToolkitReboot -Message "Rimozione completata" -Seconds $CountdownSeconds -SuppressIndividualReboot:$SuppressIndividualReboot } } -function VideoDriverInstall { +function AutoVideoDriverInstall { [CmdletBinding()] param( [int]$CountdownSeconds = 30, [switch]$SuppressIndividualReboot ) - Start-ToolkitSession -ToolName "VideoDriverInstall" -SubTitle "Video Driver Install Toolkit" - $GitHubAssetBaseUrl = $AppConfig.URLs.GitHubAssetBaseUrl - $DriverToolsLocalPath = $AppConfig.Paths.Drivers - $DesktopPath = $AppConfig.Paths.Desktop - function Get-GpuManufacturer { - $pnpDevices = Get-PnpDevice -Class Display -ErrorAction SilentlyContinue - if (-not $pnpDevices) { - Write-StyledMessage -Type 'Warning' -Text "Nessun dispositivo display Plug and Play rilevato." - return 'Unknown' - } - foreach ($device in $pnpDevices) { - $manufacturer = $device.Manufacturer - $friendlyName = $device.FriendlyName - if ($friendlyName -match 'NVIDIA|GeForce|Quadro|Tesla' -or $manufacturer -match 'NVIDIA') { - return 'NVIDIA' - } - elseif ($friendlyName -match 'AMD|Radeon|ATI' -or $manufacturer -match 'AMD|ATI') { - return 'AMD' - } - elseif ($friendlyName -match 'Intel|Iris|UHD|HD Graphics' -or $manufacturer -match 'Intel') { - return 'Intel' - } - } - return 'Unknown' - } + Start-ToolkitSession -ToolName "AutoVideoDriverInstall" -SubTitle "Auto Video Driver Install" + $desktopPath = $AppConfig.Paths.Desktop function Set-BlockWindowsUpdateDrivers { - Write-StyledMessage -Type 'Info' -Text "Configurazione per bloccare download driver da Windows Update." - $regPath = $AppConfig.Registry.WindowsUpdatePolicies + Write-StyledMessage -Type 'Info' -Text "Blocco driver automatici da Windows Update." try { - Set-RegistryValue -Path $regPath -Name "ExcludeWUDriversInQualityUpdate" -Value 1 - Write-StyledMessage -Type 'Success' -Text "Blocco download driver da Windows Update impostato correttamente nel registro." - Write-StyledMessage -Type 'Info' -Text "Questa impostazione impedisce a Windows Update di installare driver automaticamente." - } - catch { - Write-StyledMessage -Type 'Error' -Text "Errore durante l'impostazione del blocco download driver da Windows Update: $($_.Exception.Message)." - return - } - Write-StyledMessage -Type 'Info' -Text "Aggiornamento dei criteri di gruppo in corso per applicare le modifiche." - try { - $gpupdateProcess = Invoke-WithSpinner -Activity "Aggiornamento criteri di gruppo" -Command 'gpupdate.exe' -Arguments '/force' -LogContextKey "Video-GPUpdate" - if ($gpupdateProcess.ExitCode -eq 0) { - Write-StyledMessage -Type 'Success' -Text "Criteri di gruppo aggiornati con successo." + Set-RegistryValue -Path $AppConfig.Registry.WindowsUpdatePolicies -Name "ExcludeWUDriversInQualityUpdate" -Value 1 + Write-StyledMessage -Type 'Success' -Text "Blocco WU driver impostato." + $gpupdateResult = Invoke-WithSpinner -Activity "Aggiornamento criteri di gruppo (può impiegare 1-2 minuti)" -Command 'gpupdate.exe' -Arguments '/force' -LogContextKey "Video-GPUpdate" -TimeoutSeconds 180 + if ($gpupdateResult -and $gpupdateResult.ExitCode -eq 0) { + Write-StyledMessage -Type 'Success' -Text "✅ Criteri di gruppo aggiornati." + } + elseif ($gpupdateResult) { + Write-StyledMessage -Type 'Warning' -Text "⚠️ gpupdate completato con codice: $($gpupdateResult.ExitCode). Proseguo comunque." } else { - Write-StyledMessage -Type 'Warning' -Text "Aggiornamento dei criteri di gruppo completato con codice di uscita: $($gpupdateProcess.ExitCode)." + Write-StyledMessage -Type 'Warning' -Text "⚠️ gpupdate non ha risposto. Proseguo comunque." } } catch { - Write-StyledMessage -Type 'Error' -Text "Errore durante l'aggiornamento dei criteri di gruppo: $($_.Exception.Message)." - Write-StyledMessage -Type 'Warning' -Text "Le modifiche ai criteri potrebbero richiedere un riavvio o del tempo per essere applicate." + Write-StyledMessage -Type 'Warning' -Text "⚠️ Errore blocco WU driver: $($_.Exception.Message). Proseguo comunque." } } - function Download-FileWithProgress { - param( - [Parameter(Mandatory = $true)] - [string]$Url, - [Parameter(Mandatory = $true)] - [string]$DestinationPath, - [Parameter(Mandatory = $true)] - [string]$Description, - [int]$MaxRetries = 3 - ) - Write-StyledMessage -Type 'Info' -Text "Scaricando $Description." - $destDir = Split-Path -Path $DestinationPath -Parent - if (-not (Test-Path $destDir)) { - try { - New-Item -ItemType Directory -Path $destDir -Force *>$null - } - catch { - Write-StyledMessage -Type 'Error' -Text "Impossibile creare la cartella di destinazione '$destDir': $($_.Exception.Message)." - return $false + try { + Write-StyledMessage -Type 'Info' -Text "🚀 Avvio installazione automatica driver video." + Set-BlockWindowsUpdateDrivers + Write-StyledMessage -Type 'Info' -Text "🔍 Rilevamento configurazione GPU in corso..." + $gpuAnalysis = VcardAnalizer + $gpuManufacturer = $gpuAnalysis.PrimaryManufacturer + Write-StyledMessage -Type 'Info' -Text "GPU rilevata: $gpuManufacturer." + $stableDownloadDone = $false + if ($gpuAnalysis.Matches.Count -gt 0) { + foreach ($match in $gpuAnalysis.Matches) { + if ([string]::IsNullOrWhiteSpace($match.DownloadUrl)) { continue } + $targetName = if (-not [string]::IsNullOrWhiteSpace($match.FileName)) { $match.FileName } else { "$($match.Key).exe" } + $targetPath = Join-Path $desktopPath $targetName + $displayName = if (-not [string]::IsNullOrWhiteSpace($match.DisplayName)) { $match.DisplayName } else { $match.Key } + if (Invoke-ToolkitDownload -Uri $match.DownloadUrl -OutputPath $targetPath -Description $displayName) { + Write-StyledMessage -Type 'Success' -Text "Driver stabile scaricato sul desktop: $displayName" + $stableDownloadDone = $true + } } } - for ($attempt = 1; $attempt -le $MaxRetries; $attempt++) { - try { - $webRequest = [System.Net.WebRequest]::Create($Url) - $webResponse = $webRequest.GetResponse() - $totalBytes = $webResponse.ContentLength - $responseStream = $webResponse.GetResponseStream() - $targetStream = [System.IO.FileStream]::new($DestinationPath, [System.IO.FileMode]::Create) - $buffer = New-Object byte[] 64KB - $downloadedBytes = 0 - $bytesRead = 0 - Write-Progress -Activity "Download $Description" -Status "Inizio download." -PercentComplete 0 - do { - $bytesRead = $responseStream.Read($buffer, 0, $buffer.Length) - if ($bytesRead -gt 0) { - $targetStream.Write($buffer, 0, $bytesRead) - $downloadedBytes += $bytesRead - $percentComplete = [System.Math]::Round(($downloadedBytes / $totalBytes) * 100, 1) - $speed = if ($downloadedBytes -gt 0) { [System.Math]::Round(($downloadedBytes / 1024 / 1024), 2) } else { 0 } - $totalSize = [System.Math]::Round(($totalBytes / 1024 / 1024), 2) - Write-Progress -Activity "Download $Description" -Status "$speed MB / $totalSize MB" -PercentComplete $percentComplete + if (-not $stableDownloadDone) { + Write-StyledMessage -Type 'Warning' -Text "Nessun driver stabile conosciuto trovato. Uso fallback autodetect." + switch ($gpuManufacturer) { + 'AMD' { + $amdPath = Join-Path $desktopPath "AMD-Autodetect.exe" + if (-not (Invoke-ToolkitDownload -Uri $AppConfig.URLs.AMDInstaller -OutputPath $amdPath -Description "AMD Auto-Detect Tool")) { + Write-StyledMessage -Type 'Error' -Text "❌ Impossibile scaricare installer AMD. Annullamento." + return } - } while ($bytesRead -gt 0) - Write-Progress -Activity "Download $Description" -Status "Completato" -PercentComplete 100 -Completed - $targetStream.Flush() - $targetStream.Close() - $targetStream.Dispose() - $responseStream.Dispose() - $webResponse.Close() - Write-StyledMessage -Type 'Success' -Text "Download di $Description completato." - return $true - } - catch { - Write-Progress -Activity "Download $Description" -Completed - Write-StyledMessage -Type 'Warning' -Text "Tentativo $attempt fallito per $Description`: $($_.Exception.Message)." - if ($attempt -lt $MaxRetries) { - Start-Sleep -Seconds 2 + } + 'NVIDIA' { + $nvidiaPath = Join-Path $desktopPath "NVCleanstall_1.19.0.exe" + if (-not (Invoke-ToolkitDownload -Uri $AppConfig.URLs.NVCleanstall -OutputPath $nvidiaPath -Description "NVCleanstall")) { + Write-StyledMessage -Type 'Error' -Text "❌ Impossibile scaricare NVCleanstall. Annullamento." + return + } + } + 'Intel' { + Write-StyledMessage -Type 'Info' -Text "GPU Intel: scarica driver manualmente da Intel se necessario." + } + default { + Write-StyledMessage -Type 'Warning' -Text "GPU non rilevata: driver non disponibile per l'installazione automatica." } } } - Write-StyledMessage -Type 'Error' -Text "Errore durante il download di $Description dopo $MaxRetries tentativi." - return $false } - function Handle-InstallVideoDrivers { - Write-StyledMessage -Type 'Info' -Text "Opzione 1: Avvio installazione driver video." - $gpuManufacturer = Get-GpuManufacturer - Write-StyledMessage -Type 'Info' -Text "Rilevata GPU: $gpuManufacturer." - if ($gpuManufacturer -eq 'AMD') { - $amdInstallerUrl = $AppConfig.URLs.AMDInstaller - $amdInstallerPath = Join-Path $DriverToolsLocalPath "AMD-Autodetect.exe" - if (Download-FileWithProgress -Url $amdInstallerUrl -DestinationPath $amdInstallerPath -Description "AMD Auto-Detect Tool") { - Write-StyledMessage -Type 'Info' -Text "Avvio installazione driver video AMD. Premi un tasto per chiudere correttamente il terminale quando l'installazione è completata." - Invoke-WithSpinner -Activity "Esecuzione installer AMD" -Command $amdInstallerPath -LogContextKey "Video-Install-AMD" - Write-StyledMessage -Type 'Success' -Text "Installazione driver video AMD completata o chiusa." - } + catch { + Write-StyledMessage -Type 'Error' -Text "Errore durante installazione driver: $($_.Exception.Message)" + Write-ToolkitLog -Level ERROR -Message "Errore in AutoVideoDriverInstall" -Context @{ + Line = $_.InvocationInfo.ScriptLineNumber + Exception = $_.Exception.GetType().FullName + Stack = $_.ScriptStackTrace } - elseif ($gpuManufacturer -eq 'NVIDIA') { - $nvidiaInstallerUrl = $AppConfig.URLs.NVCleanstall - $nvidiaInstallerPath = Join-Path $DriverToolsLocalPath "NVCleanstall_1.19.0.exe" - if (Download-FileWithProgress -Url $nvidiaInstallerUrl -DestinationPath $nvidiaInstallerPath -Description "NVCleanstall Tool") { - Write-StyledMessage -Type 'Info' -Text "Avvio installazione driver video NVIDIA Ottimizzato. Premi un tasto per chiudere correttamente il terminale quando l'installazione è completata." - Invoke-WithSpinner -Activity "Esecuzione installer NVIDIA" -Command $nvidiaInstallerPath -LogContextKey "Video-Install-NVIDIA" - Write-StyledMessage -Type 'Success' -Text "Installazione driver video NVIDIA completata o chiusa." + } + finally { + Write-StyledMessage -Type 'Success' -Text "🎯 Auto Video Driver Install terminato." + Write-ToolkitLog -Level INFO -Message "AutoVideoDriverInstall sessione terminata." + } +} +function VideoDriverReinstall { + [CmdletBinding()] + param( + [int]$CountdownSeconds = 30, + [switch]$SuppressIndividualReboot + ) + Start-ToolkitSession -ToolName "VideoDriverReinstall" -SubTitle "Video Driver Reinstall" + $driverToolsPath = $AppConfig.Paths.Drivers + $desktopPath = $AppConfig.Paths.Desktop + function Set-BlockWindowsUpdateDrivers { + Write-StyledMessage -Type 'Info' -Text "Blocco driver automatici da Windows Update." + try { + Set-RegistryValue -Path $AppConfig.Registry.WindowsUpdatePolicies -Name "ExcludeWUDriversInQualityUpdate" -Value 1 + Write-StyledMessage -Type 'Success' -Text "Blocco WU driver impostato." + $gpupdateResult = Invoke-WithSpinner -Activity "Aggiornamento criteri di gruppo (può impiegare 1-2 minuti)" -Command 'gpupdate.exe' -Arguments '/force' -LogContextKey "Video-GPUpdate" -TimeoutSeconds 180 + if ($gpupdateResult -and $gpupdateResult.ExitCode -eq 0) { + Write-StyledMessage -Type 'Success' -Text "✅ Criteri di gruppo aggiornati." + } + elseif ($gpupdateResult) { + Write-StyledMessage -Type 'Warning' -Text "⚠️ gpupdate completato con codice: $($gpupdateResult.ExitCode). Proseguo comunque." + } + else { + Write-StyledMessage -Type 'Warning' -Text "⚠️ gpupdate non ha risposto. Proseguo comunque." } } - elseif ($gpuManufacturer -eq 'Intel') { - Write-StyledMessage -Type 'Info' -Text "Rilevata GPU Intel. Utilizza Windows Update per aggiornare i driver integrati." - } - else { - Write-StyledMessage -Type 'Error' -Text "Produttore GPU non supportato o non rilevato per l'installazione automatica dei driver." + catch { + Write-StyledMessage -Type 'Warning' -Text "⚠️ Errore blocco WU driver: $($_.Exception.Message). Proseguo comunque." } } - function Handle-ReinstallRepairVideoDrivers { - Write-StyledMessage -Type 'Warning' -Text "Opzione 2: Avvio procedura di reinstallazione/riparazione driver video. Richiesto riavvio." - $dduZipUrl = $AppConfig.URLs.DDUZip - $dduZipPath = Join-Path $DriverToolsLocalPath "DDU.zip" - if (-not (Download-FileWithProgress -Url $dduZipUrl -DestinationPath $dduZipPath -Description "DDU (Display Driver Uninstaller)")) { - Write-StyledMessage -Type 'Error' -Text "Impossibile scaricare DDU. Annullamento operazione." + $needsReboot = $false + try { + Write-StyledMessage -Type 'Warning' -Text "🔧 Avvio procedura reinstallazione/riparazione driver video." + Set-BlockWindowsUpdateDrivers + Write-StyledMessage -Type 'Info' -Text "📥 Preparazione download strumenti necessari..." + $dduZipPath = Join-Path $driverToolsPath "DDU.zip" + if (-not (Invoke-ToolkitDownload -Uri $AppConfig.URLs.DDUZip -OutputPath $dduZipPath -Description "DDU (Display Driver Uninstaller)")) { + Write-StyledMessage -Type 'Error' -Text "Impossibile scaricare DDU. Annullamento." return } Write-StyledMessage -Type 'Info' -Text "Estrazione DDU sul Desktop." try { - Expand-Archive -Path $dduZipPath -DestinationPath $DesktopPath -Force - Write-StyledMessage -Type 'Success' -Text "DDU estratto correttamente sul Desktop." + Expand-Archive -Path $dduZipPath -DestinationPath $desktopPath -Force + Write-StyledMessage -Type 'Success' -Text "DDU estratto sul Desktop." } catch { - Write-StyledMessage -Type 'Error' -Text "Errore durante l'estrazione di DDU sul Desktop: $($_.Exception.Message)." + Write-StyledMessage -Type 'Error' -Text "Errore estrazione DDU: $($_.Exception.Message)." return } - $gpuManufacturer = Get-GpuManufacturer - Write-StyledMessage -Type 'Info' -Text "Rilevata GPU: $gpuManufacturer." - if ($gpuManufacturer -eq 'AMD') { - $amdInstallerUrl = $AppConfig.URLs.AMDInstaller - $amdInstallerPath = Join-Path $DesktopPath "AMD-Autodetect.exe" - if (-not (Download-FileWithProgress -Url $amdInstallerUrl -DestinationPath $amdInstallerPath -Description "AMD Auto-Detect Tool")) { - Write-StyledMessage -Type 'Error' -Text "Impossibile scaricare l'installer AMD. Annullamento operazione." - return + $gpuAnalysis = VcardAnalizer + $gpuManufacturer = $gpuAnalysis.PrimaryManufacturer + Write-StyledMessage -Type 'Info' -Text "GPU rilevata: $gpuManufacturer." + $stableDownloadDone = $false + if ($gpuAnalysis.Matches.Count -gt 0) { + foreach ($match in $gpuAnalysis.Matches) { + if ([string]::IsNullOrWhiteSpace($match.DownloadUrl)) { continue } + $targetName = if (-not [string]::IsNullOrWhiteSpace($match.FileName)) { $match.FileName } else { "$($match.Key).exe" } + $targetPath = Join-Path $desktopPath $targetName + $displayName = if (-not [string]::IsNullOrWhiteSpace($match.DisplayName)) { $match.DisplayName } else { $match.Key } + if (Invoke-ToolkitDownload -Uri $match.DownloadUrl -OutputPath $targetPath -Description $displayName) { + Write-StyledMessage -Type 'Success' -Text "Driver stabile scaricato sul desktop: $displayName" + $stableDownloadDone = $true + } } } - elseif ($gpuManufacturer -eq 'NVIDIA') { - $nvidiaInstallerUrl = $AppConfig.URLs.NVCleanstall - $nvidiaInstallerPath = Join-Path $DesktopPath "NVCleanstall_1.19.0.exe" - if (-not (Download-FileWithProgress -Url $nvidiaInstallerUrl -DestinationPath $nvidiaInstallerPath -Description "NVCleanstall Tool")) { - Write-StyledMessage -Type 'Error' -Text "Impossibile scaricare l'installer NVIDIA. Annullamento operazione." - return + if (-not $stableDownloadDone) { + Write-StyledMessage -Type 'Warning' -Text "Nessun driver stabile conosciuto trovato. Uso fallback autodetect." + switch ($gpuManufacturer) { + 'AMD' { + $amdPath = Join-Path $desktopPath "AMD-Autodetect.exe" + if (-not (Invoke-ToolkitDownload -Uri $AppConfig.URLs.AMDInstaller -OutputPath $amdPath -Description "AMD Auto-Detect Tool")) { + Write-StyledMessage -Type 'Error' -Text "❌ Impossibile scaricare installer AMD. Annullamento." + return + } + } + 'NVIDIA' { + $nvidiaPath = Join-Path $desktopPath "NVCleanstall_1.19.0.exe" + if (-not (Invoke-ToolkitDownload -Uri $AppConfig.URLs.NVCleanstall -OutputPath $nvidiaPath -Description "NVCleanstall")) { + Write-StyledMessage -Type 'Error' -Text "❌ Impossibile scaricare NVCleanstall. Annullamento." + return + } + } + 'Intel' { + Write-StyledMessage -Type 'Info' -Text "GPU Intel: scarica driver manualmente da Intel se necessario." + } + default { + Write-StyledMessage -Type 'Warning' -Text "GPU non rilevata: solo DDU verrà posizionato sul Desktop." + } } } - elseif ($gpuManufacturer -eq 'Intel') { - Write-StyledMessage -Type 'Info' -Text "Rilevata GPU Intel. Scarica manualmente i driver da Intel se necessario." - } - else { - Write-StyledMessage -Type 'Warning' -Text "Produttore GPU non supportato o non rilevato. Verrà posizionato solo DDU sul desktop." - } - Write-StyledMessage -Type 'Info' -Text "DDU e l'installer dei Driver (se rilevato) sono stati posizionati sul desktop." - $batchFilePath = Join-Path $DesktopPath "Switch to Normal Mode.bat" + $batchPath = Join-Path $desktopPath "Switch to Normal Mode.bat" try { - Set-Content -Path $batchFilePath -Value 'bcdedit /deletevalue {current} safeboot' -Encoding ASCII - Write-StyledMessage -Type 'Info' -Text "File batch 'Switch to Normal Mode.bat' creato sul desktop per disabilitare la Modalità Provvisoria." + Set-Content -Path $batchPath -Value 'bcdedit /deletevalue {current} safeboot' -Encoding ASCII + Write-StyledMessage -Type 'Info' -Text "Batch 'Switch to Normal Mode.bat' creato sul Desktop." } catch { - Write-StyledMessage -Type 'Warning' -Text "Impossibile creare il file batch: $($_.Exception.Message)." + Write-StyledMessage -Type 'Warning' -Text "Impossibile creare batch Safe Mode: $($_.Exception.Message)." } - Write-StyledMessage -Type 'Error' -Text "ATTENZIONE: Il sistema sta per riavviarsi in modalità provvisoria." - Write-StyledMessage -Type 'Info' -Text "Configurazione del sistema per l'avvio automatico in Modalità Provvisoria." + Write-StyledMessage -Type 'Error' -Text "ATTENZIONE: Il sistema si riavvierà in modalità provvisoria." + Write-StyledMessage -Type 'Info' -Text "In Safe Mode: esegui DDU per pulire i driver, poi reinstalla con l'installer sul Desktop. Infine usa il batch per tornare alla modalità normale." try { - Invoke-WithSpinner -Activity "Configurazione bcdedit" -Command 'bcdedit.exe' -Arguments '/set {current} safeboot minimal' -LogContextKey "Video-BCDEdit" - Write-StyledMessage -Type 'Success' -Text "Modalità Provvisoria configurata per il prossimo avvio." + $null = Invoke-WithSpinner -Activity "Configurazione Safe Mode (bcdedit)" -Command 'bcdedit.exe' ` + -Arguments '/set {current} safeboot minimal' -LogContextKey "Video-BCDEdit" + Write-StyledMessage -Type 'Success' -Text "Modalità provvisoria configurata per il prossimo avvio." + $needsReboot = $true } catch { - Write-StyledMessage -Type 'Error' -Text "Errore durante la configurazione della Modalità Provvisoria tramite bcdedit: $($_.Exception.Message)." - Write-StyledMessage -Type 'Warning' -Text "Il riavvio potrebbe non avvenire in Modalità Provvisoria. Procedere manualmente." - return - } - if ($SuppressIndividualReboot) { - $Global:NeedsFinalReboot = $true - Write-StyledMessage -Type 'Info' -Text "🚫 Riavvio in modalità provvisoria soppresso (esecuzione concatenata)." - Write-StyledMessage -Type 'Warning' -Text "⚠️ DDU e installer driver sono sul Desktop. Al prossimo riavvio sarai in SAFE MODE." - } - else { - $shouldReboot = Start-InterruptibleCountdown -Seconds 30 -Message "Riavvio in modalità provvisoria in corso." - if ($shouldReboot) { - try { - Restart-Computer -Force - Write-StyledMessage -Type 'Success' -Text "Comando di riavvio inviato." - } - catch { - Write-StyledMessage -Type 'Error' -Text "Errore durante l'esecuzione del comando di riavvio: $($_.Exception.Message)." - } - } + Write-StyledMessage -Type 'Error' -Text "Errore configurazione Safe Mode: $($_.Exception.Message)." } } - Write-StyledMessage -Type 'Info' -Text '🔧 Inizializzazione dello Script di Installazione Driver Video.' - Start-Sleep -Seconds 2 - Set-BlockWindowsUpdateDrivers - $choice = "" - do { - Write-StyledMessage -Type 'Info' -Text 'Seleziona un''opzione:' - Write-StyledMessage -Type 'Info' -Text ' [1] 🚀 Installa Driver Video (Rilevamento Automatico)' - Write-StyledMessage -Type 'Info' -Text ' [2] 🔧 Reinstalla/Ripara Driver Video (Richiede Riavvio in Safe Mode)' - Write-StyledMessage -Type 'Info' -Text ' [0] ❌ Torna al Menu Principale' - $selections = Read-ValidatedChoice -Min 0 -Max 2 -Prompt "La tua scelta" - $choice = $selections[0] - switch ($choice.ToUpper()) { - "1" { Handle-InstallVideoDrivers } - "2" { Handle-ReinstallRepairVideoDrivers } - "0" { Write-StyledMessage -Type 'Info' -Text 'Tornando al menu principale.' } - default { Write-StyledMessage -Type 'Warning' -Text "Scelta non valida. Riprova." } - } - if ($choice.ToUpper() -ne "0") { - $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') - Clear-Host - Show-Header -SubTitle "Video Driver Install Toolkit" + catch { + Write-StyledMessage -Type 'Error' -Text "Errore critico durante reinstallazione driver: $($_.Exception.Message)" + Write-ToolkitLog -Level ERROR -Message "Errore in VideoDriverReinstall" -Context @{ + Line = $_.InvocationInfo.ScriptLineNumber + Exception = $_.Exception.GetType().FullName + Stack = $_.ScriptStackTrace } - } while ($choice.ToUpper() -ne "0") + } + finally { + Write-StyledMessage -Type 'Success' -Text "🎯 Video Driver Reinstall terminato." + Write-ToolkitLog -Level INFO -Message "VideoDriverReinstall sessione terminata." + } + if ($needsReboot) { + Invoke-ToolkitReboot -Message "Riavvio in Safe Mode per DDU" -Seconds $CountdownSeconds -SuppressIndividualReboot:$SuppressIndividualReboot + } } function GamingToolkit { [CmdletBinding()] param( + [Parameter()] + [ValidateRange(0, 300)] [int]$CountdownSeconds = 30, [switch]$SuppressIndividualReboot ) @@ -4285,27 +4437,28 @@ function WinExportLog { } $menuStructure = @( @{ 'Name' = 'Windows'; 'Icon' = '🔧'; 'Scripts' = @( - [pscustomobject]@{Name = 'WinRepairToolkit'; Description = 'Riparazione Windows'; Action = 'RunFunction' }, - [pscustomobject]@{Name = 'WinUpdateReset'; Description = 'Reset Windows Update'; Action = 'RunFunction' }, - [pscustomobject]@{Name = 'WinReinstallStore';Description = 'Winget/WinStore Reset'; Action = 'RunFunction' }, - [pscustomobject]@{Name = 'WinBackupDriver'; Description = 'Backup Driver PC'; Action = 'RunFunction' }, - [pscustomobject]@{Name = 'WinCleaner'; Description = 'Pulizia File Temporanei'; Action = 'RunFunction' }, - [pscustomobject]@{Name = 'DisableBitlocker'; Description = 'Disabilita Bitlocker'; Action = 'RunFunction' } + [pscustomobject]@{Name = 'WinRepairToolkit'; Description = 'Riparazione Windows'; Action = 'RunFunction' }, + [pscustomobject]@{Name = 'WinUpdateReset'; Description = 'Reset Windows Update'; Action = 'RunFunction' }, + [pscustomobject]@{Name = 'WinReinstallStore'; Description = 'Winget/WinStore Reset'; Action = 'RunFunction' }, + [pscustomobject]@{Name = 'WinBackupDriver'; Description = 'Backup Driver PC'; Action = 'RunFunction' }, + [pscustomobject]@{Name = 'WinCleaner'; Description = 'Pulizia File Temporanei'; Action = 'RunFunction' }, + [pscustomobject]@{Name = 'DisableBitlocker'; Description = 'Disabilita Bitlocker'; Action = 'RunFunction' } ) }, @{ 'Name' = 'Office'; 'Icon' = '🏢'; 'Scripts' = @( - [pscustomobject]@{Name = 'Install-Office'; Description = 'Installa Office Basic'; Action = 'RunFunction' }, - [pscustomobject]@{Name = 'Repair-Office'; Description = 'Ripara Office'; Action = 'RunFunction' }, - [pscustomobject]@{Name = 'Uninstall-Office'; Description = 'Rimuovi Office'; Action = 'RunFunction' } + [pscustomobject]@{Name = 'Install-Office'; Description = 'Installa Office Basic'; Action = 'RunFunction' }, + [pscustomobject]@{Name = 'Repair-Office'; Description = 'Ripara Office'; Action = 'RunFunction' }, + [pscustomobject]@{Name = 'Uninstall-Office'; Description = 'Rimuovi Office'; Action = 'RunFunction' } ) }, @{ 'Name' = 'Driver & Gaming'; 'Icon' = '🎮'; 'Scripts' = @( - [pscustomobject]@{Name = 'VideoDriverInstall';Description = 'Driver Video Toolkit'; Action = 'RunFunction' }, - [pscustomobject]@{Name = 'GamingToolkit'; Description = 'Gaming Toolkit'; Action = 'RunFunction' } + [pscustomobject]@{Name = 'AutoVideoDriverInstall'; Description = 'Installa Driver Video (Auto)'; Action = 'RunFunction' }, + [pscustomobject]@{Name = 'VideoDriverReinstall'; Description = 'Reinstalla Driver Video (Safe Mode)'; Action = 'RunFunction' }, + [pscustomobject]@{Name = 'GamingToolkit'; Description = 'Gaming Toolkit'; Action = 'RunFunction' } ) }, @{ 'Name' = 'Supporto'; 'Icon' = '🕹️'; 'Scripts' = @( - [pscustomobject]@{Name = 'WinExportLog'; Description = 'Esporta Log WinToolkit'; Action = 'RunFunction' } + [pscustomobject]@{Name = 'WinExportLog'; Description = 'Esporta Log WinToolkit'; Action = 'RunFunction' } ) } ) @@ -4335,13 +4488,13 @@ if (-not $ImportOnly -and -not $Global:GuiSessionActive) { Write-Host "🔧 Nome PC: $($si.ComputerName)" -ForegroundColor White Write-Host "🧠 RAM: $($si.TotalRAM) GB" -ForegroundColor White Write-Host "💾 Disco: " -NoNewline -ForegroundColor White - $diskFreeGB = $si.FreeDisk + $diskFreeGB = $si.FreeDisk $displayString = "$($si.FreePercentage)% Libero ($($diskFreeGB) GB)" - $diskColor = if ($diskFreeGB -lt 50) { "Red" } elseif ($diskFreeGB -le 80) { "Yellow" } else { "Green" } + $diskColor = if ($diskFreeGB -lt 50) { "Red" } elseif ($diskFreeGB -le 80) { "Yellow" } else { "Green" } Write-Host $displayString -ForegroundColor $diskColor -NoNewline Write-Host "" $blStatus = Get-BitlockerStatus - $blColor = if ($blStatus -match 'Disattivato|Non configurato|Off') { 'Green' } else { 'Red' } + $blColor = if ($blStatus -match 'Disattivato|Non configurato|Off') { 'Green' } else { 'Red' } Write-Host "🔒 Stato Bitlocker: " -NoNewline -ForegroundColor White Write-Host "$blStatus" -ForegroundColor $blColor } @@ -4382,9 +4535,9 @@ if (-not $ImportOnly -and -not $Global:GuiSessionActive) { Start-Sleep -Seconds 2 continue } - $Global:ExecutionLog = @() - $Global:NeedsFinalReboot = $false - $isMultiScript = ($selections.Count -gt 1) + $Global:ExecutionLog = @() + $Global:NeedsFinalReboot = $false + $isMultiScript = ($selections.Count -gt 1) Write-Host '' if ($isMultiScript) { Write-StyledMessage -Type 'Info' -Text "🚀 Esecuzione sequenziale di $($selections.Count) operazioni..." @@ -4396,7 +4549,7 @@ if (-not $ImportOnly -and -not $Global:GuiSessionActive) { Write-Host '' try { if ($isMultiScript) { & ([scriptblock]::Create("$($scriptToRun.Name) -SuppressIndividualReboot")) } - else { & $ExecutionContext.InvokeCommand.GetCommand($scriptToRun.Name, 'Function') } + else { & $ExecutionContext.InvokeCommand.GetCommand($scriptToRun.Name, 'Function') } $Global:ExecutionLog += @{ Name = $scriptToRun.Description; Success = $true } } catch { @@ -4412,8 +4565,8 @@ if (-not $ImportOnly -and -not $Global:GuiSessionActive) { } Show-ConsoleTable -Rows $tableRows -Columns @( @{ Header = 'Operazione'; Key = 'Operazione' }, - @{ Header = 'Stato'; Key = 'Stato' }, - @{ Header = 'Dettaglio'; Key = 'Dettaglio' } + @{ Header = 'Stato'; Key = 'Stato' }, + @{ Header = 'Dettaglio'; Key = 'Dettaglio' } ) -Title '📊 Riepilogo Esecuzione' Write-Host '' } @@ -4438,4 +4591,4 @@ else { Write-Verbose " 💎 Versione: $ToolkitVersion" Write-Verbose "═══════════════════════════════════════════════════════════" $Global:menuStructure = $menuStructure -} \ No newline at end of file +} diff --git a/asset/DriverOverrides.json b/asset/DriverOverrides.json new file mode 100644 index 0000000..af1a066 --- /dev/null +++ b/asset/DriverOverrides.json @@ -0,0 +1,11 @@ +[ + { + "Key": "AMD_RX_580", + "Manufacturer": "AMD", + "NamePattern": "RX 580|RX-580|Radeon RX 580", + "PnpIdPattern": "PCI\\VEN_1002&DEV_67DF*", + "DownloadUrl": "https://drivers.amd.com/drivers/whql-amd-software-adrenalin-edition-23.8.2-win10-win11-aug31.exe", + "FileName": "AMD-RX580-23.8.2-WHQL.exe", + "DisplayName": "AMD RX 580 Stable Driver (23.8.2 WHQL)" + } +] diff --git a/tool/AutoVideoDriverInstall.ps1 b/tool/AutoVideoDriverInstall.ps1 index 2020192..942e395 100644 --- a/tool/AutoVideoDriverInstall.ps1 +++ b/tool/AutoVideoDriverInstall.ps1 @@ -13,71 +13,76 @@ function AutoVideoDriverInstall { Start-ToolkitSession -ToolName "AutoVideoDriverInstall" -SubTitle "Auto Video Driver Install" - $driverToolsPath = $AppConfig.Paths.Drivers - - function Get-GpuManufacturer { - $pnpDevices = Get-PnpDevice -Class Display -ErrorAction SilentlyContinue - if (-not $pnpDevices) { - Write-StyledMessage -Type 'Warning' -Text "Nessun dispositivo display PnP rilevato." - return 'Unknown' - } - foreach ($device in $pnpDevices) { - $name = $device.FriendlyName - $mfr = $device.Manufacturer - if ($name -match 'NVIDIA|GeForce|Quadro|Tesla' -or $mfr -match 'NVIDIA') { return 'NVIDIA' } - if ($name -match 'AMD|Radeon|ATI' -or $mfr -match 'AMD|ATI') { return 'AMD' } - if ($name -match 'Intel|Iris|UHD|HD Graphics' -or $mfr -match 'Intel') { return 'Intel' } - } - return 'Unknown' - } + $desktopPath = $AppConfig.Paths.Desktop function Set-BlockWindowsUpdateDrivers { Write-StyledMessage -Type 'Info' -Text "Blocco driver automatici da Windows Update." try { Set-RegistryValue -Path $AppConfig.Registry.WindowsUpdatePolicies -Name "ExcludeWUDriversInQualityUpdate" -Value 1 Write-StyledMessage -Type 'Success' -Text "Blocco WU driver impostato." - $gpupdateResult = Invoke-WithSpinner -Activity "Aggiornamento criteri di gruppo" -Command 'gpupdate.exe' -Arguments '/force' -LogContextKey "Video-GPUpdate" - if ($gpupdateResult.ExitCode -eq 0) { - Write-StyledMessage -Type 'Success' -Text "Criteri di gruppo aggiornati." + $gpupdateResult = Invoke-WithSpinner -Activity "Aggiornamento criteri di gruppo (può impiegare 1-2 minuti)" -Command 'gpupdate.exe' -Arguments '/force' -LogContextKey "Video-GPUpdate" -TimeoutSeconds 180 + if ($gpupdateResult -and $gpupdateResult.ExitCode -eq 0) { + Write-StyledMessage -Type 'Success' -Text "✅ Criteri di gruppo aggiornati." + } + elseif ($gpupdateResult) { + Write-StyledMessage -Type 'Warning' -Text "⚠️ gpupdate completato con codice: $($gpupdateResult.ExitCode). Proseguo comunque." } else { - Write-StyledMessage -Type 'Warning' -Text "gpupdate completato con codice: $($gpupdateResult.ExitCode)." + Write-StyledMessage -Type 'Warning' -Text "⚠️ gpupdate non ha risposto. Proseguo comunque." } } catch { - Write-StyledMessage -Type 'Warning' -Text "Errore blocco WU driver: $($_.Exception.Message)." + Write-StyledMessage -Type 'Warning' -Text "⚠️ Errore blocco WU driver: $($_.Exception.Message). Proseguo comunque." } } try { Write-StyledMessage -Type 'Info' -Text "🚀 Avvio installazione automatica driver video." Set-BlockWindowsUpdateDrivers - - $gpuManufacturer = Get-GpuManufacturer + + Write-StyledMessage -Type 'Info' -Text "🔍 Rilevamento configurazione GPU in corso..." + $gpuAnalysis = VcardAnalizer + $gpuManufacturer = $gpuAnalysis.PrimaryManufacturer Write-StyledMessage -Type 'Info' -Text "GPU rilevata: $gpuManufacturer." - switch ($gpuManufacturer) { - 'AMD' { - $amdPath = Join-Path $driverToolsPath "AMD-Autodetect.exe" - if (Invoke-ToolkitDownload -Uri $AppConfig.URLs.AMDInstaller -OutputPath $amdPath -Description "AMD Auto-Detect Tool") { - Write-StyledMessage -Type 'Info' -Text "Avvio installer AMD. Chiudi il terminale al termine dell'installazione." - $null = Invoke-WithSpinner -Activity "Esecuzione installer AMD" -Command $amdPath -LogContextKey "Video-Install-AMD" - Write-StyledMessage -Type 'Success' -Text "Installazione driver AMD completata." + $stableDownloadDone = $false + if ($gpuAnalysis.Matches.Count -gt 0) { + foreach ($match in $gpuAnalysis.Matches) { + if ([string]::IsNullOrWhiteSpace($match.DownloadUrl)) { continue } + $targetName = if (-not [string]::IsNullOrWhiteSpace($match.FileName)) { $match.FileName } else { "$($match.Key).exe" } + $targetPath = Join-Path $desktopPath $targetName + $displayName = if (-not [string]::IsNullOrWhiteSpace($match.DisplayName)) { $match.DisplayName } else { $match.Key } + + if (Invoke-ToolkitDownload -Uri $match.DownloadUrl -OutputPath $targetPath -Description $displayName) { + Write-StyledMessage -Type 'Success' -Text "Driver stabile scaricato sul desktop: $displayName" + $stableDownloadDone = $true } } - 'NVIDIA' { - $nvidiaPath = Join-Path $driverToolsPath "NVCleanstall_1.19.0.exe" - if (Invoke-ToolkitDownload -Uri $AppConfig.URLs.NVCleanstall -OutputPath $nvidiaPath -Description "NVCleanstall") { - Write-StyledMessage -Type 'Info' -Text "Avvio NVCleanstall. Chiudi il terminale al termine dell'installazione." - $null = Invoke-WithSpinner -Activity "Esecuzione NVCleanstall" -Command $nvidiaPath -LogContextKey "Video-Install-NVIDIA" - Write-StyledMessage -Type 'Success' -Text "Installazione driver NVIDIA completata." + } + + if (-not $stableDownloadDone) { + Write-StyledMessage -Type 'Warning' -Text "Nessun driver stabile conosciuto trovato. Uso fallback autodetect." + switch ($gpuManufacturer) { + 'AMD' { + $amdPath = Join-Path $desktopPath "AMD-Autodetect.exe" + if (-not (Invoke-ToolkitDownload -Uri $AppConfig.URLs.AMDInstaller -OutputPath $amdPath -Description "AMD Auto-Detect Tool")) { + Write-StyledMessage -Type 'Error' -Text "❌ Impossibile scaricare installer AMD. Annullamento." + return + } + } + 'NVIDIA' { + $nvidiaPath = Join-Path $desktopPath "NVCleanstall_1.19.0.exe" + if (-not (Invoke-ToolkitDownload -Uri $AppConfig.URLs.NVCleanstall -OutputPath $nvidiaPath -Description "NVCleanstall")) { + Write-StyledMessage -Type 'Error' -Text "❌ Impossibile scaricare NVCleanstall. Annullamento." + return + } + } + 'Intel' { + Write-StyledMessage -Type 'Info' -Text "GPU Intel: scarica driver manualmente da Intel se necessario." + } + default { + Write-StyledMessage -Type 'Warning' -Text "GPU non rilevata: driver non disponibile per l'installazione automatica." } - } - 'Intel' { - Write-StyledMessage -Type 'Info' -Text "GPU Intel rilevata. Usa Windows Update per aggiornare i driver integrati." - } - default { - Write-StyledMessage -Type 'Error' -Text "Produttore GPU non supportato o non rilevato per l'installazione automatica." } } } diff --git a/tool/GamingToolkit.ps1 b/tool/GamingToolkit.ps1 index 280bde2..c6b64a0 100644 --- a/tool/GamingToolkit.ps1 +++ b/tool/GamingToolkit.ps1 @@ -16,6 +16,8 @@ function GamingToolkit { [CmdletBinding()] param( + [Parameter()] + [ValidateRange(0, 300)] [int]$CountdownSeconds = 30, [switch]$SuppressIndividualReboot ) diff --git a/tool/VideoDriverReinstall.ps1 b/tool/VideoDriverReinstall.ps1 index 988261b..7646241 100644 --- a/tool/VideoDriverReinstall.ps1 +++ b/tool/VideoDriverReinstall.ps1 @@ -17,37 +17,24 @@ function VideoDriverReinstall { $driverToolsPath = $AppConfig.Paths.Drivers $desktopPath = $AppConfig.Paths.Desktop - function Get-GpuManufacturer { - $pnpDevices = Get-PnpDevice -Class Display -ErrorAction SilentlyContinue - if (-not $pnpDevices) { - Write-StyledMessage -Type 'Warning' -Text "Nessun dispositivo display PnP rilevato." - return 'Unknown' - } - foreach ($device in $pnpDevices) { - $name = $device.FriendlyName - $mfr = $device.Manufacturer - if ($name -match 'NVIDIA|GeForce|Quadro|Tesla' -or $mfr -match 'NVIDIA') { return 'NVIDIA' } - if ($name -match 'AMD|Radeon|ATI' -or $mfr -match 'AMD|ATI') { return 'AMD' } - if ($name -match 'Intel|Iris|UHD|HD Graphics' -or $mfr -match 'Intel') { return 'Intel' } - } - return 'Unknown' - } - function Set-BlockWindowsUpdateDrivers { Write-StyledMessage -Type 'Info' -Text "Blocco driver automatici da Windows Update." try { Set-RegistryValue -Path $AppConfig.Registry.WindowsUpdatePolicies -Name "ExcludeWUDriversInQualityUpdate" -Value 1 Write-StyledMessage -Type 'Success' -Text "Blocco WU driver impostato." - $gpupdateResult = Invoke-WithSpinner -Activity "Aggiornamento criteri di gruppo" -Command 'gpupdate.exe' -Arguments '/force' -LogContextKey "Video-GPUpdate" - if ($gpupdateResult.ExitCode -eq 0) { - Write-StyledMessage -Type 'Success' -Text "Criteri di gruppo aggiornati." + $gpupdateResult = Invoke-WithSpinner -Activity "Aggiornamento criteri di gruppo (può impiegare 1-2 minuti)" -Command 'gpupdate.exe' -Arguments '/force' -LogContextKey "Video-GPUpdate" -TimeoutSeconds 180 + if ($gpupdateResult -and $gpupdateResult.ExitCode -eq 0) { + Write-StyledMessage -Type 'Success' -Text "✅ Criteri di gruppo aggiornati." + } + elseif ($gpupdateResult) { + Write-StyledMessage -Type 'Warning' -Text "⚠️ gpupdate completato con codice: $($gpupdateResult.ExitCode). Proseguo comunque." } else { - Write-StyledMessage -Type 'Warning' -Text "gpupdate completato con codice: $($gpupdateResult.ExitCode)." + Write-StyledMessage -Type 'Warning' -Text "⚠️ gpupdate non ha risposto. Proseguo comunque." } } catch { - Write-StyledMessage -Type 'Warning' -Text "Errore blocco WU driver: $($_.Exception.Message)." + Write-StyledMessage -Type 'Warning' -Text "⚠️ Errore blocco WU driver: $($_.Exception.Message). Proseguo comunque." } } @@ -56,7 +43,8 @@ function VideoDriverReinstall { try { Write-StyledMessage -Type 'Warning' -Text "🔧 Avvio procedura reinstallazione/riparazione driver video." Set-BlockWindowsUpdateDrivers - + + Write-StyledMessage -Type 'Info' -Text "📥 Preparazione download strumenti necessari..." # Download e estrazione DDU $dduZipPath = Join-Path $driverToolsPath "DDU.zip" if (-not (Invoke-ToolkitDownload -Uri $AppConfig.URLs.DDUZip -OutputPath $dduZipPath -Description "DDU (Display Driver Uninstaller)")) { @@ -75,29 +63,48 @@ function VideoDriverReinstall { } # Download installer driver rilevato (sul Desktop per uso in Safe Mode) - $gpuManufacturer = Get-GpuManufacturer + $gpuAnalysis = VcardAnalizer + $gpuManufacturer = $gpuAnalysis.PrimaryManufacturer Write-StyledMessage -Type 'Info' -Text "GPU rilevata: $gpuManufacturer." - switch ($gpuManufacturer) { - 'AMD' { - $amdPath = Join-Path $desktopPath "AMD-Autodetect.exe" - if (-not (Invoke-ToolkitDownload -Uri $AppConfig.URLs.AMDInstaller -OutputPath $amdPath -Description "AMD Auto-Detect Tool")) { - Write-StyledMessage -Type 'Error' -Text "Impossibile scaricare installer AMD. Annullamento." - return + $stableDownloadDone = $false + if ($gpuAnalysis.Matches.Count -gt 0) { + foreach ($match in $gpuAnalysis.Matches) { + if ([string]::IsNullOrWhiteSpace($match.DownloadUrl)) { continue } + $targetName = if (-not [string]::IsNullOrWhiteSpace($match.FileName)) { $match.FileName } else { "$($match.Key).exe" } + $targetPath = Join-Path $desktopPath $targetName + $displayName = if (-not [string]::IsNullOrWhiteSpace($match.DisplayName)) { $match.DisplayName } else { $match.Key } + + if (Invoke-ToolkitDownload -Uri $match.DownloadUrl -OutputPath $targetPath -Description $displayName) { + Write-StyledMessage -Type 'Success' -Text "Driver stabile scaricato sul desktop: $displayName" + $stableDownloadDone = $true } } - 'NVIDIA' { - $nvidiaPath = Join-Path $desktopPath "NVCleanstall_1.19.0.exe" - if (-not (Invoke-ToolkitDownload -Uri $AppConfig.URLs.NVCleanstall -OutputPath $nvidiaPath -Description "NVCleanstall")) { - Write-StyledMessage -Type 'Error' -Text "Impossibile scaricare NVCleanstall. Annullamento." - return + } + + if (-not $stableDownloadDone) { + Write-StyledMessage -Type 'Warning' -Text "Nessun driver stabile conosciuto trovato. Uso fallback autodetect." + switch ($gpuManufacturer) { + 'AMD' { + $amdPath = Join-Path $desktopPath "AMD-Autodetect.exe" + if (-not (Invoke-ToolkitDownload -Uri $AppConfig.URLs.AMDInstaller -OutputPath $amdPath -Description "AMD Auto-Detect Tool")) { + Write-StyledMessage -Type 'Error' -Text "❌ Impossibile scaricare installer AMD. Annullamento." + return + } + } + 'NVIDIA' { + $nvidiaPath = Join-Path $desktopPath "NVCleanstall_1.19.0.exe" + if (-not (Invoke-ToolkitDownload -Uri $AppConfig.URLs.NVCleanstall -OutputPath $nvidiaPath -Description "NVCleanstall")) { + Write-StyledMessage -Type 'Error' -Text "❌ Impossibile scaricare NVCleanstall. Annullamento." + return + } + } + 'Intel' { + Write-StyledMessage -Type 'Info' -Text "GPU Intel: scarica driver manualmente da Intel se necessario." + } + default { + Write-StyledMessage -Type 'Warning' -Text "GPU non rilevata: solo DDU verrà posizionato sul Desktop." } - } - 'Intel' { - Write-StyledMessage -Type 'Info' -Text "GPU Intel: scarica driver manualmente da Intel se necessario." - } - default { - Write-StyledMessage -Type 'Warning' -Text "GPU non rilevata: solo DDU verrà posizionato sul Desktop." } }