A common scenario that I encounter is needing to dig through registry hives on remote Windows workstations to programmatically collect information – using PowerShell. This poses an interesting challenge because sometimes there are an unknown quantity of elements contained within a registry key: the names & quantities of the values, and the names & quantities of each [sub-]key’s sub-keys. For example, in the screenshot below, the Hardware key contains multiple sub-keys. Some of these sub-keys contains contain values and additional nested sub-keys.
Because the Hardware sub-keys’ depth & values may not always be known, to dig through these keys programmatically, we need a special type of function. We use what is known as a recursive function, which is a function that contains code that calls that same function again. As a general example of a recursive function: in the following snippet of code, the function someRecursiveFunction accepts a parameter named $value. Within the function, an if statement evaluates a condition. If the condition returns TRUE, the function is called again. Recursive functions have the ability crawl through structures with unknown depth, and are often used in these types of scenarios.
Function someRecursiveFuncton($value) { If(condition) { #recursion… someRecursiveFunction -value $value } }
Our registry-key-digging recursive function will go around-and-around, digging through and unknown number of registry sub-keys, each having an unknown depth. The functionality & logic are simple, but initially it can be a bit confusing to visualize. The first thing that our function will do is open the initial key, or starting point, and check to see if it contains sub-keys. It retrieves all the key’s values (if there are any). If the key contains sub-keys, it passes each sub-key back into the same function. Again, the function retrieves values, and passes any sub-keys back to the same function, and so on – until all sub-keys & values have been probed.
Process
Example Scenario
In the scenario illustrated below, the order of operations is as follows:
- Open Key(0)
- Get values from SubKey(0).
- Get Values from SubKey(0,0)
- Get values from SubKey(0,1)
- Get values from SubKey(0,2)
- Get values from SubKey(1)
- Get values from SubKey(1,0)
- Get values form SubKey(1,0,0)
PowerShell Script
The script below opens HKEY Local Machine, and recursively probes all the Hardware\Description key’s values, along with all of its sub-keys and their values. These values are stored in an array-list. When the scan has completed, the array is sorted, and output is sent to the console as a table.
To modify this code to probe a different key, change the initial RegPath value when calling the RegOpenInitialKey function. It is currently set to, “HARDWARE\DESCRIPTION”. To probe a remote machine, change the ComputerName value to the name of the remote computer that you wish to scan.
<#PSScriptInfo .VERSION 1.0.0 .GUID .AUTHOR Richard West .COMPANYNAME .COPYRIGHT .TAGS .LICENSEURI .PROJECTURI 'https://github.com/richardwestseattle/RecursiveRegistryKeyProbe' .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES #> <# .DESCRIPTION Recursively probe registry key's sub-key's and values and output a sorted array. #> Function RecursiveRegKey() { param ( [Parameter(Mandatory=$true)] [String]$ComputerName, [Parameter(Mandatory=$true)] [String]$RegPath ) #Declare an arraylist to which the recursive function below can append values. $RegKeyFields = "KeyName","ValueName","Value"; [System.Collections.ArrayList]$RegKeysArray = $RegKeyFields; $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $ComputerName) $RegKey= $Reg.OpenSubKey($RegPath); Function DigThroughKeys() { param ( [Parameter(Mandatory=$true)] [AllowNull()] [AllowEmptyString()] [Microsoft.Win32.RegistryKey]$Key ) #If it has no subkeys, retrieve the values and append to them to the global array. if($Key.SubKeyCount-eq 0) { Foreach($value in $Key.GetValueNames()) { if($null -ne $Key.GetValue($value)) { $item = New-Object psobject; $item | Add-Member -NotePropertyName "KeyName" -NotePropertyValue $Key.Name; $item | Add-Member -NotePropertyName "ValueName" -NotePropertyValue $value.ToString(); $item | Add-Member -NotePropertyName "Value" -NotePropertyValue $Key.GetValue($value); [void]$RegKeysArray.Add($item); } } } else { if($Key.ValueCount -gt 0) { Foreach($value in $Key.GetValueNames()) { if($null -ne $Key.GetValue($value)) { $item = New-Object PSObject; $item | Add-Member -NotePropertyName "KeyName" -NotePropertyValue $Key.Name; $item | Add-Member -NotePropertyName "ValueName" -NotePropertyValue $value.ToString(); $item | Add-Member -NotePropertyName "Value" -NotePropertyValue $Key.GetValue($value); [void]$RegKeysArray.Add($item); } } } #Recursive lookup happens here. If the key has subkeys, send the key(s) back to this same function. if($Key.SubKeyCount -gt 0) { ForEach($subKey in $Key.GetSubKeyNames()) { DigThroughKeys -Key $Key.OpenSubKey($subKey); } } } } #Replace the value following ComputerName to fit your needs. This works, and is most useful, when scanning remote computers. DigThroughKeys -Key $RegKey #Write the output to the console. $RegKeysArray | Select-Object KeyName, ValueName, Value | Sort-Object ValueName | Format-Table $Reg.Close(); return $RegKeysArray; }
Call the function like so:
RecursiveRegKey -ComputerName $(hostname) -RegPath "HARDWARE\DESCRIPTION"
Output
Please do not hesitate to message me, or post a comment, if you have any questions or suggestions.