WinPrefs.psm1
function ReplaceFullHiveNameWithShortName { param ( [Parameter(Mandatory, HelpMessage = "Registry path with long name and no drive syntax.")] [string] $Path) $_unused, $HkeyParts = $Path.ToUpper().Split('\')[0].Split('_') $Path -Replace '^HKEY_[^\\]+\\', "HK$($(foreach ($Item in $HkeyParts) { $Item[0] }) -Join ''):" } function GetFullHiveName { param ( [Parameter(Mandatory, HelpMessage = "Registry path.")] [ValidatePattern('^HK(LM|CU|CR|U|CC|PD):')] [string] $Path ) switch -Wildcard ($Path) { 'HKCC:*' { 'HKEY_CURRENT_CONFIG' } 'HKCR:*' { 'HKEY_CLASSES_ROOT' } 'HKCU:*' { 'HKEY_CURRENT_USER' } 'HKLM:*' { 'HKEY_LOCAL_MACHINE' } 'HKU:*' { 'HKEY_USERS' } default { throw } } } function GetRegType() { param ( [Parameter(Mandatory)] [AllowNull()] [ValidatePattern('^(Binary|(D|Q)Word|(Multi|Expand)String|String|None)')] $Value ) if ($null -eq $Value) { return 'REG_NONE' } switch ($Value) { 'Binary' { 'REG_BINARY' } 'DWord' { 'REG_DWORD' } 'ExpandString' { 'REG_EXPAND_SZ' } 'MultiString' { 'REG_MULTI_SZ' } 'None' { 'REG_NONE' } 'QWord' { 'REG_QWORD' } 'String' { 'REG_SZ' } default { throw "$Value" } } } function FixVParameter { param ( [Parameter(Mandatory)] [string] $Prop ) if ($Prop -eq '(default)') { '/ve ' } else { "/v ""$(Escape $Prop)"" " } } function Escape { param ( [Parameter(Mandatory)] [AllowNull()] [AllowEmptyString()] [string]$Value ) if ($null -eq $Value) { return "" } $Value -Replace '"', '""' -Replace '%', '%%' # -Replace "(`r)?`n", "!LF!" } function ConvertValueForReg { param ( [Parameter(Mandatory)] [ValidatePattern('^REG_(BINARY|(?:Q|D)WORD|(?:(?:EXPAND|MULTI)_)?SZ|NONE)')] [string] $RegType, [Parameter(Mandatory)] [AllowNull()] $Value ) if ($null -eq $RegType) { return " " } switch -Regex ($RegType) { '^REG_BINARY$' { " /d $($(for ($i = 0; $i -lt $Value.Length; $i++) { "{0:x2}" -f $i}) -Join '') " } '^REG_MULTI_SZ$' { " /d ""$(Escape $($Value -Join "`0"))"" " } '^REG_(?:EXPAND_)?SZ$' { " /d ""$(Escape $Value)"" " } '^REG_(?:Q|D)WORD$' { " /d $Value " } '^REG_NONE$' { " " } default { throw "$RegType" } } } function DoWriteRegCommand { param ( [Parameter(Mandatory)] [Microsoft.Win32.RegistryKey]$RegKeyObj, [Parameter(Mandatory)] [string]$Prop, [Parameter(Mandatory)] [string]$RegKey ) $GetValuePropArg = if ($Prop -eq '(default)') { $null } else { $Prop } $Value = $RegKeyObj.GetValue($GetValuePropArg, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) $RegProp = FixVParameter $Prop try { $ValueKind = $RegKeyObj.GetValueKind($GetValuePropArg).ToString() } catch { Write-Debug "Skipping $RegKey\$Prop. GetValueKind() failed." return } if ($ValueKind -eq 'Unknown') { Write-Debug "Skipping $RegKey\$Prop of unknown type." return } try { $RegType = GetRegType $ValueKind } catch { Write-Debug "Unable to determine registry type: RegKeyObj = $RegKeyObj, Prop = $Prop" return } if ($Value -match "(`r)?`n") { Write-Debug "Skipping $RegKeyObj $Prop because it contains newlines." return } $RegValue = ConvertValueForReg -RegType $RegType -Value $Value "reg add ""$(Escape $RegKey)"" $RegProp/t $RegType$RegValue/f" } function DoWriteRegCommands { param ( [Parameter(Mandatory, HelpMessage = "Registry path.")] [ValidatePattern('^HK(LM|CU|CR|U|CC):')] [string]$Path ) $Hive = switch -Wildcard ($Path) { 'HKCC:*' { [Microsoft.Win32.Registry]::CurrentConfig } 'HKCR:*' { [Microsoft.Win32.Registry]::ClassesRoot } 'HKCU:*' { [Microsoft.Win32.Registry]::CurrentUser } 'HKLM:*' { [Microsoft.Win32.Registry]::LocalMachine } 'HKPD:*' { [Microsoft.Win32.Registry]::PerformanceData } 'HKU:*' { [Microsoft.Win32.Registry]::Users } default { throw } } $PathWithoutPrefix = $Path -Replace '^HK(LM|CU|CR|U|CC):', '' $RegKey = $Path -Replace ':', '\' -Replace '\\\\', '\' $RegKeyObj = $Hive.OpenSubKey($PathWithoutPrefix.TrimStart('\')) foreach ($Prop in $(Get-Item -ErrorAction SilentlyContinue $Path | Select-Object -ExpandProperty Property)) { DoWriteRegCommand $RegKeyObj $Prop $RegKey } } $LIMIT = 100 $SKIP_RE = '(^HK..:.*\\CurrentVersion\\Explorer\\.*MRU.*)|(\\\*$)|(.*\\Shell\\Bags\\[0-9]+\\Shell\\\{.*)' function DoWriteRegCommandsRecursive { param ( [Parameter(Mandatory, HelpMessage = "Registry path.")] [ValidatePattern('^^HK(LM|CU|CR|U|CC):')] [string]$Path, [Parameter(HelpMessage = "Current depth level. Used internally.")] [int]$Depth) if ($Depth -ge $LIMIT) { Write-Debug "Skipping $Path due to depth limit." return } if ($Path -match $SKIP_RE) { Write-Debug "Skipping $Path because it matched the skip RE." continue } try { # SilentlyContinue is needed to skip HKLM\SECURITY $Items = Get-ChildItem -ErrorAction SilentlyContinue -Path $Path } catch { Write-Debug "Skipping $Path. Does the location exist?" return } if (!$Items) { $out = DoWriteRegCommands $(ReplaceFullHiveNameWithShortName $Path) if (!$out) { # Assume it is a full path to a value $Hive = switch -Wildcard ($Path) { 'HKCC:*' { [Microsoft.Win32.Registry]::CurrentConfig } 'HKCR:*' { [Microsoft.Win32.Registry]::ClassesRoot } 'HKCU:*' { [Microsoft.Win32.Registry]::CurrentUser } 'HKLM:*' { [Microsoft.Win32.Registry]::LocalMachine } 'HKPD:*' { [Microsoft.Win32.Registry]::PerformanceData } 'HKU:*' { [Microsoft.Win32.Registry]::Users } default { throw } } $Components = $($Path -Replace '^HK(LM|CU|CR|U|CC):', '').TrimStart('\').Split('\') $RegKeyObj = $Hive.OpenSubKey($($Components[0..($Components.Length - 2)] -Join '\')) DoWriteRegCommand $RegKeyObj $($Path.Split('\')[-1]) $($Path -Replace ':', '') } else { Write-Output $out } return } foreach ($Item in $Items) { $ItemStr = $Item.ToString() $PathShort = ReplaceFullHiveNameWithShortName $ItemStr try { $Children = Get-ChildItem -Path $PathShort -ErrorAction SilentlyContinue } catch { Write-Debug "Skipping $Path because Get-ChildItem failed." continue } if ($Children) { DoWriteRegCommandsRecursive -Path $PathShort -Depth $($Depth + 1) } else { DoWriteRegCommands -Path $PathShort } } } <# .SYNOPSIS Convert a registry path to a series of reg commands for copying into a script. By default only HKCU: and HKLM: are mounted in PowerShell. Others need to be mounted and must be under the appropriate name such as HKU for HKEY_USERS. Keys are skipped under these conditions: - Recursion limit (100) - Value contains newlines - Key that cannot be read for any reason such as permissions. An example of an always skipped key under normal circumstances is HKLM\SECURITY, even if this is run as administrator. WARNING: If you save an entire tree such as HKLM to a file and attempt to run said script, you probably will break your OS. The output of this tool is meant for getting a single command at time, testing it, and then using it in an appropriate script. The author will not be held responsible for any damages. .EXAMPLE # Dump reg commands for desktop settings. Write-RegCommands 'HKCU:\Control Panel\Desktop' # Use the alias. prefs-export 'HKCU:\Control Panel\Desktop' # Dump the entire HKLM (note skipped keys above) and save to a script prefs-export HKLM: > hklm.bat #> function Write-RegCommands { param ( [Parameter(Mandatory, HelpMessage = "Registry path.")] [ValidatePattern('^^HK(LM|CU|CR|U|CC):')] [string]$Path ) # Write-Output "setlocal EnableDelayedExpansion" # Write-Output "(set LF=^" # Write-Output "%=EMPTY=%" # Write-Output ")" DoWriteRegCommandsRecursive $Path } Set-Alias -Name prefs-export -Value Write-RegCommands Export-ModuleMember -Function Write-RegCommands -Alias prefs-export |