Gordon
Gordon

Reputation: 6883

Get-ItemProperty and then extract the property NAME

Given a variable $property and a variable $path this code will populate the $property variable appropriately.

$path = 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Source\Key\PropertyInKey'
$property = Get-ItemProperty -path:(Split-Path $path -parent) -name:(Split-Path $path -leaf)

Now I want to be able to pass the $property variable and extract the kind and value of that property. Working from the path rather than the property object I can do that with

$key = Get-Item (Split-Path $path -parent)
$value =$key.GetValue((Split-Path $path -leaf))
$kind = $key.GetValueKind((Split-Path $path -leaf))

But the $property variable is an odd [PSCustomObject] where kind and value are not directly accessible. And the named properties that I can directly access are really about the parent key. PSPath is actually the parent key, and PSParentPath & PSChildName also really applies to the parent key as well.

PropertyInKey : SomeTextHere
PSPath        : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Source\Key
PSParentPath  : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Source
PSChildName   : Key
PSProvider    : Microsoft.PowerShell.Core\Registry

I can use PSPath with Get-Item as shown to get that key, but to get the kind and value data I need to know the property name. My first thought was to filter properties and assume the one that doesn't start with PS is the Property Name. But that doesn't work because vendors who write to the registry aren't going to respect this PowerShell prefix. Indeed, some registry property names could predate the existence of PowerShell. So then I thought I could create a list of the four PS properties of the object that I don't want, and the one remaining property is the one I do want. At least, I assume there are no other hidden properties I also have to filter. So, wondering here if there IS an elegant way to extract the property NAME from this semi-useless object so I can get the data I want, or am I forced to actually path the path so I can handle it with Split-Path? The code that will be calling this function uses Get-Item to then pass files, folders and keys, so being able to Get-ItemProperty and pass that would be consistent. But since Get-ItemProperty lies, and doesn't get a property, it gets partial data and bundles it into a PS custom object, I am banging my head a bit.

Why @%$#$ Microsoft doesn't at least output Name, Value and Kind properties here I have no clue. And then, why a custom object, rather than actually having a real Property object from the registry, the way a Key is a [Microsoft.Win32.RegistryKey]?

EDIT: Based on looking at hidden stuff with

$property | Get-Member -Force | Format-Table

which produces

Name          MemberType   Definition                                                                                                                                                                          
----          ----------   ----------                                                                                                                                                                          
pstypenames   CodeProperty System.Collections.ObjectModel.Collection`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] pstypenames{get=PSTypeNames;}             
psadapted     MemberSet    psadapted {PropertyInKey, PSPath, PSParentPath, PSChildName, PSProvider, ToString, Equals, GetHashCode, GetType}                                                                    
psbase        MemberSet    psbase {BaseObject, Members, Properties, Methods, ImmediateBaseObject, TypeNames, get_BaseObject, get_Members, get_Properties, get_Methods, get_ImmediateBaseObject, ToString, Co...
psextended    MemberSet    psextended {PropertyInKey, PSPath, PSParentPath, PSChildName, PSProvider}                                                                                                           
psobject      MemberSet    psobject {BaseObject, Members, Properties, Methods, ImmediateBaseObject, TypeNames, get_BaseObject, get_Members, get_Properties, get_Methods, get_ImmediateBaseObject, ToString, ...
Equals        Method       bool Equals(System.Object obj)                                                                                                                                                      
GetHashCode   Method       int GetHashCode()                                                                                                                                                                   
GetType       Method       type GetType()                                                                                                                                                                      
ToString      Method       string ToString()                                                                                                                                                                   
PropertyInKey NoteProperty string PropertyInKey=SomeTextHere                                                                                                                                                   
PSChildName   NoteProperty string PSChildName=Key                                                                                                                                                              
PSParentPath  NoteProperty string PSParentPath=Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\PxTools_Copy\Source                                                                             
PSPath        NoteProperty string PSPath=Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\PxTools_Copy\Source\Key                                                                               
PSProvider    NoteProperty ProviderInfo PSProvider=Microsoft.PowerShell.Core\Registry  

It seems like I could filter on MemberType is NoteProperty and then filter again on Name is not in @(PSChildName, PSParentPath, PSPath, PSProvider) and end up with what I am looking for. But HOW to do that filtering is making my Brian hurt.

EDIT2: Well, here is what I have, and it works, but good lord, what a mess, just to get some data that Microsoft should have made easily available.

($property | Get-Member -MemberType Properties | Select-Object -Property Name | Where {$_.Name -notin @('PSChildName', 'PSParentPath', 'PSPath', 'PSProvider')}).Name

Upvotes: 2

Views: 1686

Answers (1)

mklement0
mklement0

Reputation: 440431

You can streamline your own attempt to get all of a registry key's values (properties) by value name (property name) and value data (property value), excluding the properties that PowerShell itself adds, using the intrinsic psobject property:

$keyPath = 'HKLM:\Software\Classes\htmlfile' # sample path

(Get-ItemProperty -LiteralPath $keyPath).psobject.Properties |
  Where Name -notin PSChildName, PSPath, PSProvider, PSDrive, PSParentPath |
  Select Name, Value

For a given, single value (property):

$keyAndValuePath = 'HKLM:\Software\Classes\htmlfile\EditFlags'

(Get-ItemProperty -LiteralPath (Split-Path $keyAndValuePath)).
  psobject.Properties[(Split-Path -Leaf $keyAndValuePath)] |
  Select Name, Value

Note: This is somewhat inefficient, because it involves the retrieval of all values; you could add -Name (Split-Path -Leaf $keyAndValuePath) to the Get-ItemProperty call to avoid that, which has been omitted here for brevity and convenience.


However, in order to also obtain the values' registry kind information, however, which you have to glean from the [Microsoft.Win32.RegistryKey] instance that Get-Item - not Get-ItemProperty - returns:

For all values (properties) of a given registry key:

$keyPath = 'HKLM:\Software\Classes\htmlfile'

Get-Item -LiteralPath $keyPath |
  ForEach-Object {
    foreach ($valueName in $_.GetValueNames()) {
      [pscustomobject] @{
        Name  = $valueName
        Value = $_.GetValue($valueName)
        Kind  = $_.GetValueKind($valueName)
      }
    }
  }

For a given, single value (property):

$keyAndValuePath = 'HKLM:\Software\Classes\htmlfile\EditFlags'

Get-Item -LiteralPath (Split-Path $keyAndValuePath) |
  ForEach-Object {
    $valueName = Split-Path -Leaf $keyAndValuePath
    [pscustomobject] @{
      Name  = $valueName
      Value = $_.GetValue($valueName)
      Kind  = $_.GetValueKind($valueName)
    }
  }

Background information:

The .NET registry API, [Microsoft.Win32.RegistryKey], has no object model for what is called a registry value, which can be conceived of as a key's property with a name, value (data, in registry terms), and a kind (a registry-specific data type), which is mapped onto a .NET type. That is, you can query the list of value names (.GetValueNames()), and you can ask for a specific value's information (e.g. GetValueKind($name)), but a registry value entity as a whole has no object (.NET type) representation.

PowerShell's registry provider superimposes an incomplete, awkward object model in the context of its Get-ItemProperty cmdlet:

  • Instead of a collection of objects each representing a registry value, it returns a single object, whose properties each represent a registry value, incompletely (given that a property has just a name and a value, so that additional information such as the kind cannot be represented).

  • Because the property names of the resulting objects - representing the value names - can by definition differ from key to key, no single .NET type can be used, and PowerShell's ad-hoc "property-bag" [pscustomobject] instances are used instead.

    • PowerShell additionally decorates the instances with PS* ETS (Extended Type System) provider properties reflecting information about the parent key, such as PSPath.

    • The upshot is:

      • You must filter out those ETS properties in order to get only those properties that truly represent registry values, as shown in the top section.

      • There is at least a hypothetical chance of naming conflicts.

      • To get additional information about a registry value, notably its kind, you need to resort to the underlying .NET API directly, as shown in the middle section, which is in essence an attempt to establish a more useful, custom object model.

Upvotes: 4

Related Questions