# diag.ps1 - Laptop/Surface diagnostics # Usage: irm https:// | iex (OOBE via Shift+F10 > powershell, or on a user's session) # Works with and without admin. Without admin some checks are skipped and reported as such. # Report: C:\Diag\__.txt + one line in C:\Diag\summary.csv $ErrorActionPreference = 'SilentlyContinue' $ProgressPreference = 'SilentlyContinue' # ============ helpers ============ function Test-Admin { $id = [Security.Principal.WindowsIdentity]::GetCurrent() (New-Object Security.Principal.WindowsPrincipal $id).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } function GB($b) { if ($b) { [math]::Round($b / 1GB, 1) } else { 0 } } $isAdmin = Test-Admin $lines = New-Object System.Collections.Generic.List[string] $flags = New-Object System.Collections.Generic.List[object] # RED / ORANGE / INFO function Add($t = '') { $lines.Add($t) } function Flag($sev, $txt) { $flags.Add([pscustomobject]@{ Sev = $sev; Txt = $txt }) } $today = Get-Date # ============ system ============ $cs = Get-CimInstance Win32_ComputerSystem $csp = Get-CimInstance Win32_ComputerSystemProduct $bios = Get-CimInstance Win32_BIOS $os = Get-CimInstance Win32_OperatingSystem $cpu = Get-CimInstance Win32_Processor | Select-Object -First 1 $gpu = Get-CimInstance Win32_VideoController | Select-Object -First 1 $model = $cs.Model $sku = $csp.Version $serial = $bios.SerialNumber $ramGB = GB $cs.TotalPhysicalMemory $fw = $env:firmware_type; if (-not $fw) { $fw = 'unknown' } $fwVer = $bios.SMBIOSBIOSVersion $fwDate = $bios.ReleaseDate $osName = $os.Caption $osBuild = $os.BuildNumber $osVer = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion').DisplayVersion $lastBoot = $os.LastBootUpTime $uptime = if ($lastBoot) { [math]::Round(($today - $lastBoot).TotalDays, 1) } else { $null } $sbReg = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\State' $secureBoot = if ($sbReg) { [bool]$sbReg.UEFISecureBootEnabled } else { $null } # Surface model > release year (Microsoft typically provides 6 years of firmware/driver support after release) $surfaceYears = @( @('Surface Pro, 11th', 2024), @('Surface Pro 10', 2024), @('Surface Pro 9', 2022), @('Surface Pro 8', 2021), @('Surface Pro 7+', 2021), @('Surface Pro 7', 2019), @('Surface Pro 6', 2018), @('Surface Pro X', 2019), @('Surface Pro 4', 2015), @('Surface Pro$', 2017), @('Surface Laptop, 7th', 2024), @('Surface Laptop 6', 2024), @('Surface Laptop 5', 2022), @('Surface Laptop 4', 2021), @('Surface Laptop 3', 2019), @('Surface Laptop 2', 2018), @('Surface Laptop$', 2017), @('Surface Laptop Go 3', 2023), @('Surface Laptop Go 2', 2022), @('Surface Laptop Go', 2020), @('Surface Laptop Studio 2', 2023), @('Surface Laptop Studio', 2021), @('Surface Go 4', 2023), @('Surface Go 3', 2021), @('Surface Go 2', 2020), @('Surface Go$', 2018), @('Surface Book 3', 2020), @('Surface Book 2', 2017), @('Surface Book$', 2015), @('Surface Studio 2\+', 2022), @('Surface Studio 2', 2018) ) $releaseYear = $null foreach ($e in $surfaceYears) { if ($model -match "^$($e[0])") { $releaseYear = $e[1]; break } } $ageYears = if ($releaseYear) { $today.Year - $releaseYear } else { $null } if ($ageYears -ne $null) { if ($ageYears -ge 7) { Flag 'RED' "Model from $releaseYear ($ageYears years): outside the Surface support window, no more firmware/driver updates expected" } elseif ($ageYears -ge 6) { Flag 'ORANGE' "Model from $releaseYear ($ageYears years): at the end of the Surface support window" } elseif ($ageYears -ge 4) { Flag 'INFO' "Model from $releaseYear ($ageYears years)" } } elseif ($model -like 'Surface*') { Flag 'INFO' "Surface model not in year table: $model" } if ($fwDate -and ($today - $fwDate).TotalDays -gt 548) { Flag 'ORANGE' "Firmware dated $($fwDate.ToString('yyyy-MM-dd')): over 1.5 years old, device no longer receives updates or has not been updated in a long time" } # ============ Windows version and support ============ $isEnt = ($osName -match 'Enterprise|Education') $eolTable = @{ 'W10-22H2' = @('2025-10-14', '2025-10-14') 'W11-22H2' = @('2024-10-08', '2025-10-14') 'W11-23H2' = @('2025-11-11', '2026-11-10') 'W11-24H2' = @('2026-10-13', '2027-10-12') 'W11-25H2' = @('2027-10-12', '2028-10-10') } $osKey = if ($osName -like '*Windows 11*') { "W11-$osVer" } else { "W10-$osVer" } $eol = $null if ($eolTable[$osKey]) { $eol = [datetime]($eolTable[$osKey][ [int]$isEnt ]) } if ($osName -like '*Windows 10*') { Flag 'RED' "Still running Windows 10 ($osVer), end of support" } elseif ($eol -and $eol -lt $today) { Flag 'ORANGE' "Windows 11 $osVer is out of support since $($eol.ToString('yyyy-MM-dd')), feature update required" } elseif ($eol -and ($eol - $today).TotalDays -lt 90) { Flag 'INFO' "Windows 11 $osVer goes out of support on $($eol.ToString('yyyy-MM-dd'))" } $lastHotfix = (Get-HotFix | Where-Object InstalledOn | Sort-Object InstalledOn -Descending | Select-Object -First 1).InstalledOn if ($lastHotfix -and ($today - $lastHotfix).TotalDays -gt 90) { Flag 'ORANGE' "Last Windows update on $($lastHotfix.ToString('yyyy-MM-dd')), more than 90 days ago" } $lic = Get-CimInstance SoftwareLicensingProduct -Filter "PartialProductKey IS NOT NULL AND Name LIKE 'Windows%'" | Select-Object -First 1 $activated = if ($lic) { $lic.LicenseStatus -eq 1 } else { $null } if ($activated -eq $false) { Flag 'INFO' 'Windows is not activated' } # ============ battery ============ $batDesign = $null; $batFull = $null; $batCycles = $null; $batPct = $null $tmpXml = Join-Path $env:TEMP 'batteryreport.xml' Remove-Item $tmpXml -Force powercfg /batteryreport /xml /output $tmpXml | Out-Null if (Test-Path $tmpXml) { [xml]$bx = Get-Content $tmpXml $b = $bx.BatteryReport.Batteries.Battery | Select-Object -First 1 if ($b) { $batDesign = [int]$b.DesignCapacity; $batFull = [int]$b.FullChargeCapacity; $batCycles = [int]$b.CycleCount if ($batDesign -gt 0) { $batPct = [math]::Round(100 * $batFull / $batDesign) } } Remove-Item $tmpXml -Force } $wb = Get-CimInstance Win32_Battery | Select-Object -First 1 $batStatus = switch ($wb.BatteryStatus) { 1 {'discharging'} 2 {'on AC'} 3 {'full'} 4 {'low'} 5 {'critical'} 6 {'charging'} 7 {'charging, high'} 8 {'charging, low'} 9 {'charging, critical'} default {'unknown'} } $batCharge = $wb.EstimatedChargeRemaining if ($batPct -ne $null) { if ($batPct -lt 50) { Flag 'RED' "Battery at $batPct% of design capacity, replace or retire" } elseif ($batPct -lt 70) { Flag 'ORANGE' "Battery at $batPct% of design capacity" } if ($batCycles -gt 800) { Flag 'ORANGE' "Battery has $batCycles charge cycles" } } elseif ($model -match 'Pro|Laptop|Go|Book') { Flag 'ORANGE' 'No battery information available on a mobile device' } # ============ disk ============ $diskModel = ''; $diskGB = 0; $diskHealth = 'unknown'; $diskMedia = ''; $diskBus = '' $diskWear = $null; $diskTemp = $null; $diskPowerOn = $null; $diskRErr = $null; $diskWErr = $null $pd = Get-PhysicalDisk | Where-Object { $_.BusType -ne 'USB' } | Sort-Object Size -Descending | Select-Object -First 1 if ($pd) { $diskModel = $pd.FriendlyName; $diskGB = GB $pd.Size; $diskHealth = "$($pd.HealthStatus)"; $diskMedia = "$($pd.MediaType)"; $diskBus = "$($pd.BusType)" if ($isAdmin) { $rc = $pd | Get-StorageReliabilityCounter if ($rc) { $diskWear = $rc.Wear; $diskTemp = $rc.Temperature; $diskPowerOn = $rc.PowerOnHours; $diskRErr = $rc.ReadErrorsUncorrected; $diskWErr = $rc.WriteErrorsUncorrected } } } else { $dd = Get-CimInstance Win32_DiskDrive | Where-Object { $_.InterfaceType -ne 'USB' } | Sort-Object Size -Descending | Select-Object -First 1 if ($dd) { $diskModel = $dd.Model; $diskGB = GB $dd.Size; $diskHealth = $dd.Status } } $sysDrive = Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='$($env:SystemDrive)'" $freeGB = GB $sysDrive.FreeSpace if ($diskHealth -notin @('Healthy','OK','unknown')) { Flag 'RED' "Disk reports status '$diskHealth'" } if ($diskWear -ne $null) { if ($diskWear -ge 80) { Flag 'RED' "SSD wear $diskWear%, end of life" } elseif ($diskWear -ge 50) { Flag 'ORANGE' "SSD wear $diskWear%" } } if (($diskRErr -gt 0) -or ($diskWErr -gt 0)) { Flag 'RED' "Disk has uncorrectable errors (read $diskRErr, write $diskWErr)" } if ($diskMedia -eq 'HDD') { Flag 'ORANGE' 'Mechanical hard drive, slow for daily use' } if ($freeGB -lt 15) { Flag 'ORANGE' "Only $freeGB GB free on $($env:SystemDrive)" } # WinSAT scores (if ever run; scale 1-9.9) $ws = Get-CimInstance Win32_WinSAT if ($ws -and $ws.WinSATAssessmentState -eq 1) { if ($ws.DiskScore -lt 6.5) { Flag 'ORANGE' "WinSAT disk score $($ws.DiskScore) (low)" } if ($ws.CPUScore -lt 6) { Flag 'ORANGE' "WinSAT CPU score $($ws.CPUScore) (low)" } } # BitLocker (admin) $blStatus = 'admin required' if ($isAdmin) { $bl = Get-BitLockerVolume -MountPoint $env:SystemDrive if ($bl) { $blStatus = "$($bl.ProtectionStatus) ($($bl.VolumeStatus), $($bl.EncryptionMethod))" } else { $blStatus = 'not available' } if ($bl -and $bl.ProtectionStatus -ne 'On') { Flag 'INFO' 'BitLocker is not enabled on the system drive' } } # ============ RAM ============ $dimms = Get-CimInstance Win32_PhysicalMemory $ramSpeed = ($dimms | Select-Object -First 1).ConfiguredClockSpeed if ($ramGB -lt 7) { Flag 'RED' "$ramGB GB RAM, too little" } elseif ($ramGB -lt 15) { Flag 'ORANGE' "$ramGB GB RAM, tight for Teams + Office + browser" } # ============ TPM / Secure Boot / Win11 ============ $tpmPresent = $null; $tpmVersion = ''; $tpmReady = $null if ($isAdmin) { $tpm = Get-CimInstance -Namespace root\cimv2\Security\MicrosoftTpm -ClassName Win32_Tpm if ($tpm) { $tpmPresent = $true; $tpmVersion = ($tpm.SpecVersion -split ',')[0].Trim(); $tpmReady = $tpm.IsEnabled_InitialValue -and $tpm.IsActivated_InitialValue } else { $tpmPresent = $false } } $cpuName = $cpu.Name.Trim(); $cpuOk = $null if ($cpuName -match 'Core\(TM\)\s+i[3579]-(\d{4,5})') { $num = $matches[1]; $gen = if ($num.Length -eq 5) { [int]$num.Substring(0,2) } else { [int]$num.Substring(0,1) }; $cpuOk = $gen -ge 8 } elseif ($cpuName -match 'Core\(TM\)\s+Ultra|Core\s+\d+\s+\d{3}') { $cpuOk = $true } elseif ($cpuName -match 'Core\(TM\)\s+m[357]') { $cpuOk = $false } elseif ($cpuName -match 'Ryzen') { $cpuOk = -not ($cpuName -match 'Ryzen\s+[357]\s+[12]\d{3}') } elseif ($cpuName -match 'Snapdragon|Microsoft SQ') { $cpuOk = $true } elseif ($cpuName -match 'Pentium|Celeron') { $cpuOk = $null } $w11Checks = @( [pscustomobject]@{ Name='TPM 2.0'; Ok = if ($tpmPresent -eq $null) { $null } else { ($tpmPresent -and $tpmVersion -like '2.*') } } [pscustomobject]@{ Name='Secure Boot'; Ok = $secureBoot } [pscustomobject]@{ Name='UEFI'; Ok = ($fw -eq 'UEFI') } [pscustomobject]@{ Name='RAM >= 4GB'; Ok = ($ramGB -ge 4) } [pscustomobject]@{ Name='Disk >= 64GB'; Ok = ($diskGB -ge 64) } [pscustomobject]@{ Name='CPU'; Ok = $cpuOk } ) $w11Fail = @($w11Checks | Where-Object { $_.Ok -eq $false }).Count $w11Unknown = @($w11Checks | Where-Object { $_.Ok -eq $null }).Count if ($osName -like '*Windows 11*') { $w11Text = 'already running Windows 11' } elseif ($w11Fail -gt 0) { $w11Text = 'NOT eligible'; Flag 'RED' "Not eligible for Windows 11: $(($w11Checks | Where-Object { $_.Ok -eq $false }).Name -join ', ')" } elseif ($w11Unknown -gt 0) { $w11Text = 'uncertain (see checks)' } else { $w11Text = 'eligible' } if ($secureBoot -eq $false) { Flag 'ORANGE' 'Secure Boot is disabled' } # ============ hardware / peripherals ============ $pnpErr = Get-PnpDevice -PresentOnly | Where-Object { $_.Status -eq 'Error' -or $_.Status -eq 'Degraded' } $touch = Get-PnpDevice -PresentOnly | Where-Object { $_.FriendlyName -match 'touch screen|touchscreen' -and $_.Status -eq 'OK' } $camera = Get-PnpDevice -PresentOnly -Class Camera,Image | Where-Object Status -eq 'OK' $typeCover = Get-PnpDevice -PresentOnly | Where-Object { $_.FriendlyName -match 'Type Cover|Surface Keyboard|Surface Pro Keyboard' -and $_.Status -eq 'OK' } $wifi = Get-NetAdapter -Physical | Where-Object { $_.PhysicalMediaType -match '802.11|Native' -or $_.Name -match 'Wi-?Fi' } | Select-Object -First 1 $wifiDrv = if ($wifi) { (Get-NetAdapter -Name $wifi.Name).DriverVersion } else { '' } $bt = Get-PnpDevice -PresentOnly -Class Bluetooth | Where-Object Status -eq 'OK' | Select-Object -First 1 $monitors = Get-CimInstance -Namespace root\wmi WmiMonitorID $res = if ($gpu) { "$($gpu.CurrentHorizontalResolution)x$($gpu.CurrentVerticalResolution)" } else { '' } foreach ($d in $pnpErr) { Flag 'ORANGE' "Device with problem: $($d.FriendlyName) [$($d.Status), code $($d.ProblemCode)]" } if ($model -like 'Surface*' -and -not $touch) { Flag 'ORANGE' 'No working touchscreen found (all Surfaces have touch)' } if ($model -like 'Surface*' -and -not $camera) { Flag 'ORANGE' 'No working camera found' } if (-not $wifi) { Flag 'RED' 'No Wi-Fi adapter found' } elseif ($wifi.Status -ne 'Up' -and $wifi.Status -ne 'Disconnected') { Flag 'ORANGE' "Wi-Fi adapter status: $($wifi.Status)" } if (-not $bt) { Flag 'INFO' 'No Bluetooth found' } if ($model -match 'Surface Pro|Surface Go' -and -not $typeCover) { Flag 'INFO' 'No Type Cover attached (not tested)' } # ============ stability (event log, 30 days) ============ $since = $today.AddDays(-30) $bsod = @(Get-WinEvent -FilterHashtable @{LogName='System'; ProviderName='Microsoft-Windows-WER-SystemErrorReporting'; Id=1001; StartTime=$since}).Count $kp41 = @(Get-WinEvent -FilterHashtable @{LogName='System'; ProviderName='Microsoft-Windows-Kernel-Power'; Id=41; StartTime=$since}).Count $whea = @(Get-WinEvent -FilterHashtable @{LogName='System'; ProviderName='Microsoft-Windows-WHEA-Logger'; StartTime=$since}).Count $diskEv = @(Get-WinEvent -FilterHashtable @{LogName='System'; ProviderName='disk','storahci','stornvme','Ntfs'; Level=1,2; StartTime=$since}).Count $minidumps = @(Get-ChildItem "$env:SystemRoot\Minidump" -Filter *.dmp).Count $thermal = @(Get-WinEvent -FilterHashtable @{LogName='System'; ProviderName='Microsoft-Windows-Kernel-Processor-Power'; Id=37; StartTime=$since}).Count if ($bsod -ge 3) { Flag 'RED' "$bsod blue screens in 30 days" } elseif ($bsod -ge 1){ Flag 'ORANGE' "$bsod blue screen(s) in 30 days" } if ($kp41 -ge 3) { Flag 'ORANGE' "$kp41 unexpected shutdowns/reboots (Kernel-Power 41) in 30 days: battery, overheating or crash" } if ($whea -gt 0) { Flag 'RED' "$whea hardware errors (WHEA) in 30 days: CPU/RAM/PCIe" } if ($diskEv -gt 0) { Flag 'ORANGE' "$diskEv disk/NTFS errors in event log in 30 days" } if ($thermal -gt 5) { Flag 'ORANGE' "$thermal CPU throttling events (thermal/power) in 30 days" } if ($minidumps -gt 0 -and $bsod -eq 0) { Flag 'INFO' "$minidumps crash dump(s) present (older than 30 days)" } # ============ management / identity ============ $dsreg = dsregcmd /status 2>$null function DsVal($k) { ($dsreg | Where-Object { $_ -match "^\s*$k\s*:" } | Select-Object -First 1) -replace "^\s*$k\s*:\s*", '' } $aadJoined = DsVal 'AzureAdJoined'; $domainJoined = DsVal 'DomainJoined'; $tenant = DsVal 'TenantName'; $mdmUrl = DsVal 'MdmUrl' $mdm = if ($mdmUrl) { 'yes (Intune)' } else { 'no' } $profiles = @(Get-CimInstance Win32_UserProfile | Where-Object { -not $_.Special }) $profText = ($profiles | ForEach-Object { "$(Split-Path $_.LocalPath -Leaf) ($(GB $_.Size)GB, last used $($_.LastUseTime.ToString('yyyy-MM-dd')))" }) -join '; ' if ($aadJoined -eq 'YES' -and $mdm -eq 'no') { Flag 'INFO' 'Entra joined but not enrolled in Intune' } if ($profiles.Count -gt 0) { Flag 'INFO' "$($profiles.Count) user profile(s) present, wipe required before reuse" } # ============ verdict ============ $red = @($flags | Where-Object Sev -eq 'RED'); $orange = @($flags | Where-Object Sev -eq 'ORANGE') if ($red.Count -gt 0) { $final = 'NOT FIT FOR USE (see red items: repair or retire)' } elseif ($orange.Count -ge 3) { $final = 'DOUBTFUL (multiple orange items, judge for yourself)' } elseif ($orange.Count -gt 0) { $final = 'USABLE WITH REMARKS' } else { $final = 'GOOD' } if (-not $isAdmin) { $final += ' [no admin: SSD wear, TPM and BitLocker not checked]' } # ============ report ============ $now = $today.ToString('yyyy-MM-dd HH:mm') Add "==================================================================" Add " DIAGNOSTICS $model" Add " Serial $serial $now admin: $(if ($isAdmin) {'yes'} else {'no'})" Add "==================================================================" Add "" Add " VERDICT: $final" Add "" Add " FINDINGS" if ($flags.Count -eq 0) { Add " none" } foreach ($sev in 'RED','ORANGE','INFO') { foreach ($f in ($flags | Where-Object Sev -eq $sev)) { Add (" [{0,-6}] {1}" -f $sev, $f.Txt) } } Add "" Add "------ Device ------" Add " Model: $model $(if ($sku) {"($sku)"}) release: $(if ($releaseYear) {"$releaseYear, $ageYears years"} else {'unknown'})" Add " Serial: $serial name: $env:COMPUTERNAME" Add " CPU: $cpuName" Add " GPU: $($gpu.Name) display: $res monitors: $(@($monitors).Count)" Add " RAM: $ramGB GB $(if ($ramSpeed) {"@ $ramSpeed MHz"}) ($(@($dimms).Count) module(s))" Add " Firmware: $fw version $fwVer dated $(if ($fwDate) {$fwDate.ToString('yyyy-MM-dd')})" Add " Secure Boot: $(if ($secureBoot -eq $null) {'unknown'} elseif ($secureBoot) {'on'} else {'OFF'})" Add " TPM: $(if ($tpmPresent -eq $null) {'admin required'} elseif ($tpmPresent) {"version $tpmVersion, ready: $tpmReady"} else {'NOT found'})" Add "" Add "------ Windows ------" Add " Version: $osName $osVer (build $osBuild) supported until: $(if ($eol) {$eol.ToString('yyyy-MM-dd')} else {'unknown'})" Add " Activation: $(if ($activated -eq $null) {'unknown'} elseif ($activated) {'activated'} else {'NOT activated'})" Add " Last update: $(if ($lastHotfix) {$lastHotfix.ToString('yyyy-MM-dd')} else {'unknown'}) uptime: $uptime days" Add " Windows 11: $w11Text" foreach ($c in $w11Checks) { $s = if ($c.Ok -eq $null) {'?'} elseif ($c.Ok) {'OK'} else {'NO'}; Add (" {0,-16} {1}" -f $c.Name, $s) } Add " BitLocker: $blStatus" Add "" Add "------ Battery ------" if ($batPct -eq $null) { Add " No battery information" } else { Add " Capacity: $batFull / $batDesign mWh = $batPct %" Add " Cycles: $batCycles current: $batStatus, $batCharge %" } Add "" Add "------ Disk ------" Add " Model: $diskModel ($diskMedia, $diskBus)" Add " Size: $diskGB GB free on $($env:SystemDrive): $freeGB GB" Add " Health: $diskHealth" if ($diskWear -ne $null) { Add " Wear: $diskWear % temp $diskTemp C power-on hours $diskPowerOn errors R/W $diskRErr/$diskWErr" } elseif (-not $isAdmin) { Add " Wear: admin required" } if ($ws -and $ws.WinSATAssessmentState -eq 1) { Add " WinSAT: CPU $($ws.CPUScore) RAM $($ws.MemoryScore) disk $($ws.DiskScore) GPU $($ws.GraphicsScore)" } Add "" Add "------ Hardware ------" Add " Touchscreen: $(if ($touch) {'OK'} else {'not found'}) camera: $(if ($camera) {'OK'} else {'not found'}) Bluetooth: $(if ($bt) {'OK'} else {'not found'})" Add " Wi-Fi: $(if ($wifi) {"$($wifi.InterfaceDescription), $($wifi.Status), driver $wifiDrv"} else {'NOT found'})" Add " Type Cover: $(if ($typeCover) {'attached'} else {'not attached'})" Add " PnP errors: $(@($pnpErr).Count)" Add "" Add "------ Stability (30 days) ------" Add " Blue screens: $bsod unexpected shutdowns: $kp41 WHEA: $whea disk errors: $diskEv thermal: $thermal dumps: $minidumps" Add "" Add "------ Management ------" Add " Entra joined: $aadJoined domain joined: $domainJoined tenant: $tenant" Add " Intune (MDM): $mdm" Add " Profiles: $(if ($profiles.Count) {$profText} else {'none'})" Add "" Add "------ Not tested (manually or via Surface Diagnostic Toolkit) ------" Add " Keyboard/trackpad, dead pixels, hinge/chassis, speaker/microphone, ports, pen" Add "" # ============ save ============ $safeModel = ($model -replace '[^\w\-]', '_'); $safeSerial = ($serial -replace '[^\w\-]', '_') $fileName = "{0}_{1}_{2}.txt" -f $safeModel, $safeSerial, $today.ToString('yyyyMMdd-HHmm') $outDir = $null foreach ($try in @('C:\Diag', [Environment]::GetFolderPath('Desktop'), $env:TEMP)) { if (-not $try) { continue } New-Item $try -ItemType Directory -Force | Out-Null try { [IO.File]::WriteAllText((Join-Path $try '.w'), 'x'); Remove-Item (Join-Path $try '.w') -Force; $outDir = $try; break } catch {} } $outFile = Join-Path $outDir $fileName $lines | Set-Content $outFile -Encoding UTF8 # summary line for comparing multiple devices $csv = Join-Path $outDir 'summary.csv' if (-not (Test-Path $csv)) { 'Date;Model;Serial;Verdict;Red;Orange;BatteryPct;Cycles;SSDwear;RAMGB;DiskGB;CPU;Windows;Win11;Release;Intune' | Set-Content $csv -Encoding UTF8 } "$now;$model;$serial;$($final -replace '\s{2,}.*$','');$($red.Count);$($orange.Count);$batPct;$batCycles;$diskWear;$ramGB;$diskGB;$cpuName;$osName $osVer;$w11Text;$releaseYear;$mdm" | Add-Content $csv -Encoding UTF8 $lines | ForEach-Object { Write-Host $_ } Write-Host "Report: $outFile" -ForegroundColor Cyan if ($env:USERNAME -ne 'SYSTEM' -and $env:USERNAME -notlike '*$') { Start-Process notepad.exe $outFile }