Null-valued expression after starting RemoteRegistry service

This topic contains 7 replies, has 2 voices, and was last updated by  Dave Wyatt 4 years, 3 months ago.

  • Author
    Posts
  • #9631

    Charles Downing
    Participant

    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     :
    
    
    
    PS F:\Storage\Scripts\Windows\Tools> get-date
    
    Tuesday, August 20, 2013 12:45:19 PM
    
    
    PS F:\Storage\Scripts\Windows\Tools>
    
  • #9637

    Charles Downing
    Participant

    Got a workaround in place, but still no idea why it wasn't working originally...

    function OpenRemoteRegistryKey {
    	Param(
    		[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 -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 -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
    			$reg = [microsoft.win32.registrykey]::OpenRemoteBaseKey('LocalMachine',$ComputerName)
    			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 {
    	}
    }
    
  • #9640

    Dave Wyatt
    Moderator

    Your OpenRemoteRegistryKey function has a bug. In both places where you're calling the function recursively, you should assign the result to $reg; as written, you're just throwing the result away (and returning $null as a result, even if the recursive calls were successful).

    function OpenRemoteRegistryKey {
    	Param(
    		[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
    						$reg = OpenRemoteRegistryKey -ComputerName $ComputerName -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
    						$reg = OpenRemoteRegistryKey -ComputerName $ComputerName -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
    	}
    }
    
  • #9641

    Dave Wyatt
    Moderator

    Actually, now that I think about it, my description of the bug probably wasn't accurate. It wasn't that you were throwing away the results of the recursive call (because PowerShell should still write those return values to the output stream), but that you were also writing $null to the output stream afterward (in your finally block, when the value of $reg hasn't changed). So you probably were getting a collection back from OpenRemoteRegistryKey in some cases, and some elements of the collection were null.

    Either way, assigning the results of those recursive calls to $reg should fix the problem (and ensure that your function only returns a single value, in all cases).

  • #9668

    Charles Downing
    Participant

    Thanks, Dave! That seems to work, but I'm still not sure I understand why. I'll have to look at this one for a while... I appreciate the help, though!

  • #9670

    Dave Wyatt
    Moderator

    Here's a stripped-down version of your original code which will hopefully make the problem more clear:

    function ExampleFunction
    {
        $reg = $null
    	
        try
        {
    	    $reg = SomeMethodWhichMayThrowAnException
        }
        catch [Exception]
        {
            if ($_.Exception.Message -like "*The network path was not found*")
            {
                # If the method was successful the second time, it will return an object.
                # Because you're not assigning this object to a variable, it will be written to the
                # output stream (a common PowerShell "gotcha"; sometimes unexpected things show up
                # in function output because of this).
    
                ExampleFunction
            }
            else
            {
                # Not technically necessary; $reg is already $null, since you set it before the try block.
                $reg = $null
            }
        }
        finally
        {
            # This line will send the value of $reg to the output stream.  Because we didn't assign the value
            # of the recursive call to ExampleFunction to a variable, this may still be $null (even if the recursive
            # call was successful).
    
            $reg
        }
    }
    

    By assigning the value of ExampleFunction to $reg, you avoid both problems. In the finally block, it will contain an object if the function was successful (whether on the first try or in a recursive call), and will be $null otherwise. You also won't be sending output down the pipeline from any unexpected places.

  • #9684

    Charles Downing
    Participant

    Yeah, I finally figured out how my logic was wrong; I was thinking of the recursion as a loop instead of as recursion. Basically, in my head, I was thinking of the flow like this:

    1. Enter OpenRemoteRegistryKey
    2. Try to set $reg and fail
    3. Restart RemoteRegistry service
    4. Dump everything in the call stack, close the OpenRemoteRegistryKey function scope and enter OpenRemoteRegistryKey again

    But! The actual flow is more like this:

    1. Enter OpenRemoteRegistryKey
    2. Try to set $reg and fail
    3. Restart RemoteRegistry service
    4. Leave the call stack intact, leave the OpenRemoteRegistryKey function scope open, create a new OpenRemoteRegistryKey scope, and start over
    5. Repeat as necessary
    6. Return a response from the last scope opened and close that scope
    7. Finish execution in the parent scope and then return a response from that scope to its parent
    8. Rinse and repeat

    When I followed the actual flow, I realized that $reg was being set before the recursive call and was never being set again! So now the assignment changes that you suggested actually make sense to me! With those change, what is essentially happening is that the deepest recursive call is setting $reg in that try block and then returning that value of $reg up the call stack to the original call.

    Thanks for making me think, David!

  • #9688

    Dave Wyatt
    Moderator

    No problem! If you wanted to, you could have implemented this without recursion (personal preference there. Sometimes one is better than the other, sometimes it just doesn't matter). Here's an example of what the code structure with a loop might look like:

    function ExampleFunction
    {
        $reg = $null
    	
        # Assuming that you still want to limit this to 3 attempts:
    
        for ($i = 0; $i -lt 3; $i++)
        {
            try
            {
                $reg = SomeMethodWhichMayThrowAnException
                
                # This statement will not execute if the last line threw an Exception
                break
            }
            catch [Exception]
            {
                # Not technically necessary; $reg is already $null, since you set it before the try block.
                $reg = $null
            }
        }
    
        # Write whatever $reg's value is (might be null, if all attempts failed) to the output stream.
    
        $reg
    }
    
    
    

You must be logged in to reply to this topic.