Search code examples
.netpowershellregistry

Powershell - .net remote registry returning blank


When using the .NET method [Microsoft.win32.registrykey] to try to query a remote registry key I'm only getting null values every step of the way

Establish some variables like so:

$computer = '192.168.200.10'
$key = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\7-zip"
$type = [Microsoft.Win32.RegistryHive]::LocalMachine
$reg = [Microsoft.win32.registrykey]::OpenRemoteBaseKey($type, $computer)

If I then try something like this:

$reg.opensubkey($key)

I'll get a blank "Name" and "Property" column. I've tried removing the remote aspect of this and trying it with ::OpenBaseKey to view my own HKLM hive and it still returns blank.

This all started with a script that would find the uninstall string of a given program, including if it was only registered in the HKU hive. The ultimate intent was to run it against remote computers on the LAN to find uninstall strings for whatever program I was interested in.
Trying to do it without importing any other modules as I'd like to be able to share it as is. Just scratching my head figuring out how to query remote registry.

Here's an example of how my queries look locally.

$64bit = get-itemproperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*,HKU:\${sid}\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, UninstallString, PSChildName | Where-Object { $_.DisplayName -like "$process*"}
$32bit = get-itemproperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, UninstallString, PSChildName | Where-Object { $_.DisplayName -like "$process*"}

the $sid variable is found earlier based on the logged in user.


Solution

  • I'll get a blank "Name" and "Property" column.

    That does not indicate a problem per se; it is merely a display problem - see bottom section.

    While you could use the [Microsoft.Win32.RegistryHive] type directly to get what you want, it'll be more complex and more verbose than using PowerShell's Get-ItemProperty cmdlet.

    Get-ItemProperty by itself lacks the ability to target a remote registry, but you can combine it with PowerShell remoting, which may require prior setup on the target computer - see about_Remote_FAQ.

    # Create a session on the target computer.
    # The target machine must have PowerShell remoting enabled.
    # You may have to specify credentials with -Credential
    $session = New-PSSession -ComputerName '192.168.200.10' 
    
    # Use Invoke-Command with the session to execute your commands remotely.
    $64bit = Invoke-Command -Session $session { 
      get-itemproperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*, HKU:\${using:sid}\Software\Microsoft\Windows\CurrentVersion\Uninstall\* |
        Select-Object DisplayName, DisplayVersion, UninstallString, PSChildName |
          Where-Object { $_.DisplayName -like "${using:process}*"}
    }
    
    $32bit = Invoke-Command -Session $session { 
      get-itemproperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* |
        Select-Object DisplayName, DisplayVersion, UninstallString, PSChildName |
          Where-Object { $_.DisplayName -like "${using:process}*"}
    }
    
    # Close the session.
    Remove-PSSession $session
    

    Important: Note how local variables $sid and $process are referenced as ${using:sid} and ${using:process}, which is necessary for the remotely executed script block to see them.

    Note that output received via remoting undergoes serialization on the remote side and deserialization on the local side, which means that only a few well-known types are deserialized as their original types. All others are approximations of the original objects with static property values, based on the [pscustomobject] type.

    While that can be a problem on occasion, it won't be in your case, since the objects that your command outputs are [pscustomobject] instances to begin with, due to use of Select-Object.


    As for your original symptom:

    The seemingly blank properties are a display problem and is unrelated to targeting a remote registry[1]:

    The output formatting that is applied to $reg.opensubkey($key) is based on PowerShell formatting instructions for type Microsoft.Win32.RegistryKey, yet these instructions rely on such instances to be augmented with PowerShell provider properties, which are only present if you obtained the Microsoft.Win32.RegistryKey instances via Get-Item / Get-ChildItem.

    By contrast, Microsoft.Win32.RegistryKey instances obtained directly via the .NET API lack the provider-added properties that formatting relies upon, causing the formatting definitions to render blank column values.

    As stated, this problem exits independently of whether you're targeting the local or a remote registry, so let me demonstrate the problem with a local key:

    $base = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::CurrentUser, 'Registry64')
    $subkey = $base.OpenSubKey('Console')
    
    $subkey   # output
    

    The above, even though opening HKEY_CURRENT_USER]\Console succeeded, yields a seemingly empty object:

    
    Name                           Property
    ----                           --------
    
    

    A simple workaround is to force explicity display of the true properties with Format-List *, for instance:

    PS> $subkey | Format-List *
    
    SubKeyCount : 2
    View        : Registry64
    Handle      : Microsoft.Win32.SafeHandles.SafeRegistryHandle
    ValueCount  : 46
    Name        : HKEY_CURRENT_USER\Console
    

    Note that the above won't provide a display of the target key's values and data, the way PowerShell provides by default, as a courtesy, via the calculated Property column; you'd have to do more work to display that too.


    [1] As an aside: There is currently (PowerShell Core 7.0.0-preview.3) a separate, but related problem when targeting a remote registry, but only if you do use Get-Item / Get-ChildItem: see this post