Last active
July 2, 2024 21:40
-
-
Save davidlu1001/459f8877acaf63de8ad9a6e12df79eca to your computer and use it in GitHub Desktop.
Get REG baed on Key or Value
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[CmdletBinding()] | |
param ( | |
[Parameter(Mandatory=$false)] | |
[string]$regKeyPattern = '.*', | |
[Parameter(Mandatory=$false)] | |
[string]$regValuePattern = '.*', | |
[Parameter(Mandatory=$false)] | |
[string[]]$registryPaths = @("HKLM:\SOFTWARE\WOW6432Node\Google"), | |
[Parameter(Mandatory=$false)] | |
[string[]]$excludePaths = @("HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Google\Update*"), | |
[Parameter(Mandatory=$false)] | |
[string[]]$computerNames = @("localhost"), | |
[Parameter(Mandatory=$false)] | |
[string]$outputFile = "RegPatternMatches.csv", | |
[Parameter(Mandatory=$false)] | |
[ValidateSet("AND", "OR")] | |
[string]$matchLogic = "AND" | |
) | |
# Validate that at least one pattern is specified | |
if ([string]::IsNullOrEmpty($regKeyPattern) -and [string]::IsNullOrEmpty($regValuePattern)) { | |
throw "At least one of regKeyPattern or regValuePattern must be specified." | |
} | |
function Search-RegistryValuesLocal { | |
[CmdletBinding()] | |
param ( | |
[string]$path, | |
[string]$keyPattern, | |
[string]$valuePattern, | |
[string[]]$exclude, | |
[string]$logic | |
) | |
Write-Verbose "Starting Search-RegistryValuesLocal" | |
Write-Verbose "Path: '$path'" | |
Write-Verbose "Key Pattern: '$keyPattern'" | |
Write-Verbose "Value Pattern: '$valuePattern'" | |
Write-Verbose "Exclude: $($exclude -join ', ')" | |
Write-Verbose "Logic: $logic" | |
if ([string]::IsNullOrEmpty($path)) { | |
Write-Error "Path is empty or null. Skipping this search." | |
return @() | |
} | |
if ([string]::IsNullOrEmpty($keyPattern) -and [string]::IsNullOrEmpty($valuePattern)) { | |
Write-Error "Both keyPattern and valuePattern are empty. At least one should be specified." | |
return @() | |
} | |
$ErrorActionPreference = 'Continue' | |
$localMatches = @() | |
$stack = New-Object System.Collections.Stack | |
$path = $path -replace '^HKLM:\\', 'HKEY_LOCAL_MACHINE\' | |
$rootKey = $path.Split('\')[0] | |
$subKey = $path.Substring($rootKey.Length + 1) | |
Write-Verbose "Processed path: $rootKey\$subKey" | |
$hive = switch ($rootKey) { | |
'HKEY_LOCAL_MACHINE' { [Microsoft.Win32.RegistryHive]::LocalMachine } | |
default { throw "Unsupported registry hive: $rootKey" } | |
} | |
try { | |
$baseKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey($hive, [Microsoft.Win32.RegistryView]::Default) | |
$stack.Push(@{Key = $baseKey.OpenSubKey($subKey); Path = $subKey}) | |
Write-Verbose "Opened base key and pushed initial subkey to stack" | |
while ($stack.Count -gt 0) { | |
$current = $stack.Pop() | |
$currentKey = $current.Key | |
$currentPath = $current.Path | |
if ($null -eq $currentKey) { | |
Write-Verbose "Null key encountered, skipping" | |
continue | |
} | |
$fullPath = "${rootKey}\${currentPath}" | |
Write-Verbose "Processing: $fullPath" | |
if ($exclude | Where-Object { $fullPath -like $_ }) { | |
Write-Verbose "Skipping excluded path: $fullPath" | |
continue | |
} | |
try { | |
$keyName = Split-Path -Leaf $fullPath | |
$keyMatches = [string]::IsNullOrEmpty($keyPattern) -or ($keyName -match $keyPattern) | |
if ($keyMatches -and [string]::IsNullOrEmpty($valuePattern)) { | |
$localMatches += [PSCustomObject]@{ | |
ComputerName = $env:COMPUTERNAME | |
Path = $fullPath | |
Name = "(Key)" | |
Value = "" | |
MatchType = "Key" | |
} | |
Write-Verbose "Matched Key: $fullPath" | |
} | |
foreach ($valueName in $currentKey.GetValueNames()) { | |
$value = $currentKey.GetValue($valueName) | |
Write-Verbose "Checking value: $valueName = $value" | |
$valueMatches = if (![string]::IsNullOrEmpty($valuePattern)) { | |
if ($value -is [string]) { | |
$value -match $valuePattern | |
} elseif ($value -is [byte[]]) { | |
[System.Text.Encoding]::UTF8.GetString($value) -match $valuePattern | |
} elseif ($null -ne $value) { | |
$value.ToString() -match $valuePattern | |
} else { | |
$false | |
} | |
} else { | |
$true | |
} | |
$matchFound = if ($logic -eq "AND") { | |
$keyMatches -and $valueMatches | |
} else { | |
$keyMatches -or $valueMatches | |
} | |
if ($matchFound) { | |
$localMatches += [PSCustomObject]@{ | |
ComputerName = $env:COMPUTERNAME | |
Path = $fullPath | |
Name = $valueName | |
Value = if ($value -is [byte[]]) { [System.BitConverter]::ToString($value) } else { $value } | |
MatchType = "Value" | |
} | |
Write-Verbose "Matched Value: $valueName = $value" | |
} | |
} | |
foreach ($subKeyName in $currentKey.GetSubKeyNames()) { | |
$subKey = $currentKey.OpenSubKey($subKeyName) | |
if ($null -ne $subKey) { | |
$stack.Push(@{Key = $subKey; Path = "$currentPath\$subKeyName"}) | |
Write-Verbose "Pushed subkey to stack: $currentPath\$subKeyName" | |
} | |
} | |
} | |
catch [System.Security.SecurityException] { | |
Write-Verbose "Access denied to $fullPath" | |
} | |
catch { | |
Write-Verbose "Error accessing $fullPath`: $_" | |
} | |
finally { | |
if ($currentKey -ne $baseKey) { | |
$currentKey.Dispose() | |
} | |
} | |
} | |
} | |
finally { | |
if ($null -ne $baseKey) { | |
$baseKey.Dispose() | |
} | |
} | |
Write-Verbose "Search-RegistryValuesLocal completed. Found $($localMatches.Count) matches." | |
return $localMatches | |
} | |
function Search-RegistryValuesRemote { | |
[CmdletBinding()] | |
param ( | |
[string]$computerName, | |
[string]$path, | |
[string]$keyPattern, | |
[string]$valuePattern, | |
[string[]]$exclude, | |
[string]$logic | |
) | |
Write-Verbose "Starting Search-RegistryValuesRemote for computer: $computerName" | |
Write-Verbose "Path: '$path'" | |
Write-Verbose "Key Pattern: '$keyPattern'" | |
Write-Verbose "Value Pattern: '$valuePattern'" | |
Write-Verbose "Exclude: $($exclude -join ', ')" | |
Write-Verbose "Logic: $logic" | |
if ([string]::IsNullOrEmpty($path)) { | |
Write-Error "Path is empty or null for computer $computerName. Skipping this search." | |
return @() | |
} | |
if ([string]::IsNullOrEmpty($keyPattern) -and [string]::IsNullOrEmpty($valuePattern)) { | |
Write-Error "Both keyPattern and valuePattern are empty for computer $computerName. At least one should be specified." | |
return @() | |
} | |
try { | |
$scriptBlock = { | |
param($path, $keyPattern, $valuePattern, $exclude, $logic, $functionDef) | |
$VerbosePreference = 'Continue' | |
Write-Verbose "Remote execution started on $env:COMPUTERNAME" | |
Write-Verbose "Received Path: '$path'" | |
Write-Verbose "Received Key Pattern: '$keyPattern'" | |
Write-Verbose "Received Value Pattern: '$valuePattern'" | |
# Define the function in the remote session | |
${function:Search-RegistryValuesLocal} = [ScriptBlock]::Create($functionDef) | |
if ([string]::IsNullOrEmpty($path)) { | |
Write-Error "Path is empty or null on remote computer. Skipping this search." | |
return @() | |
} | |
Write-Verbose "Search-RegistryValuesLocal function loaded on remote computer" | |
# Call the function with explicit parameter names | |
$localResults = Search-RegistryValuesLocal ` | |
-path $path ` | |
-keyPattern $keyPattern ` | |
-valuePattern $valuePattern ` | |
-exclude $exclude ` | |
-logic $logic ` | |
-Verbose | |
Write-Verbose "Remote execution completed. Returning $($localResults.Count) results." | |
return $localResults | |
} | |
$functionDef = ${function:Search-RegistryValuesLocal}.ToString() | |
Write-Verbose "Invoking command on $computerName" | |
$result = Invoke-Command -ComputerName $computerName -ScriptBlock $scriptBlock -ArgumentList @($path, $keyPattern, $valuePattern, $exclude, $logic, $functionDef) -ErrorAction Stop | |
Write-Verbose "Command invoked successfully on $computerName. Received $($result.Count) results." | |
return $result | |
} | |
catch { | |
Write-Error "Error searching registry on $computerName`: $_" | |
Write-Verbose "Error details: $($_.Exception.Message)" | |
Write-Verbose "Stack trace: $($_.ScriptStackTrace)" | |
return @() | |
} | |
} | |
$results = @() | |
$totalPaths = $computerNames.Count * $registryPaths.Count | |
$currentPath = 0 | |
foreach ($computerName in $computerNames) { | |
Write-Host "Searching on computer: $computerName" | |
foreach ($path in $registryPaths) { | |
$currentPath++ | |
Write-Progress -Activity "Searching registry" -Status "Processing $computerName : $path" -PercentComplete (($currentPath / $totalPaths) * 100) | |
Write-Host " Searching path: $path" | |
Write-Verbose "Path: $path" | |
Write-Verbose "Key Pattern: $regKeyPattern" | |
Write-Verbose "Value Pattern: $regValuePattern" | |
if ([string]::IsNullOrEmpty($path)) { | |
Write-Warning "Empty path detected. Skipping this iteration." | |
continue | |
} | |
try { | |
if ($computerName -eq "localhost" -or $computerName -eq "127.0.0.1" -or $computerName -eq $env:COMPUTERNAME) { | |
$localResults = Search-RegistryValuesLocal -path $path -keyPattern $regKeyPattern -valuePattern $regValuePattern -exclude $excludePaths -logic $matchLogic -Verbose | |
} | |
else { | |
$localResults = Search-RegistryValuesRemote -computerName $computerName -path $path -keyPattern $regKeyPattern -valuePattern $regValuePattern -exclude $excludePaths -logic $matchLogic -Verbose | |
} | |
if ($null -eq $localResults) { | |
Write-Host " No results found or an error occurred." | |
Write-Verbose "localResults is null for $computerName, $path" | |
} | |
else { | |
$itemCount = @($localResults).Count | |
Write-Host " Found $itemCount item(s)." | |
Write-Verbose "Results for $computerName, $path : $($localResults | Out-String)" | |
if ($itemCount -gt 0) { | |
$results += $localResults | |
# Display results for each computer immediately | |
$localResults | Format-Table -AutoSize | |
} | |
} | |
} | |
catch { | |
Write-Error "Error processing $computerName, $path`: $_" | |
Write-Verbose "Error details: $($_.Exception.Message)" | |
Write-Verbose "Stack trace: $($_.ScriptStackTrace)" | |
} | |
} | |
} | |
Write-Progress -Activity "Searching registry" -Completed | |
if ($results.Count -gt 0) { | |
$results | Export-Csv -Path $outputFile -NoTypeInformation | |
Write-Host "Results exported to $outputFile" | |
Write-Host "`nSummary of results:" | |
$results | Group-Object ComputerName | Format-Table @{Label="Computer"; Expression={$_.Name}}, @{Label="Matches"; Expression={$_.Count}} -AutoSize | |
} | |
else { | |
Write-Host "No matches found." | |
} | |
Write-Verbose "Script completed." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment