<# .SYNOPSIS Tester - Automatic specification gathering, testing, and configuration tool. .DESCRIPTION Performs automated provisioning tasks including: - Smart Windows Update with reboot resume - GPU detection and discrete driver installation - Hardware specification gathering - OEM BIOS configuration (HP, Dell, Lenovo) - BurnInTest automation - Secure erase tooling - Cleanup .PARAMETER Help Display usage information. .PARAMETER LogFile Path to a .log file for transcript logging. .PARAMETER QROnly Only retrieve system specifications as a QR code. .PARAMETER BiosOnly Only handle BIOS for Lenovo, HP, and Dell systems. .PARAMETER TestOnly Only install and run BurnInTest. .PARAMETER EraseOnly Only install and run the data eraser. .PARAMETER Deprecated Use handwritten specs like a pleb. .NOTES Author: Josh Ashton Email: joshua.ashton@npsstore.com Secondary Email: me@joshashton.dev Last Modified: 2026-04-20 .CHANGELOG v0.0.1 Initial version (HP BIOS) v0.0.2 Added Dell BIOS, resolution, BurnInTest v0.0.3 Fixed resolution bug, added cleanup v0.0.4 Improved cleanup & logging v0.0.5 Fixed Dell BIOS logic v0.0.6 Logging improvements v0.0.7 Lenovo BIOS password detection and GPU-aware Windows Update with reboot resume v0.0.8 Added BiosOnly flag. v0.0.9 Updated SpecOnly to SpecsOnly, and added DDR5 PartNumber to Specs. v0.1.0 Updated Get-Drives to include drive power on hours. v0.2.0 Data transfer to NPS computer via QR code and Google Chrome extension for auto-filling Pricer. v0.2.1 Modified SpecsOnly flag to QROnly, added automatic chassis detection to include screen info. v0.2.2 Feature removed - drive power on hours as it's generally N/A. v0.2.3 Added output to console with device specs. #> param( [switch]$Help, [switch]$QROnly, [switch]$BiosOnly, [switch]$TestOnly, [switch]$EraseOnly, [switch]$Deprecated, [ValidateScript({ if ($_ -notmatch "\.log$") { throw "LogFile must be a .log file" } $true })] [System.IO.FileInfo]$LogFile = "C:\Users\Administrator\Desktop\nps.log" ) # ================================================== # Configuration # ================================================== $RegPath = "HKLM:\SOFTWARE\NPS\Provisioning" $RegName = "WindowsUpdated" # ================================================== # Registry helpers # ================================================== function Get-UpdateFlag { if (Test-Path $RegPath) { (Get-ItemProperty -Path $RegPath -Name $RegName -ErrorAction SilentlyContinue).$RegName } } function Set-UpdateFlag { if (-not (Test-Path $RegPath)) { New-Item -Path $RegPath -Force | Out-Null } Set-ItemProperty -Path $RegPath -Name $RegName -Value 1 -Type DWord } function Clear-UpdateFlag { if (Test-Path $RegPath) { Remove-Item -Path $RegPath -Recurse -Force } } # ================================================== # Windows Update # ================================================== function Update-Windows { Install-Module PSWindowsUpdate -Force -Confirm:$false Import-Module PSWindowsUpdate Get-WindowsUpdate -MicrosoftUpdate -Install -AcceptAll -IgnoreReboot Set-UpdateFlag Restart-Computer -Force exit } # ================================================== # GPU handling # ================================================== function Install-Nvidia { Invoke-WebRequest -Uri "https://us6-dl.techpowerup.com/files/9op87Lz6fZ9K3oUZLZlWxg/1769245820/NVCleanstall_1.19.0.exe" -Outfile "C:\Users\Administrator\Desktop\NVCleanstall.exe" Start-Process "C:\Users\Administrator\Desktop\NVCleanstall.exe" -Wait } function Install-AMD { } function Handle-GPU { $Adapters = @(Get-CimInstance Win32_VideoController | Select-Object -ExpandProperty Name) $Updated = Get-UpdateFlag if (-not $Updated) { if ($Adapters.Count -ge 1 -and "Microsoft Basic Display Adapter" -In $Adapters) { Write-Host "Basic Display Adapter detected. Running Windows Update." Update-Windows } return } if ($Updated -and $Adapters.Count -ge 2 -and ("Microsoft Basic Display Adapter" -In $Adapters)) { Write-Host "Second adapter still Basic Display Adapter. Discrete GPU detected." $GpuDevices = Get-PnpDevice -Class Display -Status OK foreach ($gpu in $GpuDevices) { $ids = (Get-PnpDeviceProperty ` -InstanceId $gpu.InstanceId ` -KeyName 'DEVPKEY_Device_HardwareIds' ` -ErrorAction SilentlyContinue).Data if ($ids -match 'VEN_10DE') { Install-Nvidia } elseif ($ids -match 'VEN_1002') { Write-Host "Automated installation of AMD Radeon Graphics drivers is not supported yet!" -ForegroundColor Yellow } } } Clear-UpdateFlag } # ================================================== # Specs # ================================================== function Get-RAM { # Sum the capacity of all physical sticks to get the "Marketing" total $PhysicalMem = Get-CimInstance Win32_PhysicalMemory $RawSize = ($PhysicalMem | Measure-Object -Property Capacity -Sum).Sum $Size = [Math]::Round($RawSize / 1GB) # Get the first stick for metadata $MemStick = $PhysicalMem | Select-Object -First 1 $Type = $MemStick.SMBIOSMemoryType $PartNumber = $MemStick.PartNumber.Trim() $TypeStr = switch ($Type) { 34 { "DDR5" } 26 { "DDR4" } 24 { "DDR3" } default { "DDR" } } if ($TypeStr -eq "DDR5") { return "$Size GB $TypeStr ($PartNumber)" } else { return "$Size GB $TypeStr" } } function Get-CDDrive { $cd = Get-CimInstance Win32_CDROMDrive if (-not $cd) { "---" } elseif ($cd.CanWrite) { "Read/Write" } else { "Read-only" } } function Get-Drives { $PhysicalDisks = Get-PhysicalDisk | Where-Object { $_.BusType -ne 'USB' } | Select-Object DeviceId, SerialNumber, BusType, MediaType, Size return $PhysicalDisks | Select-Object ` DeviceId, BusType, MediaType, @{Name="Size"; Expression={ $rawGB = $_.Size / 1GB if ($rawGB -gt 900 -and $rawGB -lt 1024) { 1000 } elseif ($rawGB -gt 470 -and $rawGB -lt 512) { 512 } elseif ($rawGB -gt 450 -and $rawGB -lt 500) { 500 } elseif ($rawGB -gt 230 -and $rawGB -lt 256) { 256 } elseif ($rawGB -gt 115 -and $rawGB -lt 128) { 128 } elseif ($rawGB -gt 100 -and $rawGB -lt 115) { 128 } else { [Math]::Round($rawGB) } }} } function Get-Resolution { Add-Type -AssemblyName System.Windows.Forms $b = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds "$($b.Width)x$($b.Height)" } function Get-Specs { Handle-GPU $Model = (Get-CimInstance Win32_ComputerSystem).Model $CPU = (Get-CimInstance Win32_Processor).Name $RAM = Get-RAM $CD = Get-CDDrive $GPU = (Get-CimInstance Win32_VideoController | Select-Object -ExpandProperty Name) -join ", " $OS = (Get-CimInstance Win32_OperatingSystem).Caption $Res = Get-Resolution $Drives = Get-Drives Write-Host "`nSystem Information`n------------------" -ForegroundColor Cyan Write-Host @" Model: $Model CPU: $CPU RAM: $RAM CD: $CD GPU: $GPU OS: $OS Resolution: $Res Drives: "@ $Drives | Format-Table -AutoSize } # ================================================== # BIOS handling # ================================================== function Handle-BIOS { Write-Host "Configuring BIOS ..." -ForegroundColor Yellow $Maker = (Get-CimInstance Win32_ComputerSystem).Manufacturer switch ($Maker) { "HP" { Invoke-WebRequest -Uri "https://hpia.hpcloud.hp.com/downloads/cmsl/hp-cmsl-1.8.5.exe" ` -OutFile "C:\Users\Administrator\Desktop\hp.exe" Start-Process "C:\Users\Administrator\Desktop\hp.exe" -Wait $env:Path += "C:\Program Files\WindowsPowerShell\Scripts\HP.ClientScriptLibrary;" if ((Get-HPBIOSSetupPasswordIsSet) -eq $false) { if ((Get-HPBIOSSetting -Name "Absolute Persistence Module Current State").Active -ne $false) { Set-HPBIOSSettingValue -Name "Permanent Disable Absolute Persistence Module Set Once" -Value "Yes" Write-Host "Absolute Persistence Module disabled." -ForegroundColor Green } } else { Write-Host "BIOS Admin and/or System password is set and cannot disable BIOS tracking! Attempt to clear password with hardware pins and reboot." -ForegroundColor Red } } "Dell Inc." { Install-Module DellBIOSProvider -Force Import-Module DellBIOSProvider if ((Get-Item DellSmbios:\Security\IsAdminPasswordSet) -and (Get-Item DellSmbios:\Security\IsSystemPasswordSet)) { Set-Item DellSmbios:\Security\Absolute "PermanentlyDisabled" Write-Host "Dell Absolute disabled." -ForegroundColor Green } else { Write-Host "BIOS Admin and/or System password is set and cannot disable BIOS tracking! Attempt to clear password with hardware pins and reboot." -ForegroundColor Red } } "Lenovo" { $PasswordState = (Get-WmiObject -Namespace root\wmi -Class Lenovo_BiosPasswordSettings).PasswordState switch ($PasswordState) { 0 { Write-Host "No BIOS passwords are set! Enter BIOS settings manually to permanently disable Computrace/Absolute BIOS tracking." -ForegroundColor Green } 1 { Write-Host "Power on Password is set! How did we get here ... Attempt to manually clear password with hardware pins." -ForegroundColor Red } 2 { Write-Host "Supervisor password is set! Attempt to manually clear password with hardware pins." -ForegroundColor Red } 3 { Write-Host "Power on and supervisor passwords are set! How did we get here ... Attempt to manually clear passwords with hardware pins." -ForegroundColor Red } 4 { Write-Host "Hard drive password is set! How did we get here ... Replace the hard drive." -ForegroundColor Red } 5 { Write-Host "Power on and hard drive passwords are set! Really, how did we get here? ... Attempt to manually clear password with hardware pins and replace the hard drive." -ForegroundColor Red } 6 { Write-Host "Supervisor and hard drive passwords are set! How did we get here ... Attempt to manually clear password with hardware pins and replace the hard drive." -ForegroundColor Red } 7 { Write-Host "Supervisor, power on, and hard drive passwords are set! Really, how did we get here? I'm in agony ... Attempt to manually clear password with hardware pins and replace the hard drive." -ForegroundColor Red } 64 { Write-Host "System management password is set! Attempt to manually clear password with hardware pins, but it probably won't work ... PAIN" -ForegroundColor Red } 65 { Write-Host "System management and power on passwords are set! How did we get here ... Attempt to manually clear password with hardware pins, but it probably won't work ... PAIN" -ForegroundColor Red } 66 { Write-Host "System management and supervisor passwords are set! Execute Order 66 ... Attempt to manually clear password with hardware pins, but it probably won't work ... PAIN" -ForegroundColor Red } 67 { Write-Host "System management, supervisor, and power on passwords are set! Really, how did we get here? I'm in agony ... Attempt to manually clear passwords with hardware pins and replace the hard drive, but it probably won't work ... PAIN" -ForegroundColor Red } 68 { Write-Host "System management and hard drive passwords are set! How did we get here ... Attempt to manually clear password with hardware pins and replace the hard drive, but it probably won't work ... PAIN" -ForegroundColor Red } 69 { Write-Host "System management, power on, and hard drive passwords are set! Nice code. How did we get here ... Attempt to manually clear password with hardware pins and replace the hard drive, but it probably won't work ... PAIN" -ForegroundColor Red } 70 { Write-Host "System management, supervisor, and hard drive passwords are set! How did we get here ... Attempt to manually clear passwords with hardware pins and replace the hard drive, but it probably won't work ... PAIN" -ForegroundColor Red } 71 { Write-Host "System management, supervisor, power on, and hard drive passwords are set! Really, how did we get here? I'm in agony ... Attempt to manually clear passwords with hardware pins and replace the hard drive, but it probably won't work ... PAIN" -ForegroundColor Red } } } } } # ================================================== # BurnInTest / Eraser / Cleanup # ================================================== function Start-BurnInTest { Write-Host "Installing BurnInTest ..." -ForegroundColor Yellow Start-Process ".\BurnInTest_Windows_x86-64.exe" -Wait Write-Host "Running BurnInTest, press continue in the pop-up ..." -ForegroundColor Yellow Start-Process "C:\Program Files\BurnInTest\bit.exe" -ArgumentList "-R","-C",".\config.bitcfg" -Wait } function Start-Eraser { Write-Host "Installing Eraser ..." -ForegroundColor Yellow Start-Process ".\Eraser 6.2.0.2970.exe" -Wait Write-Host "Running Eraser. Once completed, exit through the System Tray." -ForegroundColor Yellow Start-Process "C:\Program Files\Eraser\Eraser.exe" -Wait } function Cleanup { Write-Host "Cleaning up..." -ForegroundColor Yellow $Maker = (Get-CimInstance Win32_ComputerSystem).Manufacturer switch ($Maker) { "HP" { Remove-Item -Path "C:\Users\Administrator\Desktop\hp.exe" } "Dell Inc." {} "Lenovo" {} } Remove-Item -Recurse -Path "C:\Program Files\Eraser" Start-Process -FilePath "C:\Program Files\BurnInTest\unins000.exe" -Wait Write-Host "Cleanup complete!" -ForegroundColor Green } # ================================================== # Help # ================================================== function Help { Write-Host @" Tester - Automatic specification gathering, testing, and configuration tool. Usage: tester.ps1 tester.ps1 -Help tester.ps1 -QROnly tester.ps1 -BiosOnly tester.ps1 -TestOnly tester.ps1 -EraseOnly "@ -ForegroundColor Green } # ================================================== # User Inputs # ================================================== function Get-UserInputs { Write-Host "`n--- Condition & Type Entry ---" -ForegroundColor Yellow $ChassisMap = @{ "1" = "Other" "2" = "Unknown" "3" = "Desktop" "4" = "Low Profile Desktop" "5" = "Pizza Box" "6" = "Mini Tower" "7" = "Tower" "8" = "Portable" "9" = "Laptop" "10" = "Notebook" "11" = "Handheld" "12" = "Docking Station" "13" = "All-in-One" "14" = "Sub-Notebook" "15" = "Space-Saving" "16" = "Lunch box" "17" = "Main System Chassis" "18" = "Expansion Chassis" "19" = "SubChassis" "20" = "Bus Expansion Chassis" "21" = "Peripheral Chassis" "22" = "Storage Chassis" "23" = "Rack Mount Chassis" "24" = "Sealed-Case" "30" = "Tablet" "31" = "Convertible" "32" = "Detachable" } $ChassisCode = (Get-CimInstance -ClassName Win32_SystemEnclosure).ChassisTypes $Screen = $null # 1. Device Type if ($ChassisCode -match "1|2|5|12|15|16|17|18|19|20|21|24") { $Chassis = Read-Host "Ambiguous chassis code, please enter PC type (ie. laptop, tower, etc.):" } else { $Chassis = $ChassisMap["$ChassisCode"] } if ($ChassisCode -match "8|9|10|11|13|30|31|32" -OR $Chassis -match "laptop|Laptop|All-in-One|AIO") { $MonitorParams = Get-CimInstance -Namespace "root\wmi" -ClassName WmiMonitorBasicDisplayParams | Where-Object { $_.InstanceName -eq $InternalMonitor.InstanceName } $WidthCM = $MonitorParams.MaxHorizontalImageSize $HeightCM = $MonitorParams.MaxVerticalImageSize if ($WidthCM -gt 0) { $DiagonalInches = [Math]::Round(([Math]::Sqrt([Math]::Pow($WidthCM, 2) + [Math]::Pow($HeightCM, 2)) / 2.54), 1) $Res = Get-Resolution $Screen = "$($DiagonalInches)in @ $($Res), " } } # 2. Condition Category $Cats = @("New - 0%", "Used - 0%", "AS-IS - 40%", "New other (see details) - 0%") for ($i=0; $i -lt $Cats.Count; $i++) { Write-Host "[$($i+1)] $($Cats[$i])" } $catIdx = Read-Host "Select Condition Category" $ConditionCat = $Cats[($catIdx-1)] # 3. Description $Description = Read-Host "Enter short condition description (e.g. Minor scratches on lid)" return @{ Chassis = $Chassis Screen = $Screen Cat = $ConditionCat Desc = $Description } } function Generate-CompressedJson { # 1. Gather raw data from your existing functions $Inputs = Get-UserInputs # The custom prompts we built $Model = (Get-CimInstance Win32_ComputerSystem).Model $Brand = (Get-CimInstance Win32_ComputerSystem).Manufacturer.Split(' ')[0] # Gets "Dell" from "Dell Inc." $CPU = (Get-CimInstance Win32_Processor).Name.Trim() $RAM = Get-RAM $OS = (Get-CimInstance Win32_OperatingSystem).Caption -replace "Microsoft ", "" $Serial = (Get-CimInstance Win32_Bios).SerialNumber if ($Serial -eq "Default string") { $Serial = "" } # 2. Process Drives & Optical $DriveList = Get-Drives $DriveStr = ($DriveList | ForEach-Object { "$($_.Size)GB $($_.BusType) $($_.MediaType)" }) -join ", " $CD = Get-CDDrive $OptStr = if ($CD -ne "---") { ", $CD Optical Drive" } else { "" } # 3. Process GPUs $GPUList = Get-CimInstance Win32_VideoController | Select-Object -ExpandProperty Name if ($GPUList.Count -gt 1) { $GPUStr = $GPUList -join " & " } else { $GPUStr = $GPUList } # 4. Construct the Master Description String # Format: Type (CPU, RAM, Drives, Optical, GPUs, OS) $FullDescription = "$($Inputs.Chassis) PC ($($Inputs.Screen)$CPU, $RAM, $DriveStr$OptStr, $GPUStr, $OS)" # 5. Build Final JSON Object $FinalObj = @{ "b" = $Brand.ToUpper() # Brand "d" = $FullDescription # Description "c" = $Inputs.Cat # Condition "n" = $Inputs.Desc # Notes "s" = $Model # Style "e" = $Serial # Serial } Write-Host $($FinalObj | ConvertTo-Json) $Json = $FinalObj | ConvertTo-Json -Compress $Bytes = [System.Text.Encoding]::UTF8.GetBytes($Json) return [Convert]::ToBase64String($Bytes) } function Show-QR-Local { $Data = Generate-CompressedJson $OutPath = "$env:TEMP\specs_qr.png" # $PSScriptRoot points to the folder where the script is running (your USB) $ExePath = Join-Path $PSScriptRoot "qrencode.exe" if (Test-Path $ExePath) { # -s 10: Pixel size (makes it large/readable) # -m 2: Margin size # -l H: High error correction (good for scanning off screens) & $ExePath -o $OutPath -s 10 -m 2 -l H "$Data" winget install photos Start-Process $OutPath } else { Write-Host "Local QR Tool not found at $ExePath" -ForegroundColor Red } } # ================================================== # Entry point # ================================================== Start-Transcript -Path $LogFile -Append if ($Help) { Help } elseif ($QROnly) { Show-QR-Local } elseif ($BiosOnly) { Handle-BIOS } elseif ($TestOnly) { Start-BurnInTest } elseif ($EraseOnly) { Start-Eraser } elseif ($Deprecated) { Get-Specs; Handle-BIOS; Start-BurnInTest; Start-Eraser; Cleanup } else { Handle-GPU; Handle-BIOS; Start-BurnInTest; Start-Eraser; Cleanup; Show-QR-Local } Stop-Transcript