1

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.

mklement0
  • 382,024
  • 64
  • 607
  • 775
Cole Cash
  • 23
  • 4
  • are you actually using `$host` as a variable? Because I believe that's a system variable mate, try using a different name for it – Panomosh Aug 29 '19 at 22:30
  • Also running your commands above I also see blank Name and Property, but if I pipe `| fl * -force` onto the end, I get some more info. No Property column though! – Panomosh Aug 29 '19 at 22:33
  • These are not the variables I actually have as I've only been testing in ISE with various things I've found during research. I Did just discover however that I can remotely start the WinRM service, and then stop it when i'm done. This might work as I can use invoke-command to do the same get-itemproperty command I know works (tested and it works on a remote host) – Cole Cash Aug 29 '19 at 22:50

2 Answers2

0

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

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Thank you. This is a great explanation. While I can see some use for this, for my particular purpose at the moment I'm not sure if I can 'bend it to my will.' Rather than being interested in one specific key's value, I need to be able to search through all of the keys in a few "uninstall" keys specifically looking for the value of key uninstallstring. This works with the get-itemproperty cmdlet and the fact that hives are, or can be mounted with psdrives. I'll keep looking and see what I can come up with. – Cole Cash Aug 30 '19 at 14:34
  • @ColeCash: Understood - please see my update, which shows you how to use PowerShell remoting with your `Get-ItemProperty` commands. – mklement0 Aug 30 '19 at 16:20
  • 1
    excellent followup. I will try your techniques out on Tuesday – Cole Cash Sep 01 '19 at 23:23
0

This isn't exactly an answer, but a followup to my question to show that I got what I wanted out of this, and I appreciate the help.

This script successfully works locally, and remotely (within same domain) to collect uninstall strings from a given program. The next hard part is actually remotely using those uninstall strings...

#Requires -RunAsAdministrator
[cmdletbinding()]
param(
    [parameter(mandatory=$true)]
    [Validatescript({
        $regex = '[!$%^&*()_+|~=`{}\[\]:";\<>?\.\/]'
        if ($_ -match $regex) {
            throw "Please exclude special characters from your input"
            }
        return $true
    })]
    [string]$process,
    [string]$computer = 'localhost'
    )



if ($computer -match 'localhost') { # if -computer parameter is not defined it defaults to localhost
    New-PSDrive HKU Registry HKEY_USERS | out-null
    $strings = get-itemproperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*,HKU:\*\Software\Microsoft\Windows\CurrentVersion\Uninstall\*,HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, UninstallString, PSChildName | Where-Object { $_.DisplayName -match "^*$process*"}
    Remove-PSDrive HKU
    $results = if ($strings.DisplayName.count -eq 0) {
        Write-Host "Cannot find the uninstall string" -ForegroundColor Red
        }
        else {
            if ($strings -match "msiexec.exe") {
            [PSCustomObject]@{
                'DisplayName' = $process
                'DisplayVersion' = $strings.DisplayVersion
                'UninstallString' = $strings.UninstallString -replace 'msiexec.exe /i','msiexec.exe /x'
                'PSComputerName' = $computer
                }
            }
            else {
            [PSCustomObject]@{
                'DisplayName' = $process
                'DisplayVersion' = $strings.DisplayVersion
                'UninstallString' = $strings.UninstallString
                'PSComputerName' = $computer
                }
            }
            }
    } # end of localhost
    else { # beginning of remote computers
        # set up variables
        $RE1 = '^((\d{1,3}\.){3}\d{1,3})'
        if ($computer -match $RE1) {
        $computerip = $computer
        $computername = (Resolve-DnsName $computer).namehost.split('.')[0]
        }
        else {
        $computername = $computer
        $computerip = (Resolve-DnsName $computer).ipaddress
        }
        $svcname = 'WinRM'
        $service = get-service -name $svcname -ComputerName $computername
        Write-Host "Checking if the WinRM service is running on $computer" -ForegroundColor Cyan
    if ($service.Status -eq "stopped" ) {
        Write-Host " "
        Write-Host "Windows Remote Management serivce is stopped" -ForegroundColor Green
        Write-Host " "
        Write-Host "Starting the $svcname service on $computer " -ForegroundColor Green
        (Get-WmiObject -computer $computerip Win32_service -filter "Name='$svcname'").invokemethod("StartService",$null) | Out-Null
        }
        else {
            Write-Host " "
            Write-Host " Windows Remote Management serivce is already running on $computer" -ForegroundColor Green
            Write-Host " "
        }
        $results = invoke-command -ComputerName $computername -ArgumentList $process -ScriptBlock {
        param($process)
        New-PSDrive HKU Registry HKEY_USERS | out-null
        get-itemproperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*,HKU:\*\Software\Microsoft\Windows\CurrentVersion\Uninstall\*,HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, UninstallString, PSChildName | Where-Object { $_.DisplayName -match "^*$process*"}
        Remove-PSDrive HKU

        }
        Write-Host " "
        Write-Host "Stopping the $svcname service on $computer..." -ForegroundColor Green
        Write-Host " "
        (Get-WmiObject -computer $computerip Win32_service -filter "Name='$svcname'").invokemethod("StopService",$null) | Out-Null

    }

$results | ft displayname,displayversion,uninstallstring,pscomputername
Cole Cash
  • 23
  • 4
  • As for the regex `"^*$process*"`: it _kind of_ works, but it suggests a fundamental confusion with wildcard patterns; for instance, `'foZ' -match '^*foA*'` returns `$true`, which is probably not what you intended. Unlike in wildcard patterns, a `*` in a regex quantifies the preceding subexpression: `^*` doesn't really make any sense, because it states: match the start of the string, `^`, _zero or more times_ (`*`). Similarly, the trailing `*` after `$process` effectively means: match the _last_ character in the value of `$process` zero or more times. – mklement0 Aug 31 '19 at 04:20