Thursday, December 23, 2010

Generate remote registry .reg file export with powershell

Why create a .reg file with powershell when regedit /e switch or file / export on regedit work without any problem ?
Well, just for fun, but this solution can be useful if you need to do this for 3000 computers.
Some others solution exist like for example psexec with regedit (or hacked regedit for gpo security problem), but c$ must exist (that's not always the case for security reasons)

And why export .reg ?
A .reg file is more easy to analyze or compare with another registry file because it is in a string format. And you can re-use it directly without use any other tool than regedit integrated windows tool (except if you can't run regedit on your computer)

[EDIT 2011-03-23]
A new version with bug correction in multistring format


You can download this powershell script here and to test it you can use this testfr.reg file

How to use this powershell script:
.\ExportRegistyToFile.ps1 'key_to_export' 'outputfile.txt'
for example if you want to test with my registry created with testfr.reg
.\ExportRegistyToFile.ps1 'HKEY_CURRENT_USER\Software\TestFr' 'output_testfr.txt'




#
# Export registry to .reg file like regedit /e
#
# by F.Richard 2010-10
# Modified 2011-03 : bug correction in multistring format
#


# Working with Registry Entries
# http://technet.microsoft.com/en-us/library/dd315394.aspx

# [Enum]::GetNames([Microsoft.Win32.RegistryValueKind])
# -> Unknown / String / ExpandString / Binary / DWord / MultiString / QWord

# [Enum]::GetNames([Microsoft.Win32.RegistryHive])
# -> ClassesRoot / CurrentUser / LocalMachine / Users / PerformanceData / CurrentConfig / DynData

set-psdebug -strict

$debug = $True
#$debug = $False

$errorPref = "Continue" # "Continue" "Stop" "Inquire" "SilentlyContinue"
$ErrorActionPreference = $errorPref

# Script Directory
$strCurDir = Split-Path -parent $MyInvocation.MyCommand.Path
Set-Location $strCurDir | Out-Null

# ----------------------------

# DllImport using http://www.pinvoke.net
$signature = @"
[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
public static extern int RegOpenKeyEx(HDEFKEY hKey, string subKey, int ulOptions, REGSAM samDesired, out UIntPtr hkResult);

[DllImport("advapi32.dll", SetLastError=true)]
public static extern int RegCloseKey(UIntPtr hKey);

[DllImport("advapi32.dll", CharSet = CharSet.Unicode, EntryPoint = "RegQueryValueExW", SetLastError = true)]
public static extern int RegQueryValueEx(UIntPtr hKey, string lpValueName, int lpReserved, out uint lpType, System.Text.StringBuilder lpData, ref uint lpcbData);

[DllImport("advapi32.dll", EntryPoint = "RegEnumKeyEx")]
public static extern int RegEnumKeyEx(UIntPtr hKey, uint index, System.Text.StringBuilder lpName, ref uint lpcbName, IntPtr reserved, IntPtr lpClass, IntPtr lpcbClass, out long lpftLastWriteTime);

public enum REGSAM : int {
KEY_ALL_ACCESS = 0xF003F,
KEY_CREATE_LINK = 0x0020,
KEY_CREATE_SUB_KEY = 0x0004,
KEY_ENUMERATE_SUB_KEYS = 0x0008,
KEY_EXECUTE = 0x20019,
KEY_NOTIFY = 0x0010,
KEY_QUERY_VALUE = 0x0001,
KEY_READ = 0x20019,
KEY_SET_VALUE = 0x0002,
KEY_WOW64_32KEY = 0x0200,
KEY_WOW64_64KEY = 0x0100,
KEY_WRITE = 0x20006
}

public enum HDEFKEY : uint {
HKEY_CLASSES_ROOT = 0x80000000,
HKEY_CURRENT_USER = 0x80000001,
HKEY_LOCAL_MACHINE = 0x80000002,
HKEY_USERS = 0x80000003,
HKEY_CURRENT_CONFIG = 0x80000005,
HKEY_DYN_DATA = 0x80000006
}


"@

# Reg Val Types Declaration
# REG_NONE = 0 / REG_SZ = 1 / REG_EXPAND = 2 / REG_BINARY = 3 / REG_DWORD = 4 / REG_DWORD_BIG_ENDIAN = 5 / REG_LINK = 6
# REG_MULTI_SZ = 7 / REG_RESSOURCE_LIST = 8 / REG_FULL_RESSOURCE_DESCRIPTOR = 9 / REG_RESOURCE_REQUIREMENTS_LIST = 10 / REG_QWORD = 11

$RegValTypes = @{"0" = "Unknown"; "1" = "String"; "2" = "ExpandString"; "3" = "Binary";
"4" = "DWord"; "5" = "DWord_Big_Endian"; "6" = "Link"; "7" = "MultiString";
"8" = "Ressource_List"; "9" = "Full_Ressource_Descriptor"; "10" = "Ressource_Equirement_list"; "11" = "QWord"
}

# ----------------------------

Function transformBinReg {
Param(
[Ref] $ref_result_property,
[Ref] $ref_line,
[Ref] $ref_values,
[Int] $nb_values,
[Ref] $ref_comma,
[Ref] $ref_nb_bin,
[String] $multi
)
$ref_comma.value = $(if ($ref_comma.value) {$ref_comma.value} else {""})
$ref_nb_bin.value = $(if ($ref_nb_bin.value) {$ref_nb_bin.value} else {0})
$multi = $(if ($multi) {$multi} else {$False})

For ($i=0; $i -lt $nb_values; $i++) {
$ref_line.value = $ref_line.value + $ref_comma.value + ([String]::Format("{0:x2}", $ref_values.value[$i]))
$ref_nb_bin.value++
$ref_comma.value = ","
if (($ref_line.value.length -gt 75) -or ($ref_nb_bin.value -eq 25)) {
if (($result_property.length - $result_property.LastIndexOfAny("`r`n")) -gt 73) {
$ref_result_property.value = $ref_result_property.value + ",\`r`n "
}
$ref_result_property.value = $ref_result_property.value + $ref_line.value
If ($i -lt ($nb_values-1)) {
$ref_result_property.value = $ref_result_property.value + ",\`r`n "
}
$ref_comma.value = ""
$ref_line.value = ""
$ref_nb_bin.value=0
}
}

If ($multi -eq $True) {
if ($ref_nb_bin.value -eq 0) {
if ($ref_result_property.value.substring($ref_result_property.value.length - 5 , 5) -eq "2a,00" ) { # 00,\
$ref_result_property.value = $ref_result_property.value.substring(0,($ref_result_property.value.length-5)) + "00,00" # replace end string - "2a" by "00"
}
} elseif ($ref_nb_bin.value -eq 1) {
if ($ref_result_property.value.substring($ref_result_property.value.length - 8, 4) -eq "2a,\" ) { # 2a,\
$ref_result_property.value = $ref_result_property.value.substring(0,($ref_result_property.value.length-8)) + "00" + ",\`r`n " # replace end string - "2a" by "00"
}
} else {
$ref_line.value = $ref_line.value.substring(0,($ref_line.value.length-5)) + "00,00" # replace end string - "2a,00" by "00,00"
}
}
}


# ----------------------------

Function transformStringReg {
Param($strReg)

$result = $strReg -replace("\\", "\\") # replace \ by \\
$result = $result -replace('"', '\"')
return $result
}

# ----------------------------

Function ExportReg {
Param(
[String] $hiveKey,
[String] $registrypath,
[String] $Computername,
[String] $outputfile
)
$computerKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($hiveKey, $Computername)
$Key = $computerKey.OpenSubKey($registrypath,$false)
If(!($Key)){Return}

$result = "[" + $Key.Name + "]" + "`r`n"
If ($debug) { Write-Host $result }
$SubKeyValues = $Key.GetValueNames()
foreach($SubKeyValue in $SubKeyValues) {
$propertyname = $SubKeyValue
$propertyvalue = $Key.GetValue($SubKeyValue)
$result_property = ""
$TypeNameOfValue = ""
If ($propertyname -eq "") {
$TypeNameOfValue = "(default)"
$hKey = New-Object UIntPtr
$keyname = $key.Name
$hKeyLeft = $keyname.Substring(0, $keyname.IndexOf('\'))
$hKeyRight = $keyname.Substring( ($keyname.IndexOf('\') + 1) , ($keyname.Length - $keyname.IndexOf('\') - 1) )
$result_func = [Win32Functions.Registry]::RegOpenKeyEx($hKeyLeft, $hKeyRight, 0, "KEY_READ", [ref] $hKey)
if ($result_func -eq 0) {
$valuename = "" # (default) = ""
$lpType = New-Object uint32
$lpData = New-Object System.Text.StringBuilder
$lpcbData = New-Object uint32
# Get Type & Size
$result_func = [Win32Functions.Registry]::RegQueryValueEx($hKey, $valuename, $Null, [ref] $lpType, $lpData, [ref] $lpcbData)
# ERROR_FILE_NOT_FOUND = 2 $result_func
$TypeNameOfValue = $RegValTypes["$lpType"]
}
$result_func = [Win32Functions.Registry]::RegCloseKey($hKey)
$result_name = "@"
$property_name = ""

} else {
$ErrorActionPreference = "Continue" #"SilentlyContinue"
$TypeNameOfValue = $key.GetValueKind($propertyname) # $property.TypeNameOfValue not so precise String = ExpandString too
If ($TypeNameOfValue -eq "") { # OpenSaveMRU\* problem
$TypeNameOfValue = "TrapError"
}
$ErrorActionPreference = $errorPref
$result_name = "`"" + $(transformStringReg($propertyname)) + "`""
$property_name = $propertyname
}

Switch ($TypeNameOfValue) {
# TrapError
"TrapError" {
$result_property = ""
} # End TrapError

# String "System.String"
"String" {
$result_val = transformStringReg($propertyvalue)
$result_property = $result_name + "=`"" + $result_val + "`""
} # end String

# ExpandString "System.String"
"ExpandString" {
$result_property = ""
$tmpvalues = ($key.GetValue($property_name,"error",[Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames))
$values = [System.Text.Encoding]::UNICODE.GetBytes($tmpvalues + "*") # * = 2a
$line = $result_name + "=hex(2):"
$comma = ""
$nb_bin = 0
transformBinReg ([Ref]$result_property) ([Ref]$line) ([Ref]$values) ($values.count) ([Ref]$comma) ([Ref]$nb_bin) $True
$result_property = $result_property + $line
} # ExpandString


# MultiString "System.String[]"
"MultiString" {
$result_property = ""
$tmpvalues = $propertyvalue
$line = $result_name + "=hex(7):"

$comma = ""
$nb_bin=0
foreach ($val in $tmpvalues) {
$values = [System.Text.Encoding]::UNICODE.GetBytes($val + "*") # * = 2a
transformBinReg ([Ref]$result_property) ([Ref]$line) ([Ref]$values) ($values.count) ([Ref]$comma) ([Ref]$nb_bin) $True
}
$result_property = $result_property + $line

# Multistring
if ($result_property.substring($result_property.length - 1, 1) -eq ":") { # test if multistring has a value
$result_property = $result_property + "00,00" # End of multistring
} else {
if ( ($result_property.length - $result_property.LastIndexOfAny("`r`n")) -gt 73) {
$result_property = $result_property + ",00,\`r`n 00" # End of multistring
} else {
$result_property = $result_property + ",00,00" # End of multistring
}
}
} # MultiString

# Binary "System.Byte[]"
"Binary" {
$result_property = ""
$values = $propertyvalue
$line = $result_name + "=hex:"
$comma = ""
$nb_bin=0
transformBinReg ([Ref] $result_property) ([Ref] $line) ([Ref] $values) $values.count ([Ref] $comma) ([Ref] $nb_bin)
$result_property = $result_property + $line
} # End Binary


# DWord "System.Int32"
"DWord" {
$val = ([String]::Format("{0:x8}", $propertyvalue))
$result_property = $result_name + "=dword:$val"
} # DWord

# QWord
"QWord" {
$val = ([String]::Format("{0:x16}", $propertyvalue))
$result_property = $result_name + "=hex(b):" + $val.Substring(14,2) + "," + $val.Substring(12,2) + "," + $val.Substring(10,2) + "," + $val.Substring(8,2) + "," + $val.Substring(6,2) + "," + $val.Substring(4,2) + "," + $val.Substring(2,2) + "," + $val.Substring(0,2)
} # QWord

# Unknown
"Unknown" {
# GetValue does not support reading values of type REG_NONE or REG_LINK
# http://msdn.microsoft.com/en-us/library/kk88y0s0.aspx
$hKey = New-Object UIntPtr
$keyname = $key.Name
$hKeyLeft = $keyname.Substring(0, $keyname.IndexOf('\'))
$hKeyRight = $keyname.Substring( ($keyname.IndexOf('\') + 1) , ($keyname.Length - $keyname.IndexOf('\') - 1) )
$result_func = [Win32Functions.Registry]::RegOpenKeyEx($hKeyLeft, $hKeyRight, 0, "KEY_READ", [ref] $hKey)

$values = ""
$nb_values = 0
if ($result_func -eq 0) {
$lpType = New-Object uint32
$lpData = New-Object System.Text.StringBuilder
$lpcbData = New-Object uint32
# Get Type & Size
$result_func = [Win32Functions.Registry]::RegQueryValueEx($hKey, $property_name, $Null, [ref] $lpType, $lpData, [ref] $lpcbData)
# ERROR_MORE_DATA = 234
if ($result_func -eq 234) {
If ($lpType -eq 0) {
$lpData = New-Object System.Text.StringBuilder ([int] $lpcbData)
$result_func = [Win32Functions.Registry]::RegQueryValueEx($hKey, $property_name, $Null, [ref] $lpType, $lpData, [ref] $lpcbData)
$values = [System.Text.Encoding]::UNICODE.GetBytes($lpData) # $values.count can be different from $lpcbData
$nb_values = $lpcbData
}

}
}
$result_func = [Win32Functions.Registry]::RegCloseKey($hKey)

$result_property = ""
$line = $result_name + "=hex(0):"
$comma = ""
$nb_bin=0
transformBinReg ([Ref]$result_property) ([Ref]$line) ([Ref]$values) ($nb_values) ([Ref]$comma) ([Ref]$nb_bin)
$result_property = $result_property + $line
} # End Unknown

# Default -> error typename not defined
Default {
$result_property=";ERROR: type $TypeNameOfValue not defined " + "path:" + $key.PsPath + " Name:" + $propertyname
Write-Host $result_property
} # end Default
}
$result = $result + $result_property + "`r`n"
}
#If ($debug) { Write-Host $result }
$result | Out-File -append $outputfile
$result=""

$SubKeyNames = $Key.GetSubKeyNames()
foreach($subkeyname in $SubKeyNames) {
ExportReg $hiveKey "$registrypath\$subkeyname" $Computername $outputfile
}


}


# ----------------------------

Function TestFilePath {
Param($filename)

If ((Test-Path("$filename")) -eq $False){
If ((Test-Path("$strCurDir\$filename")) -eq $False){
Write-Host "ERROR : file $filename NOT exist"
Write-Host "ERROR : file $strCurDir\$filename NOT exist"
Exit
} Else {
$filename = "$strCurDir\$filename"
}
}

return $filename
}

# ----------------------------

#
# MAIN PROGRAM
#

# Verify Arguments Number
If($Args.Count -lt 2) {
Write-Host "Syntax:"$MyInvocation.MyCommand.Name "registry outputfilename [computername]"
Write-Host " Example:"$MyInvocation.MyCommand.Name" 'HKCM:\Software\Microsoft\Windows\CurrentVersion\Run' 'outputfile.txt' "
Write-Host " Example:"$MyInvocation.MyCommand.Name" 'HKEY_CURRENT_USER\Control Panel\Desktop' 'outputfile.txt' computername"
Write-Host
Break
}

# Registry Path
$registrypath = $Args[0]

# Output file
$outputfile = $Args[1]

# Computername
if ($Args.Count -gt 2) {
$Computername = $Args[2]
} else {
$Computername = $env:Computername
}
If ($debug) { Write-Host $Computername }


# Register registry functions
If (-not ("Win32Functions.Registry" -as [Type])) {
$type = Add-Type -MemberDefinition $signature -Name Registry -Namespace Win32Functions -Using System.Text -PassThru
} else {
If ($debug) { Write-Host "Win32Functions.Registry already Registered" }
}

# Write output file with registry informations
$Content = "Windows Registry Editor Version 5.00" + "`r`n" # "REGEDIT4"
$Content | Out-File $outputfile
$hKeyLeft = $registrypath.Substring(0, $registrypath.IndexOf('\'))
$hKeyRight = $registrypath.Substring( ($registrypath.IndexOf('\') + 1) , ($registrypath.Length - $registrypath.IndexOf('\') - 1) )
switch ($hKeyLeft) {
"HKEY_CLASSES_ROOT" { $hiveKey = "ClassesRoot"}
"HKEY_CURRENT_USER" { $hiveKey = "CurrentUser"}
"HKEY_LOCAL_MACHINE" { $hiveKey = "LocalMachine"}
"HKEY_USERS" { $hiveKey = "Users"}
"HHKEY_CURRENT_CONFIG" { $hiveKey = "CurrentConfig"}
default {
Write-Host "ERROR: you must use one of the following key: HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKCU, HKLM, HKU, HKCC"
Exit 1
}
}

ExportReg $hiveKey $hKeyRight $Computername $outputfile

# ----------------------------

6 comments:

Anonymous said...

This is great work, thanks Frank for putting it out there!

Steve said...

Your script works once I worked out the requirements. It won't accept HKLM as an abbreviation, you must type out 'Hkey_Local_Machine instead'. But once I fathomed that out, it does what it says on the tin. Well done.

Anonymous said...

Hi Franck,

I am looking for PS script to export HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\client into a file with a .reg extension. I need to be able to import that file to other machines.

Your script is the closest I have found but I try to test it and I am getting the follow error when creating the function in PS console. Can you please help me to understand what I am missing?

Split-Path : Cannot bind argument to parameter 'Path' because it is null.
At line:27 char:24
+ $strCurDir = Split-Path <<<< -parent $MyInvocation.MyCommand.Path
+ CategoryInfo : InvalidData: (:) [Split-Path], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.SplitPathCommand

The variable '$strCurDir' cannot be retrieved because it has not been set.
At line:28 char:24
+ Set-Location $strCurDir <<<< | Out-Null
+ CategoryInfo : InvalidOperation: (strCurDir:Token) [], RuntimeException
+ FullyQualifiedErrorId : VariableIsUndefined

Anonymous said...

This is awesome and saved me a boat load of time. Thank you so much. Btw, I echo Steve's comment. When I tried 'HKCU:\Software' etc, it complained that it wouldn't work. Using the full key works.

Here's the bug fix though:

ORIGINAL:
===========================
switch ($hKeyLeft) {
"HKEY_CLASSES_ROOT" { $hiveKey = "ClassesRoot"}
"HKEY_CURRENT_USER" { $hiveKey = "CurrentUser"}
"HKEY_LOCAL_MACHINE" { $hiveKey = "LocalMachine"}
"HKEY_USERS" { $hiveKey = "Users"}
"HHKEY_CURRENT_CONFIG" { $hiveKey = "CurrentConfig"}
default {
Write-Host "ERROR: you must use one of the following key: HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKCU, HKLM, HKU, HKCC"
Exit 1
}
}
===============================

BUG FIX (includes fixing the duplicate "H" for HKEY_CURRENT_CONFIG)

===============================
switch ($hKeyLeft) {
"HKEY_CLASSES_ROOT" { $hiveKey = "ClassesRoot"}
"HKEY_CURRENT_USER" { $hiveKey = "CurrentUser"}
"HKCU:" { $hiveKey = "CurrentUser"}
"HKEY_LOCAL_MACHINE" { $hiveKey = "LocalMachine"}
"HKLM:" { $hiveKey = "LocalMachine"}
"HKEY_USERS" { $hiveKey = "Users"}
"HKU:" { $hiveKey = "Users"}
"HKEY_CURRENT_CONFIG" { $hiveKey = "CurrentConfig"}
"HKCC:" { $hiveKey = "CurrentConfig"}
default {
Write-Host "ERROR: you must use one of the following key: HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKCU, HKLM, HKU, HKCC"
Exit 1
}
}
===============================

Unknown said...

While running the script I got this error:


Split-Path : Cannot bind argument to parameter 'Path' because it is null.
At line:27 char:33
+ $strCurDir = Split-Path -parent $MyInvocation.MyCommand.Path
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Split-Path], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.SplitPath
Command

The variable '$strCurDir' cannot be retrieved because it has not been set.
At line:28 char:14
+ Set-Location $strCurDir | Out-Null
+ ~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (strCurDir:String) [], RuntimeException
+ FullyQualifiedErrorId : VariableIsUndefined

Anonymous said...

Updated this to properly parameterize so that it can be called by Start-Job or Invoke-Command with arguments passed ex:

Start-Job -filepath "C:\Registry Backups\Reg Export Script.ps1" -ArgumentList $regkeytobackup, $filename, $computername -Name $computername

Had to leave out the bulk due to character limit... two key parts included below:

#
# Export registry to .reg file like regedit /e
#
# by F.Richard 2010-10
# Modified 2011-03 : bug correction in multistring format
#


# Working with Registry Entries
# http://technet.microsoft.com/en-us/library/dd315394.aspx

# [Enum]::GetNames([Microsoft.Win32.RegistryValueKind])
# -> Unknown / String / ExpandString / Binary / DWord / MultiString / QWord

# [Enum]::GetNames([Microsoft.Win32.RegistryHive])
# -> ClassesRoot / CurrentUser / LocalMachine / Users / PerformanceData / CurrentConfig / DynData

param(
[Parameter(Mandatory=$true)][string] $key = "",
[Parameter(Mandatory=$true)][string] $filename = "",
[Parameter(Mandatory=$false)][string] $computer = ""
)

set-psdebug -strict

.
.
.


#
# MAIN PROGRAM
#

# Verify Arguments Number
#If($Args.Count -lt 2) {
#Write-Host "Syntax:"$MyInvocation.MyCommand.Name "registry outputfilename [computername]"
#Write-Host " Example:"$MyInvocation.MyCommand.Name" 'HKCM:\Software\Microsoft\Windows\CurrentVersion\Run' 'outputfile.txt' "
#Write-Host " Example:"$MyInvocation.MyCommand.Name" 'HKEY_CURRENT_USER\Control Panel\Desktop' 'outputfile.txt' computername"
#Write-Host
#Break
#}

# Registry Path
$registrypath = $key #$Args[0]

# Output file
$outputfile = $filename #$Args[1]

# Computername
#if ($Args.Count -gt 2) {
#$Computername = $Args[2]
#} else {
#$Computername = $env:Computername
#}
if($computer -ne '')
{
$Computername = $computer
}
else
{
$Computername = $env:Computername
}
If ($debug) { Write-Host $Computername }


# Register registry functions
If (-not ("Win32Functions.Registry" -as [Type])) {
$type = Add-Type -MemberDefinition $signature -Name Registry -Namespace Win32Functions -Using System.Text -PassThru
} else {
If ($debug) { Write-Host "Win32Functions.Registry already Registered" }
}