Tags

, , ,


It’s often important to be able to read the registry of a remote machine. Unfortunately, by default in Windows 7, the Remote Registry service is not running. This also prevents Get-Process working against a remote machine!
I therefore wanted to write a robust re-usable function for use in any situation (where remote registry access is required) to start that service. I wanted it to be fool-proof (fingers-crossed) and capable of trapping errors. I decided the best solution was to return an object from the function with various useful properties. The most obvious being a Boolean “Success” property and a String that could hold a more verbose completion/error message. I added in properties holding information about the initial state of the service and what changed (such as the Start Mode) in case a script using this function should need to keep track of these to to set things back again to how they were initially at a later point.

I deliberately avoided PSRemoting and then used WMI (since .NET doesn’t let you change the Start Mode … why??!!). The down-side is that when you start a service via WMI, the call returns immediately and so repeated WMI queries have to be done to wait for it to start. The Do loop that handles this with an Exit in the middle and a timer variable felt dirty to write! The feel-good alternative would have been a [System.ServiceProcess.ServiceController] and its WaitForStatus(State,Timespan) method. Unfortunately, instantiating one of these and calling the method is a whole extra layer of things that might go wrong as well as WMI and so I kept to one technology and swallowed my pride at Exiting a Do loop!

function Enable-RemoteRegistryAccess {
<#
.SYNOPSIS
    Enables and starts the Remote Registry Service on remote machines.
.DESCRIPTION
    This function starts the Remote Registry Service on one or more remote machines.
    If the service Start Mode was previously set to Disabled then it will be set to Manual.
    PowerShell Remoting is not required but the machine(s) must permit remote management via WMI.

    A returned object for each target computer details the results of the operation via the following properties:
        [string] ComputerName     = hostname of the remote PC
        [bool]   Online           = whether the PC was pingable
        [string] InitialStartMode = the initial Start Mode of the PC's Remote Registry service
        [string] InitialState     = the initial State of the PC's Remote Registry service
        [bool]   StartModeChanged = whether the Start Mode of the PC's Remote Registry service had to be changed from Disabled to Manual
        [bool]   StateChanged     = whether the State of the PC's Remote Registry service had to be changed from Stopped to Running
        [bool]   Verified         = whether an attempt was made to connect to the remote registry using .NET
        [bool]   Success          = whether the operation as a whole succeeded
        [string] Details          = a detailed status report including exceptions if the operation failed

    (C) Cantoris Computing, August 2014 - https://cantoriscomputing.wordpress.com/
.PARAMETER computerName
    Specifies the computer(s) against which to perform the operation.
.PARAMETER verify
    Causes the remote registry connectivity via .NET to be subsequently verified.
    The success or otherwise of this will be reflected in the Success property of the returned object.
.EXAMPLE
    Enable-RemoteRegistryAccess -computerName Host1,Host2
    This will start the Remote Registry Service on computers Host1 and Host2.
.EXAMPLE
    Host1,Host2 | Enable-RemoteRegistryAccess
    This will start the Remote Registry Service on computers Host1 and Host2.
.EXAMPLE
    if ( (Enable-RemoteRegistryAccess -computerName Host1).Success -eq $false ) { Write-Error "Could not enable remote registry editing." }
    This will start the Remote Registry service on computer Host1 and capture an error.
#>
    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    Param(
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   ValueFromPipelineByPropertyName=$true, 
                   HelpMessage="Enter one or more computer names separated by commas.")]
        [Alias("Hostname")]
        [ValidateNotNullOrEmpty()]
        [string[]]$computerName,
        [Parameter(Mandatory=$false)]
        [switch]$verify
    )
    #Requires -Version 3
    BEGIN {}
    PROCESS {
        $computerName | ForEach-Object {
            $computer = $_
            $props = [ordered]@{
                "ComputerName" = $computer;
                "Online" = $false;
                "InitialStartMode" = "Unknown";
                "InitialState" = "Unknown";
                "StartModeChanged" = $false;
                "StateChanged" = $false;
                "Verified" = $false;
                "Success" = $false;
                "Details" = "UNKNOWN"
            }
        # Check it's online.
            if (Test-Connection -ComputerName $computer -Count 1 -Quiet) {
                $props["Online"] = $true
        # Get the service through WMI.
                $wmiSuccess = $true
                try {
                    $rrSvcWMI = Get-WmiObject -ComputerName $computer -Class Win32_Service -Filter "Name='RemoteRegistry'" -ErrorAction Stop # Otherwise non-terminating
                } catch {
                    $wmiSuccess = $false
                    $props["Details"] = ("ERROR: The call to WMI failed: '{0}'." -f $Error[0].Message) # Not an Exception
                }
                if ($wmiSuccess) {
                    $props["InitialStartMode"] = $rrSvcWMI.StartMode
                    $props["InitialState"] = $rrSvcWMI.State
        # Is the start-mode set to Disabled?
                    $readyToStart = $true
                    if ($rrSvcWMI.StartMode -eq "Disabled") {
        # Try and change start mode.
                        try {
                            # Note that this mode change method appears to be instant, unlike a service start.
                            $result = Invoke-WmiMethod -InputObject $rrSvcWMI -Name ChangeStartMode -ArgumentList "Manual" # Will not necessarily cause any type of error
                        } catch {
                            $readyToStart = $false
                            $props["Details"] = ("ERROR: Could not change the start mode of the Remote Registry service: '{0}'." -f $Error[0].Exception.Message)
                        }
        # If the call appeared to work, still need to check the return code.
                        if ($readyToStart) {
                            if ($result.ReturnValue -ne 0) {
                                $readyToStart = $false
                                $props["Details"] = ("ERROR: Could not change the start mode of the Remote Registry service: Return code {0}." -f $result.ReturnValue)
                            } else {
                                $props["StartModeChanged"] = $true
                            }
                        }
                    } # If StartMode = Disabled
        # Now ready to see if the service needs starting.
                    $serviceStarted = $true
                    if ($readyToStart) {
                        if ($rrSvcWMI.State -ne "Running") {
                            try {
                                # This method returns before the start has finished!
                                $result = Invoke-WmiMethod -InputObject $rrSvcWMI -Name StartService
                            } catch {
                                $serviceStarted = $false
                                $props["Detail"] = ("ERROR: Could not start the Remote Registry service: '{0}'." -f $Error[0].Exception.Message)
                            }
        # If the call appeared to work, check return code.
                            if ($serviceStarted) {
                                if ($result.ReturnValue -ne 0) {
                                    $serviceStarted = $false
                                    $props["Details"] = ("ERROR: Could not start the Remote Registry service: Return code {0}." -f $result.ReturnValue)
                                }
                            }
        # If all OK, now verify and wait for the state to change.
        # .NET ServiceController and .WaitForStatus() would be neater.
        #  - but could fail separately from WMI - would have to trap both instantiation and method call!
                            if ($serviceStarted) {
                                $timer = 0
                                do {
                                    $timer += 1
                                    if ($timer -eq 20) {
                                        $serviceStarted = $false
                                        $props["Details"] = "ERROR: Timeout waiting for the Remote Registry service to start."
                                        exit # Leave the Do loop
                                    }
                                    Start-Sleep -Milliseconds 500
                                    $rrSvcWMI = Get-WmiObject -ComputerName $computer -Class Win32_Service -Filter "Name='RemoteRegistry'"
                                } until ($rrSvcWMI.State -eq "Running")
                            }
        # Crunch time.
                            if ($serviceStarted) {
                                $props["StateChanged"] = $true
                                $props["Success"] = $true
                                $props["Details"] = "The Remote Registry service has been started."
                            }
                        } else {
                            # Service was already started
                            $props["Success"] = $true
                            $props["Details"] = "The Remote Registry service was already running."
                        } # If State <> Running
                    } # If $readyToStart
        # Optionally verify the result.
                    if ($serviceStarted -and $verify) {
                        $props["Verified"] = $true
                        $props["Details"] = "The ability to connect to the remote registry via .NET has been confirmed."
                        try {
                            $remoteHKLM = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::"LocalMachine",$computer)
                        } catch {
                            $props["Success"] = $false
                            $props["Details"] = ("ERROR: Could not connect to the remote registry via .NET: '{0}'." -f $Error[0].Exception.Message)
                        } finally {
                            $remoteHKLM = $null
                        }
                    }
                } # If $wmiSuccess
            } else {
                 # PC was not online.
                $props["Details"] = "ERROR: The PC appears to be offline."
            } # Test-Connection
            $outputObject = New-Object -TypeName PSObject -Property $props
            Write-Output $outputObject
        }
    }
    END {}
}

Enable-RemoteRegistryAccess -computerName Osiris -verify
Advertisements