how to filter name/value pairs under a registry key by name and value in PowerShell?

A two part answer.

We start with the $key from the registry:

$path = 'hkcu:\Software\Microsoft\Windows\CurrentVersion\Extensions'
$key = Get-Item $path
$key
$key | Get-Member

Since $key is a Microsoft.Win32.RegistryKey, you cannot get the name value pairs out directly.

The first step is to create a list of PSCustomObjects with Name/Value pairs. The Name comes from the GetValueNames piped through a ForEach-Object. For each of those Names, we get the Value through GetValue:

$namevalues = $key.GetValueNames() | 
  ForEach-Object { 
    [PSCustomObject] @{ 
      Name = $_; 
      Value = $key.GetValue($_) 
    } 
  }
$namevalues | Format-Table

An alternative for the first step is to use the Select-Object using -ExpandProperty as explained by Scott Saad:

$namevalues = $key | Select-Object -ExpandProperty Property | 
  ForEach-Object { 
    [PSCustomObject] @{ 
      Name = $_; 
      Value = $key.GetValue($_) 
    } 
  }
$namevalues | Format-Table

The second step is to filter the $namevalues either by Name or by Value.

The Where-Object has some pretty cool Comparison operators that accept regular expressions like match, notMatch, etc.

To make the code more readable, you can wrap lines (thanks Joey!) either use the backtick (`) or take advantage of the places in the PowerShell syntax where it does accept line breaks, like after a pipe (|) or opening brace ({):

$matches = $namevalues | 
  Where-Object { 
    $_.Name -match '^xls' `
    -or $_.Value -match 'msaccess.exe$' 
  }
$matches | Format-Table

The result is as wanted:

Name    Value                                                                                         
----    -----                                                                                         
xlsx    C:\PROGRA~2\MICROS~1\Office15\EXCEL.EXE                                                       
xls     C:\PROGRA~2\MICROS~1\Office15\EXCEL.EXE                                                       
mdb     C:\PROGRA~2\MICROS~1\Office15\MSACCESS.EXE                                                    
mda     C:\PROGRA~2\MICROS~1\Office15\MSACCESS.EXE

There's a much more clever way to enumerate registry values (found it here). And it's more powershell-way IMO.

I've turned it into a one-liner:

(Get-ItemProperty $path).psobject.properties | 
   where {$_.name -like "xls*" -or $_.value -like "*\MSACCESS.EXE"} | 
      select name,value

Update: As noted in comments by @mklement0, you should be careful about properties PSPath, PSParentPath, PSChildName, PSDrive, and PSProvider within the psobject.properties.


A more robust PSv4+ alternative to montonero's helpful answer:[1]

Get-Item $path -PipelineVariable key | ForEach-Object Property | ForEach-Object {
  $name = ($_, '')[$_ -eq '(default)'] # translate '(default)' to '' for API calls
  if ($name -like 'xls*' -or ($value = $key.GetValue($name)) -like "*\MSACCESS.EXE")
    { [pscustomobject] @{ Name = $name; Value = $value } }
 }
  • -PipelineVariable key stores the [Microsoft.Win32.RegistryKey] instance returned by Get-Item in variable $key for later use in the pipeline.

  • ForEach-Object Property enumerates the target key's value names (via the .Property note property that PowerShell adds to the output [Microsoft.Win32.RegistryKey] instance).

  • Inside the Where-Object script block, $_ then refers to the value name at hand, and $key.GetValue(<valueName>) is used to retrieve the associated data.

    • Important: In the .Property array, PowerShell translates the default value name, which is the empty string ('') at the API level, into name '(default)'; thus, if $_ is '(default)', you must translate it to '' before calling $_.GetValue(<valueName>), which is what
      ($_, '')[$_ -eq '(default)'] does.
  • [pscustomobject] @{ ... } then constructs and outputs a [pscustomobject] instance with .Name and .Value properties reflecting the matching value's name and data.


[1] montonero's answer is concise and works well in the case at hand, but it comes with caveats:

PowerShell's registry provider automatically adds the following additional note properties (members of type NoteProperty, as reflected in the output from Get-Member) containing metadata about the targeted registry keys to the [pscustomobject] instance that Get-ItemProperty outputs:

  • PSPath, PSParentPath, PSChildName, PSDrive, PSProvider

These can interfere with filtering based on .psobject.properties in two ways:

  • Using wildcard matching against $_.Name can accidentally include these properties.

    • E.g., $_.Name -like '*drive*' would match the PSDrive property, even though it isn't actually part of the registry key.
  • Perhaps more hypothetically, if the registry key happens to have values of the same name as these provider properties, the provider properties shadow (override) these values.

    • E.g., if the key has a PSPath value, $_.Value will report the provider property value instead.