Search-Registry.ps1
# Search-Registry.ps1 # Written by Bill Stewart (bstewart@iname.com) #requires -version 2 <# .SYNOPSIS Searches the registry on one or more computers for a specified text pattern. .DESCRIPTION Searches the registry on one or more computers for a specified text pattern. Supports searching for any combination of key names, value names, and/or value data. The text pattern is a case-insensitive regular expression. .PARAMETER StartKey Starts searching at the specified key. The key name uses the following format: subtree[:][\[keyname[\keyname...]]] subtree can be any of the following: HKCR or HKEY_CLASSES_ROOT HKCU or HKEY_CURRENT_USER HKLM or HKEY_LOCAL_MACHINE HKU or HKEY_USERS This parameter's format is compatible with PowerShell registry drive (e.g., HKLM:\SOFTWARE), reg.exe (e.g., HKLM\SOFTWARE), and regedit.exe (e.g., HKEY_LOCAL_MACHINE\SOFTWARE). .PARAMETER Pattern Searches for the specified regular expression pattern. The pattern is not case-sensitive. See help topic about_Regular_Expressions for more information. .PARAMETER MatchKey Matches registry key names. You must specify at least one of -MatchKey, -MatchValue, or -MatchData. .PARAMETER MatchValue Matches registry value names. You must specify at least one of -MatchKey, -MatchValue, or -MatchData. .PARAMETER MatchData Matches registry value data. You must specify at least one of -MatchKey, -MatchValue, or -MatchData. .PARAMETER MaximumMatches Specifies the maximum number of results per computer searched. 0 means "return the maximum number of possible matches." The default is 0. This parameter is useful when searching the registry on remote computers in order to minimize unnecessary network traffic. .PARAMETER ComputerName Searches the registry on the specified computer. This parameter supports piped input. .OUTPUTS PSObjects with the following properties: ComputerName The computer name on which the match occurred Key The key name (e.g., HKLM:\SOFTWARE) Value The registry value (empty for the default value) Data The registry value's data .EXAMPLE PS C:\> Search-Registry -StartKey HKLM -Pattern $ENV:USERNAME -MatchData Searches HKEY_LOCAL_MACHINE (i.e., HKLM) on the current computer for registry values whose data contains the current user's name. .EXAMPLE PS C:\> Search-Registry -StartKey HKLM:\SOFTWARE\Classes\Installer -Pattern LastUsedSource -MatchValue | Select-Object Key,Value,Data | Format-List Outputs the LastUsedSource registry entries on the current computer. .EXAMPLE PS C:\> Search-Registry -StartKey HKCR\.odt -Pattern .* -MatchKey -MaximumMatches 1 Outputs at least one match if the specified reistry key exists. This command returns a result if the current computer has a program registered to open files with the .odt extension. The pattern .* means 0 or more of any character (i.e., match everything). .EXAMPLE PS C:\> Get-Content Computers.txt | Search-Registry -StartKey "HKLM:\SOFTWARE\My Application\Installed" -Pattern "Installation Complete" -MatchValue -MaximumMatches 1 | Export-CSV C:\Reports\MyReport.csv -NoTypeInformation Searches for the specified value name pattern in the registry on each computer listed in the file Computers.txt starting at the specified subkey. Output is sent to the specifed CSV file. #> [CmdletBinding()] param( [parameter(Position=0,Mandatory=$TRUE)] [String] $StartKey, [parameter(Position=1,Mandatory=$TRUE)] [String] $Pattern, [Switch] $MatchKey, [Switch] $MatchValue, [Switch] $MatchData, [UInt32] $MaximumMatches=0, [parameter(Mandatory=$FALSE)] [Switch] $ExactMatch, [parameter(ValueFromPipeline=$TRUE)] [String[]] $ComputerName=$ENV:COMPUTERNAME ) begin { $PIPELINEINPUT = (-not $PSBOUNDPARAMETERS.ContainsKey("ComputerName")) -and (-not $ComputerName) # Throw an error if -Pattern is not valid try { "" -match $Pattern | out-null } catch [System.Management.Automation.RuntimeException] { throw "-Pattern parameter not valid - $($_.Exception.Message)" } # You must specify at least one matching criteria if (-not ($MatchKey -or $MatchValue -or $MatchData)) { throw "You must specify at least one of: -MatchKey -MatchValue -MatchData" } # Interpret zero as "maximum possible number of matches" if ($MaximumMatches -eq 0) { $MaximumMatches = [UInt32]::MaxValue } # These two hash tables speed up lookup of key names and hive types $HiveNameToHive = @{ "HKCR" = [Microsoft.Win32.RegistryHive] "ClassesRoot"; "HKEY_CLASSES_ROOT" = [Microsoft.Win32.RegistryHive] "ClassesRoot"; "HKCU" = [Microsoft.Win32.RegistryHive] "CurrentUser"; "HKEY_CURRENT_USER" = [Microsoft.Win32.RegistryHive] "CurrentUser"; "HKLM" = [Microsoft.Win32.RegistryHive] "LocalMachine"; "HKEY_LOCAL_MACHINE" = [Microsoft.Win32.RegistryHive] "LocalMachine"; "HKU" = [Microsoft.Win32.RegistryHive] "Users"; "HKEY_USERS" = [Microsoft.Win32.RegistryHive] "Users"; } $HiveToHiveName = @{ [Microsoft.Win32.RegistryHive] "ClassesRoot" = "HKCR"; [Microsoft.Win32.RegistryHive] "CurrentUser" = "HKCU"; [Microsoft.Win32.RegistryHive] "LocalMachine" = "HKLM"; [Microsoft.Win32.RegistryHive] "Users" = "HKU"; } # Search for 'hive:\startkey'; ':' and starting key optional $StartKey | select-string "([^:\\]+):?\\?(.+)?" | foreach-object { $HiveName = $_.Matches[0].Groups[1].Value $StartPath = $_.Matches[0].Groups[2].Value } if (-not $HiveNameToHive.ContainsKey($HiveName)) { throw "Invalid registry path" } else { $Hive = $HiveNameToHive[$HiveName] $HiveName = $HiveToHiveName[$Hive] } # Recursive function that searches the registry function search-registrykey($computerName, $rootKey, $keyPath, [Ref] $matchCount) { # Write error and return if unable to open the key path as read-only try { $subKey = $rootKey.OpenSubKey($keyPath, $FALSE) } catch [System.Management.Automation.MethodInvocationException] { $message = $_.Exception.Message write-error "$message - $HiveName\$keyPath" return } # Write error and return if the key doesn't exist if (-not $subKey) { write-error "Key does not exist: $HiveName\$keyPath" -category ObjectNotFound return } # Search for value and/or data; -MatchValue also returns the data if ($MatchValue -or $MatchData) { if ($matchCount.Value -lt $MaximumMatches) { foreach ($valueName in $subKey.GetValueNames()) { $valueData = $subKey.GetValue($valueName) if ($ExactMatch) { if (($MatchValue -and ($valueName -contains $Pattern)) -or ($MatchData -and ($valueData -contains $Pattern))) { "" | select-object ` @{N="ComputerName"; E={$computerName}}, @{N="Key"; E={"$HiveName\$keyPath"}}, @{N="Value"; E={$valueName}}, @{N="Data"; E={$valueData}} $matchCount.Value++ } } else { if (($MatchValue -and ($valueName -match $Pattern)) -or ($MatchData -and ($valueData -match $Pattern))) { "" | select-object ` @{N="ComputerName"; E={$computerName}}, @{N="Key"; E={"$HiveName\$keyPath"}}, @{N="Value"; E={$valueName}}, @{N="Data"; E={$valueData}} $matchCount.Value++ } } if ($matchCount.Value -eq $MaximumMatches) { break } } } } # Iterate and recurse through subkeys; if -MatchKey requested, output # objects only report computer and key (keys do not have values or data) if ($matchCount.Value -lt $MaximumMatches) { foreach ($keyName in $subKey.GetSubKeyNames()) { if ($keyPath -eq "") { $subkeyPath = $keyName } else { $subkeyPath = $keyPath + "\" + $keyName } if ($ExactMatch) { if ($MatchKey -and ($keyName -contains $Pattern)) { "" | select-object ` @{N="ComputerName"; E={$computerName}}, @{N="Key"; E={"$HiveName\$subkeyPath"}}, @{N="Value"; E={}}, @{N="Data"; E={}} $matchCount.Value++ } } else { if ($MatchKey -and ($keyName -match $Pattern)) { "" | select-object ` @{N="ComputerName"; E={$computerName}}, @{N="Key"; E={"$HiveName\$subkeyPath"}}, @{N="Value"; E={}}, @{N="Data"; E={}} $matchCount.Value++ } } # $matchCount is a reference search-registrykey $computerName $rootKey $subkeyPath $matchCount if ($matchCount.Value -eq $MaximumMatches) { break } } } # Close opened subkey $subKey.Close() } # Core function opens the registry on a computer and initiates searching function search-registry2($computerName) { # Write error and return if unable to open the key on the computer try { $rootKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($Hive, $computerName) } catch [System.Management.Automation.MethodInvocationException] { $message = $_.Exception.Message write-error "$message - $computerName" return } # $matchCount is per computer; pass to recursive function as reference $matchCount = 0 search-registrykey $computerName $rootKey $StartPath ([Ref] $matchCount) $rootKey.Close() } } process { if ($PIPELINEINPUT) { search-registry2 $_ } else { $ComputerName | foreach-object { search-registry2 $_ } } } |