Powershell by default provides access to the registry via a PSProvider. Running Get-PSDrive shows this, the namespace HKCU and HKLM are available along with the defaults for the local file system and other locations.

As of Powershell 4.0 the registry PSProvider can only access a registry hive that is already loaded into the currently logged on profile, it is not possible to load a hive directly from a file. It is however possible to make use of the tool reg.exe and the registry PSProvider together to load an external file. The blog post here explains the basics nicely.

Usage

Lets wrap up the steps from the linked blog into a couple of cmdlets, Import-RegistryHive and Remove-RegistryHive. As an example we will edit the NTUSER.DAT hive from the Default user profile in Windows 8.1, helpful if you are not able apply profile settings via Group Policy for example. Please remember running any scripts is at your own risk, make a backup first!

# load the NTUSER.DAT into HKLM\TEMP_HIVE, this can be accessed using the PSDrive TempHive
Import-RegistryHive -File 'C:\Users\Default\NTUSER.DAT' -Key 'HKLM\TEMP_HIVE' -Name TempHive

# using TempHive we make changes to turn on "boot to desktop" in the Default profile
New-Item -Path 'TempHive:\Software\Microsoft\Windows\CurrentVersion\Explorer\StartPage' -Force | Out-Null
New-ItemProperty -Path 'TempHive:\Software\Microsoft\Windows\CurrentVersion\Explorer\StartPage' -Name 'OpenAtLogon' -Value '0' -PropertyType 'DWORD' | Out-Null

# remove TempHive and unload the registry hive HKLM\TEMP_HIVE
Remove-RegistryHive -Name TempHive

Download Example1.ps1

Advanced Usage

This example provides a solution for unload errors where resources related to the loaded key have not been released in time. Removing the the pipe to Out-Null for the registry edits used in the previous example should show this happening, as (on my machine) printing out the result of the registry changes delays execution enough to require a second attempt at removal.

Import-RegistryHive -File 'C:\Users\Default\NTUSER.DAT' -Key 'HKLM\TEMP_HIVE' -Name TempHive

New-Item -Path 'TempHive:\Software\Microsoft\Windows\CurrentVersion\Explorer\StartPage' -Force
New-ItemProperty -Path 'TempHive:\Software\Microsoft\Windows\CurrentVersion\Explorer\StartPage' -Name 'OpenAtLogon' -Value '0' -PropertyType 'DWORD'

# attempt Remove-RegistryHive a maximum of 3 times
$attempt = 0
while($true)
{
    try
    {
        # when Remove-RegistryHive is successful break will stop the loop
        $attempt++
        Remove-RegistryHive -Name TempHive
        Write-Host 'NTUSER.DAT updated successfully!'
        break
    }
    catch
    {
        if ($attempt -eq 3)
        {
            # rethrow the exception, we gave up
            throw
        }

        Write-Host 'Remove-RegistryHive failed, trying again...'

        # wait for 100ms and trigger the garbage collector
        Start-Sleep -Milliseconds 100
        [gc]::Collect()
    }
}

Download Example2.ps1

The Functions

This is the code for the two cmdlets. The download link just bellow has a Powershell module file containing these. The functions in the module have some custom exceptions and extra help text that clutters the code, so it was omitted for the example on the page here.

The module can be loaded into Powershell using the command Import-Module ImportRegistryHive, the module file must be placed in one of the default locations Powershell defines. In most cases that will be %USERPROFILE%\Documents\WindowsPowerShell\Modules\ImportRegistryHive\ImportRegistryHive.psm1.

Function Import-RegistryHive
{
    [CmdletBinding()]
    Param(
        [String][Parameter(Mandatory=$true)]$File,
        # check the registry key name is not an invalid format
        [String][Parameter(Mandatory=$true)][ValidatePattern('^(HKLM\\|HKCU\\)[a-zA-Z0-9- _\\]+$')]$Key,
        # check the PSDrive name does not include invalid characters
        [String][Parameter(Mandatory=$true)][ValidatePattern('^[^;~/\\\.\:]+$')]$Name
    )

    # check whether the drive name is available
    $TestDrive = Get-PSDrive -Name $Name -EA SilentlyContinue
    if ($TestDrive -ne $null)
    {
        throw [Management.Automation.SessionStateException] "A drive with the name '$Name' already exists."
    }

    $Process = Start-Process -FilePath "$env:WINDIR\system32\reg.exe" -ArgumentList "load $Key $File" -WindowStyle Hidden -PassThru -Wait

    if ($Process.ExitCode)
    {
        throw [Management.Automation.PSInvalidOperationException] "The registry hive '$File' failed to load. Verify the source path or target registry key."
    }

    try
    {
        # validate patten on $Name in the Params and the drive name check at the start make it very unlikely New-PSDrive will fail
        New-PSDrive -Name $Name -PSProvider Registry -Root $Key -Scope Global -EA Stop | Out-Null
    }
    catch
    {
        throw [Management.Automation.PSInvalidOperationException] "A critical error creating drive '$Name' has caused the registy key '$Key' to be left loaded, this must be unloaded manually."
    }
}

Function Remove-RegistryHive
{
    [CmdletBinding()]
    Param(
        [String][Parameter(Mandatory=$true)][ValidatePattern('^[^;~/\\\.\:]+$')]$Name
    )

    # set -ErrorAction Stop as we never want to proceed if the drive doesnt exist
    $Drive = Get-PSDrive -Name $Name -EA Stop
    # $Drive.Root is the path to the registry key, save this before the drive is removed
    $Key = $Drive.Root

    # remove the drive, the only reason this should fail is if the reasource is busy
    Remove-PSDrive $Name -EA Stop

    $Process = Start-Process -FilePath "$env:WINDIR\system32\reg.exe" -ArgumentList "unload $Key" -WindowStyle Hidden -PassThru -Wait
    if ($Process.ExitCode)
    {
        # if "reg unload" fails due to the resource being busy, the drive gets added back to keep the original state
        New-PSDrive -Name $Name -PSProvider Registry -Root $Key -Scope Global -EA Stop | Out-Null
        throw [Management.Automation.PSInvalidOperationException] "The registry key '$Key' could not be unloaded, the key may still be in use."
    }
}

Download ImportRegistryHive.psm1

Custom Exceptions

It is unlikely a custom exception will be needed in most situations, however there is a use case when integrating an external process, or if simply making the exception more clear is helpful. This is covered when using reg.exe in the functions above, so I took the opportunity to add some custom exceptions to the code.

The best (and only?) online resource is this post on the subject http://www.powershellmagazine.com/2011/09/14/custom-errors/, which covers pretty much everything. Also helpful are some basics from MSDN, Error Reporting Concepts, Interpreting ErrorRecord Objects and a list of error categories in ErrorCategory Enumeration.


Comments

comments powered by Disqus