# =============================================================================
# Diagnostica Windows - Strumento avanzato con GUI WPF
# Compatibile con Windows 10 e Windows 11
#
# Avvio rapido (da PowerShell normale, non serve admin):
# irm https://get.23022.it | iex
#
# Lo script si auto-eleva via UAC e si rilancia con -ExecutionPolicy Bypass
# Nessuna modifica permanente al sistema (eccetto reset rete su richiesta)
# =============================================================================
# ---- Auto-bootstrap: garantisce admin + STA + Bypass policy ----------------
$ScriptUrl = 'https://get.23022.it'
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
[Security.Principal.WindowsBuiltInRole]::Administrator)
$isStaThread = ([Threading.Thread]::CurrentThread.GetApartmentState() -eq 'STA')
if (-not $isAdmin -or -not $isStaThread) {
Write-Host ""
Write-Host " Diagnostica Windows" -ForegroundColor Cyan
if (-not $isAdmin) {
Write-Host " Richiesta elevazione amministratore via UAC..." -ForegroundColor Gray
} else {
Write-Host " Riavvio in modalita STA (richiesto per WPF)..." -ForegroundColor Gray
}
Write-Host ""
$tempPath = Join-Path $env:TEMP ("Diagnostica-Windows-{0}.ps1" -f ([Guid]::NewGuid().ToString('N').Substring(0,8)))
try {
$oldTls = [Net.ServicePointManager]::SecurityProtocol
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Invoke-RestMethod -Uri $ScriptUrl -OutFile $tempPath -UseBasicParsing -ErrorAction Stop
[Net.ServicePointManager]::SecurityProtocol = $oldTls
} catch {
Write-Host (" Impossibile scaricare lo script da " + $ScriptUrl) -ForegroundColor Red
Write-Host (" " + $_.Exception.Message) -ForegroundColor Red
Write-Host ""
Read-Host "Premi INVIO per chiudere"
return
}
$argList = @(
'-NoProfile'
'-ExecutionPolicy', 'Bypass'
'-STA'
'-WindowStyle', 'Hidden'
'-File', ('"' + $tempPath + '"')
)
try {
if (-not $isAdmin) {
Start-Process -FilePath 'powershell.exe' -ArgumentList $argList -Verb RunAs -ErrorAction Stop
} else {
Start-Process -FilePath 'powershell.exe' -ArgumentList $argList -ErrorAction Stop
}
} catch {
Write-Host " Elevazione annullata dall'utente." -ForegroundColor Yellow
}
return
}
# ----------------------------------------------------------------------------
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8
$ErrorActionPreference = 'Continue'
Add-Type -AssemblyName PresentationFramework
Add-Type -AssemblyName PresentationCore
Add-Type -AssemblyName WindowsBase
Add-Type -AssemblyName System.Windows.Forms
# -----------------------------------------------------------------------------
# XAML - definizione interfaccia
# -----------------------------------------------------------------------------
[xml]$xaml = @'
Pronto.
Seleziona uno strumento dalla barra laterale per iniziare, oppure clicca DIAGNOSTICA COMPLETA per eseguire tutte le verifiche.
'@
# -----------------------------------------------------------------------------
# Carica XAML e bind controlli
# -----------------------------------------------------------------------------
$reader = New-Object System.Xml.XmlNodeReader $xaml
$window = [Windows.Markup.XamlReader]::Load($reader)
$ctrl = @{}
foreach ($name in @(
'TitleBar','BtnMinimize','BtnMaximize','BtnClose',
'BtnSysInfo','BtnSFC','BtnDISM','BtnCHKDSK','BtnEvents','BtnDrivers','BtnPerf','BtnTasks','BtnBootTime','BtnBSOD',
'BtnBitLocker','BtnActivation','BtnUpdates','BtnFirewall','BtnFailedLogons','BtnTPM',
'BtnPrograms','BtnServices','BtnHosts','BtnBrowser',
'BtnNetFull','BtnNetConfig','BtnPing','BtnDNS','BtnMTU','BtnTrace','BtnWiFi','BtnPorts','BtnTTFB','BtnAntivirus',
'BtnDisk','BtnMemory','BtnGPU','BtnBattery','BtnTemp','BtnMonitors','BtnAudio','BtnUSB',
'BtnFlushDNS','BtnCleanTemp','BtnWinSxS','BtnBrowserCache','BtnResetNet',
'BtnRunAll','BtnStop','BtnSave','BtnClear','BtnFull',
'LblTitle','LblSubtitle','LblStatus','Progress','OutputBox',
'LogoSmall','LogoFallback','LogoBig'
)) {
$ctrl[$name] = $window.FindName($name)
}
# -----------------------------------------------------------------------------
# Mappa key -> pulsante sidebar (per evidenziare la selezione corrente)
# -----------------------------------------------------------------------------
$script:btnMap = @{
'full' = $ctrl.BtnFull
'sysinfo' = $ctrl.BtnSysInfo
'sfc' = $ctrl.BtnSFC
'dism' = $ctrl.BtnDISM
'chkdsk' = $ctrl.BtnCHKDSK
'events' = $ctrl.BtnEvents
'drivers' = $ctrl.BtnDrivers
'perf' = $ctrl.BtnPerf
'tasks' = $ctrl.BtnTasks
'netfull' = $ctrl.BtnNetFull
'netconfig' = $ctrl.BtnNetConfig
'ping' = $ctrl.BtnPing
'dns' = $ctrl.BtnDNS
'mtu' = $ctrl.BtnMTU
'trace' = $ctrl.BtnTrace
'wifi' = $ctrl.BtnWiFi
'ports' = $ctrl.BtnPorts
'ttfb' = $ctrl.BtnTTFB
'antivirus' = $ctrl.BtnAntivirus
'disk' = $ctrl.BtnDisk
'memory' = $ctrl.BtnMemory
'gpu' = $ctrl.BtnGPU
'battery' = $ctrl.BtnBattery
'flushdns' = $ctrl.BtnFlushDNS
'cleantemp' = $ctrl.BtnCleanTemp
'resetnet' = $ctrl.BtnResetNet
'boottime' = $ctrl.BtnBootTime
'bsod' = $ctrl.BtnBSOD
'bitlocker' = $ctrl.BtnBitLocker
'activation' = $ctrl.BtnActivation
'updates' = $ctrl.BtnUpdates
'firewall' = $ctrl.BtnFirewall
'failedlogons'= $ctrl.BtnFailedLogons
'tpm' = $ctrl.BtnTPM
'programs' = $ctrl.BtnPrograms
'services' = $ctrl.BtnServices
'hosts' = $ctrl.BtnHosts
'browser' = $ctrl.BtnBrowser
'temp' = $ctrl.BtnTemp
'monitors' = $ctrl.BtnMonitors
'audio' = $ctrl.BtnAudio
'usb' = $ctrl.BtnUSB
'winsxs' = $ctrl.BtnWinSxS
'browsercache'= $ctrl.BtnBrowserCache
}
function Set-SidebarSelection {
param([string]$Key)
foreach ($k in $script:btnMap.Keys) {
$b = $script:btnMap[$k]
if (-not $b) { continue }
if ($k -eq $Key) { $b.Tag = 'selected' } else { $b.Tag = '' }
}
}
# -----------------------------------------------------------------------------
# Caricamento logo (file locale o base64 embedded da Deploy.ps1)
# -----------------------------------------------------------------------------
$LogoBase64 = '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' # Sostituito da Deploy.ps1
$script:logoStatus = @()
function New-BitmapFromBytes {
param([byte[]]$Bytes, [string]$Source)
try {
if (-not $Bytes -or $Bytes.Length -lt 8) {
$script:logoStatus += "[$Source] byte array vuoto o troppo piccolo"
return $null
}
# Verifica header PNG (89 50 4E 47) o JPG (FF D8) o ICO (00 00 01 00)
$sig = '{0:X2}{1:X2}{2:X2}{3:X2}' -f $Bytes[0], $Bytes[1], $Bytes[2], $Bytes[3]
$script:logoStatus += "[$Source] $($Bytes.Length) bytes, header: $sig"
$ms = New-Object IO.MemoryStream(,$Bytes)
$bmp = [Windows.Media.Imaging.BitmapImage]::new()
$bmp.BeginInit()
$bmp.CacheOption = [Windows.Media.Imaging.BitmapCacheOption]::OnLoad
$bmp.StreamSource = $ms
$bmp.EndInit()
$bmp.Freeze()
$script:logoStatus += "[$Source] bitmap creato: $($bmp.PixelWidth)x$($bmp.PixelHeight)"
return $bmp
} catch {
$script:logoStatus += "[$Source] ERRORE bitmap: $($_.Exception.Message)"
return $null
}
}
function Get-LogoBitmap {
$script:logoStatus += "PSScriptRoot: '$PSScriptRoot' PWD: '$($PWD.Path)'"
$startsWithMarker = $LogoBase64.StartsWith('@@')
$script:logoStatus += "Base64 length: $($LogoBase64.Length) e' placeholder: $startsWithMarker"
# 1) Base64 embedded da Deploy.ps1 (caso iex)
# NB: NON usiamo confronto col testo del placeholder perche Deploy lo sostituisce
# in TUTTE le occorrenze del file (incluso questo if), spezzando la logica.
if ($LogoBase64 -and -not $startsWithMarker -and $LogoBase64.Length -gt 100) {
try {
$bytes = [Convert]::FromBase64String($LogoBase64)
$r = New-BitmapFromBytes -Bytes $bytes -Source 'base64'
if ($r) { return $r }
} catch {
$script:logoStatus += "[base64] decode fallito: $($_.Exception.Message)"
}
}
# 2) File locale (caso .bat: cerca logo.png accanto al .ps1)
$candidates = @()
if ($PSScriptRoot) { $candidates += (Join-Path $PSScriptRoot 'logo.png') }
$candidates += (Join-Path $PWD.Path 'logo.png')
foreach ($path in $candidates) {
$script:logoStatus += "test path: $path esiste: $(Test-Path -LiteralPath $path)"
if ($path -and (Test-Path -LiteralPath $path)) {
try {
$bytes = [IO.File]::ReadAllBytes($path)
$r = New-BitmapFromBytes -Bytes $bytes -Source "file:$path"
if ($r) { return $r }
} catch {
$script:logoStatus += "[file] read fallito: $($_.Exception.Message)"
}
}
}
return $null
}
$logoImg = Get-LogoBitmap
if ($logoImg) {
$ctrl.LogoSmall.Source = $logoImg
$ctrl.LogoBig.Source = $logoImg
$script:logoStatus += "RISULTATO: logo caricato e applicato ai controlli."
} else {
# Fallback: pallino blu + nasconde le Image
$ctrl.LogoSmall.Visibility = [Windows.Visibility]::Collapsed
$ctrl.LogoFallback.Visibility = [Windows.Visibility]::Visible
$ctrl.LogoFallback.ToolTip = ($script:logoStatus -join "`n")
$script:logoStatus += "RISULTATO: logo NON caricato, mostrato fallback."
}
# -----------------------------------------------------------------------------
# Stato condiviso (UI <-> Runspace)
# -----------------------------------------------------------------------------
$script:syncHash = [hashtable]::Synchronized(@{})
$script:syncHash.Window = $window
$script:syncHash.OutputBox = $ctrl.OutputBox
$script:syncHash.LblStatus = $ctrl.LblStatus
$script:syncHash.Progress = $ctrl.Progress
$script:syncHash.Stop = $false
$script:syncHash.Running = $false
$script:runspace = $null
$script:psInstance = $null
$script:asyncResult = $null
# -----------------------------------------------------------------------------
# Info diagnostiche (titolo, sottotitolo, descrizione dettagliata, rischio)
# -----------------------------------------------------------------------------
$diagInfo = @{}
$diagInfo['sysinfo'] = @{
Title='Info sistema'; Subtitle='OS, hardware, uptime e versione di Windows.'
Time='~5 secondi'; Risk='sicuro'
Detail=@(
'Raccoglie informazioni di base sul sistema:',
' * Versione, edizione, build di Windows',
' * Produttore, modello, BIOS',
' * RAM totale, hostname, utente attivo',
' * Uptime dall ultimo riavvio',
'',
'Solo lettura: nessuna modifica al sistema.'
)
}
$diagInfo['sfc'] = @{
Title='SFC - Verifica integrita file'; Subtitle='System File Checker - ripara file di sistema corrotti.'
Time='5-15 minuti'; Risk='moderato'
Detail=@(
'Esegue "sfc /scannow":',
' * Verifica tutti i file di sistema protetti',
' * Confronta versioni con il catalog Windows',
' * Ripara file danneggiati dalla cache locale',
'',
'Quando serve:',
' - Errori casuali del sistema o di Esplora risorse',
' - Crash, blocchi, schermate blu',
' - Dopo uno spegnimento improvviso',
'',
'Sicurezza: modifica SOLO file di sistema corrotti.',
'Non tocca dati utente, programmi o registro.'
)
}
$diagInfo['dism'] = @{
Title='DISM - Riparazione immagine Windows'; Subtitle='Verifica e ripara il component store di Windows.'
Time='10-25 minuti'; Risk='moderato'
Detail=@(
'Esegue tre fasi in sequenza:',
' 1) DISM /CheckHealth - verifica rapida problemi noti',
' 2) DISM /ScanHealth - scansione approfondita',
' 3) DISM /RestoreHealth - ripristina componenti danneggiati',
'',
'Quando serve:',
' - SFC trova errori che non riesce a riparare',
' - Aggiornamenti di Windows che falliscono',
' - Sistema instabile dopo update problematici',
'',
'Richiede connessione internet per scaricare i file di ripristino.'
)
}
$diagInfo['chkdsk'] = @{
Title='CHKDSK - Verifica disco'; Subtitle='Analisi read-only del disco di sistema.'
Time='2-10 minuti'; Risk='sicuro'
Detail=@(
'Esegue "chkdsk C:" in SOLA LETTURA:',
' * Verifica errori del file system NTFS',
' * Controlla settori danneggiati nei metadati',
' * NON modifica nulla sul disco',
'',
'Per riparare gli errori trovati serve "chkdsk /F" che richiede',
'un riavvio: non incluso qui per sicurezza.'
)
}
$diagInfo['events'] = @{
Title='Errori recenti (Event Log)'; Subtitle='Estrae errori critici delle ultime 48 ore.'
Time='~10 secondi'; Risk='sicuro'
Detail=@(
'Legge i Windows Event Logs di:',
' * Log Sistema (kernel, driver, servizi)',
' * Log Applicazioni (software installato)',
'',
'Filtra solo eventi di Livello 1 (Critico) e 2 (Errore)',
'nelle ultime 48 ore.',
'',
'Mostra fonte, ID, timestamp e prima riga del messaggio.',
'Utile per identificare la causa di crash o blocchi recenti.'
)
}
$diagInfo['drivers'] = @{
Title='Driver e periferiche'; Subtitle='Periferiche con errori e driver installati.'
Time='~5 secondi'; Risk='sicuro'
Detail=@(
'Esegue query WMI per:',
' * Trovare periferiche con codici errore (punto esclamativo)',
' * Elencare i driver firmati piu importanti',
' * Mostrare versione e produttore di ogni driver',
'',
'Utile per identificare hardware non funzionante.'
)
}
$diagInfo['perf'] = @{
Title='Prestazioni e processi'; Subtitle='Uso CPU/RAM, top processi attivi, avvio automatico.'
Time='~5 secondi'; Risk='sicuro'
Detail=@(
'Mostra:',
' * Carico CPU medio e percentuale RAM in uso',
' * Top 10 processi per uso CPU',
' * Top 10 processi per uso RAM',
' * Programmi che partono allavvio del PC',
'',
'Utile per identificare bloatware o processi sospetti.'
)
}
$diagInfo['tasks'] = @{
Title='Utilita di pianificazione'; Subtitle='Attivita pianificate non-Microsoft e task in errore.'
Time='~10 secondi'; Risk='sicuro'
Detail=@(
'Analizza le attivita di Utilita di pianificazione (Task Scheduler):',
'',
' * Riepilogo numerico (totali, utente, attive, disabilitate)',
' * Attivita NON-Microsoft (aggiunte da utente o software terzo)',
' con autore, stato, ultima/prossima esecuzione e comando eseguito',
' * Top 10 attivita con LastTaskResult diverso da zero',
' (esecuzioni fallite recenti)',
'',
'I task Microsoft vengono filtrati perche fanno parte di Windows.',
'Quelli non-Microsoft sono i piu interessanti: software installato,',
'updater, eventuali residui di malware.',
'',
'Solo lettura: nessuna modifica.'
)
}
$diagInfo['boottime'] = @{
Title='Boot time e avvio'; Subtitle='Uptime, tempo ultimo avvio, eventi di boot rallentati.'
Time='~5 secondi'; Risk='sicuro'
Detail=@(
'Mostra:',
' * Data e ora ultimo boot del PC',
' * Uptime corrente',
' * Durata dellultimo boot (in secondi)',
' * Eventi di Diagnostics-Performance con rallentamenti critici',
'',
'Utile per capire se il PC ci mette troppo ad accendersi e perche.'
)
}
$diagInfo['bsod'] = @{
Title='BSOD e minidump'; Subtitle='Schermate blu recenti, file .dmp da analizzare.'
Time='~5 secondi'; Risk='sicuro'
Detail=@(
'Cerca:',
' * File .dmp in C:\Windows\Minidump (ultimi 10)',
' * Eventi BugCheck recenti nel log Sistema',
'',
'Mostra timestamp, dimensione e nome di ogni minidump.',
'Suggerisce come aprire i .dmp con WinDbg Preview.'
)
}
$diagInfo['bitlocker'] = @{
Title='BitLocker - Stato cifratura'; Subtitle='Volumi cifrati, percentuale, metodi, key protector.'
Time='~5 secondi'; Risk='sicuro'
Detail=@(
'Per ogni volume del PC mostra:',
' * Stato (FullyEncrypted / FullyDecrypted / EncryptionInProgress)',
' * Percentuale completamento cifratura',
' * Metodo di cifratura (AES-128, AES-256, XTS-AES)',
' * Tipi di Key Protector (TPM, Password, Recovery, Network)',
'',
'Solo lettura: nessuna modifica.'
)
}
$diagInfo['activation'] = @{
Title='Attivazione Windows'; Subtitle='Stato licenza, edizione, genuino si/no.'
Time='~3 secondi'; Risk='sicuro'
Detail=@(
'Mostra:',
' * Edizione Windows e nome prodotto',
' * Stato licenza (Licensed / Unlicensed / Grace)',
' * Product key parziale',
' * Eventuale grace period residuo',
'',
'Equivalente di "slmgr /xpr" ma con dettagli completi.'
)
}
$diagInfo['updates'] = @{
Title='Windows Update history'; Subtitle='Ultimi 20 update installati + errori recenti.'
Time='~5 secondi'; Risk='sicuro'
Detail=@(
'Mostra:',
' * Ultimi 20 update installati (KB, descrizione, data)',
' * Errori critici di WindowsUpdateClient nelle ultime 72 ore',
'',
'Utile per:',
' - Verificare se gli update sono installati regolarmente',
' - Identificare update problematici (errori ricorrenti)',
' - Pinpoint correlazione tra problemi e update recenti'
)
}
$diagInfo['firewall'] = @{
Title='Firewall e Defender'; Subtitle='Stato firewall per profilo + Defender + esclusioni.'
Time='~3 secondi'; Risk='sicuro'
Detail=@(
'Mostra:',
' * Stato firewall per ogni profilo (Domain, Private, Public)',
' * Azioni default Inbound/Outbound',
' * Stato Windows Defender (antivirus, real-time, firme)',
' * Esclusioni path/estensione di Defender',
'',
'Le esclusioni sono importanti: malware le aggiunge spesso',
'per evitare la scansione.'
)
}
$diagInfo['failedlogons'] = @{
Title='Tentativi di login falliti'; Subtitle='Event ID 4625 negli ultimi 7 giorni.'
Time='~5 secondi'; Risk='sicuro'
Detail=@(
'Legge il log Security e estrae:',
' * Ultimi 30 tentativi di logon falliti (EventID 4625)',
' * Nome utente tentato e indirizzo IP sorgente',
' * Timestamp di ogni tentativo',
'',
'Utile per:',
' - Identificare attacchi brute-force RDP/SSH',
' - Vedere se qualcuno tenta credenziali errate',
'',
'Richiede privilegi admin per leggere il log Security.'
)
}
$diagInfo['tpm'] = @{
Title='TPM e Secure Boot'; Subtitle='Compatibilita Windows 11, stato cifratura hardware.'
Time='~3 secondi'; Risk='sicuro'
Detail=@(
'Verifica:',
' * Presenza TPM e versione spec (1.2 / 2.0)',
' * Stato TPM: Ready, Enabled, Owned',
' * Stato Secure Boot UEFI',
' * Riepilogo compatibilita Windows 11',
'',
'TPM 2.0 + Secure Boot sono requisiti per Windows 11.'
)
}
$diagInfo['programs'] = @{
Title='Programmi installati'; Subtitle='Lista completa con versione, publisher, dimensione.'
Time='~10 secondi'; Risk='sicuro'
Detail=@(
'Legge tre chiavi del registro per ottenere lelenco completo:',
' * HKLM\Software\...\Uninstall (64-bit)',
' * HKLM\Software\WOW6432Node\...\Uninstall (32-bit)',
' * HKCU\Software\...\Uninstall (per-user)',
'',
'Per ogni programma: nome, versione, publisher, spazio occupato.',
'Piu accurato e veloce di "Programmi e funzionalita" del Pannello.'
)
}
$diagInfo['services'] = @{
Title='Servizi anomali'; Subtitle='Servizi Auto non running e servizi non-Microsoft.'
Time='~5 secondi'; Risk='sicuro'
Detail=@(
'Mostra:',
' * Servizi configurati come Automatic ma NON in esecuzione',
' (potenzialmente bloccati o crashati)',
' * Top 30 servizi non-Microsoft attualmente attivi',
' (software terzo, possibili rallentamenti)',
'',
'Utile per identificare servizi che non si avviano o',
'bloatware che gira costantemente.'
)
}
$diagInfo['hosts'] = @{
Title='File hosts'; Subtitle='Contenuto custom + hash SHA256 per integrita.'
Time='~2 secondi'; Risk='sicuro'
Detail=@(
'Legge C:\Windows\System32\drivers\etc\hosts e mostra:',
' * Tutte le voci attive (non commentate)',
' * Voci sospette colorate (non-localhost)',
' * Hash SHA256 del file per verifica integrita',
' * Data di ultima modifica',
'',
'Modifiche al file hosts possono indicare:',
' - Pi-hole/AdGuard locale (normale)',
' - Sviluppatori (normale)',
' - Malware che dirotta domini (problema!)'
)
}
$diagInfo['browser'] = @{
Title='Browser di default e config'; Subtitle='Browser predefinito, profili, dimensioni cache.'
Time='~5 secondi'; Risk='sicuro'
Detail=@(
'Mostra:',
' * Browser di default (registry UserChoice)',
' * Browser installati (Edge, Chrome, Firefox, Brave, Opera)',
' * Versione di ognuno',
' * Profili utente con dimensione cartelle dati',
'',
'Utile per troubleshooting browser e capire occupazione disco.'
)
}
$diagInfo['temp'] = @{
Title='Temperature hardware'; Subtitle='Sensori ACPI (CPU/zone termiche).'
Time='~3 secondi'; Risk='sicuro'
Detail=@(
'Tenta di leggere i sensori termici via ACPI (MSAcpi_ThermalZoneTemperature):',
' * Temperatura di ogni zona termica esposta dal BIOS',
' * Carico CPU corrente',
' * Clock CPU corrente vs massimo',
'',
'NB: molti PC non espongono sensori via ACPI.',
'Per monitoring affidabile usa HWMonitor, HWiNFO o Core Temp.'
)
}
$diagInfo['monitors'] = @{
Title='Monitor e risoluzione'; Subtitle='Schermi collegati, EDID, DPI scaling.'
Time='~3 secondi'; Risk='sicuro'
Detail=@(
'Mostra per ogni monitor collegato:',
' * Produttore e modello (da EDID)',
' * Serial number',
' * Anno di produzione',
' * Risoluzione corrente e refresh rate',
' * Profondita colore (bit per pixel)',
' * DPI scaling applicato'
)
}
$diagInfo['audio'] = @{
Title='Dispositivi audio'; Subtitle='Schede audio installate e stato.'
Time='~2 secondi'; Risk='sicuro'
Detail=@(
'Elenca tutti i dispositivi audio installati con:',
' * Nome del device',
' * Produttore',
' * Stato operativo',
'',
'Utile per troubleshooting audio (driver mancanti, conflitti).'
)
}
$diagInfo['usb'] = @{
Title='Dispositivi USB connessi'; Subtitle='Lista USB attivi + hub e controller.'
Time='~3 secondi'; Risk='sicuro'
Detail=@(
'Mostra:',
' * Tutti i dispositivi USB attualmente collegati',
' * Stato di ognuno (OK, Error, Unknown)',
' * Hub e controller USB del PC',
'',
'Utile per troubleshooting periferiche USB.'
)
}
$diagInfo['winsxs'] = @{
Title='Pulizia WinSxS'; Subtitle='DISM cleanup del component store - libera GB di spazio.'
Time='10-20 minuti'; Risk='moderato'
Confirm=$true
ConfirmMessage="Verra eseguito DISM /Online /Cleanup-Image /StartComponentCleanup.`n`nLoperazione e SICURA ma puo richiedere 10-20 minuti.`n`nProcedere?"
Detail=@(
'Esegue:',
' 1) DISM /AnalyzeComponentStore (mostra spazio recuperabile)',
' 2) DISM /StartComponentCleanup (rimuove versioni vecchie)',
'',
'Cosa fa:',
' - Rimuove versioni superate di componenti Windows',
' - Disinstalla manifest di update gia installati',
' - Compatta il component store',
'',
'Spazio recuperato tipico: 1-5 GB su PC con storia di update.',
'',
'Sicurezza:',
' - Operazione ufficiale Microsoft',
' - Non puoi piu disinstallare gli update gia applicati',
' (ma rollback al boot precedente rimane possibile per 10 giorni)',
' - Nessun rischio per dati utente o programmi'
)
}
$diagInfo['browsercache'] = @{
Title='Cache browser'; Subtitle='Pulisce cache Edge/Chrome/Firefox (NON cookies o storia).'
Time='1-3 minuti'; Risk='moderato'
Confirm=$true
ConfirmMessage="Verra svuotata la cache di Edge, Chrome e Firefox.`n`nCookies e cronologia NON verranno toccati.`n`nProcedere?"
Detail=@(
'Elimina i file dalle cartelle cache di:',
' * Edge: AppData\Local\Microsoft\Edge\User Data\...\Cache',
' * Chrome: AppData\Local\Google\Chrome\User Data\...\Cache',
' * Firefox: AppData\Local\Mozilla\Firefox\Profiles',
'',
'Cosa NON viene toccato:',
' * Cookies (sessioni rimangono attive)',
' * Cronologia',
' * Password salvate',
' * Bookmark',
'',
'I browser dovrebbero essere CHIUSI prima di lanciare la pulizia.'
)
}
$diagInfo['netconfig'] = @{
Title='Configurazione IP'; Subtitle='Adattatori attivi, IP, gateway, DNS.'
Time='~5 secondi'; Risk='sicuro'
Detail=@(
'Mostra:',
' * Tutti gli adattatori di rete attivi',
' * Velocita link (Mbps), MAC address, stato media',
' * Indirizzi IPv4 e IPv6 assegnati',
' * Gateway predefinito e DNS in uso',
' * Output completo di "ipconfig /all"'
)
}
$diagInfo['ping'] = @{
Title='Test ping multi-target'; Subtitle='Verifica raggiungibilita gateway e internet.'
Time='~30 secondi'; Risk='sicuro'
Detail=@(
'Esegue 4 ping verso:',
' * Gateway LAN (router locale)',
' * Google DNS (8.8.8.8)',
' * Cloudflare DNS (1.1.1.1)',
' * Quad9 DNS (9.9.9.9)',
' * google.com',
' * microsoft.com',
'',
'Per ogni target: min/medio/max ms e pacchetti persi.',
'Identifica subito se il problema e LAN o internet.'
)
}
$diagInfo['dns'] = @{
Title='DNS - Test e benchmark'; Subtitle='Confronta tempi di risposta tra 5 server DNS.'
Time='~30 secondi'; Risk='sicuro'
Detail=@(
'Testa la risoluzione di 5 domini su 5 server DNS:',
' * DNS configurato sul sistema',
' * Google (8.8.8.8)',
' * Cloudflare (1.1.1.1)',
' * Quad9 (9.9.9.9)',
' * OpenDNS (208.67.222.222)',
'',
'Svuota la cache DNS prima di ogni test (misure pulite).',
'Se un DNS pubblico e molto piu veloce del tuo, suggerisce',
'di cambiare.'
)
}
$diagInfo['mtu'] = @{
Title='MTU - Scoperta automatica'; Subtitle='Calcola la MTU ottimale verso internet.'
Time='~20 secondi'; Risk='sicuro'
Detail=@(
'Usa "ping -f -l " con ricerca binaria per trovare',
'la dimensione massima di pacchetto senza frammentazione.',
'',
'Interpretazione:',
' * 1500 = ethernet standard (FTTH, fibra)',
' * 1492 = PPPoE tipico ADSL/FTTH',
' * <1450 = VPN attiva o doppio NAT',
'',
'MTU bassa = problemi con download grandi o timeout.'
)
}
$diagInfo['trace'] = @{
Title='Traceroute'; Subtitle='Percorso di rete verso 3 destinazioni.'
Time='~1 minuto'; Risk='sicuro'
Detail=@(
'Traceroute custom basato su .NET Ping (1 probe per hop,',
'3x piu veloce del tracert.exe nativo).',
'',
'Destinazioni: 8.8.8.8, 1.1.1.1, google.com',
'Max 18 hop, timeout 1.2 secondi per hop.',
'',
'Identifica il punto della rete dove si verifica il problema',
'(LAN, ISP, internet).'
)
}
$diagInfo['wifi'] = @{
Title='Wi-Fi e profili wireless'; Subtitle='Stato, profili salvati, driver wireless.'
Time='~5 secondi'; Risk='sicuro'
Detail=@(
'Esegue "netsh wlan" per mostrare:',
' * Interfacce Wi-Fi attive con SSID e segnale',
' * Tutti i profili Wi-Fi salvati sul PC',
' * Driver wireless installato e capabilities'
)
}
$diagInfo['ports'] = @{
Title='Porte e connessioni'; Subtitle='Porte in ascolto, connessioni attive, top app.'
Time='~10 secondi'; Risk='sicuro'
Detail=@(
'Mostra:',
' * Porte LISTENING con processo associato',
' * Connessioni TCP ESTABLISHED',
' * Test connettivita su porte comuni (HTTP/HTTPS/SMTP/IMAPS)',
' * Top 5 processi per connessioni TCP attive'
)
}
$diagInfo['ttfb'] = @{
Title='TTFB - Tempo risposta web'; Subtitle='Time-To-First-Byte verso 7 siti popolari.'
Time='~30 secondi'; Risk='sicuro'
Detail=@(
'Effettua richieste HTTPS HEAD verso:',
' google.com, microsoft.com, github.com,',
' cloudflare.com, amazon.it, repubblica.it, youtube.com',
'',
'Soglie:',
' < 500 ms -> ottimo',
' < 1500 ms -> accettabile',
' > 1500 ms -> navigazione lenta',
'',
'Indicatore reale della velocita percepita nel browsing.'
)
}
$diagInfo['antivirus'] = @{
Title='Antivirus e HTTPS scanning'; Subtitle='Rileva AV terzi che intercettano il traffico HTTPS.'
Time='~5 secondi'; Risk='sicuro'
Detail=@(
'Verifica:',
' * Antivirus registrati in WMI SecurityCenter',
' * Processi attivi di 18 antivirus noti',
' (Avast, AVG, Kaspersky, Bitdefender, ESET, McAfee,',
' Norton, Trend, Sophos, F-Secure, Malwarebytes, ecc.)',
' * Stato di Windows Defender (firme, real-time)',
'',
'Se hai un AV terzo e la navigazione e lenta, disattivare la',
'"scansione HTTPS/SSL" risolve nel 80% dei casi.'
)
}
$diagInfo['netfull'] = @{
Title='Diagnostica rete completa'; Subtitle='Tutti i test di rete in sequenza.'
Time='3-5 minuti'; Risk='sicuro'
Detail=@(
'Esegue in sequenza:',
' 1) IP pubblico + geolocalizzazione',
' 2) Configurazione IP completa',
' 3) Test ping multi-target',
' 4) Benchmark DNS (5 server)',
' 5) Scoperta MTU automatica',
' 6) Traceroute (3 destinazioni)',
' 7) Porte e connessioni',
' 8) TTFB HTTPS (7 siti)',
' 9) Rilevamento antivirus HTTPS scan',
'',
'La diagnostica piu completa per problemi di internet/rete.'
)
}
$diagInfo['disk'] = @{
Title='Dischi e stato SMART'; Subtitle='Spazio libero, SMART, cartelle temporanee.'
Time='~10 secondi'; Risk='sicuro'
Detail=@(
'Mostra:',
' * Tutte le unita logiche con spazio libero',
' * Dischi fisici con stato SMART (Healthy/Unhealthy)',
' * Dimensione cartelle TEMP e SoftwareDistribution',
'',
'SMART Unhealthy = il disco sta morendo, fai backup subito.'
)
}
$diagInfo['memory'] = @{
Title='Memoria RAM'; Subtitle='Moduli installati, utilizzo, file di paging.'
Time='~5 secondi'; Risk='sicuro'
Detail=@(
'Mostra:',
' * Ogni slot RAM: capacita, MHz, produttore, part-number',
' * RAM totale/libera/in uso',
' * Configurazione del file di paging',
'',
'Utile per planning upgrade RAM.'
)
}
$diagInfo['gpu'] = @{
Title='GPU - Scheda video'; Subtitle='Modello, driver, risoluzione corrente.'
Time='~3 secondi'; Risk='sicuro'
Detail=@(
'Per ogni GPU installata mostra:',
' * Nome e versione driver',
' * Data del driver (per capire se e obsoleto)',
' * Risoluzione e refresh rate attuale',
' * VRAM disponibile'
)
}
$diagInfo['battery'] = @{
Title='Batteria (laptop)'; Subtitle='Stato + report HTML dettagliato sul Desktop.'
Time='~5 secondi'; Risk='sicuro'
Detail=@(
'Per dispositivi con batteria:',
' * Livello carica e stato',
' * Tempo residuo stimato',
' * Tipo (Li-Ion, ecc.)',
'',
'Genera anche un report HTML completo via "powercfg /batteryreport"',
'sul Desktop, che mostra capacita di design vs capacita attuale',
'(degrado), cicli di carica, storico utilizzo.'
)
}
$diagInfo['flushdns'] = @{
Title='Pulizia cache DNS'; Subtitle='ipconfig /flushdns - operazione istantanea.'
Time='Istantaneo'; Risk='sicuro'
Detail=@(
'Svuota la cache DNS locale di Windows.',
'',
'Quando serve:',
' - Hai cambiato i DNS e i vecchi sembrano incollati',
' - Un sito non si apre ma da altri PC funziona',
' - Dopo aver modificato il file hosts',
'',
'Sicurezza totale, fatta da milioni di utenti ogni giorno.'
)
}
$diagInfo['cleantemp'] = @{
Title='Pulizia file temporanei'; Subtitle='Elimina TEMP utente, TEMP sistema, Prefetch.'
Time='1-3 minuti'; Risk='moderato'
Confirm=$true
ConfirmMessage="Verranno eliminati i file dalle cartelle TEMP utente, TEMP sistema e Prefetch.`n`nProcedere?"
Detail=@(
'Elimina file da:',
' * %TEMP% (cartella temp utente)',
' * C:\Windows\Temp (cartella temp sistema)',
' * C:\Windows\Prefetch (cache di avvio app)',
'',
'I file in uso vengono saltati automaticamente.',
'Nessun dato personale o documento viene toccato.',
'',
'Effetti positivi: libera spazio (spesso GB), risolve installer',
'che falliscono.',
'',
'Effetti collaterali minimi: i primi avvii di alcune app saranno',
'leggermente piu lenti (Prefetch viene ricostruito).'
)
}
$diagInfo['resetnet'] = @{
Title='Reset stack di rete'; Subtitle='Ripristina Winsock, TCP/IP, ARP, DHCP, DNS.'
Time='~30 secondi + RIAVVIO'; Risk='attenzione'
Confirm=$true
ConfirmMessage="ATTENZIONE: l operazione richiede il RIAVVIO del computer.`n`nVerranno resettati Winsock, TCP/IP e cache DNS.`n`nProcedere?"
Detail=@(
'Esegue in sequenza:',
' * netsh winsock reset (catalog Winsock di default)',
' * netsh int ip reset (stack TCP/IP)',
' * netsh interface ip delete arpcache',
' * ipconfig /release + /renew + /flushdns',
'',
'Quando serve:',
' - Internet non funziona inspiegabilmente',
' - Errori "DNS non risponde" persistenti',
' - Dopo disinstallazione di VPN/proxy/antivirus che',
' hanno lasciato residui',
'',
'IMPORTANTE:',
' - Verra richiesto di riavviare il PC alla fine',
' - Configurazioni VPN potrebbero richiedere ri-setup'
)
}
$diagInfo['full'] = @{
Title='Diagnostica completa'; Subtitle='Tutti i test non distruttivi in sequenza.'
Time='15-30 minuti'; Risk='sicuro'
Detail=@(
'Esegue in sequenza tutti i test non distruttivi:',
'',
' 1) Info sistema',
' 2) Prestazioni e processi',
' 3) Dischi e SMART',
' 4) Memoria RAM',
' 5) GPU',
' 6) Driver e periferiche',
' 7) Eventi del log Sistema/Applicazioni',
' 8) Diagnostica rete completa (9 sub-test)',
' 9) SFC - verifica integrita file',
' 10) DISM - riparazione immagine Windows',
' 11) CHKDSK - verifica disco (read-only)',
'',
'Le pulizie e i reset distruttivi NON sono inclusi:',
'vanno avviati manualmente dalla sidebar.'
)
}
# -----------------------------------------------------------------------------
# Funzioni gestione UI
# -----------------------------------------------------------------------------
$script:selectedDiag = $null
function Write-PreviewLine {
param([string]$Text, [string]$Color = '#C0CAF5')
$doc = $ctrl.OutputBox.Document
$last = $doc.Blocks | Select-Object -Last 1
if (-not $last) {
$last = New-Object Windows.Documents.Paragraph
$last.Margin = New-Object Windows.Thickness(0)
$doc.Blocks.Add($last)
}
$run = New-Object Windows.Documents.Run $Text
$run.Foreground = (New-Object Windows.Media.BrushConverter).ConvertFrom($Color)
$last.Inlines.Add($run)
$last.Inlines.Add((New-Object Windows.Documents.LineBreak))
}
function Show-Preview {
param([string]$Key)
if ($script:syncHash.Running) { return }
$info = $diagInfo[$Key]
if (-not $info) { return }
$script:selectedDiag = $Key
Set-SidebarSelection $Key
$ctrl.LblTitle.Text = $info.Title
$ctrl.LblSubtitle.Text = $info.Subtitle
$ctrl.OutputBox.Document.Blocks.Clear()
$p = New-Object Windows.Documents.Paragraph
$p.Margin = New-Object Windows.Thickness(0)
$ctrl.OutputBox.Document.Blocks.Add($p)
$riskColor = switch ($info.Risk) {
'sicuro' { '#9ECE6A' }
'moderato' { '#E0AF68' }
'attenzione' { '#F7768E' }
default { '#C0CAF5' }
}
Write-PreviewLine ''
Write-PreviewLine ('=' * 80) '#414868'
Write-PreviewLine (' ' + $info.Title) '#7AA2F7'
Write-PreviewLine ('=' * 80) '#414868'
Write-PreviewLine ''
Write-PreviewLine (' ' + $info.Subtitle) '#C0CAF5'
Write-PreviewLine ''
Write-PreviewLine (' Tempo stimato : ' + $info.Time) '#7DCFFF'
Write-PreviewLine (' Livello rischio : ' + $info.Risk) $riskColor
Write-PreviewLine ''
Write-PreviewLine ('-' * 80) '#292E42'
Write-PreviewLine ''
foreach ($line in $info.Detail) {
Write-PreviewLine (' ' + $line) '#C0CAF5'
}
Write-PreviewLine ''
Write-PreviewLine ('-' * 80) '#292E42'
Write-PreviewLine ''
if ($info.Risk -eq 'attenzione') {
Write-PreviewLine ' >>> Operazione SENSIBILE. Verra chiesta conferma prima di partire.' '#F7768E'
} else {
Write-PreviewLine ' >>> Clicca AVVIA in alto a destra per eseguire questa diagnostica.' '#BB9AF7'
}
Write-PreviewLine ''
# Aggiorna il pulsante Avvia
$shortTitle = ($info.Title -split ' - ')[0].ToUpper()
$ctrl.BtnRunAll.Content = "AVVIA: $shortTitle"
}
function Set-Header {
param([string]$Title, [string]$Subtitle)
$ctrl.LblTitle.Text = $Title
$ctrl.LblSubtitle.Text = $Subtitle
}
function Set-Running {
param([bool]$Running, [string]$StatusText = "")
$script:syncHash.Running = $Running
$ctrl.BtnStop.IsEnabled = $Running
foreach ($n in $ctrl.Keys) {
if ($n -like 'Btn*' -and $n -notin @('BtnStop','BtnSave','BtnClear','BtnMinimize','BtnMaximize','BtnClose')) {
$ctrl[$n].IsEnabled = -not $Running
}
}
if ($Running) {
$ctrl.Progress.IsIndeterminate = $true
$ctrl.LblStatus.Text = $StatusText
} else {
$ctrl.Progress.IsIndeterminate = $false
$ctrl.Progress.Value = if ($StatusText -like '*Interrotto*') { 0 } else { 100 }
$ctrl.LblStatus.Text = $StatusText
}
}
# -----------------------------------------------------------------------------
# Funzione output runspace (definita nel runspace)
# -----------------------------------------------------------------------------
$helpersBlock = @'
function W { param($T, $C = '#C0CAF5')
if ($null -eq $T) { $T = '' }
$text = "$T"
$color = $C
$sb = {
$doc = $syncHash.OutputBox.Document
$last = $doc.Blocks | Select-Object -Last 1
if (-not $last) {
$last = New-Object Windows.Documents.Paragraph
$last.Margin = New-Object Windows.Thickness(0)
$doc.Blocks.Add($last)
}
$run = New-Object Windows.Documents.Run $text
$run.Foreground = (New-Object Windows.Media.BrushConverter).ConvertFrom($color)
$last.Inlines.Add($run)
$last.Inlines.Add((New-Object Windows.Documents.LineBreak))
$syncHash.OutputBox.ScrollToEnd()
}.GetNewClosure()
$syncHash.Window.Dispatcher.Invoke([action]$sb) | Out-Null
}
function H1 { param($T)
W ''
W ('================================================================================') '#414868'
W (" $T") '#7AA2F7'
W ('================================================================================') '#414868'
}
function H2 { param($T)
W ''
W (">>> $T") '#BB9AF7'
W ('--------------------------------------------------------------------------------') '#292E42'
}
function OK { param($T) W "[OK] $T" '#9ECE6A' }
function WARN { param($T) W "[!] $T" '#E0AF68' }
function ERR { param($T) W "[X] $T" '#F7768E' }
function INFO { param($T) W " $T" '#C0CAF5' }
function DIM { param($T) W " $T" '#7DCFFF' }
function RAW { param($T) W "$T" '#565F89' }
function Status { param($T)
$msg = "$T"
$sb = { $syncHash.LblStatus.Text = $msg }.GetNewClosure()
$syncHash.Window.Dispatcher.Invoke([action]$sb) | Out-Null
}
function Should-Stop { return $syncHash.Stop }
function Run-Native {
param([string]$Cmd, [string[]]$Arguments)
$oReg = $null; $eReg = $null
try {
$psi = New-Object System.Diagnostics.ProcessStartInfo
$psi.FileName = $Cmd
$psi.Arguments = ($Arguments -join ' ')
$psi.UseShellExecute = $false
$psi.RedirectStandardOutput = $true
$psi.RedirectStandardError = $true
$psi.CreateNoWindow = $true
$psi.StandardOutputEncoding = [System.Text.Encoding]::UTF8
$psi.StandardErrorEncoding = [System.Text.Encoding]::UTF8
$proc = New-Object System.Diagnostics.Process
$proc.StartInfo = $psi
$bag = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList))
$handler = {
if (-not [string]::IsNullOrEmpty($EventArgs.Data)) {
[void]$Event.MessageData.Add($EventArgs.Data)
}
}
$oReg = Register-ObjectEvent -InputObject $proc -EventName OutputDataReceived -Action $handler -MessageData $bag
$eReg = Register-ObjectEvent -InputObject $proc -EventName ErrorDataReceived -Action $handler -MessageData $bag
[void]$proc.Start()
$proc.BeginOutputReadLine()
$proc.BeginErrorReadLine()
$idx = 0
while ((-not $proc.HasExited) -or ($idx -lt $bag.Count)) {
if ($syncHash.Stop -and -not $proc.HasExited) {
try { $proc.Kill() } catch {}
WARN 'Esecuzione interrotta.'
Start-Sleep -Milliseconds 200
}
while ($idx -lt $bag.Count) {
RAW $bag[$idx]
$idx++
}
if (-not $proc.HasExited) { Start-Sleep -Milliseconds 80 }
}
return $proc.ExitCode
} catch {
ERR ("Errore esecuzione $Cmd : " + $_.Exception.Message)
} finally {
if ($oReg) { Unregister-Event -SourceIdentifier $oReg.Name -ErrorAction SilentlyContinue }
if ($eReg) { Unregister-Event -SourceIdentifier $eReg.Name -ErrorAction SilentlyContinue }
}
}
'@
# -----------------------------------------------------------------------------
# Blocchi di diagnostica
# -----------------------------------------------------------------------------
$diagBlocks = @{}
# ----- INFO SISTEMA -----
$diagBlocks['sysinfo'] = @'
H1 "INFORMAZIONI SISTEMA"
Status 'Raccolta informazioni sistema...'
try {
$os = Get-CimInstance Win32_OperatingSystem
$cs = Get-CimInstance Win32_ComputerSystem
$bios = Get-CimInstance Win32_BIOS
H2 'Sistema operativo'
INFO ("Nome OS : " + $os.Caption)
INFO ("Versione : " + $os.Version + " (build " + $os.BuildNumber + ")")
INFO ("Architettura : " + $os.OSArchitecture)
INFO ("Installato il : " + $os.InstallDate)
INFO ("Ultimo boot : " + $os.LastBootUpTime)
$uptime = (Get-Date) - $os.LastBootUpTime
INFO ("Uptime : {0} giorni, {1} ore, {2} minuti" -f $uptime.Days, $uptime.Hours, $uptime.Minutes)
H2 'Hardware'
INFO ("Produttore : " + $cs.Manufacturer)
INFO ("Modello : " + $cs.Model)
INFO ("BIOS : " + $bios.Manufacturer + " " + $bios.SMBIOSBIOSVersion)
INFO ("Utente attivo : " + $cs.UserName)
INFO ("Hostname : " + $cs.Name)
INFO ("Dominio : " + $cs.Domain)
$totalRamGB = [math]::Round($cs.TotalPhysicalMemory / 1GB, 2)
INFO ("RAM totale : $totalRamGB GB")
OK 'Informazioni sistema raccolte.'
} catch { ERR $_.Exception.Message }
'@
# ----- SFC -----
$diagBlocks['sfc'] = @'
H1 "SFC - SYSTEM FILE CHECKER"
W "Verifica integrita dei file di sistema Windows." '#7DCFFF'
W "Questa operazione puo richiedere 5-15 minuti." '#565F89'
W ''
Status 'Esecuzione sfc /scannow in corso...'
$ec = Run-Native -Cmd 'sfc.exe' -Arguments @('/scannow')
W ''
if ($ec -eq 0) { OK 'SFC completato senza errori.' }
elseif ($null -eq $ec) { WARN 'SFC interrotto.' }
else { WARN ("SFC terminato con codice: $ec") }
'@
# ----- DISM -----
$diagBlocks['dism'] = @'
H1 "DISM - DEPLOYMENT IMAGE SERVICING"
W "Verifica e ripara la salute dell immagine di Windows." '#7DCFFF'
W ''
H2 'DISM /Online /Cleanup-Image /CheckHealth'
Status 'DISM CheckHealth...'
Run-Native -Cmd 'DISM.exe' -Arguments @('/Online','/Cleanup-Image','/CheckHealth') | Out-Null
if (Should-Stop) { return }
H2 'DISM /Online /Cleanup-Image /ScanHealth'
Status 'DISM ScanHealth (puo richiedere alcuni minuti)...'
Run-Native -Cmd 'DISM.exe' -Arguments @('/Online','/Cleanup-Image','/ScanHealth') | Out-Null
if (Should-Stop) { return }
H2 'DISM /Online /Cleanup-Image /RestoreHealth'
Status 'DISM RestoreHealth (puo richiedere 10-20 minuti)...'
$ec = Run-Native -Cmd 'DISM.exe' -Arguments @('/Online','/Cleanup-Image','/RestoreHealth')
W ''
if ($ec -eq 0) { OK 'DISM RestoreHealth completato senza errori.' }
else { WARN ("DISM terminato con codice: $ec") }
'@
# ----- CHKDSK -----
$diagBlocks['chkdsk'] = @'
H1 "CHKDSK - VERIFICA FILE SYSTEM"
W "Modalita read-only: nessuna modifica al disco." '#7DCFFF'
W "Per riparare gli errori serve riavviare con chkdsk /F." '#565F89'
W ''
$drive = $env:SystemDrive
H2 ("Analisi disco $drive")
Status "CHKDSK $drive (read-only)..."
$ec = Run-Native -Cmd 'chkdsk.exe' -Arguments @($drive)
W ''
if ($ec -eq 0) { OK 'CHKDSK: nessun problema rilevato.' }
else { WARN ("CHKDSK terminato con codice: $ec (errori rilevati - pianifica chkdsk /F al riavvio)") }
'@
# ----- EVENT LOG ERRORS -----
$diagBlocks['events'] = @'
H1 "EVENT LOG - ERRORI RECENTI"
W "Estrazione errori critici dalle ultime 48 ore." '#7DCFFF'
$since = (Get-Date).AddHours(-48)
H2 "Sistema - Errori e critici (livello 1-2)"
Status 'Lettura log Sistema...'
try {
$sys = Get-WinEvent -FilterHashtable @{LogName='System'; Level=1,2; StartTime=$since} -MaxEvents 30 -ErrorAction Stop
if ($sys) {
foreach ($e in $sys) {
if (Should-Stop) { return }
$lvl = if ($e.Level -eq 1) {'CRIT'} else {'ERR '}
W ("[$lvl] " + $e.TimeCreated.ToString('dd/MM HH:mm') + " " + $e.ProviderName + " ID:" + $e.Id) '#F7768E'
$msg = ($e.Message -split "`n")[0]
if ($msg.Length -gt 140) { $msg = $msg.Substring(0,140) + '...' }
RAW (" " + $msg)
}
OK ("Trovati " + $sys.Count + " eventi nel log Sistema.")
} else {
OK 'Nessun errore critico nel log Sistema.'
}
} catch { WARN ("Impossibile leggere log Sistema: " + $_.Exception.Message) }
H2 "Applicazioni - Errori (livello 2)"
Status 'Lettura log Applicazioni...'
try {
$app = Get-WinEvent -FilterHashtable @{LogName='Application'; Level=1,2; StartTime=$since} -MaxEvents 20 -ErrorAction Stop
if ($app) {
foreach ($e in $app) {
if (Should-Stop) { return }
W ("[ERR ] " + $e.TimeCreated.ToString('dd/MM HH:mm') + " " + $e.ProviderName + " ID:" + $e.Id) '#E0AF68'
$msg = ($e.Message -split "`n")[0]
if ($msg.Length -gt 140) { $msg = $msg.Substring(0,140) + '...' }
RAW (" " + $msg)
}
OK ("Trovati " + $app.Count + " eventi nel log Applicazioni.")
} else {
OK 'Nessun errore critico nel log Applicazioni.'
}
} catch { WARN ("Impossibile leggere log Applicazioni: " + $_.Exception.Message) }
'@
# ----- DRIVERS -----
$diagBlocks['drivers'] = @'
H1 "DRIVER E PERIFERICHE"
Status 'Analisi periferiche...'
try {
$devs = Get-CimInstance Win32_PnPEntity | Where-Object { $_.ConfigManagerErrorCode -and $_.ConfigManagerErrorCode -ne 0 }
H2 'Periferiche con problemi'
if ($devs) {
foreach ($d in $devs) {
if (Should-Stop) { return }
ERR ($d.Name + " (codice errore: " + $d.ConfigManagerErrorCode + ")")
}
} else {
OK 'Nessuna periferica con problemi rilevati.'
}
H2 'Driver firmati (riepilogo)'
$drv = Get-CimInstance Win32_PnPSignedDriver | Where-Object { $_.DeviceName } | Select-Object -First 25
foreach ($d in $drv) {
RAW (" " + $d.DeviceName.PadRight(50).Substring(0,50) + " v" + $d.DriverVersion)
}
OK 'Analisi driver completata.'
} catch { ERR $_.Exception.Message }
'@
# ----- PERFORMANCE -----
$diagBlocks['perf'] = @'
H1 "PRESTAZIONI E PROCESSI"
Status 'Lettura uso CPU e memoria...'
try {
H2 'Carico sistema'
$cpu = (Get-CimInstance Win32_Processor | Measure-Object -Property LoadPercentage -Average).Average
$os = Get-CimInstance Win32_OperatingSystem
$totMem = [math]::Round($os.TotalVisibleMemorySize/1MB, 2)
$freeMem = [math]::Round($os.FreePhysicalMemory/1MB, 2)
$usedPct = [math]::Round((($totMem-$freeMem)/$totMem)*100, 1)
INFO ("CPU media : $cpu %")
INFO ("RAM totale : $totMem GB")
INFO ("RAM libera : $freeMem GB")
INFO ("RAM utilizzata : $usedPct %")
H2 'Top 10 processi per CPU'
Get-Process | Sort-Object CPU -Descending | Select-Object -First 10 | ForEach-Object {
$mem = [math]::Round($_.WorkingSet64/1MB, 1)
RAW (" " + $_.ProcessName.PadRight(30) + " CPU:" + ([string]([math]::Round($_.CPU,1))).PadLeft(8) + "s RAM:" + $mem + " MB")
}
H2 'Top 10 processi per RAM'
Get-Process | Sort-Object WorkingSet64 -Descending | Select-Object -First 10 | ForEach-Object {
$mem = [math]::Round($_.WorkingSet64/1MB, 1)
RAW (" " + $_.ProcessName.PadRight(30) + " RAM:" + ($mem.ToString()).PadLeft(8) + " MB")
}
H2 'Programmi in avvio automatico'
$startup = Get-CimInstance Win32_StartupCommand
foreach ($s in $startup) {
RAW (" " + $s.Name.PadRight(35).Substring(0,[math]::Min(35,$s.Name.Length)) + " " + $s.Location)
}
OK 'Analisi prestazioni completata.'
} catch { ERR $_.Exception.Message }
'@
# ----- BOOT TIME -----
$diagBlocks['boottime'] = @'
H1 "BOOT TIME E AVVIO"
try {
H2 'Ultimo boot'
$os = Get-CimInstance Win32_OperatingSystem
$boot = $os.LastBootUpTime
$uptime = (Get-Date) - $boot
INFO ("Avviato il : " + $boot)
INFO ("Uptime : " + $uptime.Days + "g " + $uptime.Hours + "h " + $uptime.Minutes + "m")
H2 'Tempo di boot ultimo avvio'
$bootEvt = Get-WinEvent -ProviderName 'Microsoft-Windows-Diagnostics-Performance' -MaxEvents 1 -FilterXPath "*[System[(EventID=100)]]" -ErrorAction SilentlyContinue
if ($bootEvt) {
$xml = [xml]$bootEvt.ToXml()
$bootMs = ($xml.Event.EventData.Data | Where-Object Name -eq 'BootTime').'#text'
if ($bootMs) {
$sec = [math]::Round([int]$bootMs / 1000, 1)
$clr = if ($sec -lt 30) { '#9ECE6A' } elseif ($sec -lt 60) { '#E0AF68' } else { '#F7768E' }
W ("Tempo di avvio totale: $sec secondi") $clr
}
} else { DIM 'Diagnostics-Performance log non disponibile.' }
H2 'Rallentamenti critici di boot (ultimi 10)'
$slow = Get-WinEvent -ProviderName 'Microsoft-Windows-Diagnostics-Performance' -MaxEvents 50 -ErrorAction SilentlyContinue | Where-Object { $_.Id -in @(101, 102, 103) }
if ($slow) {
$found = $false
foreach ($e in $slow | Select-Object -First 10) {
if (Should-Stop) { return }
$xml = [xml]$e.ToXml()
$name = ($xml.Event.EventData.Data | Where-Object Name -eq 'Name').'#text'
$time = ($xml.Event.EventData.Data | Where-Object Name -eq 'TotalTime').'#text'
if ($time -and $name) {
$found = $true
WARN (" " + [math]::Round([int]$time/1000,1) + "s rallentamento da: " + $name)
}
}
if (-not $found) { OK 'Nessun rallentamento critico rilevato.' }
} else { OK 'Nessun rallentamento critico di boot rilevato.' }
} catch { ERR $_.Exception.Message }
'@
# ----- BSOD -----
$diagBlocks['bsod'] = @'
H1 "BSOD E MINIDUMP"
$dumpPath = "$env:SystemRoot\Minidump"
if (-not (Test-Path $dumpPath)) {
OK ("Cartella " + $dumpPath + " non esiste -> nessun BSOD registrato.")
} else {
H2 ("Minidump trovati in " + $dumpPath)
$dumps = Get-ChildItem $dumpPath -Filter '*.dmp' -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending
if (-not $dumps) { OK 'Nessun minidump trovato.' }
else {
INFO ("Totale minidump: " + $dumps.Count)
foreach ($d in $dumps | Select-Object -First 10) {
if (Should-Stop) { return }
$size = [math]::Round($d.Length/1KB, 0)
WARN (" [" + $d.LastWriteTime.ToString('dd/MM/yyyy HH:mm') + "] " + $d.Name + " (" + $size + " KB)")
}
}
}
H2 'Eventi BSOD recenti (BugCheck nel log Sistema)'
$bc = Get-WinEvent -FilterHashtable @{LogName='System'; ID=1001; ProviderName='Microsoft-Windows-WER-SystemErrorReporting'} -ErrorAction SilentlyContinue | Select-Object -First 5
if ($bc) {
foreach ($e in $bc) {
WARN ("[" + $e.TimeCreated.ToString('dd/MM HH:mm') + "]")
$msg = ($e.Message -split "`n")[0]
RAW (" " + $msg)
}
} else { OK 'Nessun evento BugCheck recente.' }
H2 'Come analizzare i minidump'
INFO '1) Scarica WinDbg Preview dal Microsoft Store (gratis)'
INFO '2) Apri WinDbg, File -> Open Dump File, scegli un .dmp'
INFO '3) Comando: !analyze -v (mostra bug check code e driver responsabile)'
'@
# ----- BITLOCKER -----
$diagBlocks['bitlocker'] = @'
H1 "BITLOCKER - STATO CIFRATURA"
try {
$vols = Get-BitLockerVolume -ErrorAction Stop
if (-not $vols) { WARN 'Nessun volume BitLocker rilevato.' }
else {
foreach ($v in $vols) {
if (Should-Stop) { return }
$clr = switch ("$($v.VolumeStatus)") {
'FullyEncrypted' { '#9ECE6A' }
'EncryptionInProgress'{ '#E0AF68' }
'FullyDecrypted' { '#F7768E' }
default { '#7DCFFF' }
}
W (" " + $v.MountPoint + " " + $v.VolumeStatus + " Cifratura: " + $v.EncryptionPercentage + "%") $clr
INFO (" Metodo : " + $v.EncryptionMethod)
INFO (" Protezione attiva : " + $v.ProtectionStatus)
INFO (" Volume type : " + $v.VolumeType)
if ($v.KeyProtector) {
foreach ($k in $v.KeyProtector) {
RAW (" Protector : " + $k.KeyProtectorType + " ID: " + $k.KeyProtectorId)
}
}
W '' '#C0CAF5'
}
OK 'Analisi BitLocker completata.'
}
} catch {
WARN ("BitLocker non disponibile o errore: " + $_.Exception.Message)
DIM 'Causa tipica: edizione Home non supporta BitLocker (solo Pro/Enterprise).'
}
'@
# ----- ACTIVATION -----
$diagBlocks['activation'] = @'
H1 "ATTIVAZIONE WINDOWS"
try {
$lic = Get-CimInstance SoftwareLicensingProduct -Filter "PartialProductKey IS NOT NULL" -ErrorAction Stop | Where-Object { $_.Name -like '*Windows*' }
if (-not $lic) { WARN 'Nessuna licenza Windows attiva trovata.' }
else {
foreach ($l in $lic) {
$statusText = switch ([int]$l.LicenseStatus) {
0 { 'NON LICENZIATO' }
1 { 'LICENZIATO' }
2 { 'OOB Grace' }
3 { 'OOT Grace' }
4 { 'Non-Genuine Grace' }
5 { 'Notifica' }
6 { 'Grace extended' }
default { 'Sconosciuto' }
}
$clr = if ([int]$l.LicenseStatus -eq 1) { '#9ECE6A' } else { '#F7768E' }
W ("[" + $statusText + "] " + $l.Name) $clr
INFO (" Descrizione : " + $l.Description)
INFO (" Product key : *****-*****-*****-*****-" + $l.PartialProductKey)
if ($l.GracePeriodRemaining -gt 0) {
$days = [math]::Round($l.GracePeriodRemaining / 1440, 1)
WARN (" Grace residuo: $days giorni")
}
}
}
H2 'Info aggiuntive'
$cs = Get-CimInstance Win32_ComputerSystem
INFO ("Hostname: " + $cs.Name)
INFO ("Dominio : " + $cs.Domain)
OK 'Analisi attivazione completata.'
} catch { ERR $_.Exception.Message }
'@
# ----- WINDOWS UPDATE HISTORY -----
$diagBlocks['updates'] = @'
H1 "WINDOWS UPDATE HISTORY"
try {
H2 'Ultimi 20 update installati'
$hf = Get-HotFix -ErrorAction SilentlyContinue | Sort-Object InstalledOn -Descending | Select-Object -First 20
if ($hf) {
foreach ($h in $hf) {
$date = if ($h.InstalledOn) { $h.InstalledOn.ToString('dd/MM/yyyy') } else { 'sconosciuta' }
$desc = if ($h.Description) { $h.Description } else { '' }
RAW (" " + $h.HotFixID.PadRight(12) + " " + $desc.PadRight(20).Substring(0,[math]::Min(20,$desc.Length)).PadRight(20) + " installato il " + $date)
}
} else { WARN 'Get-HotFix non ha restituito risultati.' }
H2 'Errori di Windows Update (ultime 72 ore)'
$since = (Get-Date).AddHours(-72)
$errs = Get-WinEvent -FilterHashtable @{LogName='System'; ProviderName='Microsoft-Windows-WindowsUpdateClient'; Level=1,2; StartTime=$since} -ErrorAction SilentlyContinue
if ($errs) {
foreach ($e in $errs | Select-Object -First 10) {
if (Should-Stop) { return }
ERR ("[" + $e.TimeCreated.ToString('dd/MM HH:mm') + "] ID:" + $e.Id)
$msg = ($e.Message -split "`n")[0]
if ($msg.Length -gt 130) { $msg = $msg.Substring(0,130) + '...' }
RAW (" " + $msg)
}
} else { OK 'Nessun errore di Windows Update nelle ultime 72 ore.' }
OK 'Analisi Windows Update completata.'
} catch { ERR $_.Exception.Message }
'@
# ----- FIREWALL / DEFENDER -----
$diagBlocks['firewall'] = @'
H1 "FIREWALL E DEFENDER"
try {
H2 'Profili Windows Firewall'
Get-NetFirewallProfile -ErrorAction Stop | ForEach-Object {
$clr = if ($_.Enabled) { '#9ECE6A' } else { '#F7768E' }
$stato = if ($_.Enabled) { 'ATTIVO' } else { 'DISATTIVATO' }
W (" [" + $stato + "] Profilo: " + $_.Name) $clr
INFO (" Inbound default : " + $_.DefaultInboundAction)
INFO (" Outbound default: " + $_.DefaultOutboundAction)
INFO (" Notify on block : " + $_.NotifyOnListen)
}
H2 'Windows Defender - stato'
try {
$d = Get-MpComputerStatus -ErrorAction Stop
if ($d.AntivirusEnabled) { OK 'Antivirus ATTIVO' } else { ERR 'Antivirus DISATTIVATO' }
if ($d.RealTimeProtectionEnabled) { OK 'Real-time protection ATTIVA' } else { WARN 'Real-time protection DISATTIVA' }
if ($d.IoavProtectionEnabled) { OK 'Scansione allegati abilitata' }
if ($d.BehaviorMonitorEnabled) { OK 'Behavior monitor abilitato' }
INFO ("Versione firme : " + $d.AntivirusSignatureVersion)
INFO ("Ultimo aggiorn. : " + $d.AntivirusSignatureLastUpdated)
INFO ("Ultima scansione : " + $d.QuickScanEndTime)
} catch { DIM 'Get-MpComputerStatus non disponibile.' }
H2 'Esclusioni di Defender (potenziali tampering vector)'
try {
$pref = Get-MpPreference -ErrorAction Stop
if ($pref.ExclusionPath) {
WARN ("Esclusioni path configurate: " + $pref.ExclusionPath.Count)
foreach ($p in $pref.ExclusionPath) { RAW (" " + $p) }
} else { OK 'Nessuna esclusione di path configurata.' }
if ($pref.ExclusionExtension) {
WARN ("Esclusioni per estensione: " + $pref.ExclusionExtension.Count)
foreach ($e in $pref.ExclusionExtension) { RAW (" ." + $e) }
}
if ($pref.ExclusionProcess) {
WARN ("Esclusioni per processo: " + $pref.ExclusionProcess.Count)
foreach ($e in $pref.ExclusionProcess) { RAW (" " + $e) }
}
} catch { DIM 'Get-MpPreference non disponibile.' }
} catch { ERR $_.Exception.Message }
'@
# ----- FAILED LOGONS -----
$diagBlocks['failedlogons'] = @'
H1 "TENTATIVI DI LOGIN FALLITI"
W "Eventi 4625 dal log Security degli ultimi 7 giorni." '#7DCFFF'
try {
$since = (Get-Date).AddDays(-7)
$events = Get-WinEvent -FilterHashtable @{LogName='Security'; ID=4625; StartTime=$since} -MaxEvents 30 -ErrorAction Stop
if ($events) {
H2 ("Trovati " + $events.Count + " tentativi falliti")
foreach ($e in $events) {
if (Should-Stop) { return }
$userM = ([regex]'Account Name:\s+(\S+)').Matches($e.Message)
$user = if ($userM.Count -gt 0) { $userM[$userM.Count-1].Groups[1].Value } else { '?' }
$srcM = [regex]::Match($e.Message, 'Source Network Address:\s+(\S+)')
$src = if ($srcM.Success) { $srcM.Groups[1].Value } else { 'locale' }
WARN ("[" + $e.TimeCreated.ToString('dd/MM HH:mm:ss') + "] user: " + $user + " da: " + $src)
}
} else { OK 'Nessun login fallito negli ultimi 7 giorni.' }
} catch {
WARN ("Errore lettura log Security: " + $_.Exception.Message)
DIM 'Servono privilegi admin per leggere il log Security.'
}
'@
# ----- TPM / SECURE BOOT -----
$diagBlocks['tpm'] = @'
H1 "TPM E SECURE BOOT"
try {
H2 'TPM (Trusted Platform Module)'
try {
$tpm = Get-Tpm -ErrorAction Stop
if ($tpm.TpmPresent) { OK ("TPM presente. Versione spec: " + $tpm.SpecVersion) }
else { ERR 'TPM NON presente.' }
if ($tpm.TpmReady) { OK 'TPM ready' } else { WARN 'TPM non ready' }
if ($tpm.TpmEnabled) { OK 'TPM enabled' } else { WARN 'TPM disabled (controlla BIOS)' }
if ($tpm.TpmOwned) { INFO 'TPM owned (gia inizializzato dal sistema)' } else { INFO 'TPM non owned' }
INFO ("ManagedAuthLevel: " + $tpm.ManagedAuthLevel)
$tpmOk = $tpm.TpmPresent -and ("$($tpm.SpecVersion)" -match '2\.')
} catch {
ERR ("Get-Tpm fallito: " + $_.Exception.Message)
$tpmOk = $false
}
H2 'Secure Boot UEFI'
$sbOk = $false
try {
$sb = Confirm-SecureBootUEFI -ErrorAction Stop
if ($sb) { OK 'Secure Boot ATTIVO'; $sbOk = $true }
else { WARN 'Secure Boot DISATTIVO' }
} catch {
WARN ("Secure Boot non interrogabile: " + $_.Exception.Message)
DIM 'Causa tipica: sistema su BIOS legacy invece di UEFI.'
}
H2 'Compatibilita Windows 11'
if ($tpmOk -and $sbOk) { OK 'TPM 2.0 + Secure Boot: requisiti Win11 soddisfatti.' }
else { WARN 'Requisiti Win11 NON tutti soddisfatti.' }
} catch { ERR $_.Exception.Message }
'@
# ----- PROGRAMMI INSTALLATI -----
$diagBlocks['programs'] = @'
H1 "PROGRAMMI INSTALLATI"
try {
$progs = @()
foreach ($k in @(
'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
'HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
)) {
$progs += Get-ItemProperty $k -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName }
}
$progs = $progs | Sort-Object DisplayName -Unique
H2 ("Programmi installati: " + $progs.Count)
foreach ($p in $progs) {
if (Should-Stop) { return }
$ver = if ($p.DisplayVersion) { "$($p.DisplayVersion)" } else { '?' }
$pub = if ($p.Publisher) { "$($p.Publisher)" } else { '?' }
$sz = if ($p.EstimatedSize) { [math]::Round($p.EstimatedSize/1024,1).ToString()+' MB' } else { '?' }
$dn = $p.DisplayName
if ($dn.Length -gt 50) { $dn = $dn.Substring(0,47) + '...' }
if ($pub.Length -gt 30) { $pub = $pub.Substring(0,27) + '...' }
if ($ver.Length -gt 18) { $ver = $ver.Substring(0,15) + '...' }
RAW (" " + $dn.PadRight(50) + " v" + $ver.PadRight(18) + " " + $pub.PadRight(30) + " " + $sz)
}
OK 'Lista programmi completata.'
} catch { ERR $_.Exception.Message }
'@
# ----- SERVIZI ANOMALI -----
$diagBlocks['services'] = @'
H1 "SERVIZI ANOMALI"
try {
H2 'Servizi Auto che NON sono running'
$auto = Get-CimInstance Win32_Service | Where-Object {
$_.StartMode -eq 'Auto' -and $_.State -ne 'Running' -and $_.Name -notin @('gupdate','MapsBroker','RemoteRegistry','TrustedInstaller','WbioSrvc')
}
if ($auto) {
foreach ($s in $auto) {
if (Should-Stop) { return }
WARN (" [" + $s.State + "] " + $s.Name + " (" + $s.DisplayName + ")")
INFO (" ExitCode: " + $s.ExitCode + " PathName: " + $s.PathName)
}
} else { OK 'Tutti i servizi Auto sono running.' }
H2 'Top 30 servizi NON-Microsoft attivi'
$tp = Get-CimInstance Win32_Service | Where-Object {
$_.PathName -and $_.State -eq 'Running' -and $_.PathName -notmatch 'System32\\\\' -and $_.PathName -notmatch 'svchost'
}
foreach ($s in $tp | Select-Object -First 30) {
if (Should-Stop) { return }
RAW (" " + $s.Name.PadRight(35) + " " + $s.StartMode.PadRight(10) + " " + $s.DisplayName)
}
OK 'Analisi servizi completata.'
} catch { ERR $_.Exception.Message }
'@
# ----- HOSTS FILE -----
$diagBlocks['hosts'] = @'
H1 "FILE HOSTS"
$hf = "$env:SystemRoot\System32\drivers\etc\hosts"
if (-not (Test-Path $hf)) {
WARN ("Hosts file non trovato in " + $hf)
} else {
H2 ("Path: " + $hf)
try {
$lines = Get-Content -LiteralPath $hf -ErrorAction Stop
$custom = $lines | Where-Object { $_ -and -not ($_.Trim().StartsWith('#')) -and $_.Trim() -ne '' }
if (-not $custom -or $custom.Count -eq 0) {
OK 'Solo voci commentate (file hosts di default).'
} else {
H2 ("Voci attive non-commentate: " + $custom.Count)
foreach ($l in $custom) {
if ($l -match '127\.0\.0\.1\s+localhost' -or $l -match '::1\s+localhost') {
DIM (" " + $l)
} else {
WARN (" " + $l)
}
}
}
H2 'Integrita file'
$fi = Get-Item -LiteralPath $hf
$hash = (Get-FileHash -LiteralPath $hf -Algorithm SHA256).Hash
INFO ("Dimensione : " + $fi.Length + " bytes")
INFO ("Ultima modifica : " + $fi.LastWriteTime)
INFO ("SHA256 : " + $hash)
} catch { ERR $_.Exception.Message }
}
'@
# ----- BROWSER -----
$diagBlocks['browser'] = @'
H1 "BROWSER DI DEFAULT E CONFIGURAZIONE"
try {
H2 'Browser predefinito (HTTPS)'
$progid = (Get-ItemProperty 'HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\https\UserChoice' -ErrorAction SilentlyContinue).ProgId
if ($progid) {
OK ("ProgId: " + $progid)
$cmd = $null
foreach ($base in @('HKCU:\Software\Classes','HKLM:\Software\Classes')) {
$cls = Get-ItemProperty -Path ($base + '\' + $progid + '\shell\open\command') -ErrorAction SilentlyContinue
if ($cls) { $cmd = $cls.'(default)'; break }
}
if ($cmd) { INFO ("Comando: " + $cmd) }
} else { WARN 'Browser di default non determinabile.' }
H2 'Browser installati'
$browsers = @(
@{ Name='Edge (Chromium)'; Path="$env:ProgramFiles\Microsoft\Edge\Application\msedge.exe" }
@{ Name='Chrome'; Path="$env:ProgramFiles\Google\Chrome\Application\chrome.exe" }
@{ Name='Chrome (x86)'; Path="${env:ProgramFiles(x86)}\Google\Chrome\Application\chrome.exe" }
@{ Name='Firefox'; Path="$env:ProgramFiles\Mozilla Firefox\firefox.exe" }
@{ Name='Brave'; Path="$env:ProgramFiles\BraveSoftware\Brave-Browser\Application\brave.exe" }
@{ Name='Opera'; Path="$env:ProgramFiles\Opera\opera.exe" }
@{ Name='Vivaldi'; Path="$env:LOCALAPPDATA\Vivaldi\Application\vivaldi.exe" }
)
foreach ($b in $browsers) {
if (Test-Path $b.Path) {
try {
$ver = (Get-Item $b.Path).VersionInfo.FileVersion
OK (" " + $b.Name.PadRight(20) + " v" + $ver)
} catch { OK (" " + $b.Name) }
}
}
H2 'Dimensione profili utente'
$profiles = @(
@{ Name='Edge'; Path="$env:LOCALAPPDATA\Microsoft\Edge\User Data" }
@{ Name='Chrome'; Path="$env:LOCALAPPDATA\Google\Chrome\User Data" }
@{ Name='Firefox'; Path="$env:APPDATA\Mozilla\Firefox\Profiles" }
@{ Name='Brave'; Path="$env:LOCALAPPDATA\BraveSoftware\Brave-Browser\User Data" }
)
foreach ($p in $profiles) {
if (Should-Stop) { return }
if (Test-Path $p.Path) {
try {
$s = (Get-ChildItem $p.Path -Recurse -Force -ErrorAction SilentlyContinue | Measure-Object Length -Sum).Sum
$mb = [math]::Round($s/1MB, 1)
INFO (" " + $p.Name.PadRight(10) + " " + $mb + " MB " + $p.Path)
} catch {}
}
}
} catch { ERR $_.Exception.Message }
'@
# ----- TEMPERATURE -----
$diagBlocks['temp'] = @'
H1 "TEMPERATURE HARDWARE"
try {
H2 'Sensori ACPI'
$tz = $null
try { $tz = Get-CimInstance -Namespace 'root/wmi' -ClassName 'MSAcpi_ThermalZoneTemperature' -ErrorAction Stop } catch {}
if ($tz) {
foreach ($t in $tz) {
$celsius = [math]::Round($t.CurrentTemperature / 10 - 273.15, 1)
$clr = if ($celsius -gt 80) { '#F7768E' } elseif ($celsius -gt 60) { '#E0AF68' } else { '#9ECE6A' }
W (" " + $t.InstanceName + " " + $celsius + " gradi C") $clr
}
} else {
WARN 'Nessun sensore termico ACPI esposto.'
INFO 'Molti PC non espongono i sensori via ACPI.'
INFO 'Per monitoring affidabile usa HWiNFO64, HWMonitor o Core Temp.'
}
H2 'CPU - stato corrente'
Get-CimInstance Win32_Processor | ForEach-Object {
INFO ("Modello : " + $_.Name)
INFO ("Carico medio : " + $_.LoadPercentage + " %")
INFO ("Clock corrente: " + $_.CurrentClockSpeed + " MHz (max " + $_.MaxClockSpeed + " MHz)")
INFO ("Core fisici : " + $_.NumberOfCores + " Logical: " + $_.NumberOfLogicalProcessors)
}
} catch { ERR $_.Exception.Message }
'@
# ----- MONITORS -----
$diagBlocks['monitors'] = @'
H1 "MONITOR E VISUALIZZAZIONE"
try {
H2 'Monitor collegati (EDID)'
$mons = $null
try { $mons = Get-CimInstance -Namespace 'root/wmi' -ClassName 'WmiMonitorID' -ErrorAction Stop } catch {}
if ($mons) {
foreach ($m in $mons) {
$manuf = -join (($m.ManufacturerName | Where-Object { $_ -ne 0 }) | ForEach-Object { [char]$_ })
$name = -join (($m.UserFriendlyName | Where-Object { $_ -ne 0 }) | ForEach-Object { [char]$_ })
$serial = -join (($m.SerialNumberID | Where-Object { $_ -ne 0 }) | ForEach-Object { [char]$_ })
H2 ($manuf + ' ' + $name)
INFO (" Serial : " + $serial)
INFO (" Anno : " + $m.YearOfManufacture)
INFO (" Settimana: " + $m.WeekOfManufacture)
}
} else {
WARN 'EDID monitor non leggibili.'
}
H2 'Risoluzione e refresh rate'
Get-CimInstance Win32_VideoController | ForEach-Object {
if ($_.CurrentHorizontalResolution) {
INFO ("GPU: " + $_.Name)
INFO (" Risoluzione : " + $_.CurrentHorizontalResolution + " x " + $_.CurrentVerticalResolution)
INFO (" Refresh : " + $_.CurrentRefreshRate + " Hz")
INFO (" Profondita : " + $_.CurrentBitsPerPixel + "-bit")
}
}
H2 'DPI scaling'
$dpi = Get-ItemProperty 'HKCU:\Control Panel\Desktop\WindowMetrics' -ErrorAction SilentlyContinue
if ($dpi -and $dpi.AppliedDPI) {
$scale = [math]::Round($dpi.AppliedDPI / 96 * 100, 0)
INFO ("DPI corrente: " + $dpi.AppliedDPI + " (" + $scale + "%)")
}
} catch { ERR $_.Exception.Message }
'@
# ----- AUDIO -----
$diagBlocks['audio'] = @'
H1 "DISPOSITIVI AUDIO"
try {
H2 'Schede audio installate'
Get-CimInstance Win32_SoundDevice -ErrorAction Stop | ForEach-Object {
$clr = if ($_.Status -eq 'OK') { '#9ECE6A' } else { '#E0AF68' }
W (" [" + $_.Status + "] " + $_.Name) $clr
INFO (" Produttore: " + $_.Manufacturer)
}
H2 'Periferiche audio PnP'
Get-PnpDevice -Class 'Media' -PresentOnly -ErrorAction SilentlyContinue | ForEach-Object {
$clr = if ($_.Status -eq 'OK') { '#9ECE6A' } else { '#F7768E' }
W (" [" + $_.Status + "] " + $_.FriendlyName) $clr
}
OK 'Analisi audio completata.'
} catch { ERR $_.Exception.Message }
'@
# ----- USB -----
$diagBlocks['usb'] = @'
H1 "DISPOSITIVI USB CONNESSI"
try {
H2 'Periferiche USB attualmente collegate'
$usb = Get-PnpDevice -Class USB -PresentOnly -ErrorAction SilentlyContinue | Sort-Object FriendlyName
if ($usb) {
foreach ($d in $usb) {
$clr = if ($d.Status -eq 'OK') { '#9ECE6A' } else { '#F7768E' }
W (" [" + $d.Status + "] " + $d.FriendlyName) $clr
}
} else { WARN 'Nessun dispositivo USB rilevato.' }
H2 'Hub e controller USB del sistema'
Get-CimInstance Win32_USBController | ForEach-Object {
INFO (" " + $_.Name)
}
OK 'Analisi USB completata.'
} catch { ERR $_.Exception.Message }
'@
# ----- WINSXS CLEANUP -----
$diagBlocks['winsxs'] = @'
H1 "PULIZIA WINSXS"
W "DISM /Online /Cleanup-Image /StartComponentCleanup" '#7DCFFF'
W "Rimuove versioni vecchie di componenti Windows - libera GB di spazio." '#565F89'
H2 'Dimensione WinSxS attuale'
$path = "$env:SystemRoot\WinSxS"
if (Test-Path $path) {
try {
Status 'Calcolo dimensione WinSxS (puo richiedere 1-2 minuti)...'
$size = (Get-ChildItem $path -Recurse -Force -ErrorAction SilentlyContinue | Measure-Object Length -Sum).Sum
$gb = [math]::Round($size/1GB, 2)
INFO ("WinSxS attuale: $gb GB")
} catch { WARN 'Impossibile calcolare dimensione.' }
}
if (Should-Stop) { return }
H2 'Analisi component store (DISM AnalyzeComponentStore)'
Status 'DISM analizzando component store...'
Run-Native -Cmd 'DISM.exe' -Arguments @('/Online','/Cleanup-Image','/AnalyzeComponentStore') | Out-Null
if (Should-Stop) { return }
H2 'Esecuzione StartComponentCleanup'
Status 'DISM cleanup in corso (10-20 minuti)...'
$ec = Run-Native -Cmd 'DISM.exe' -Arguments @('/Online','/Cleanup-Image','/StartComponentCleanup')
if ($ec -eq 0) {
OK 'Pulizia WinSxS completata.'
H2 'Dimensione WinSxS dopo cleanup'
try {
$size = (Get-ChildItem $path -Recurse -Force -ErrorAction SilentlyContinue | Measure-Object Length -Sum).Sum
$gbAfter = [math]::Round($size/1GB, 2)
INFO ("WinSxS dopo: $gbAfter GB")
} catch {}
} else { WARN ("DISM cleanup terminato con exit code: $ec") }
'@
# ----- BROWSER CACHE -----
$diagBlocks['browsercache'] = @'
H1 "PULIZIA CACHE BROWSER"
W "Pulisce SOLO la cache (no cookies, no cronologia, no password)." '#7DCFFF'
W "Chiudere i browser per risultati migliori." '#565F89'
$caches = @(
@{ N='Edge cache'; P="$env:LOCALAPPDATA\Microsoft\Edge\User Data\Default\Cache" }
@{ N='Edge code cache'; P="$env:LOCALAPPDATA\Microsoft\Edge\User Data\Default\Code Cache" }
@{ N='Chrome cache'; P="$env:LOCALAPPDATA\Google\Chrome\User Data\Default\Cache" }
@{ N='Chrome code cache';P="$env:LOCALAPPDATA\Google\Chrome\User Data\Default\Code Cache" }
@{ N='Brave cache'; P="$env:LOCALAPPDATA\BraveSoftware\Brave-Browser\User Data\Default\Cache" }
)
$totalFreed = 0
foreach ($c in $caches) {
if (Should-Stop) { return }
if (-not (Test-Path $c.P)) { continue }
H2 ($c.N + ': ' + $c.P)
Status ('Pulizia ' + $c.N + '...')
try {
$before = (Get-ChildItem $c.P -Recurse -Force -ErrorAction SilentlyContinue | Measure-Object Length -Sum).Sum
} catch { $before = 0 }
$deleted = 0; $skipped = 0
Get-ChildItem $c.P -Recurse -Force -ErrorAction SilentlyContinue | ForEach-Object {
if (Should-Stop) { return }
try { Remove-Item -LiteralPath $_.FullName -Force -Recurse -ErrorAction Stop; $deleted++ } catch { $skipped++ }
}
try {
$after = (Get-ChildItem $c.P -Recurse -Force -ErrorAction SilentlyContinue | Measure-Object Length -Sum).Sum
} catch { $after = 0 }
$freed = [math]::Round(($before - $after)/1MB, 1)
$totalFreed += $freed
OK ("Liberati $freed MB (eliminati: $deleted, in uso: $skipped)")
}
H2 'Cache Firefox (profili)'
$ffProfiles = "$env:LOCALAPPDATA\Mozilla\Firefox\Profiles"
if (Test-Path $ffProfiles) {
Get-ChildItem $ffProfiles -Directory -ErrorAction SilentlyContinue | ForEach-Object {
if (Should-Stop) { return }
$cdir = Join-Path $_.FullName 'cache2'
if (Test-Path $cdir) {
Status ("Pulizia Firefox: " + $_.Name)
try { $before = (Get-ChildItem $cdir -Recurse -Force -ErrorAction SilentlyContinue | Measure-Object Length -Sum).Sum } catch { $before = 0 }
$del = 0; $skp = 0
Get-ChildItem $cdir -Recurse -Force -ErrorAction SilentlyContinue | ForEach-Object {
try { Remove-Item -LiteralPath $_.FullName -Force -Recurse -ErrorAction Stop; $del++ } catch { $skp++ }
}
try { $after = (Get-ChildItem $cdir -Recurse -Force -ErrorAction SilentlyContinue | Measure-Object Length -Sum).Sum } catch { $after = 0 }
$f = [math]::Round(($before-$after)/1MB, 1)
$totalFreed += $f
OK ("Firefox " + $_.Name + ": " + $f + " MB (elim: $del, in uso: $skp)")
}
}
}
W ''
OK ("Spazio totale liberato: $totalFreed MB")
INFO 'NB: cookies, cronologia, password e bookmark NON sono stati toccati.'
'@
# ----- SCHEDULED TASKS -----
$diagBlocks['tasks'] = @'
H1 "UTILITA DI PIANIFICAZIONE"
W "Attivita pianificate non-Microsoft e task con errori recenti." '#7DCFFF'
Status 'Lettura attivita pianificate...'
try {
$all = Get-ScheduledTask -ErrorAction Stop
$total = $all.Count
$msTasks = ($all | Where-Object { $_.TaskPath -match '^\\Microsoft\\' }).Count
$userTasks = $total - $msTasks
$ready = ($all | Where-Object { $_.State -eq 'Ready' }).Count
$running = ($all | Where-Object { $_.State -eq 'Running' }).Count
$disabled = ($all | Where-Object { $_.State -eq 'Disabled' }).Count
H2 'Riepilogo'
INFO ("Totale attivita pianificate : $total")
INFO ("Microsoft (sistema) : $msTasks")
INFO ("Non-Microsoft (utente) : $userTasks")
INFO ("Stato Ready : $ready")
INFO ("In esecuzione : $running")
INFO ("Disabilitate : $disabled")
H2 'Attivita NON-Microsoft (filtrate)'
$userOnly = $all | Where-Object { $_.TaskPath -notmatch '^\\Microsoft\\' } | Sort-Object State, TaskName
if (-not $userOnly -or $userOnly.Count -eq 0) {
INFO 'Nessuna attivita non-Microsoft trovata.'
} else {
foreach ($t in $userOnly) {
if (Should-Stop) { return }
$info = $null
try { $info = $t | Get-ScheduledTaskInfo -ErrorAction Stop } catch {}
$stateColor = switch ("$($t.State)") {
'Running' { '#7DCFFF' }
'Ready' { '#9ECE6A' }
'Disabled' { '#565F89' }
default { '#E0AF68' }
}
$taskName = "$($t.TaskPath)$($t.TaskName)"
W (" [" + ("$($t.State)").PadRight(8) + "] " + $taskName) $stateColor
if ($t.Author) { RAW (" Autore : " + $t.Author) }
if ($t.Description) {
$desc = ($t.Description -split "`n")[0]
if ($desc.Length -gt 100) { $desc = $desc.Substring(0,100) + '...' }
RAW (" Descriz. : " + $desc)
}
if ($info) {
if ($info.LastRunTime -and $info.LastRunTime.Year -gt 1900) {
$code = '0x{0:X8}' -f $info.LastTaskResult
$resColor = if ($info.LastTaskResult -eq 0) { '' } else { ' [ERRORE]' }
RAW (" Ultima : " + $info.LastRunTime.ToString('dd/MM/yyyy HH:mm') + " esito: $code$resColor")
}
if ($info.NextRunTime -and $info.NextRunTime.Year -gt 1900) {
RAW (" Prossima : " + $info.NextRunTime.ToString('dd/MM/yyyy HH:mm'))
}
}
foreach ($a in $t.Actions) {
if ($a.Execute) {
$argStr = if ($a.Arguments) { ' ' + $a.Arguments } else { '' }
$exec = $a.Execute
if (($exec + $argStr).Length -gt 110) {
$argStr = $argStr.Substring(0, [math]::Min($argStr.Length, 110 - $exec.Length)) + '...'
}
RAW (" -> Esegue: " + $exec + $argStr)
}
}
W '' '#C0CAF5'
}
}
H2 'Top 10 attivita con esecuzione fallita di recente'
$errored = @()
foreach ($t in $all) {
if (Should-Stop) { return }
try {
$i = $t | Get-ScheduledTaskInfo -ErrorAction Stop
if ($i.LastTaskResult -ne 0 -and $i.LastTaskResult -ne 267011 -and $i.LastRunTime.Year -gt 1900) {
$errored += [PSCustomObject]@{
Name = "$($t.TaskPath)$($t.TaskName)"
Code = '0x{0:X8}' -f $i.LastTaskResult
When = $i.LastRunTime
}
}
} catch {}
}
$errored = $errored | Sort-Object When -Descending | Select-Object -First 10
if ($errored) {
foreach ($e in $errored) {
WARN (" " + $e.Name.PadRight(60).Substring(0, [math]::Min(60, $e.Name.Length)) + " " + $e.Code + " " + $e.When.ToString('dd/MM HH:mm'))
}
} else {
OK 'Nessuna attivita in errore recente.'
}
OK 'Analisi attivita pianificate completata.'
} catch {
ERR ("Errore lettura task scheduler: " + $_.Exception.Message)
}
'@
# ----- NET CONFIG -----
$diagBlocks['netconfig'] = @'
H1 "CONFIGURAZIONE IP E ADATTATORI"
Status 'Analisi configurazione di rete...'
try {
H2 'Adattatori attivi'
Get-NetAdapter | Where-Object Status -eq 'Up' | ForEach-Object {
OK ($_.Name + " [" + $_.InterfaceDescription + "]")
INFO (" Velocita link : " + $_.LinkSpeed)
INFO (" Indirizzo MAC : " + $_.MacAddress)
INFO (" Stato media : " + $_.MediaConnectionState)
}
H2 'Configurazione IP'
Get-NetIPConfiguration | ForEach-Object {
if ($_.IPv4Address) {
INFO ($_.InterfaceAlias)
INFO (" IPv4 : " + ($_.IPv4Address.IPAddress -join ', '))
INFO (" Gateway IPv4 : " + ($_.IPv4DefaultGateway.NextHop -join ', '))
INFO (" DNS : " + ($_.DNSServer.ServerAddresses -join ', '))
INFO (" Profilo rete : " + $_.NetProfile.Name + " (" + $_.NetProfile.NetworkCategory + ")")
}
}
H2 'Output completo ipconfig /all'
Run-Native -Cmd 'ipconfig.exe' -Arguments @('/all') | Out-Null
OK 'Configurazione di rete completata.'
} catch { ERR $_.Exception.Message }
'@
# ----- PING -----
$diagBlocks['ping'] = @'
H1 "TEST PING - CONNETTIVITA"
Status 'Test connettivita...'
try {
$gw = (Get-NetIPConfiguration | Where-Object { $_.IPv4DefaultGateway -ne $null } | Select-Object -First 1).IPv4DefaultGateway.NextHop
$targets = @()
if ($gw) { $targets += [pscustomobject]@{ Name = 'Gateway LAN'; Host = $gw } }
$targets += [pscustomobject]@{ Name = 'Google DNS'; Host = '8.8.8.8' }
$targets += [pscustomobject]@{ Name = 'Cloudflare DNS'; Host = '1.1.1.1' }
$targets += [pscustomobject]@{ Name = 'Quad9 DNS'; Host = '9.9.9.9' }
$targets += [pscustomobject]@{ Name = 'google.com'; Host = 'google.com' }
$targets += [pscustomobject]@{ Name = 'microsoft.com'; Host = 'microsoft.com' }
foreach ($t in $targets) {
if (Should-Stop) { return }
H2 ($t.Name + " -> " + $t.Host)
Status ("Ping verso " + $t.Host + "...")
try {
$r = Test-Connection -ComputerName $t.Host -Count 4 -ErrorAction Stop
$rtts = $r | ForEach-Object { $_.ResponseTime }
$avg = [math]::Round(($rtts | Measure-Object -Average).Average, 1)
$mn = ($rtts | Measure-Object -Minimum).Minimum
$mx = ($rtts | Measure-Object -Maximum).Maximum
$loss = 4 - $r.Count
OK ("Min: $mn ms Med: $avg ms Max: $mx ms Persi: $loss/4")
} catch {
ERR ("Host non raggiungibile: " + $t.Host)
}
}
OK 'Test ping completato.'
} catch { ERR $_.Exception.Message }
'@
# ----- DNS TEST -----
$diagBlocks['dns'] = @'
H1 "DNS - TEST E BENCHMARK"
W "Verifica risoluzione DNS e confronta tempi di risposta tra server." '#7DCFFF'
Status 'Test DNS in corso...'
$dnsServers = @(
@{ Name='Sistema (default)'; Ip=$null },
@{ Name='Google'; Ip='8.8.8.8' },
@{ Name='Cloudflare'; Ip='1.1.1.1' },
@{ Name='Quad9'; Ip='9.9.9.9' },
@{ Name='OpenDNS'; Ip='208.67.222.222' }
)
$testDomains = @('google.com','github.com','microsoft.com','cloudflare.com','wikipedia.org')
$dnsMedi = @{}
foreach ($srv in $dnsServers) {
if (Should-Stop) { return }
if ($srv.Ip) { H2 ("Server DNS: " + $srv.Name + " (" + $srv.Ip + ")") }
else { H2 ("Server DNS: " + $srv.Name) }
$totalMs = 0; $ok = 0
foreach ($d in $testDomains) {
if (Should-Stop) { return }
try {
Clear-DnsClientCache -ErrorAction SilentlyContinue
$sw = [System.Diagnostics.Stopwatch]::StartNew()
if ($srv.Ip) {
$res = Resolve-DnsName -Name $d -Server $srv.Ip -DnsOnly -NoHostsFile -Type A -ErrorAction Stop -QuickTimeout
} else {
$res = Resolve-DnsName -Name $d -DnsOnly -NoHostsFile -Type A -ErrorAction Stop -QuickTimeout
}
$sw.Stop()
$ms = $sw.ElapsedMilliseconds
$ip = ($res | Where-Object Type -eq 'A' | Select-Object -First 1).IPAddress
if (-not $ip) { $ip = '(no A)' }
$c = if ($ms -lt 100) {'#9ECE6A'} elseif ($ms -lt 500) {'#E0AF68'} else {'#F7768E'}
W (" " + $d.PadRight(20) + " -> " + $ip.PadRight(18) + " " + $ms + " ms") $c
$totalMs += $ms; $ok++
} catch {
ERR (" " + $d.PadRight(20) + " -> FALLITO")
}
}
if ($ok -gt 0) {
$avg = [math]::Round($totalMs/$ok, 1)
OK ("Media tempo risposta: $avg ms ($ok/$($testDomains.Count) risolti)")
$dnsMedi[$srv.Name] = $avg
} else {
ERR 'Nessuna risoluzione riuscita.'
}
}
H2 'Classifica DNS (dal piu veloce)'
$dnsMedi.GetEnumerator() | Sort-Object Value | ForEach-Object {
$c = if ($_.Value -lt 100) {'#9ECE6A'} elseif ($_.Value -lt 500) {'#E0AF68'} else {'#F7768E'}
W (" " + ($_.Name.PadRight(28)) + " " + $_.Value + " ms") $c
}
$confName = $dnsServers[0].Name
$confMedia = $dnsMedi[$confName]
if ($confMedia) {
$altri = $dnsMedi.GetEnumerator() | Where-Object { $_.Name -ne $confName } | Sort-Object Value | Select-Object -First 1
if ($altri -and $confMedia -gt ($altri.Value * 1.5)) {
WARN ("Il DNS '" + $altri.Name + "' e' molto piu' veloce del tuo (" + $altri.Value + " ms vs " + $confMedia + " ms). Valuta di cambiare.")
} else {
OK 'Il DNS configurato e in linea con i migliori pubblici.'
}
}
H2 'Verifica DNS leak'
try {
$publicDns = (Resolve-DnsName -Name 'whoami.akamai.net' -Type A -ErrorAction Stop -QuickTimeout)
if ($publicDns) {
INFO ("DNS pubblico in uscita: " + ($publicDns | Where-Object Type -eq 'A' | Select-Object -First 1).IPAddress)
}
} catch { WARN 'Impossibile determinare il DNS pubblico in uscita.' }
OK 'Test DNS completato.'
'@
# ----- MTU DISCOVERY -----
$diagBlocks['mtu'] = @'
H1 "MTU - SCOPERTA AUTOMATICA"
W "Trova la MTU massima verso 8.8.8.8 tramite ricerca binaria." '#7DCFFF'
W "MTU corretta = ultimo size + 28 byte (header IP+ICMP)." '#565F89'
Status 'Ricerca MTU in corso...'
$target = '8.8.8.8'
function Test-MtuSize { param([int]$size, [string]$tg)
$r = & ping.exe -n 1 -w 1500 -f -l $size $tg 2>$null
return ($r -match 'TTL=')
}
H2 'Test MTU per interfaccia attiva'
Get-NetAdapter | Where-Object Status -eq 'Up' | ForEach-Object {
INFO (" Interfaccia: " + $_.Name + " MTU configurata: " + $_.MtuSize)
}
H2 "Ricerca binaria verso $target"
$low = 1200
$high = 1500
$lastOk = 0
INFO 'Test rapido a 1472 (ethernet standard - 28)...'
if (Test-MtuSize -size 1472 -tg $target) {
OK ' 1472 OK -> MTU >= 1500'
$lastOk = 1472
} else {
WARN ' 1472 fallito -> MTU minore di 1500'
}
if ($lastOk -lt 1472) {
INFO 'Inizio ricerca binaria...'
while (($high - $low) -gt 1) {
if (Should-Stop) { return }
$mid = [math]::Floor(($low + $high) / 2)
Status "Test MTU size $mid..."
if (Test-MtuSize -size $mid -tg $target) {
DIM (" $mid : OK")
$low = $mid
$lastOk = $mid
} else {
DIM (" $mid : FRAGMENT")
$high = $mid
}
}
}
$mtu = $lastOk + 28
W ''
OK ("MTU ottimale stimata: $mtu byte (payload max: $lastOk)")
if ($mtu -ge 1500) { INFO 'MTU standard ethernet (1500). Connessione ottimale.' }
elseif ($mtu -ge 1492) { INFO 'MTU tipica PPPoE (1492). Connessione ADSL/FTTH con PPPoE.' }
elseif ($mtu -ge 1450) { WARN 'MTU ridotta. Potresti avere VPN o doppio NAT.' }
else { WARN 'MTU molto bassa. Verifica configurazione router/VPN.' }
'@
# ----- TRACEROUTE -----
$diagBlocks['trace'] = @'
H1 "TRACEROUTE - PERCORSO DI RETE"
W "Traceroute custom basato su .NET Ping (1 probe per hop)." '#7DCFFF'
W "Veloce e interrompibile in qualsiasi momento." '#565F89'
$targets = @('8.8.8.8','1.1.1.1','google.com')
$maxHops = 18
$timeoutMs = 1200
foreach ($t in $targets) {
if (Should-Stop) { return }
H2 ("Verso " + $t)
Status ("Traceroute verso " + $t + "...")
$ping = New-Object System.Net.NetworkInformation.Ping
$opts = New-Object System.Net.NetworkInformation.PingOptions(1, $false)
$bytes = [System.Text.Encoding]::ASCII.GetBytes(('X' * 32))
$reached = $false
for ($ttl = 1; $ttl -le $maxHops; $ttl++) {
if (Should-Stop) { $ping.Dispose(); return }
$opts.Ttl = $ttl
try {
$sw = [System.Diagnostics.Stopwatch]::StartNew()
$reply = $ping.Send($t, $timeoutMs, $bytes, $opts)
$sw.Stop()
$ms = $sw.ElapsedMilliseconds
$addr = if ($reply -and $reply.Address) { $reply.Address.ToString() } else { '*' }
$stat = if ($reply) { $reply.Status.ToString() } else { 'NoReply' }
$line = (" hop {0,2} {1,5} ms {2,-18} [{3}]" -f $ttl, $ms, $addr, $stat)
if ($stat -eq 'Success') {
W $line '#9ECE6A'
$reached = $true
break
} elseif ($stat -eq 'TtlExpired') {
W $line '#7DCFFF'
} else {
W (" hop {0,2} ----- ms * [{1}]" -f $ttl, $stat) '#565F89'
}
} catch {
W (" hop {0,2} ----- ms * [errore]" -f $ttl) '#565F89'
}
}
try { $ping.Dispose() } catch {}
if (-not $reached) { WARN ("Destinazione non raggiunta entro $maxHops hop.") }
else { OK ("Destinazione raggiunta.") }
}
OK 'Traceroute completato.'
'@
# ----- WIFI -----
$diagBlocks['wifi'] = @'
H1 "WI-FI E PROFILI WIRELESS"
Status 'Analisi Wi-Fi...'
try {
H2 'Interfacce Wi-Fi'
Run-Native -Cmd 'netsh.exe' -Arguments @('wlan','show','interfaces') | Out-Null
if (Should-Stop) { return }
H2 'Profili Wi-Fi salvati'
Run-Native -Cmd 'netsh.exe' -Arguments @('wlan','show','profiles') | Out-Null
if (Should-Stop) { return }
H2 'Driver wireless'
Run-Native -Cmd 'netsh.exe' -Arguments @('wlan','show','drivers') | Out-Null
OK 'Analisi Wi-Fi completata.'
} catch { ERR $_.Exception.Message }
'@
# ----- PORTS -----
$diagBlocks['ports'] = @'
H1 "PORTE E CONNESSIONI ATTIVE"
Status 'Analisi porte e connessioni...'
try {
H2 'Porte in ascolto (LISTENING)'
$listen = Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue | Sort-Object LocalPort -Unique
foreach ($c in $listen | Select-Object -First 30) {
if (Should-Stop) { return }
$proc = try { (Get-Process -Id $c.OwningProcess -ErrorAction Stop).ProcessName } catch { '?' }
RAW (" " + ($c.LocalAddress + ":" + $c.LocalPort).PadRight(28) + " " + $proc)
}
H2 'Connessioni stabilite'
$estab = Get-NetTCPConnection -State Established -ErrorAction SilentlyContinue | Select-Object -First 20
foreach ($c in $estab) {
if (Should-Stop) { return }
$proc = try { (Get-Process -Id $c.OwningProcess -ErrorAction Stop).ProcessName } catch { '?' }
RAW (" " + ($c.LocalAddress + ":" + $c.LocalPort).PadRight(28) + " -> " + ($c.RemoteAddress + ":" + $c.RemotePort).PadRight(28) + " " + $proc)
}
H2 'Test porte comuni in uscita'
$portTests = @(
@{ Host='google.com'; Port=80; Name='HTTP' },
@{ Host='google.com'; Port=443; Name='HTTPS' },
@{ Host='gmail.com'; Port=587; Name='SMTP' },
@{ Host='gmail.com'; Port=993; Name='IMAPS' }
)
foreach ($p in $portTests) {
if (Should-Stop) { return }
$r = Test-NetConnection -ComputerName $p.Host -Port $p.Port -InformationLevel Quiet -WarningAction SilentlyContinue
if ($r) { OK ($p.Name + " (" + $p.Host + ":" + $p.Port + ") aperta") }
else { ERR ($p.Name + " (" + $p.Host + ":" + $p.Port + ") bloccata") }
}
H2 'Top 5 processi per connessioni TCP attive'
$byProc = Get-NetTCPConnection -State Established -ErrorAction SilentlyContinue |
Group-Object -Property OwningProcess | Sort-Object Count -Descending | Select-Object -First 5
foreach ($g in $byProc) {
if (Should-Stop) { return }
$proc = try { Get-Process -Id $g.Name -ErrorAction Stop } catch { $null }
if ($proc) {
$name = $proc.ProcessName
INFO (" " + $name.PadRight(30).Substring(0,[math]::Min(30,$name.Length)) + " -> " + $g.Count + " connessioni attive")
}
}
OK 'Analisi porte completata.'
} catch { ERR $_.Exception.Message }
'@
# ----- TTFB (TEMPO RISPOSTA WEB) -----
$diagBlocks['ttfb'] = @'
H1 "TTFB - TEMPO RISPOSTA SITI WEB (HTTPS)"
W "Misura il Time-To-First-Byte verso siti popolari." '#7DCFFF'
W "Indica la velocita reale percepita durante la navigazione." '#565F89'
Status 'Test TTFB in corso...'
$siti = @('google.com','microsoft.com','github.com','cloudflare.com','amazon.it','repubblica.it','youtube.com')
$tempi = @()
foreach ($d in $siti) {
if (Should-Stop) { return }
try {
$sw = [System.Diagnostics.Stopwatch]::StartNew()
$null = Invoke-WebRequest -Uri ("https://" + $d) -Method Head -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop
$sw.Stop()
$ms = [math]::Round($sw.ElapsedMilliseconds, 0)
$tempi += $ms
$c = if ($ms -lt 500) {'#9ECE6A'} elseif ($ms -lt 1500) {'#E0AF68'} else {'#F7768E'}
W (" " + $d.PadRight(28) + " " + ($ms.ToString().PadLeft(6)) + " ms") $c
} catch {
W (" " + $d.PadRight(28) + " TIMEOUT / ERRORE") '#F7768E'
}
}
W ''
if ($tempi.Count -gt 0) {
$avg = [math]::Round(($tempi | Measure-Object -Average).Average, 0)
$min = ($tempi | Measure-Object -Minimum).Minimum
$max = ($tempi | Measure-Object -Maximum).Maximum
INFO ("Min: $min ms Media: $avg ms Max: $max ms")
if ($avg -gt 1500) { ERR ("TTFB medio MOLTO alto: $avg ms - navigazione molto lenta") }
elseif ($avg -gt 700) { WARN ("TTFB medio alto: $avg ms - possibile congestione o DNS lento") }
else { OK ("TTFB medio buono: $avg ms") }
} else {
ERR 'Nessun sito raggiungibile.'
}
'@
# ----- ANTIVIRUS / HTTPS SCANNING -----
$diagBlocks['antivirus'] = @'
H1 "ANTIVIRUS E SCANSIONE HTTPS"
W "Gli antivirus di terze parti intercettano spesso il traffico HTTPS." '#7DCFFF'
W "Questo puo' rallentare drasticamente la navigazione." '#565F89'
Status 'Rilevamento antivirus...'
H2 'Software antivirus registrato in Windows Security Center'
try {
$av = Get-CimInstance -Namespace 'root/SecurityCenter2' -ClassName 'AntiVirusProduct' -ErrorAction Stop
foreach ($a in $av) {
$state = '0x{0:X}' -f $a.productState
INFO (" " + $a.displayName.PadRight(40) + " stato: " + $state + " path: " + $a.pathToSignedProductExe)
}
if (-not $av) { WARN 'Nessun antivirus registrato in WMI SecurityCenter.' }
} catch { WARN 'Impossibile leggere SecurityCenter (necessari privilegi admin).' }
H2 'Processi antivirus attivi (terze parti)'
$avList = @('avast','avg','kaspersky','bitdefender','eset','nod32','mcafee','norton','trend','sophos','f-secure','gdata','comodo','webroot','malwarebytes','panda','crowdstrike','sentinelone')
$procs = Get-Process -ErrorAction SilentlyContinue
$found = @()
foreach ($a in $avList) {
$match = $procs | Where-Object { $_.Name -match $a -or $_.Company -match $a }
if ($match) {
$found += $a
foreach ($m in $match | Select-Object -First 3) {
INFO (" " + $m.Name + " (" + $m.Company + ")")
}
}
}
W ''
if ($found.Count -gt 0) {
WARN ("Antivirus terzo rilevato: " + ($found -join ', '))
W " Se la navigazione e' lenta:" '#E0AF68'
W " 1. Apri il pannello dell'antivirus" '#C0CAF5'
W " 2. Disattiva temporaneamente 'protezione web' o 'scansione HTTPS/SSL'" '#C0CAF5'
W " 3. Ritesta TTFB e DNS per confrontare" '#C0CAF5'
} else {
OK 'Nessun antivirus terzo rilevato. Stai usando probabilmente solo Windows Defender.'
}
H2 'Windows Defender'
try {
$def = Get-MpComputerStatus -ErrorAction Stop
INFO ("Antivirus attivo : " + $def.AntivirusEnabled)
INFO ("Real-time prot. : " + $def.RealTimeProtectionEnabled)
INFO ("Versione firme : " + $def.AntivirusSignatureVersion)
INFO ("Ultimo aggiorn. : " + $def.AntivirusSignatureLastUpdated)
} catch { DIM 'Get-MpComputerStatus non disponibile.' }
'@
# ----- NET FULL (combina tutte le verifiche di rete) -----
$diagBlocks['netfull'] = @'
H1 "DIAGNOSTICA RETE COMPLETA"
W "Esecuzione sequenziale di tutti i test di rete." '#7DCFFF'
# Public IP test
H2 'IP pubblico e geolocalizzazione'
Status 'Recupero IP pubblico...'
try {
$ip = Invoke-RestMethod -Uri 'https://api.ipify.org?format=json' -TimeoutSec 6 -ErrorAction Stop
OK ("IP pubblico: " + $ip.ip)
try {
$geo = Invoke-RestMethod -Uri ("http://ip-api.com/json/" + $ip.ip + "?fields=country,regionName,city,isp,org") -TimeoutSec 6 -ErrorAction Stop
INFO ("Provider : " + $geo.isp)
INFO ("Organizz. : " + $geo.org)
INFO ("Posizione : " + $geo.city + ", " + $geo.regionName + ", " + $geo.country)
} catch { WARN 'Geolocalizzazione non disponibile.' }
} catch { ERR 'Impossibile contattare api.ipify.org' }
if (Should-Stop) { return }
& ([scriptblock]::Create($Blocks['netconfig']))
if (Should-Stop) { return }
& ([scriptblock]::Create($Blocks['ping']))
if (Should-Stop) { return }
& ([scriptblock]::Create($Blocks['dns']))
if (Should-Stop) { return }
& ([scriptblock]::Create($Blocks['mtu']))
if (Should-Stop) { return }
& ([scriptblock]::Create($Blocks['trace']))
if (Should-Stop) { return }
& ([scriptblock]::Create($Blocks['ports']))
if (Should-Stop) { return }
& ([scriptblock]::Create($Blocks['ttfb']))
if (Should-Stop) { return }
& ([scriptblock]::Create($Blocks['antivirus']))
if (Should-Stop) { return }
H1 'DIAGNOSTICA RETE COMPLETATA'
OK 'Tutti i test di rete sono stati eseguiti.'
'@
# ----- DISK -----
$diagBlocks['disk'] = @'
H1 "DISCHI E STATO SMART"
Status 'Analisi unita di archiviazione...'
try {
H2 'Unita logiche'
Get-Volume | Where-Object DriveLetter | ForEach-Object {
$size = if ($_.Size) { [math]::Round($_.Size/1GB,1) } else { 0 }
$free = if ($_.SizeRemaining) { [math]::Round($_.SizeRemaining/1GB,1) } else { 0 }
$pct = if ($_.Size -gt 0) { [math]::Round(($_.Size-$_.SizeRemaining)/$_.Size*100,1) } else { 0 }
$clr = if ($pct -gt 90) { '#F7768E' } elseif ($pct -gt 75) { '#E0AF68' } else { '#9ECE6A' }
W (" [" + $_.DriveLetter + ":] " + $_.FileSystemLabel.PadRight(20).Substring(0,[math]::Min(20,($_.FileSystemLabel + '').Length)) + " " + $free + " GB liberi su " + $size + " GB (uso $pct%)") $clr
}
H2 'Dischi fisici e SMART'
Get-PhysicalDisk | ForEach-Object {
$sz = [math]::Round($_.Size/1GB,1)
$hc = $_.HealthStatus
$op = $_.OperationalStatus
$clr = if ($hc -eq 'Healthy') { '#9ECE6A' } else { '#F7768E' }
W (" " + $_.FriendlyName.PadRight(40).Substring(0,[math]::Min(40,$_.FriendlyName.Length)) + " " + $_.MediaType.PadRight(8) + " " + $sz + " GB SMART: " + $hc + " Stato: " + $op) $clr
}
H2 'Dimensione cartelle temp'
$tempPaths = @($env:TEMP, "$env:WINDIR\Temp", "$env:WINDIR\SoftwareDistribution\Download")
foreach ($p in $tempPaths) {
if (Should-Stop) { return }
if (Test-Path $p) {
try {
$s = (Get-ChildItem $p -Recurse -Force -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum
$mb = [math]::Round(($s/1MB),1)
INFO (" " + $p.PadRight(55).Substring(0,[math]::Min(55,$p.Length)) + " " + $mb + " MB")
} catch {}
}
}
OK 'Analisi disco completata.'
} catch { ERR $_.Exception.Message }
'@
# ----- MEMORY -----
$diagBlocks['memory'] = @'
H1 "MEMORIA RAM"
Status 'Analisi memoria...'
try {
H2 'Moduli RAM installati'
Get-CimInstance Win32_PhysicalMemory | ForEach-Object {
$cap = [math]::Round($_.Capacity/1GB,1)
$sp = $_.ConfiguredClockSpeed
$man = $_.Manufacturer
$pn = $_.PartNumber
$loc = $_.DeviceLocator
INFO (" Slot " + $loc.PadRight(15) + " " + $cap + " GB " + $sp + " MHz " + $man.Trim() + " " + $pn.Trim())
}
H2 'Utilizzo attuale'
$os = Get-CimInstance Win32_OperatingSystem
$tot = [math]::Round($os.TotalVisibleMemorySize/1MB,2)
$free = [math]::Round($os.FreePhysicalMemory/1MB,2)
$used = [math]::Round($tot-$free,2)
$pct = [math]::Round(($used/$tot)*100,1)
INFO ("Totale : $tot GB")
INFO ("Libera : $free GB")
INFO ("In uso : $used GB ($pct%)")
H2 'File di paging'
Get-CimInstance Win32_PageFileUsage | ForEach-Object {
INFO (" " + $_.Name + " " + [math]::Round($_.AllocatedBaseSize) + " MB (uso corrente " + [math]::Round($_.CurrentUsage) + " MB)")
}
OK 'Analisi memoria completata.'
} catch { ERR $_.Exception.Message }
'@
# ----- GPU -----
$diagBlocks['gpu'] = @'
H1 "GPU - SCHEDA GRAFICA"
Status 'Analisi GPU...'
try {
Get-CimInstance Win32_VideoController | ForEach-Object {
H2 ($_.Name)
$vram = if ($_.AdapterRAM) { [math]::Round($_.AdapterRAM/1GB,1) } else { 'n/d' }
INFO ("Driver : " + $_.DriverVersion)
INFO ("Data driver : " + $_.DriverDate)
INFO ("Risoluzione : " + $_.CurrentHorizontalResolution + "x" + $_.CurrentVerticalResolution + " @ " + $_.CurrentRefreshRate + "Hz")
INFO ("VRAM : $vram GB")
INFO ("Stato : " + $_.Status)
}
OK 'Analisi GPU completata.'
} catch { ERR $_.Exception.Message }
'@
# ----- BATTERY -----
$diagBlocks['battery'] = @'
H1 "BATTERIA (NOTEBOOK)"
Status 'Generazione report batteria...'
try {
$bat = Get-CimInstance Win32_Battery -ErrorAction SilentlyContinue
if (-not $bat) {
WARN 'Nessuna batteria rilevata (PC desktop?).'
return
}
$bat | ForEach-Object {
H2 ($_.Name)
INFO ("Livello carica : " + $_.EstimatedChargeRemaining + " %")
INFO ("Stato : " + $_.BatteryStatus)
INFO ("Tempo residuo : " + (if ($_.EstimatedRunTime -lt 71582788) { $_.EstimatedRunTime.ToString() + " min" } else { 'collegato a corrente' }))
INFO ("Tipo : " + $_.Chemistry)
}
H2 'Generazione report HTML dettagliato'
$outFile = Join-Path $env:USERPROFILE "Desktop\battery-report.html"
$ec = Run-Native -Cmd 'powercfg.exe' -Arguments @('/batteryreport', '/output', "`"$outFile`"")
if (Test-Path $outFile) {
OK ("Report salvato sul Desktop: " + $outFile)
}
} catch { ERR $_.Exception.Message }
'@
# ----- FLUSH DNS -----
$diagBlocks['flushdns'] = @'
H1 "PULIZIA CACHE DNS"
Status 'Pulizia cache DNS...'
$ec = Run-Native -Cmd 'ipconfig.exe' -Arguments @('/flushdns')
if ($ec -eq 0) { OK 'Cache DNS svuotata correttamente.' }
else { ERR 'Errore durante la pulizia della cache DNS.' }
'@
# ----- CLEAN TEMP -----
$diagBlocks['cleantemp'] = @'
H1 "PULIZIA FILE TEMPORANEI"
W "Vengono eliminati i file in TEMP utente, TEMP sistema e cache prefetch." '#7DCFFF'
$paths = @($env:TEMP, "$env:WINDIR\Temp", "$env:WINDIR\Prefetch")
$totalFreed = 0
foreach ($p in $paths) {
if (Should-Stop) { return }
H2 ("Pulizia: " + $p)
Status ("Pulizia $p...")
if (-not (Test-Path $p)) { WARN 'Percorso non trovato.'; continue }
$files = Get-ChildItem $p -Recurse -Force -ErrorAction SilentlyContinue
$sizeBefore = ($files | Measure-Object -Property Length -Sum).Sum
$deleted = 0; $skipped = 0
foreach ($f in $files) {
if (Should-Stop) { return }
try {
Remove-Item -LiteralPath $f.FullName -Force -Recurse -ErrorAction Stop
$deleted++
} catch { $skipped++ }
}
$filesAfter = Get-ChildItem $p -Recurse -Force -ErrorAction SilentlyContinue
$sizeAfter = ($filesAfter | Measure-Object -Property Length -Sum).Sum
$freed = [math]::Round(($sizeBefore - $sizeAfter)/1MB, 1)
$totalFreed += $freed
OK ("Liberati $freed MB (eliminati: $deleted, in uso: $skipped)")
}
W ''
OK ("Spazio totale liberato: $totalFreed MB")
'@
# ----- RESET NET (ATTENZIONE: serve riavvio) -----
$diagBlocks['resetnet'] = @'
H1 "RESET STACK DI RETE"
W "ATTENZIONE: queste operazioni richiedono il RIAVVIO del computer." '#F7768E'
W "Vengono ripristinati Winsock, TCP/IP e cache DNS." '#7DCFFF'
H2 'Reset Winsock'
Status 'netsh winsock reset...'
Run-Native -Cmd 'netsh.exe' -Arguments @('winsock','reset') | Out-Null
if (Should-Stop) { return }
H2 'Reset stack TCP/IP'
Status 'netsh int ip reset...'
Run-Native -Cmd 'netsh.exe' -Arguments @('int','ip','reset') | Out-Null
if (Should-Stop) { return }
H2 'Reset cache ARP'
Status 'netsh interface ip delete arpcache...'
Run-Native -Cmd 'netsh.exe' -Arguments @('interface','ip','delete','arpcache') | Out-Null
if (Should-Stop) { return }
H2 'Rilascio e rinnovo IP'
Run-Native -Cmd 'ipconfig.exe' -Arguments @('/release') | Out-Null
Run-Native -Cmd 'ipconfig.exe' -Arguments @('/renew') | Out-Null
Run-Native -Cmd 'ipconfig.exe' -Arguments @('/flushdns') | Out-Null
W ''
W "==================================================================" '#E0AF68'
WARN 'RIAVVIA IL COMPUTER per completare il reset.'
W "==================================================================" '#E0AF68'
'@
# ----- DIAGNOSTICA COMPLETA -----
$diagBlocks['full'] = @'
H1 "DIAGNOSTICA COMPLETA - AVVIO"
W "Verranno eseguite tutte le verifiche non distruttive." '#7DCFFF'
W "Tempo stimato: 15-30 minuti." '#565F89'
$started = Get-Date
& ([scriptblock]::Create($Blocks['sysinfo']))
if (Should-Stop) { return }
& ([scriptblock]::Create($Blocks['perf']))
if (Should-Stop) { return }
& ([scriptblock]::Create($Blocks['disk']))
if (Should-Stop) { return }
& ([scriptblock]::Create($Blocks['memory']))
if (Should-Stop) { return }
& ([scriptblock]::Create($Blocks['gpu']))
if (Should-Stop) { return }
& ([scriptblock]::Create($Blocks['drivers']))
if (Should-Stop) { return }
& ([scriptblock]::Create($Blocks['events']))
if (Should-Stop) { return }
& ([scriptblock]::Create($Blocks['netfull']))
if (Should-Stop) { return }
& ([scriptblock]::Create($Blocks['sfc']))
if (Should-Stop) { return }
& ([scriptblock]::Create($Blocks['dism']))
if (Should-Stop) { return }
& ([scriptblock]::Create($Blocks['chkdsk']))
$elapsed = (Get-Date) - $started
H1 "DIAGNOSTICA COMPLETA - TERMINATA"
OK ("Durata totale: {0}h {1}m {2}s" -f $elapsed.Hours, $elapsed.Minutes, $elapsed.Seconds)
'@
# -----------------------------------------------------------------------------
# Avvio task in runspace separato
# -----------------------------------------------------------------------------
function Start-DiagnosticTask {
param([string]$Key, [string]$Title, [string]$Subtitle)
if ($script:syncHash.Running) { return }
if (-not $diagBlocks.ContainsKey($Key)) {
Set-Header "Errore" "Diagnostica '$Key' non trovata."
return
}
# Pulisce il documento e prepara per nuovo output
$ctrl.OutputBox.Document.Blocks.Clear()
$p = New-Object Windows.Documents.Paragraph
$p.Margin = New-Object Windows.Thickness(0)
$ctrl.OutputBox.Document.Blocks.Add($p)
Set-Header $Title $Subtitle
Set-Running -Running $true -StatusText "Avvio: $Title..."
$script:syncHash.Stop = $false
$entryBody = "`n& ([scriptblock]::Create(`$Blocks[`$TaskKey]))`n"
$fullScript = $helpersBlock + $entryBody
$script:runspace = [runspacefactory]::CreateRunspace()
$script:runspace.ApartmentState = "STA"
$script:runspace.ThreadOptions = "ReuseThread"
$script:runspace.Open()
$script:runspace.SessionStateProxy.SetVariable("syncHash", $script:syncHash)
$script:runspace.SessionStateProxy.SetVariable("Blocks", $diagBlocks)
$script:runspace.SessionStateProxy.SetVariable("TaskKey", $Key)
$script:psInstance = [powershell]::Create()
$script:psInstance.Runspace = $script:runspace
[void]$script:psInstance.AddScript($fullScript)
$script:asyncResult = $script:psInstance.BeginInvoke()
}
# Timer per monitorare completamento task
$timer = New-Object System.Windows.Threading.DispatcherTimer
$timer.Interval = [TimeSpan]::FromMilliseconds(400)
$timer.Add_Tick({
if ($script:asyncResult -and $script:asyncResult.IsCompleted) {
try { $script:psInstance.EndInvoke($script:asyncResult) | Out-Null } catch {}
try { $script:psInstance.Dispose() } catch {}
try { $script:runspace.Close() } catch {}
$script:asyncResult = $null
$script:psInstance = $null
$script:runspace = $null
if ($script:syncHash.Stop) {
Set-Running -Running $false -StatusText "Interrotto dall'utente."
} else {
Set-Running -Running $false -StatusText "Operazione completata."
}
}
})
$timer.Start()
# -----------------------------------------------------------------------------
# Eventi UI - barra titolo
# -----------------------------------------------------------------------------
$ctrl.TitleBar.Add_MouseLeftButtonDown({ $window.DragMove() })
$ctrl.BtnMinimize.Add_Click({ $window.WindowState = 'Minimized' })
$ctrl.BtnMaximize.Add_Click({
if ($window.WindowState -eq 'Maximized') { $window.WindowState = 'Normal' }
else { $window.WindowState = 'Maximized' }
})
$ctrl.BtnClose.Add_Click({
if ($script:syncHash.Running) {
$r = [System.Windows.MessageBox]::Show($window, "C'e' una diagnostica in corso. Vuoi chiudere comunque?", "Conferma", "YesNo", "Warning")
if ($r -ne 'Yes') { return }
$script:syncHash.Stop = $true
if ($script:psInstance) { try { [void]$script:psInstance.BeginStop($null, $null) } catch {} }
}
$window.Close()
})
# -----------------------------------------------------------------------------
# Eventi UI - sidebar buttons
# -----------------------------------------------------------------------------
# --- Tutti i pulsanti sidebar aprono l anteprima ---
$ctrl.BtnFull.Add_Click({ Show-Preview 'full' })
$ctrl.BtnSysInfo.Add_Click({ Show-Preview 'sysinfo' })
$ctrl.BtnSFC.Add_Click({ Show-Preview 'sfc' })
$ctrl.BtnDISM.Add_Click({ Show-Preview 'dism' })
$ctrl.BtnCHKDSK.Add_Click({ Show-Preview 'chkdsk' })
$ctrl.BtnEvents.Add_Click({ Show-Preview 'events' })
$ctrl.BtnDrivers.Add_Click({ Show-Preview 'drivers' })
$ctrl.BtnPerf.Add_Click({ Show-Preview 'perf' })
$ctrl.BtnTasks.Add_Click({ Show-Preview 'tasks' })
$ctrl.BtnBootTime.Add_Click({ Show-Preview 'boottime' })
$ctrl.BtnBSOD.Add_Click({ Show-Preview 'bsod' })
$ctrl.BtnBitLocker.Add_Click({ Show-Preview 'bitlocker' })
$ctrl.BtnActivation.Add_Click({ Show-Preview 'activation' })
$ctrl.BtnUpdates.Add_Click({ Show-Preview 'updates' })
$ctrl.BtnFirewall.Add_Click({ Show-Preview 'firewall' })
$ctrl.BtnFailedLogons.Add_Click({ Show-Preview 'failedlogons' })
$ctrl.BtnTPM.Add_Click({ Show-Preview 'tpm' })
$ctrl.BtnPrograms.Add_Click({ Show-Preview 'programs' })
$ctrl.BtnServices.Add_Click({ Show-Preview 'services' })
$ctrl.BtnHosts.Add_Click({ Show-Preview 'hosts' })
$ctrl.BtnBrowser.Add_Click({ Show-Preview 'browser' })
$ctrl.BtnTemp.Add_Click({ Show-Preview 'temp' })
$ctrl.BtnMonitors.Add_Click({ Show-Preview 'monitors' })
$ctrl.BtnAudio.Add_Click({ Show-Preview 'audio' })
$ctrl.BtnUSB.Add_Click({ Show-Preview 'usb' })
$ctrl.BtnWinSxS.Add_Click({ Show-Preview 'winsxs' })
$ctrl.BtnBrowserCache.Add_Click({ Show-Preview 'browsercache' })
$ctrl.BtnNetFull.Add_Click({ Show-Preview 'netfull' })
$ctrl.BtnNetConfig.Add_Click({ Show-Preview 'netconfig' })
$ctrl.BtnPing.Add_Click({ Show-Preview 'ping' })
$ctrl.BtnDNS.Add_Click({ Show-Preview 'dns' })
$ctrl.BtnMTU.Add_Click({ Show-Preview 'mtu' })
$ctrl.BtnTrace.Add_Click({ Show-Preview 'trace' })
$ctrl.BtnWiFi.Add_Click({ Show-Preview 'wifi' })
$ctrl.BtnPorts.Add_Click({ Show-Preview 'ports' })
$ctrl.BtnTTFB.Add_Click({ Show-Preview 'ttfb' })
$ctrl.BtnAntivirus.Add_Click({ Show-Preview 'antivirus' })
$ctrl.BtnDisk.Add_Click({ Show-Preview 'disk' })
$ctrl.BtnMemory.Add_Click({ Show-Preview 'memory' })
$ctrl.BtnGPU.Add_Click({ Show-Preview 'gpu' })
$ctrl.BtnBattery.Add_Click({ Show-Preview 'battery' })
$ctrl.BtnFlushDNS.Add_Click({ Show-Preview 'flushdns' })
$ctrl.BtnCleanTemp.Add_Click({ Show-Preview 'cleantemp' })
$ctrl.BtnResetNet.Add_Click({ Show-Preview 'resetnet' })
# --- Pulsante AVVIA: esegue la diagnostica selezionata ---
$ctrl.BtnRunAll.Add_Click({
if ($script:syncHash.Running) { return }
$key = if ($script:selectedDiag) { $script:selectedDiag } else { 'full' }
$info = $diagInfo[$key]
if (-not $info) { return }
if ($info.Confirm) {
$icon = if ($info.Risk -eq 'attenzione') { 'Warning' } else { 'Question' }
$r = [System.Windows.MessageBox]::Show($window, $info.ConfirmMessage, 'Conferma esecuzione', 'YesNo', $icon)
if ($r -ne 'Yes') { return }
}
Start-DiagnosticTask -Key $key -Title $info.Title -Subtitle $info.Subtitle
})
# -----------------------------------------------------------------------------
# Bottoni inferiori
# -----------------------------------------------------------------------------
$ctrl.BtnStop.Add_Click({
$script:syncHash.Stop = $true
$ctrl.LblStatus.Text = "Interruzione in corso..."
$ctrl.BtnStop.IsEnabled = $false
if ($script:psInstance) {
try { [void]$script:psInstance.BeginStop($null, $null) } catch {}
}
})
$ctrl.BtnClear.Add_Click({
$ctrl.OutputBox.Document.Blocks.Clear()
$p = New-Object Windows.Documents.Paragraph
$p.Margin = New-Object Windows.Thickness(0)
$r = New-Object Windows.Documents.Run "Console pulita."
$r.Foreground = (New-Object Windows.Media.BrushConverter).ConvertFrom('#565F89')
$p.Inlines.Add($r)
$ctrl.OutputBox.Document.Blocks.Add($p)
})
$ctrl.BtnSave.Add_Click({
$dlg = New-Object Microsoft.Win32.SaveFileDialog
$dlg.Filter = "File di testo (*.txt)|*.txt|Tutti i file (*.*)|*.*"
$dlg.FileName = "diagnostica-windows-" + (Get-Date -Format 'yyyyMMdd-HHmmss') + ".txt"
if ($dlg.ShowDialog()) {
try {
$tr = New-Object Windows.Documents.TextRange($ctrl.OutputBox.Document.ContentStart, $ctrl.OutputBox.Document.ContentEnd)
$tr.Text | Out-File -FilePath $dlg.FileName -Encoding utf8
[System.Windows.MessageBox]::Show($window, "Log salvato:`n$($dlg.FileName)", "Salvato", "OK", "Information") | Out-Null
} catch {
[System.Windows.MessageBox]::Show($window, "Errore nel salvataggio:`n$($_.Exception.Message)", "Errore", "OK", "Error") | Out-Null
}
}
})
# Chiusura: pulisci runspace
$window.Add_Closed({
$script:syncHash.Stop = $true
if ($script:psInstance) {
try { [void]$script:psInstance.BeginStop($null, $null) } catch {}
try { $script:psInstance.Dispose() } catch {}
}
if ($script:runspace) {
try { $script:runspace.Close() } catch {}
}
$timer.Stop()
})
# -----------------------------------------------------------------------------
# Stato iniziale: mostra anteprima della diagnostica completa
# -----------------------------------------------------------------------------
Show-Preview 'full'
# -----------------------------------------------------------------------------
# Avvio
# -----------------------------------------------------------------------------
[void]$window.ShowDialog()