14

I'd like to use PowerShell to find all registry keys and values within a particular hive that contain a string foo, possibly embedded within a longer string. Finding the keys is not hard:

Get-ChildItem -path hkcu:\ -recurse -ErrorAction SilentlyContinue | Where-Object {$_.Name -like "*foo*"}

The problem is that I don't know the best way to find the values, given that I don't know the names of the properties ahead of time. I tried this:

Get-ChildItem -path hkcu:\ -recurse -erroraction silentlycontinue | get-itemproperty | where {$_.'(default)' -like "*foo*"}    

But I got this error:

get-itemproperty : Specified cast is not valid.
At line:1 char:69
+ ... u:\ -recurse -erroraction silentlycontinue | get-itemproperty | where ...
+                                                  ~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Get-ItemProperty], InvalidCastException
    + FullyQualifiedErrorId : System.InvalidCastException,Microsoft.PowerShell.Commands.GetItemPropertyCommand

even when I added -ErrorAction SilentlyContinue to the get-itemproperty.

Furthermore, that only finds the values of the (default) keys.

Also, is it possible to search all hives within a single command?

4
  • 2
    -ErrorAction SilentlyContinue? Values can be enumerated by calling Get-ItemProperty on a key. Commented Mar 22, 2017 at 22:16
  • @AnsgarWiechers, thanks for the suggestion about -ErrorAction SilentlyContinue. I had given up on that because it had failed to suppress errors with an earlier command I tried, but it does work with this one, so I edited my question accordingly. However, I get an error get-itemproperty : Specified cast is not valid when I execute this command: get-childitem -path hkcu:\ -recurse -erroraction silentlycontinue | get-itemproperty | where {$_ -like "*foo"} . What would I need to do differently? Commented Mar 22, 2017 at 22:27
  • you need to access to property in where scriptblock. where {$_.PSParentPath -like "*foo"} for example Commented Mar 22, 2017 at 22:53
  • Related: An answer to Speed up PowerShell script for Windows Registry search (currently 30 minutes) Commented Oct 21, 2019 at 18:24

5 Answers 5

16

Each key has a GetValueNames(), GetValueKind(), and GetValue() method that let you enumerate child values. You can also use the GetSubKeyNames() instead of depending on Get-ChildItem -Recurse to enumerate keys.

To answer your question about searching multiple hives: if you start with Get-ChildItem Registry::\, you can see all hives and start your search there. You'll probably want to stick with HKLM and HKCU (maybe HKU if there are other user hives loaded).

Here's a sample implementation that I created a while back on the TechNet gallery:

function Search-Registry { 
<# 
.SYNOPSIS 
Searches registry key names, value names, and value data (limited). 
 
.DESCRIPTION 
This function can search registry key names, value names, and value data (in a limited fashion). It outputs custom objects that contain the key and the first match type (KeyName, ValueName, or ValueData). 
 
.EXAMPLE 
Search-Registry -Path HKLM:\SYSTEM\CurrentControlSet\Services\* -SearchRegex "svchost" -ValueData 
 
.EXAMPLE 
Search-Registry -Path HKLM:\SOFTWARE\Microsoft -Recurse -ValueNameRegex "ValueName1|ValueName2" -ValueDataRegex "ValueData" -KeyNameRegex "KeyNameToFind1|KeyNameToFind2" 
 
#> 
    [CmdletBinding()] 
    param( 
        [Parameter(Mandatory, Position=0, ValueFromPipelineByPropertyName)] 
        [Alias("PsPath")] 
        # Registry path to search 
        [string[]] $Path, 
        # Specifies whether or not all subkeys should also be searched 
        [switch] $Recurse, 
        [Parameter(ParameterSetName="SingleSearchString", Mandatory)] 
        # A regular expression that will be checked against key names, value names, and value data (depending on the specified switches) 
        [string] $SearchRegex, 
        [Parameter(ParameterSetName="SingleSearchString")] 
        # When the -SearchRegex parameter is used, this switch means that key names will be tested (if none of the three switches are used, keys will be tested) 
        [switch] $KeyName, 
        [Parameter(ParameterSetName="SingleSearchString")] 
        # When the -SearchRegex parameter is used, this switch means that the value names will be tested (if none of the three switches are used, value names will be tested) 
        [switch] $ValueName, 
        [Parameter(ParameterSetName="SingleSearchString")] 
        # When the -SearchRegex parameter is used, this switch means that the value data will be tested (if none of the three switches are used, value data will be tested) 
        [switch] $ValueData, 
        [Parameter(ParameterSetName="MultipleSearchStrings")] 
        # Specifies a regex that will be checked against key names only 
        [string] $KeyNameRegex, 
        [Parameter(ParameterSetName="MultipleSearchStrings")] 
        # Specifies a regex that will be checked against value names only 
        [string] $ValueNameRegex, 
        [Parameter(ParameterSetName="MultipleSearchStrings")] 
        # Specifies a regex that will be checked against value data only 
        [string] $ValueDataRegex 
    ) 
 
    begin { 
        switch ($PSCmdlet.ParameterSetName) { 
            SingleSearchString { 
                $NoSwitchesSpecified = -not ($PSBoundParameters.ContainsKey("KeyName") -or $PSBoundParameters.ContainsKey("ValueName") -or $PSBoundParameters.ContainsKey("ValueData")) 
                if ($KeyName -or $NoSwitchesSpecified) { $KeyNameRegex = $SearchRegex } 
                if ($ValueName -or $NoSwitchesSpecified) { $ValueNameRegex = $SearchRegex } 
                if ($ValueData -or $NoSwitchesSpecified) { $ValueDataRegex = $SearchRegex } 
            } 
            MultipleSearchStrings { 
                # No extra work needed 
            } 
        } 
    } 
 
    process { 
        foreach ($CurrentPath in $Path) { 
            Get-ChildItem $CurrentPath -Recurse:$Recurse -ErrorAction SilentlyContinue |  
                ForEach-Object { 
                    $Key = $_ 
 
                    if ($KeyNameRegex) {  
                        Write-Verbose ("{0}: Checking KeyNamesRegex" -f $Key.Name)  
         
                        if ($Key.PSChildName -match $KeyNameRegex) {  
                            Write-Verbose "  -> Match found!" 
                            return [PSCustomObject] @{ 
                                Key = $Key 
                                Reason = "KeyName" 
                            } 
                        }  
                    } 
         
                    if ($ValueNameRegex) {  
                        Write-Verbose ("{0}: Checking ValueNamesRegex" -f $Key.Name) 
             
                        if ($Key.GetValueNames() -match $ValueNameRegex) {  
                            Write-Verbose "  -> Match found!" 
                            return [PSCustomObject] @{ 
                                Key = $Key 
                                Reason = "ValueName" 
                            } 
                        }  
                    } 
         
                    if ($ValueDataRegex) {  
                        Write-Verbose ("{0}: Checking ValueDataRegex" -f $Key.Name) 
             
                        if (($Key.GetValueNames() | % { $Key.GetValue($_) }) -match $ValueDataRegex) {  
                            Write-Verbose "  -> Match!" 
                            return [PSCustomObject] @{ 
                                Key = $Key 
                                Reason = "ValueData" 
                            } 
                        } 
                    } 
                } 
        } 
    } 
} 

I haven't looked at it in a while, and I can definitely see some parts of it that should be changed to make it better, but it should work as a starting point for you.

Sign up to request clarification or add additional context in comments.

6 Comments

This looks useful, but I'm not sure I'm using it correctly. I've tried various attempts: search-registry.ps1 -Path HKCU:\ -recurse -SearchRegex "DispFileName" ; search-registry.ps1 -Path HKCU:\ -recurse -KeyNameRegex "DispFileName" ; and even an intentionally bad call: search-registry.ps1 -bad ; but I never get any output. What am I doing wrong?
This is written as a function, not a script. Since it appears you're saving it as a file, you can either dot source it, e.g., . .\search-registry.ps1, then call Search-Registry as the command, or you can get rid of the first and last lines of the file and call it like you already were (the first line would be function Search-Registry {, and the last line would be })
This works great! However, I still see errors when I traverse into two items in the registry. I turned on the -verbose flag and saw that they happened when checking ValueDataRegex for HKEY_CURRENT_USER\System\CurrentControlSet\Control\DeviceContainers\{some_guid}. Going to those registry keys in the Registry Editor and clicking on "Properties" gave the same error, so it's not bogus. However, I want to suppress it. I think putting a try/catch block around something within the if ($ValueDataRegex) block in the script would help, but I'm not sure where it should go.
Actually, this time, I ran c:\batch\search-registry.ps1 -Path HKCU:\ -recurse -SearchRegex <search_string> -ErrorAction SilentlyContinue and did not see any error messages, so I'm fine.
I asked a new question, in case you're interested in answering it: stackoverflow.com/questions/43239949/…
|
4

This is a replacement for get-itemproperty that dumps out the registry in a simple manner. It's easy to use with where-object. You can also pipe it to set-itemproperty.

function get-itemproperty2 {
  # get-childitem skips top level key, use get-item for that
  # set-alias gp2 get-itemproperty2
  param([parameter(ValueFromPipeline)]$key)
  process {
    $key.getvaluenames() | foreach-object {
      $value = $_
      [pscustomobject] @{
        Path = $Key -replace 'HKEY_CURRENT_USER',
          'HKCU:' -replace 'HKEY_LOCAL_MACHINE','HKLM:'
        Name = $Value
        Value = $Key.GetValue($Value)
        Type = $Key.GetValueKind($Value)
      }
    }
  }
}


ls -r hkcu:\key1 | get-itemproperty2 | where name -eq name

Path            Name Value  Type
----            ---- -----  ----
HKCU:\key1\key2 name     1 DWord


ls -r hkcu:\key1 | get-itemproperty2 | where name -eq name | 
  set-itemproperty -value 0
ls -r hkcu:\key1 | get-itemproperty2 | where name -eq name

Path            Name Value  Type
----            ---- -----  ----
HKCU:\key1\key2 name     0 DWord


# pipe 2 commands to one
$(get-item hkcu:\key1; ls -r hkcu:\key1 ) | get-itemproperty2

Path                 Name  Value               Type
----                 ----  -----               ----
HKCU:\key1           multi {hi, there}  MultiString
HKCU:\key1\key2      name  0                  DWord
HKCU:\key1\key2      name2 0                 String
HKCU:\key1\key2\key3 name3 {18, 52, 80}      Binary

EDIT:

This where construction isn't bad for searching both property names and values (and the key name is a value). (Watch out for Netbeans. It creates an invalid registry dword key that causes an exception in get-itemproperty.)

get-childitem -recurse HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall | 
  get-itemproperty | where { $_ -match 'Office16.PROPLUS' }

Comments

1

I created a code script for registry issues. Use RegRootReplace -Key $Key -ResultType 'registry' in order to not having any issue for key-not-found problems.

<#
# "REGISTRY::HKEY_CLASSES_ROOT" = "HKEY_CLASSES_ROOT" = "HKCR:"
# "REGISTRY::HKEY_CURRENT_CONFIG" = "HKEY_CURRENT_CONFIG" = "HKCC:"
# "REGISTRY::HKEY_USERS" = "HKEY_USERS" = "HKU:"
# "REGISTRY::HKEY_CURRENT_USER" = "HKEY_CURRENT_USER" = "HKCU:"
# "REGISTRY::HKEY_LOCAL_MACHINE" = "HKEY_LOCAL_MACHINE" = "HKLM:"
#>
function RegRootReplace {
    <#
    .EXAMPLE
        RegRootReplace -Key $Key -ResultType $ResultType # $ResultType can be 'small', 'long' or 'registry'.
    #>

    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        $Key,

        [Parameter(Mandatory = $false)]
        [string[]]
        $ResultType = 'small'
    )

    $Key = $Key -replace 'REGISTRY::', ''
    switch ($ResultType) {
        'small' {
            $Key = $Key -replace 'HKEY_CLASSES_ROOT', 'HKCR:'
            $Key = $Key -replace 'HKEY_CURRENT_CONFIG', 'HKCC:'
            $Key = $Key -replace 'HKEY_USERS', 'HKU:'
            $Key = $Key -replace 'HKEY_CURRENT_USER', 'HKCU:'
            $Key = $Key -replace 'HKEY_LOCAL_MACHINE', 'HKLM:'
            return $Key
        }
        'long' {
            $Key = $Key -replace 'HKCR:', 'HKEY_CLASSES_ROOT'
            $Key = $Key -replace 'HKCC:', 'HKEY_CURRENT_CONFIG'
            $Key = $Key -replace 'HKU:', 'HKEY_USERS'
            $Key = $Key -replace 'HKCU:', 'HKEY_CURRENT_USER'
            $Key = $Key -replace 'HKLM:', 'HKEY_LOCAL_MACHINE'
            return $Key
        }
        'registry' {
            $Key = 'REGISTRY::' + $Key
            $Key = $Key -replace 'HKCR:', 'HKEY_CLASSES_ROOT'
            $Key = $Key -replace 'HKCC:', 'HKEY_CURRENT_CONFIG'
            $Key = $Key -replace 'HKU:', 'HKEY_USERS'
            $Key = $Key -replace 'HKCU:', 'HKEY_CURRENT_USER'
            $Key = $Key -replace 'HKLM:', 'HKEY_LOCAL_MACHINE'
            return $Key
        }
        Default {
            $Key = $Key -replace 'HKEY_CLASSES_ROOT', 'HKCR:'
            $Key = $Key -replace 'HKEY_CURRENT_CONFIG', 'HKCC:'
            $Key = $Key -replace 'HKEY_USERS', 'HKU:'
            $Key = $Key -replace 'HKEY_CURRENT_USER', 'HKCU:'
            $Key = $Key -replace 'HKEY_LOCAL_MACHINE', 'HKLM:'
            return $Key
        }
    }
}

Update: I found get-itemproperty2 code at here with a name Get-RegistryItemProperty, and I just modified it with my RegRootReplace for possible powershell path issues. The result is this:

<#
Get-ItemProperty2 -> https://stackoverflow.com/questions/42963661
Get-RegistryItemProperty -> https://carlbarrett.uk/use-powershell-to-search-for-and-delete-registry-values
#>
function Get-RegistryItemProperty {
    
    param(
        [Parameter(ValueFromPipeline)]
        $Key
    )
    process {
        $Key.GetValueNames() | ForEach-Object {
            $Value = $_
            [pscustomobject] @{
                Path  = $(RegRootReplace -Key $Key -ResultType 'registry')
                Name  = $Value
                Value = $Key.GetValue($Value)
                Type  = $Key.GetValueKind($Value)
            }
        }
    }
}

I also created a function to search registry for a search key.

function Search-Registry-KeyValueData {
    <#
    .EXAMPLE
        Search-Registry-KeyValueData $SearchKey
    #>


    param(
        [Parameter(ValueFromPipeline)]
        [string[]]
        $SearchKey
    )

    $SearchKey = $SearchKey.Replace(' ', '(([\s,\.])?)+')
    $RegHivesStr = @('HKCR:', 'HKCC:', 'HKU:', 'HKCU:', 'HKLM:')

    $RegHives = $($RegHivesStr | ForEach-Object { Get-ChildItem -Path $(RegRootReplace -Key $_ -ResultType 'registry') -Recurse -ErrorAction 'SilentlyContinue' })

    $RegHives | ForEach-Object {
        $_ | Get-RegistryItemProperty | Where-Object { $_.'Path' -match $SearchKey -or $_.'Name' -match $SearchKey -or $_.'Value' -match $SearchKey }
    }
}

Update: However, I still have an issue when I run my Search-Registry-KeyValueData code:

Get-RegistryItemProperty : Exception calling "GetValueNames" with "0" argument(s): "Silmek üzere imlenmiş bir kayıt defteri anahtarına geçersiz işlem yapılmak istendi."

My system is in Turkish language. It says that "An illegal operation was attempted on a registry key marked for deletion.". I need help for this issue.

2 Comments

I don't get it. What is this doing? Example input/output?
Maybe you need to change your shell's Execution Policy?
1

(Get-ItemProperty "HKLM:\Software\*\Microsoft\Windows\CurrentVersion\Uninstall\*" -ErrorAction SilentlyContinue | Where-Object {$_ -like "*Office*"}).PSChildName

I've used something similar to this to do the trick. Never really had an issue finding an unknown GUID.

Comments

1

This might work

# Define the word to search for
$searchTerm = "YourSearchWord"

# Function to search registry keys
function Search-RegistryKey {
    param(
        [string]$keyPath
    )

    try {
        # Get all subkeys
        $subKeys = Get-ChildItem -Path $keyPath -ErrorAction Stop

        foreach ($subKey in $subKeys) {
            # Recursively search in subkeys
            Search-RegistryKey -keyPath $subKey.PSPath
        }
    } catch {
        # Ignore inaccessible keys
    }

    try {
        # Get all values in the current key
        $values = Get-ItemProperty -Path $keyPath -ErrorAction Stop

        foreach ($value in $values.PSObject.Properties) {
            if ($value.Value -like "*$searchTerm*") {
                Write-Host "Found match in $keyPath: $($value.Name) = $($value.Value)"
            }
        }
    } catch {
        # Ignore inaccessible values
    }
}

# Define the root registry paths to search in
$rootKeys = @("HKLM:\", "HKCU:\")

foreach ($rootKey in $rootKeys) {
    Search-RegistryKey -keyPath $rootKey
}

Write-Host "Search completed."

OR

for a more simpler version try this

Get-ChildItem -Path HKLM:\,HKCU:\ -Recurse | ForEach-Object { Get-ItemProperty -Path $_.PSPath -ErrorAction SilentlyContinue } | Where-Object { $_.PSObject.Properties.Value -like "*<searchterm>*" } | ForEach-Object { Write-Host "$($_.PSPath): $($_.PSObject.Properties.Name) = $($_.PSObject.Properties.Value)" }
  1. Replace with the word you want to search for.
  2. Open PowerShell with administrator privileges.
  3. Run the command.

This command searches the HKEY_LOCAL_MACHINE (HKLM) and HKEY_CURRENT_USER (HKCU) registry hives for any values that contain the specified search term.

3 Comments

I'm running as Admin, and I still get: Get-ChildItem: Requested registry access is not allowed..
Tried again the 1-liner and by setting Execution policy to Bypass for both Process and LocalMachine and got a few results, but with several errors: Get-ChildItem: The registry key at the specified path does not exist. and again some Get-ChildItem: Requested registry access is not allowed. and Get-ItemProperty: Unable to cast object of type 'System.Byte[]' to type 'System.Int32'.
The recursive nature of this script makes it difficult to control errors and output. I ended up using this script instead.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.