Skip to main content

Powershell – Another windows registry “module” with a little remote trick

powershell




Well it is time for another pure powershell post. This time I have been working with the registry. Feels like I have been living in different hives no matter what area I have been working in in the last 15 years. It has become rather pleasant if I could say so compared to the old VBSCRIPT days and before that reg.exe and ini files.



Fist a little history and a discussion. You have a couple of options in regards to reading and/or editing settings in the registry
  • Use the built-in PS-drives (gives you access to HKLM and HKCU)
  • Codeplex module (search on codeplex or in your favorite searchengine – most used and referenced: http://psrr.codeplex.com/)
  • Use the .Net class (normally [microsoft.win32.registrykey]). This gives you access to all the hives in the registry
  • Powershell Desired State Configuration (There is a built-in resource to access the registry)
  • WMI/CIM
Which one you pick depends upon your current needs and what you want to accomplish. If you are also in a hurry and need something quick, I would go with the codeplex module since it has some nice cmdlets that is easy to use. If you have the time and want to learn something at the same time, why not go out on a limb and create your own function(s)/module that suits your needs. If you already have not guessed it, we are going to create our own script.

Overall goal:
  1. Query one or several registry values
  2. Would be nice to be able to query remote computers? Yes it would.
First we are going to create a function that will create the .Net class object for us:



Function Get-RegistryHive
{
[cmdletbinding()]
Param(
        [ValidateSet("LocalMachine","Users","CurrentUser", "CurrentConfig", "ClassesRoot")]
        [String] $Hive = "LocalMachine"
    )

    $f = $MyInvocation.MyCommand.name
    $HiveObject = $null
    $ErrorActionPreference = "stop"

    try
    {
        Write-verbose "Creating .net object and connecting to hive '$hive'"
        $HiveObject = [Microsoft.Win32.RegistryKey]::OpenBaseKey($Hive, "default")
    }
    catch
    {
         Write-Verbose "$($_.Message)"
         $_.message
    }
    finally
    {
         $ErrorActionPreference = "Continue"
    }

    $HiveObject
}

We are using the advanced function template and I have thrown in a ValidateSet on the only parameter Hive. This gives us intellisence when we type the command, example:

image

Pretty sweet eh? As you may have noticed, I declare a variable $f in the function. I usually add that to all my function. This way if I need logging, I can write the function name to the debug/verbose stream or to a log file. As a bonus I do not need to worry about updating my script if I chose to change the function name and it gives me the option to create a function template which already has the $f variable declared.

Next we create the $HiveObject and use a static method of the .Net class. If you need to specify a view other than “default” which I have in the script you could set it to “Registry64” or “Registry32”.

Now we have the registry-hive object from the .Net class. Next we need a function to specify which path or location in the registry we want to pickup our value from:


function Get-RegistryKey
{
[cmdletbinding()]
Param(
        [Parameter(
            Mandatory=$true,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true)
        ]
        [String[]] $RegPath = ""
        ,
        [ValidateSet("LocalMachine","Users","CurrentUser", "CurrentConfig", "ClassesRoot")]
        [String] $Hive = "LocalMachine"
       
      )

BEGIN{
    $f = $MyInvocation.MyCommand.name
    [string] $LoggMsg = ""
    Write-Verbose "Begin $f"
}

PROCESS{
    foreach ($path in $RegPath)
    {
        $HiveObject = Get-RegistryHive -Hive $hive
        $key = $HiveObject.OpenSubkey($path)
        if($key -ne $null)
        {
            $key
        }
        else
        {
            $LoggMsg = "Error - Could not find path '$($HiveObject.Name)\$RegPath'"
            Write-Verbose $LoggMsg
            throw($LoggMsg)
        }
    }
}

END{
    Write-Verbose "END $f"
}
}


A little more going on here. First the parameters:

  • RegPath – Notice this is an array parameter and has the ValueFromPipeline attribute set to True. This way we have our options open if we want to use the pipeline to submit values to our function.

  • Hive – Again we specify the Hive parameter with the ValidateSet statement. Notice it is used when we call the other function Get-RegistryHive

Notice I have added no error handling here. It is not very likely that the Get-RegistryHive –Hive $Hive should fail since we have validated the input parameter, however when we use the object returned from Get-RegistryHive and execute the method OpenSubkey we could specify a path that is not valid. For my purpose, it is enough to check if the return value from OpenSubkey is null. You could wrap it in a Try-Catch statement if you like.

Now we were suppose to support registry queries for remote computers and I have not forgotten. Next up is the Get-RegistryValue function and here we need the computername parameter.



Function Get-RegistryValue
{
[cmdletbinding()]
Param(
        [Parameter(
            Mandatory=$true,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true)
        ]
        [String] $RegPath = ""
        ,
        [ValidateSet("LocalMachine","Users","CurrentUser", "CurrentConfig", "ClassesRoot")]
        [String] $Hive 
        ,
        [Parameter(Mandatory=$true)]
        [String[]] $RegValueName = ""
        ,
        [string] $computername = ""
)
BEGIN{
    $f = $MyInvocation.MyCommand.name
    [string] $LoggMsg = ""
    Write-Verbose "Begin $f"
}

PROCESS{
    $ErrorActionPreference = "stop"

    Try
    {
        if($computername -eq "")
        {
            Write-Verbose "Assuming local registry query"
            $key = Get-RegistryKey -Hive $hive -RegPath $RegPath
            foreach($keyValue in $RegValueName)
            {
                $output = "" | select Name, Value
                $output.value = $key.GetValue($keyValue)
                $output.name = $keyValue
                $output
            }
        }
        else
        {
            Write-Verbose "Assuming remote registry query"
            Write-Verbose "Getting helper-functions and creating command"
            
            [string] $functions = Get-Fullcommand -Name "Get-RegistryHive"
            $Functions += Get-Fullcommand -Name "Get-RegistryKey"
            $Functions += Get-Fullcommand -Name "Get-RegistryValue"
           
            [string] $Command = "Get-RegistryValue -Hive $hive -RegPath '$RegPath' -RegValueName $RegValueName"
            if($VerbosePreference -ne "SilentlyContinue")
            {
                Write-Verbose "VerbosePreferance is turned on, adding verbose option"
                $command += " -verbose"
            }
            else
            {
                Write-Verbose "VerbosePreferance is turned OFF"
            }

            Write-Verbose "Command is: $command"
            
            $sb = [scriptblock]::Create("$functions $command")
            
            Write-Verbose "Invoking command on computer '$computername'"
            Invoke-Command -ComputerName $computername -ScriptBlock $sb
        }
    }
    catch
    {
        Write-Verbose "$($_.Message)"
        $_.message
    }
    finally
    {
        $ErrorActionPreference = "Continue"
    }
 
}

END{
}

}

Parameters:

  • RegPath – Used when we request the registry key by running the Get-RegistryKey function

  • RegValueName – The name of the Value-element we want to get.

  • Computername – The name of the computerobject we want to query

Let’s do the easy scenario first.



image


We are querying the “HKLM:\Software\Microsoft\DirectX” key and looking for the value ‘Version’ on the local computer. Here is the result:


image


According to the code in the Get-RegistryValue function, if computername equals “”, it runs the function Get-RegistryKey –Hive $hive –RegPath $RegPath. The return object $key is used in a foreach loop for all valuenames specified in the RegValueName parameter. Nothing fancy and it works. We could throw in some additional error handling if the valuename is not found etc, however in is okay for now.

Now the trick for the remote connection. If you look at the code, I call a function named Get-FullCommand. This function returns the definition of the command/function, here is the listing:



function Get-Fullcommand
{
[cmdletbinding()]
Param(
        [Parameter(Mandatory=$true)]
        [string] $Name
)
    $ErrorActionPreference = "stop"
    [String] $FullCommand = ""

    try
    {
        [string]$FunctionBody = (Get-Command -Name $Name).ScriptBlock.ToString()

        if($FunctionBody -ne $null -and $FunctionBody -ne "")
        {
            $FullCommand = "Function $name { $FunctionBody }"
        }
        else
        {
            [string] $msg = "Could not find command '$name'"
            Write-Verbose $msg
            throw($msg)
        }
    }
    Catch
    {
        Write-Verbose "$($_.Message)"
        $_.message
    }
    Finally
    {
        $ErrorActionPreference = "Continue"
    }
    $FullCommand
}


Now the idea is to use the functions we have that works locally on the “test” computer, collect them in an string variable, create a string variable for the command we are going to run on the remote computer and create a ScriptBlock that is going to be executed on by the Invoke-Command cmdlet. Sounds like a lot of work, but really, it is not.



[string] $Functions = Get-Fullcommand -Name "Get-RegistryHive"
$Functions += Get-Fullcommand -Name "Get-RegistryKey"
$Functions += Get-Fullcommand -Name "Get-RegistryValue"


First we create a [string] variable to hold the content of the first function ‘Get-RegistryHive’. Then we run the Get-FullCommand for the other 2 functions and adding the content of those functions to the $functions variable.



[string] $Command = "Get-RegistryValue -Hive $hive -RegPath '$RegPath' -RegValueName $RegValueName"

if($VerbosePreference -ne "SilentlyContinue")
{
    Write-Verbose "VerbosePreference is turned on, adding verbose option"
    $command += " -verbose"
}
else
{
    Write-Verbose "VerbosePreferance is turned OFF"
}

We then create a [string] variable for the command we want to execute and use the parameters for the function. We check the $VerbosePreference variable and echo the verbose level for the current scope and add the –verbose switch parameter if VerbosePreference is turned on.



$sb = [scriptblock]::Create("$functions $command")
            
Write-Verbose "Invoking command on computer '$computername'"
Invoke-Command -ComputerName $computername -ScriptBlock $sb


Next we create a [scriptblock] variable and use the static method Create and add the content of the $functions variable and the command we want to run. Finally we run the invoke-command cmdlet and specify the computername variable and the scriptblock variable. The final result:


image


Now, for those of you that is into .Net, you are probably screaming by now, since the class [Microsoft.Win32.RegistryKey] class has a method called OpenRemoteBaseKey that enables you to remotely query registry of another computer. So if you cannot see the nice feature of doing it my way, why don’t you jump right in and use that method in your scripts. I will not blame you, I will wish you the best of luck, however you do not need it because it works :-)


Cheers

Tore

Comments

Popular posts from this blog

Serialize data with PowerShell

Currently I am working on a big new module. In this module, I need to persist data to disk and reprocess them at some point even if the module/PowerShell session was closed. I needed to serialize objects and save them to disk. It needed to be very efficient to be able to support a high volume of objects. Hence I decided to turn this serializer into a module called HashData.



Other Serializing methods

In PowerShell we have several possibilities to serialize objects. There are two cmdlets you can use which are built in:
Export-CliXmlConvertTo-JSON
Both are excellent options if you do not care about the size of the file. In my case I needed something lean and mean in terms of the size on disk for the serialized object. Lets do some tests to compare the different types:


(Hashdata.Object.ps1)

You might be curious why I do not use the Export-CliXML cmdlet and just use the [System.Management.Automation.PSSerializer]::Serialize static method. The static method will generate the same xml, however we …

Build your local powershell module repository - ProGet

So Windows Powershell Blog released a blog a couple of days ago (link). Not too long after, a discussion emerged about it being to complicated to setup. Even though the required software is open source (nugetgalleryserver), it looks like you need to have Visual Studio Installed to compile it. I looked into doing it without visual stuidio, however I have been unable to come up with a solution. I even tweeted about it since I am not an developer. Maybe someone how is familiar with “msbuild” could do a post on how to do it without VS.

Anyhow one of my twitter-friends (@sstranger) came to the rescue and pointed me in the direction of ProGet, hence the title of this post. ProGet comes in 2 different licensing modes
Free (reduced functionality)Enterprise (paid version with extra features)The good news is that the free version supports hosting a local PowershellGet repository which was my intention anyway. So off we go and create a Configration that can install ProGet for us. This is the conf…

Monitoring Orchestrator runbook events from Operations Manager

Today I will follow up on my colleague’s post Mr ITblog (Knut Huglen) about monitoring Orchestrator Runbook events.  He has build a nice double up SNMP loopback feature that does self monitoring in Orchestrator resulting in entries written to a special Windows Eventlog. Now we need to raise alerts in SCOM when one of his runbooks fails or sends a platform event, who knows there could be trouble lurking in his paradise.

We are not going to do anything fancy, however these are the steps we will be focusing on today:
Create a Management Pack for our customizations Create rules that collects the events from the orchestrator serverOff we go then and fire up the SCOM console and a powershell window. First we create a MP, I am going to use powershell to do this, however you may use the SCOM console as well (Administration – ManagementPacks – Action: Create Management Pack):



Import the Management Pack into SCOM and move on to the Authoring section in the SCOM console. Create a new rule:



Give the…