Add test.ps1
This commit is contained in:
326
test.ps1
Normal file
326
test.ps1
Normal file
@@ -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+(?<val>.+)$') {
|
||||
$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 $_ }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user