From 67db7f18fd9d765b0b8955f598db722399170b60 Mon Sep 17 00:00:00 2001 From: Stephan Yelle Date: Thu, 5 Feb 2026 01:28:37 -0500 Subject: [PATCH] Add test.ps1 --- test.ps1 | 326 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 326 insertions(+) create mode 100644 test.ps1 diff --git a/test.ps1 b/test.ps1 new file mode 100644 index 0000000..971ef06 --- /dev/null +++ b/test.ps1 @@ -0,0 +1,326 @@ +function Invoke-ServiceImagePathAudit { +<# +.SYNOPSIS + Scans, analyzes, and optionally repairs Windows service ImagePath values for unquoted paths with spaces. + +.DESCRIPTION + Single entry-point for the classic workflow: + - Scan: Retrieve service ImagePath values from HKLM:\SYSTEM\CurrentControlSet\Services + - Analyze: Identify unquoted service paths with spaces and generate a FixedKey suggestion + - Repair: Apply FixedKey back to the registry for items marked BadKey="Yes" + - ScanAnalyze: Scan then Analyze (default) + - ScanFix: Scan then Analyze then Repair + + Supports -WhatIf and -Confirm for repairs. + +.PARAMETER Operation + What to do: + - Scan Output ComputerName/Key/ImagePath records + - Analyze Add BadKey/FixedKey to incoming records + - Repair Write FixedKey back for incoming records where BadKey="Yes" + - ScanAnalyze Scan then Analyze (default) + - ScanFix Scan then Analyze then Repair + +.PARAMETER ComputerName + One or more computer names to scan (used by Scan/ScanAnalyze/ScanFix). + Defaults to the local computer. + +.PARAMETER InputObject + Pipeline input (used by Analyze/Repair). Expected properties: + - Analyze: ComputerName, Key, ImagePath + - Repair: ComputerName, Key, BadKey, FixedKey + +.PARAMETER ShowProgress + Show progress bars during scanning/analyzing/repair. Default is enabled if you do not specify it. + +.EXAMPLE + Invoke-ServiceImagePathAudit + Scans and analyzes the local computer (default Operation is ScanAnalyze). + +.EXAMPLE + Invoke-ServiceImagePathAudit -Operation Scan -ComputerName Server1,Server2 + Scans Server1 and Server2 and returns raw ImagePath records. + +.EXAMPLE + Invoke-ServiceImagePathAudit -ComputerName Server1 | + Where-Object BadKey -eq 'Yes' + Scans and analyzes Server1 and filters to vulnerable entries. + +.EXAMPLE + Invoke-ServiceImagePathAudit -Operation ScanFix -ComputerName Server1 -WhatIf + Shows what would be repaired on Server1 without making changes. + +.EXAMPLE + Import-Csv .\scan.csv | Invoke-ServiceImagePathAudit -Operation Analyze + Analyzes previously exported scan results. + +.EXAMPLE + Import-Csv .\scan.csv | Invoke-ServiceImagePathAudit -Operation Repair -WhatIf + Dry-run repairs from previously analyzed records. + +.NOTES + Save as UTF-8 (no BOM). + Remote scan/repair requires Remote Registry access and permissions to read/write HKLM on targets. +#> + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High', DefaultParameterSetName = 'ScanSet')] + param( + [Parameter(Position = 0)] + [ValidateSet('Scan','Analyze','Repair','ScanAnalyze','ScanFix')] + [string]$Operation = 'ScanAnalyze', + + [Parameter(ParameterSetName = 'ScanSet')] + [Alias('Name','Computer','Server','__ServerName')] + [string[]]$ComputerName = $env:COMPUTERNAME, + + [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'PipelineSet')] + $InputObject, + + [switch]$ShowProgress + ) + + begin { + # Default ShowProgress to ON if user didn't specify it + if (-not $PSBoundParameters.ContainsKey('ShowProgress')) { + $ShowProgress = $true + } + + function _Scan { + param([string[]]$Targets) + + foreach ($computer in $Targets) { + $collection = @() + + if ($ShowProgress) { + Write-Progress -Id 1 -Activity "Scanning services on $computer" -Status "Connecting..." + Write-Progress -Id 2 -Activity "Parsing results" -Status "Waiting..." -PercentComplete 0 + } + + $result = & reg.exe QUERY "\\$computer\HKLM\SYSTEM\CurrentControlSet\Services" /v ImagePath /s 2>&1 + + if (-not $result -or $result[0] -match 'ERROR|Denied') { + if ($ShowProgress) { + Write-Progress -Id 1 -Activity "Scanning services on $computer" -Status "Connection Failed" + Write-Progress -Id 2 -Activity "Parsing results" -Completed + Write-Progress -Id 1 -Activity "Scanning services on $computer" -Completed + } + + $collection += [pscustomobject]@{ + ComputerName = $computer + Status = "REG Failed" + Key = "Unavailable" + ImagePath = "Unavailable" + } + + $collection + continue + } + + if ($ShowProgress) { + Write-Progress -Id 1 -Activity "Scanning services on $computer" -Status "Connected" + } + + $lines = $result | Where-Object { $_ -and $_.Trim() -ne '' } + + $currentKey = $null + $i = 0 + foreach ($line in $lines) { + $i++ + if ($ShowProgress) { + $pct = [math]::Min([math]::Round(($i / [math]::Max($lines.Count, 1)) * 100), 100) + Write-Progress -Id 2 -Activity "Parsing results" -Status "Reading $computer" -PercentComplete $pct + } + + if ($line -match '^HKEY_') { + $currentKey = $line.Trim() + continue + } + + if ($line -match '^\s*ImagePath\s+REG_\w+\s+(?.+)$') { + $collection += [pscustomobject]@{ + ComputerName = $computer + Status = "Retrieved" + Key = $currentKey + ImagePath = $Matches['val'].Trim() + } + } + } + + if ($ShowProgress) { + Write-Progress -Id 2 -Activity "Parsing results" -Completed + Write-Progress -Id 1 -Activity "Scanning services on $computer" -Completed + } + + $collection + } + } + + function _AnalyzeOne { + param($Obj) + + $outObj = $Obj | Select-Object * + $badpath = $false + $examine = $outObj.ImagePath + + if ($ShowProgress) { + Write-Progress -Activity "Analyzing ImagePath" -Status "Checking $($outObj.ComputerName)\$($outObj.Key)" + } + + if ($outObj.Key -eq "Unavailable" -or $examine -eq "Unavailable" -or [string]::IsNullOrWhiteSpace($examine)) { + $outObj | Add-Member NoteProperty BadKey "Unknown" -Force + $outObj | Add-Member NoteProperty FixedKey "Can't Fix" -Force + return $outObj + } + + # Ignore already-quoted or special \?? prefixes + if (-not $examine.StartsWith('"') -and -not $examine.StartsWith("\??")) { + + if ($examine.Contains(" ")) { + + # If we see flagged args, try to isolate a path portion + if ($examine.Contains("-") -or $examine.Contains("/")) { + + $split = $examine -split " -", 0, "simplematch" + $split = $split[0] -split " /", 0, "simplematch" + $newpath = $split[0].Trim() + + if ($newpath.Contains(" ")) { + $eval = $newpath -Replace '".*"', '' + $detunflagged = $eval -split "\\", 0, "simplematch" + + if ($detunflagged[-1].Contains(" ")) { + $fixarg = $detunflagged[-1] -split " ", 0, "simplematch" + $quoteexe = $fixarg[0] + '"' + $examine = $examine.Replace($fixarg[0], $quoteexe) + $examine = '"' + $examine.Trim('"') + '"' + $badpath = $true + } + + $examine = $examine.Replace($newpath, '"' + $newpath + '"') + $badpath = $true + } + + } else { + # No flagged args, either just a bad path or an unflagged argument scenario + $eval = $examine -Replace '".*"', '' + $detunflagged = $eval -split "\\", 0, "simplematch" + + if ($detunflagged[-1].Contains(" ")) { + $fixarg = $detunflagged[-1] -split " ", 0, "simplematch" + $quoteexe = $fixarg[0] + '"' + $examine = $examine.Replace($fixarg[0], $quoteexe) + $examine = '"' + $examine.Trim('"') + '"' + $badpath = $true + } else { + $examine = '"' + $examine.Trim('"') + '"' + $badpath = $true + } + } + } + } + + if (-not $badpath) { + $outObj | Add-Member NoteProperty BadKey "No" -Force + $outObj | Add-Member NoteProperty FixedKey "N/A" -Force + return $outObj + } + + while ($examine.EndsWith('""')) { $examine = $examine.Substring(0, $examine.Length - 1) } + + $outObj | Add-Member NoteProperty BadKey "Yes" -Force + $outObj | Add-Member NoteProperty FixedKey $examine -Force + return $outObj + } + + function _RepairOne { + param($Obj) + + $outObj = $Obj | Select-Object * + + if ($outObj.BadKey -ne 'Yes') { + return $outObj + } + + $target = "\\$($outObj.ComputerName)\$($outObj.Key)" + $data = $outObj.FixedKey + + if ($ShowProgress) { + Write-Progress -Activity "Repairing ImagePath" -Status "Fixing $($outObj.ComputerName)\$($outObj.Key)" + } + + if ([string]::IsNullOrWhiteSpace($data) -or $data -eq 'N/A' -or $data -eq "Can't Fix") { + $outObj.Status = "Skipped (no FixedKey)" + return $outObj + } + + if ($PSCmdlet.ShouldProcess($target, "Set ImagePath to: $data")) { + try { + $args = @( + 'ADD', $target, + '/v', 'ImagePath', + '/t', 'REG_EXPAND_SZ', + '/d', $data, + '/f' + ) + + $output = & reg.exe @args 2>&1 + if ($LASTEXITCODE -eq 0) { + $outObj.Status = "Fixed" + } else { + $msg = ($output | Out-String).Trim() + if ([string]::IsNullOrWhiteSpace($msg)) { $msg = "reg.exe exit code $LASTEXITCODE" } + $outObj.Status = "Failed: $msg" + } + } + catch { + $outObj.Status = "Failed: $($_.Exception.Message)" + } + } else { + $outObj.Status = "WhatIf" + } + + return $outObj + } + } + + process { + switch ($Operation) { + + 'Scan' { + if ($PSCmdlet.ParameterSetName -ne 'ScanSet') { + throw "Operation 'Scan' requires -ComputerName (not pipeline input)." + } + _Scan -Targets $ComputerName + } + + 'Analyze' { + if ($PSCmdlet.ParameterSetName -ne 'PipelineSet') { + throw "Operation 'Analyze' requires pipeline input (-InputObject)." + } + if ($null -ne $InputObject) { _AnalyzeOne -Obj $InputObject } + } + + 'Repair' { + if ($PSCmdlet.ParameterSetName -ne 'PipelineSet') { + throw "Operation 'Repair' requires pipeline input (-InputObject)." + } + if ($null -ne $InputObject) { _RepairOne -Obj $InputObject } + } + + 'ScanAnalyze' { + if ($PSCmdlet.ParameterSetName -ne 'ScanSet') { + throw "Operation 'ScanAnalyze' requires -ComputerName (not pipeline input)." + } + _Scan -Targets $ComputerName | ForEach-Object { _AnalyzeOne -Obj $_ } + } + + 'ScanFix' { + if ($PSCmdlet.ParameterSetName -ne 'ScanSet') { + throw "Operation 'ScanFix' requires -ComputerName (not pipeline input)." + } + _Scan -Targets $ComputerName | + ForEach-Object { _AnalyzeOne -Obj $_ } | + ForEach-Object { _RepairOne -Obj $_ } + } + } + } +}