So, I have a function that retrieves information from the registry about all of the installed software on a given machine. Because I’ve run into several machines with the RemoteRegistry service stopped, I wrote another function that will try to open the registry, if it gets a “The network path was not found” error, it tries to start the RemoteRegistry service and then tries to open the registry again.
function OpenRemoteRegistryKey {
Param(
[Parameter(ValueFromPipelineByPropertyName=$false,Mandatory=$true)][string]$key,
[Parameter(ValueFromPipelineByPropertyName=$false,Mandatory=$true)][string]$ComputerName,
[int]$Iteration = 1
)
$reg = $null
try {
#Create an instance of the Registry Object and open the HKLM base key
$reg = [microsoft.win32.registrykey]::OpenRemoteBaseKey('LocalMachine',$ComputerName)
}
catch [Exception] {
if ($_.Exception.Message -like "*The network path was not found*")
{
if ($Iteration -ge 3) {
Write-Error "Unable to start RemoteRegistry service on $ComputerName"
$reg = $null
}
else {
# Try to start the RemoteRegistry service on the remote machine
# http://www.sqlservercentral.com/blogs/timradney/2011/07/18/starting-and-stopping-a-remote-service-with-powershell/
$response = (Get-WmiObject -ComputerName $ComputerName -Class win32_service -filter "Name='RemoteRegistry'").StartService()
$Iteration++
switch ($response.ReturnValue) {
# Service started successfully
0 {
Start-Sleep -Seconds 10
OpenRemoteRegistryKey -ComputerName $ComputerName -key $key -Iteration $Iteration
}
# Service was already running
10 {
# Stop the RemoteRegistry service, wait 30 seconds, and retry
$response = (Get-WmiObject -ComputerName $ComputerName -Class win32_service -filter "Name='RemoteRegistry'").StopService()
Start-Sleep -Seconds 10
OpenRemoteRegistryKey -ComputerName $ComputerName -key $key -Iteration $Iteration
}
default {
Write-Error "Unable to start RemoteRegistry service on $ComputerName. ReturnValue: $($response.ReturnValue)"
$reg = $null
}
}
}
}
else {
Write-Error "$ComputerName : $_"
$reg = $null
}
}
finally {
$reg
}
}
function Get-InstalledSoftware {
[CmdletBinding()]
Param (
[Parameter(ValueFromPipelineByPropertyName=$true,Position=0,Mandatory=$true)][string[]]$ComputerName
)
BEGIN {
}
PROCESS {
$installedApps = @()
foreach($comp in $ComputerName){
#Define the variable to hold the location of Currently Installed Programs
$UninstallKey="SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
$reg = OpenRemoteRegistryKey -ComputerName $comp -key $UninstallKey
if($reg) {
#Drill down into the Uninstall key using the OpenSubKey Method and retrieve an array of strings that contain all the subkey names
$subkeys = $reg.OpenSubKey($UninstallKey).GetSubKeyNames()
#Open each Subkey and use GetValue Method to return the required values for each
foreach($key in $subkeys){
$thisSubKey = $reg.OpenSubKey($UninstallKey+"\\"+$key)
$InstallDate = $($thisSubKey.GetValue("InstallDate"))
if (($InstallDate -ne $null) -and ($InstallDate.Length -gt 0)) {
$InstallDate = [datetime]::ParseExact($($thisSubKey.GetValue("InstallDate")), "yyyyMMdd", $null)
}
$obj = New-Object -TypeName PSObject -Property @{
ComputerName=$comp
DisplayName=$($thisSubKey.GetValue("DisplayName"))
DisplayVersion=$($thisSubKey.GetValue("DisplayVersion"))
InstallLocation=$($thisSubKey.GetValue("InstallLocation"))
Publisher=$($thisSubKey.GetValue("Publisher"))
InstallDate=$InstallDate
}
$installedApps += $obj
}
}
}
$installedApps | Where-Object DisplayName | Select-Object ComputerName, DisplayName, DisplayVersion, InstallLocation, Publisher, InstallDate
}
END {
}
}
For testing, I’ll intentionally stop the RemoteRegistry service on a machine and then run Get-InstalledSoftware with that computername. It looks like everything works as expected, but I always get a “You cannot call a method on a null-valued expression.” exception when it tries to open the subkeys. If I immediately run the Get-InstalledSoftware function again, the service is started and I get a good response.
By adding in some Write-Host lines in various places in the OpenRemoteRegistryKey function, I can see that the call works: it tries to open the registry key, catches the exception when it can’t, starts the service, sees a ReturnValue of 0, sleeps for 10 secs, tries to open the registry key again, gets a good response, and returns an object to the pipeline. In the Get-InstalledSoftware function, I can add some debug code before the “if($reg)” line and see that $reg is not null. I even added the “$reg.OpenSubKey($UninstallKey).GetSubKeyNames()” line, (with the proper substitutions), to the OpenRemoteRegistryKey function and got a good response there, but it failed in the Get-InstalledSoftware function.
Any ideas?
Output with error and subsequent call:
PS F:\Storage\Scripts\Windows\Tools> get-date
Tuesday, August 20, 2013 12:44:44 PM
PS F:\Storage\Scripts\Windows\Tools> $response = (gwmi -ComputerName adm701209 -Class win32_service -filter "Name='Remot
eRegistry'").stopservice()
PS F:\Storage\Scripts\Windows\Tools> Get-InstalledSoftware "adm701209"
You cannot call a method on a null-valued expression.
At F:\Storage\Scripts\Windows\Modules\AVReport\AVReport.psm1:163 char:5
+ $subkeys = $reg.OpenSubKey($UninstallKey).GetSubKeyNames()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
PS F:\Storage\Scripts\Windows\Tools> Get-InstalledSoftware "adm701209"
ComputerName : adm701209
DisplayName : Adobe AIR
DisplayVersion : 2.7.1.19610
InstallLocation : c:\Program Files\Common Files\Adobe AIR\
Publisher : Adobe Systems Incorporated
InstallDate :
<clip>
PS F:\Storage\Scripts\Windows\Tools> get-date
Tuesday, August 20, 2013 12:45:19 PM
PS F:\Storage\Scripts\Windows\Tools>