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-CliXml ConvertTo-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 t...

Toying with audio in powershell

Controlling mute/unmute and the volume on you computer with powershell. Add-Type -TypeDefinition @' using System.Runtime.InteropServices; [Guid("5CDF2C82-841E-4546-9722-0CF74078229A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IAudioEndpointVolume { // f(), g(), ... are unused COM method slots. Define these if you care int f(); int g(); int h(); int i(); int SetMasterVolumeLevelScalar(float fLevel, System.Guid pguidEventContext); int j(); int GetMasterVolumeLevelScalar(out float pfLevel); int k(); int l(); int m(); int n(); int SetMute([MarshalAs(UnmanagedType.Bool)] bool bMute, System.Guid pguidEventContext); int GetMute(out bool pbMute); } [Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IMMDevice { int Activate(ref System.Guid id, int clsCtx, int activationParams, out IAudioEndpointVolume aev); } [Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), Inte...

Creating Menus in Powershell

I have created another Powershell module. This time it is about Console Menus you can use to ease the usage for members of your oranization. It is available on GitHub and published to the PowershellGallery . It is called cliMenu. Puppies This is a Controller module. It uses Write-Host to create a Menu in the console. Some of you may recall that using Write-Host is bad practice. Controller scripts and modules are the exception to this rule. In addition with WMF5 Write-Host writes to the Information stream in Powershell, so it really does not matter anymore. Design goal I have seen to many crappy menus that is a mixture of controller script and business logic. It is in essence a wild west out there, hence my ultimate goal is to create something that makes it as easy as possible to create a menu and change the way it looks. Make it easy to build Menus and change them Make it as "declarative" as possible Menus The module supports multiple Men...