Alan
Alan

Reputation: 2178

Use PowerShell to search for string in registry keys and values

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?

Upvotes: 14

Views: 110122

Answers (5)

gtestasker
gtestasker

Reputation: 81

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.

Upvotes: 1

Charles
Charles

Reputation: 11

(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.

Upvotes: 1

NJT145
NJT145

Reputation: 143

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.

Upvotes: 1

js2010
js2010

Reputation: 27418

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' }

Upvotes: 4

Rohn Edwards
Rohn Edwards

Reputation: 2659

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 |  
                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.

Upvotes: 17

Related Questions