Reading Registry keys and values

I am trying to use PS to find if a server has a particular GPO applied, and retrieve the display name and link values.

This is the root I am searching:
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\History\

Under History are a lot of keys in the format of {0ACAF40C-75BDC-47ba-BBC0-BF6DE7C7DA63}
Under each of these history keys are a key named 0 (zero)
So it looks like this:
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\History{0ACAF40C-75BDC-47ba-BBC0-BF6DE7C7DA63}\0
Under each of these 0 keys are values, I would like to get two values from each of these registry keys, DisplayName and Link in a neat table listing.

This is the code that works

$x = ((GET-ITEM -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\History\*").Name) | % {$_.Split("\")[7]} 
$x | % {Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\History\$_\0" -name DisplayName, Link} | Ft -AutoSize

However the output is two lines for each DisplayName and Link value.

GPOw_Windows_10_PNC_Config
LDAP://OU=OUc_Workstations,OU=OUc_Computers,DC=pncbank,DC=com

My questions are

  1. is there a more efficient way to get these values?
  2. How can I get the Link and DisplayName to show up on one line together?
GPOw_Windows_10_PNC_Config       LDAP://OU=OUc_Workstations,OU=OUc_Computers,DC=pncbank,DC=com

Every time I think I finally understand how to work with the registry in PS, something like this always comes up. Any ideas or suggestions are welcome. Thanks

What you have works, and that is important. I took a slightly different take, but it mostly does the same as what you are doing, but in the end, this produces a group of objects you could manipulate further if needed. It may not be useful, but I thought I would throw it out there.

 

[pre]

$f = get-childitem -Path 'HKLM:\software\Microsoft\Windows\CurrentVersion\Group Policy\History\'
$results = @()
foreach($key in $f.pschildname){
$data=Get-ItemPropertyValue-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\History\$key\0"-Name DisplayName, link
$results+=[PSCustomObject]@{
'Key'=$key
'DisplayName'=$data[0]
'Link'=$data[1]
}
}
$results
[/pre]

Do you have access to DC ? there are gpo cmdlets which can do this from that server.

Get-GPO
Get-GPInheritance # this will have GP link information

This will get you somewhere. That other answer by Will Prather needs a space before -Path. You can use convert-path on PSPath to make it look more normal.

get-itemproperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\History\*\0' displayname,link

DisplayName  : Local Group Policy
Link         : Local
PSPath       : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\History\{35378EAC-683F-11D2-A89A-00C04FBBCFA2}\0
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\History\{35378EAC-683F-11D2-A89A-00C04FBBCFA2}
PSChildName  : 0
PSDrive      : HKLM
PSProvider   : Microsoft.PowerShell.Core\Registry

For some reason, without -name, get-itemporperty errors with “Specified cast is not valid.”. Wow, the 1Param names all have invalid dword values. I can see it in regedit. I thought only Netbeans made those invalid dwords in the uninstall registry area. Get-itemproperty is not robust in this case. Any script scanning the properties in this area will have a terminating error for get-itemproperty.

get-itemproperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\History\*\0' lParam

get-itemproperty : Specified cast is not valid.
At line:1 char:1
+ get-itemproperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Gro ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Get-ItemProperty], InvalidCastException
    + FullyQualifiedErrorId : System.InvalidCastException,Microsoft.PowerShell.Commands.GetItemPropertyCommand

Instead of doing the Format-Table after retrieving all of the values, try putting it inside your ForEach-Object statement like this:

$x | % {Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\History\$_\0" -name DisplayName, Link | Ft -AutoSize}

Each DisplayName and Link pair should be printed as its own table that way.

As far as improving the efficiency goes, it seems that having this functionality down to just two lines of code is pretty efficient already. What is it that you think needs improving? does it operate slowly?

There is another method for getting registry values from a remote system using .NET classes, which looks like this:

[Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', "<computername>").OpenSubKey("Software\Microsoft\Windows\CurrentVersion\Group Policy\History\").GetValue("<value name>")

I like this because it doesn’t rely on PSRemoting, it only requires that the Remote Registry service is running on the system whose registry you want to query. You can also use this technique to create or edit registry keys. I don’t know if it is actually any more efficient than using the PowerShell commandlets for registry handling.

we all love PowerShell, but it is not always the right tool for the job. well, it can be, but always leverage what is purpose built first.

Such as RSoP or why not just use the GPO cmdlets and parse the XML for the value vs mining the registry?

It’s a choice, I know, but just saying.