Behind the PowerShell Pipeline logo

Behind the PowerShell Pipeline

Archives
Subscribe
February 6, 2026

More Registry Roaming

In this issue:

  • Exploring Other Registry Hives
    • Resolving SIDs
    • Querying User Registry Keys
    • Get-RegistryKeyInfo
  • What About Remoting?
    • Get-PwshRegistryInfo
  • Summary

Let's continue exploring how to use the [Microsoft.Win32.RegistryKey] class with PowerShell to work with the Windows registry. The default PSDrives for the registry are extremely useful, but only provide access to the HKEY_CURRENT_USER and HKEY_LOCAL_MACHINE hives. In this article, we'll look at how to access other registry hives and discuss considerations for remoting scenarios. There are a few other hives you might want to work with such as HKEY_USERS. This is where the .NET classes become particularly useful.

Exploring Other Registry Hives

Using regedit.exe, you can see the HKEY_USERS hive contains subkeys for each user profile on the system. Each subkey is named with the user's Security Identifier (SID).

HKEY_USERS
figure 1

To access these keys programmatically, you can open the hive:

$users = [Microsoft.Win32.RegistryKey]::OpenBaseKey('Users', 'Default')

The subkey names are what you see using regedit.exe.

PS C:\> $users.GetSubKeyNames()
.DEFAULT
S-1-5-19
S-1-5-20
S-1-5-21-3465062479-264850141-705915528-1001
S-1-5-21-3465062479-264850141-705915528-1001_Classes
S-1-5-18

I probably don't need .DEFAULT or the Classes keys so I'll filter those out.

$sids = $users.GetSubKeyNames().Where({ $_ -notMatch 'Default|Classes' })

Resolving SIDs

I know that short SIDs belong to system accounts. I can convert the SID to an account name using the [System.Security.Principal.SecurityIdentifier] class.

$sids | ForEach-Object {
    [PSCustomObject]@{
        RegistryPath = $users.Name
        Name         = $_
        Account      = [System.Security.Principal.SecurityIdentifier]::new($_).Translate('System.Security.Principal.NTAccount').value
    }
}

This gives me more meaningful output.

RegistryPath Name                                         Account
------------ ----                                         -------
HKEY_USERS   S-1-5-19                                     NT AUTHORITY\LOCAL SERVICE
HKEY_USERS   S-1-5-20                                     NT AUTHORITY\NETWORK SERVICE
HKEY_USERS   S-1-5-21-3465062479-264850141-705915528-1001 PROSPERO\Jeff
HKEY_USERS   S-1-5-18                                     NT AUTHORITY\SYSTEM

I could further filter from here. However, it might be better to filter out the SIDs I don't want earlier in the process. I can use a regular expression pattern to only match on a user SID.

$sids = $users.GetSubKeyNames().Where({ $_ -match '^S([\-\d+]){8,}$' })
$sids | ForEach-Object {
    [PSCustomObject]@{
        RegistryPath = $users.Name
        Name         = $_
        Account      = [System.Security.Principal.SecurityIdentifier]::new($_).Translate('System.Security.Principal.NTAccount').value
        ComputerName = $env:COMPUTERNAME
    }
}

This limits the results to only user SIDs.

RegistryPath Name                                         Account       ComputerName
------------ ----                                         -------       ------------
HKEY_USERS   S-1-5-21-3465062479-264850141-705915528-1001 PROSPERO\Jeff PROSPERO

Since I only have one user account, let me save the results to a variable.

$user = $sids | ForEach-Object {
    [PSCustomObject]@{
        RegistryPath = $users.Name
        Name         = $_
        Account      = [System.Security.Principal.SecurityIdentifier]::new($_).Translate('System.Security.Principal.NTAccount').value
        ComputerName = $env:COMPUTERNAME
    }
}

Querying User Registry Keys

I can use this object to build a path to a specific registry key that I can open.

$qPath = Join-Path $user.Name -ChildPath 'Software\Microsoft\Windows\CurrentVersion\Uninstall'
$uninstall = $users.OpenSubKey($qPath)

Using my Get-RegistryKeyData function from last time, I can query uninstall information.

PS C:\> Get-RegistryKeyData -RegistryKey $uninstall -KeyName $uninstall.GetSubKeyNames()[0]

RegistryPath : HKEY_USERS\S-1-5-21-3465062479-264850141-705915528-1001\Software\Microsoft\Windows\CurrentV
               ersion\Uninstall\AgileBits.1Password.CLI_Microsoft.Winget.Source_8wekyb3d8bbwe
Computername : PROSPERO
KeyName      : DisplayName
KeyType      : String
KeyValue     : 1Password CLI

RegistryPath : HKEY_USERS\S-1-5-21-3465062479-264850141-705915528-1001\Software\Microsoft\Windows\CurrentV
               ersion\Uninstall\AgileBits.1Password.CLI_Microsoft.Winget.Source_8wekyb3d8bbwe
Computername : PROSPERO
KeyName      : DisplayVersion
KeyType      : String
KeyValue     : 2.31.1
...

This gives me an object for each key. It might be easier to join objects from the same path into a single object. See if you can visualize what this code is doinbg:

```powershell
$r = $uninstall.GetSubKeyNames() |
Get-RegistryKeyData -RegistryKey $uninstall |
Group-Object RegistryPath -PipelineVariable pv |
ForEach-Object {
    Write-Host "Processing $(Split-Path $pv.Group[0].RegistryPath -Leaf)" -ForegroundColor Yellow
    $_.Group | Sort-Object KeyName |
    ForEach-Object -Begin {
        $hash = [ordered]@{
            Product      = Split-Path $pv.Group[0].RegistryPath -Leaf
            User         = $user.Account
            ComputerName = $env:COMPUTERNAME
        }
    } -Process {
       $hash.Add($_.KeyName, $_.KeyValue)
    } -End {
        #write the hashtable to the pipeline
        $hash
    }
}

I am processing all subkeys and grouping the registry key data by the registry path. For each group, I create a hashtable with some standard properties and then add each key name and value to the hashtable. Finally, I output the hashtable as an object.

PS C:\> $r.count
18
PS C:\> $r[3]

Name                           Value
----                           -----
Product                        {889610CC-4337-4BDB-AC3B-4F21806C0BDE}_is1
User                           PROSPERO\Jeff
ComputerName                   PROSPERO
DisplayIcon                    C:\Users\Jeff\AppData\Local\Programs\UniGetUI\Un…
DisplayName                    UniGetUI
DisplayVersion                 3.3.7
EstimatedSize                  244368
HelpLink                       https://github.com/marticliment/UniGetUI
Inno Setup: App Path           C:\Users\Jeff\AppData\Local\Programs\UniGetUI
Inno Setup: Deselected Tasks   portableinstall
Inno Setup: Icon Group         (Default)
Inno Setup: Language           English
Inno Setup: Selected Tasks     regularinstall,regularinstall\startmenuicon,regu…
Inno Setup: Setup Version      6.7.0
Inno Setup: User               Jeff
InstallDate                    20260204
InstallLocation                C:\Users\Jeff\AppData\Local\Programs\UniGetUI\
MajorVersion                   3
MinorVersion                   3
ModifyPath                     "C:\Users\Jeff\AppData\Local\Programs\UniGetUI\U…
NoRepair                       1
Publisher                      Martí Climent
QuietUninstallString           "C:\Users\Jeff\AppData\Local\Programs\UniGetUI\u…
UninstallString                "C:\Users\Jeff\AppData\Local\Programs\UniGetUI\u…
URLInfoAbout                   https://www.marticliment.com/unigetui/
URLUpdateInfo                  https://github.com/marticliment/UniGetUI
VersionMajor                   3
VersionMinor                   3

If you noticed, I didn't specify a typename for my custom output. That's not really possible since each uninstall entry will have different properties.

Want to read the full issue?
GitHub
Bluesky
LinkedIn
Mastodon
https://jdhitso...
Powered by Buttondown, the easiest way to start and grow your newsletter.