Skip to content

Instantly share code, notes, and snippets.

@davidlu1001
Last active July 2, 2024 21:40
Show Gist options
  • Save davidlu1001/459f8877acaf63de8ad9a6e12df79eca to your computer and use it in GitHub Desktop.
Save davidlu1001/459f8877acaf63de8ad9a6e12df79eca to your computer and use it in GitHub Desktop.
Get REG baed on Key or Value
[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