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

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