Files
utilities/tester.ps1
T
2026-08-03 20:22:03 +00:00

529 lines
20 KiB
PowerShell

<#
.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