2
\$\begingroup\$

This is a PowerShell script I wrote a while ago that converts Windows Registry files to PowerShell commands(New-Item, Remove-Item, Set-ItemProperty and Remove-ItemProperty), it supports conversion of all six registry value types (REG_SZ, REG_DWORD, REG_QWORD, REG_BINARY, REG_EXPAND_SZ and REG_MULTI_SZ), and you got it right, I figured out how to correctly convert binary types.

You can view my original script here:https://superuser.com/a/1615077/1250181

Today I improved it, and this is the final version of my script, I used reg add to add REG_BINARY values because Set-ItemProperty can't add them, I used commas instead of "\0" because that's the correct way to add them with Set-ItemProperty, and Registry PSProvider does only provide 2 PSDrives by default: HKCU: and HKLM:, so if the registry files modifies other hives I added the lines to create them.

Please review my code(Tested on PowerShell 7, lower versions may not work):

Function reg2ps1 {

    [CmdLetBinding()]
    Param(
        [Parameter(ValueFromPipeline=$true, Mandatory=$true)]
        [Alias("FullName")]
        [string]$path,
        $Encoding = "utf8"
    )

    Begin {
        $hive = @{
            "HKEY_CLASSES_ROOT" = "HKCR:"
            "HKEY_CURRENT_CONFIG" = "HKCC:"
            "HKEY_CURRENT_USER" = "HKCU:"
            "HKEY_LOCAL_MACHINE" = "HKLM:"
            "HKEY_USERS" = "HKU:"
        }
        [system.boolean]$isfolder=$false
        $addedpath=@()
    }
    Process {
        switch (test-path $path -pathtype container)
        {
            $true {$files=(get-childitem -path $path -recurse -force -file -filter "*.reg").fullname;$isfolder=$true}
            $false {if($path.endswith(".reg")){$files=$path}}
        }
        foreach($File in $Files) {
            $Commands = @()
            if ($(Get-Content -Path $File -Raw) -match "HKEY_CLASSES_ROOT") {$commands+="New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT | Out-Null"}
            elseif ($(Get-Content -Path $File -Raw) -match "HKEY_CURRENT_CONFIG") {$commands+="New-PSDrive -Name HKCC -PSProvider Registry -Root HKEY_CURRENT_CONFIG | Out-Null"}
            elseif ($(Get-Content -Path $File -Raw) -match "HKEY_USERS") {$commands+="New-PSDrive -Name HKU -PSProvider Registry -Root HKEY_USERS | Out-Null"}
            [string]$text=$nul
            $FileContent = Get-Content $File | Where-Object {![string]::IsNullOrWhiteSpace($_)} | ForEach-Object { $_.Trim() }
            $joinedlines = @()
            for ($i=0;$i -lt $FileContent.count;$i++){
                if ($FileContent[$i].EndsWith("\")) {
                    $text=$text+($FileContent[$i] -replace "\\").trim()
                } else {
                    $joinedlines+=$text+$FileContent[$i]
                    [string]$text=$nul
                }
            }

            foreach ($joinedline in $joinedlines) {
                if ($joinedline -match '\[' -and $joinedline -match '\]' -and $joinedline -match 'HKEY') {
                    $key=$joinedline -replace '\[|\]'
                    switch ($key.StartsWith("-HKEY"))
                    {
                        $true {
                            $key=$key.substring(1,$key.length-1)
                            $hivename = $key.split('\')[0]
                            $key = "`"" + ($key -replace $hivename,$hive.$hivename) + "`""
                            $Commands += 'Remove-Item -Path {0} -Force -Recurse' -f $key
                        }
                        $false {
                            $hivename = $key.split('\')[0]
                            $key = "`"" + ($key -replace $hivename,$hive.$hivename) + "`""
                            if ($addedpath -notcontains $key) {
                                $Commands += 'New-Item -Path {0} -ErrorAction SilentlyContinue | Out-Null'-f $key
                                $addedpath+=$key
                            }
                        }
                    }
                }
                elseif ($joinedline -match "`"([^`"=]+)`"=") {
                    [System.Boolean]$delete=$false
                    [System.Boolean]$binary=$false
                    $name=($joinedline | select-string -pattern "`"([^`"=]+)`"").matches.value | select-object -first 1
                    switch ($joinedline)
                    {
                        {$joinedline -match "=-"} {$commands+=$Commands += 'Remove-ItemProperty -Path {0} -Name {1} -Force' -f $key, $Name;$delete=$true}
                        {$joinedline -match '"="'} {
                            $type="String"
                            $value=$joinedline -replace "`"([^`"=]+)`"="
                        }
                        {$joinedline -match "dword"} {
                            $type="Dword"
                            $value=$joinedline -replace "`"([^`"=]+)`"=dword:"
                            $value="0x"+$value
                        }
                        {$joinedline -match "qword"} {
                            $type="Qword"
                            $value=$joinedline -replace "`"([^`"=]+)`"=qword:"
                            $value="0x"+$value
                        }
                        {$joinedline -match "hex(\([2,7,b]\))?:"} {
                            $value=($joinedline -replace "`"[^`"=]+`"=hex(\([2,7,b]\))?:").split(",")
                            $hextype=($joinedline | select-string -pattern "hex(\([2,7,b]\))?").matches.value
                            switch ($hextype)
                            {
                                {$hextype -match 'hex(\([2,7])\)'} {
                                    $ValueEx='$value=for ($i=0;$i -lt $value.count;$i+=2) {if ($value[$i] -ne "00") {[string][char][int]("0x"+$value[$i])}'
                                    switch ($hextype)
                                    {
                                        'hex(2)' {$type="ExpandString"; invoke-expression $($ValueEx+'}')}
                                        'hex(7)' {$type="MultiString"; invoke-expression $($ValueEx+' else {","}}'); $value=0..$($value.count-3) | %{$value[$_]}}
                                    }
                                    $value=$value -join ""
                                    if ($type -eq "ExpandString") {$value='"'+$value+'"'}
                                }
                                'hex(b)' {
                                    $type="Qword"
                                    $value=for ($i=$value.count-1;$i -ge 0;$i--) {$value[$i]}
                                    $value='0x'+($value -join "").trimstart('0')
                                }
                                'hex' {
                                    $type="REG_BINARY"
                                    $value=$value -join ""
                                    $binary=$true
                                }
                            }
                        }
                    }
                    if ($delete -eq $false) {
                        switch ($binary)
                        {
                            $false {$line='Set-ItemProperty -Path {0} -Name {1} -Type {2} -Value {3}'}
                            $true {$line='Reg Add {0} /v {1} /t {2} /d {3} /f';$key=$key.replace(":\","\")}
                        }
                        $commands+=$line -f $key, $name, $type, $value
                    }
                }
                elseif ($joinedline -match "@=") {
                    $name='"(Default)"';$type='string';$value=$joinedline -replace '@='
                    $commands+='Set-ItemProperty -Path {0} -Name {1} -Type {2} -Value {3}' -f $key, $name, $type, $value
                }
            
            }
            $parent=split-path $file -parent
            $filename=[System.IO.Path]::GetFileNameWithoutExtension($file)
            $Commands | out-file -path "${parent}\${filename}_reg.ps1" -encoding $encoding
        }
        if ($isfolder -eq $true) {
            $allcommands=(get-childitem -path $path -recurse -force -file -filter "*_reg.ps1").fullname | where-object {$_ -notmatch "allcommands_reg"} | foreach-object {get-content $_}
            $allcommands | out-file -path "${path}\allcommands_reg.ps1" -encoding $encoding
        }
    }
}
$path = $args[0]
reg2ps1 $path

Usage example: .\reg2ps1.ps1 "path\to\reg.reg"

Sample Input:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\DPS]
"DelayedAutoStart"=dword:00000000
"Description"="@%systemroot%\\system32\\dps.dll,-501"
"DisplayName"="Diagnostic Policy Service"
"ErrorControl"=dword:00000001
"FailureActions"=hex:80,51,01,00,00,00,00,00,00,00,00,00,03,00,00,00,14,00,00,\
  00,01,00,00,00,c0,d4,01,00,01,00,00,00,e0,93,04,00,00,00,00,00,00,00,00,00
"ImagePath"=hex(2):25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,6f,00,\
  74,00,25,00,5c,00,53,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,73,\
  00,76,00,63,00,68,00,6f,00,73,00,74,00,2e,00,65,00,78,00,65,00,20,00,2d,00,\
  6b,00,20,00,4c,00,6f,00,63,00,61,00,6c,00,53,00,65,00,72,00,76,00,69,00,63,\
  00,65,00,4e,00,6f,00,4e,00,65,00,74,00,77,00,6f,00,72,00,6b,00,20,00,2d,00,\
  70,00,00,00
"ObjectName"="NT AUTHORITY\\LocalService"
"RequiredPrivileges"=hex(7):53,00,65,00,43,00,68,00,61,00,6e,00,67,00,65,00,4e,\
  00,6f,00,74,00,69,00,66,00,79,00,50,00,72,00,69,00,76,00,69,00,6c,00,65,00,\
  67,00,65,00,00,00,53,00,65,00,43,00,72,00,65,00,61,00,74,00,65,00,47,00,6c,\
  00,6f,00,62,00,61,00,6c,00,50,00,72,00,69,00,76,00,69,00,6c,00,65,00,67,00,\
  65,00,00,00,53,00,65,00,41,00,73,00,73,00,69,00,67,00,6e,00,50,00,72,00,69,\
  00,6d,00,61,00,72,00,79,00,54,00,6f,00,6b,00,65,00,6e,00,50,00,72,00,69,00,\
  76,00,69,00,6c,00,65,00,67,00,65,00,00,00,53,00,65,00,49,00,6d,00,70,00,65,\
  00,72,00,73,00,6f,00,6e,00,61,00,74,00,65,00,50,00,72,00,69,00,76,00,69,00,\
  6c,00,65,00,67,00,65,00,00,00,00,00
"ServiceSidType"=dword:00000003
"Start"=dword:00000003
"Type"=dword:00000020

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\DPS\Parameters]
"ServiceDll"=hex(2):25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,6f,\
  00,74,00,25,00,5c,00,73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,\
  64,00,70,00,73,00,2e,00,64,00,6c,00,6c,00,00,00
"ServiceDllUnloadOnStop"=dword:00000001
"ServiceMain"="ServiceMain"

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\DPS\Security]
"Security"=hex:01,00,14,80,8c,00,00,00,98,00,00,00,14,00,00,00,30,00,00,00,02,\
  00,1c,00,01,00,00,00,02,80,14,00,ff,01,0f,00,01,01,00,00,00,00,00,01,00,00,\
  00,00,02,00,5c,00,04,00,00,00,00,00,14,00,ff,01,0f,00,01,01,00,00,00,00,00,\
  05,12,00,00,00,00,00,18,00,ff,01,02,00,01,02,00,00,00,00,00,05,20,00,00,00,\
  20,02,00,00,00,00,14,00,8d,01,02,00,01,01,00,00,00,00,00,05,04,00,00,00,00,\
  00,14,00,8d,01,02,00,01,01,00,00,00,00,00,05,06,00,00,00,01,01,00,00,00,00,\
  00,05,12,00,00,00,01,01,00,00,00,00,00,05,12,00,00,00

Sample Output:

New-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Services\DPS" -ErrorAction SilentlyContinue | Out-Null
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\DPS" -Name "DelayedAutoStart" -Type Dword -Value 0x00000000
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\DPS" -Name "Description" -Type String -Value "@%systemroot%\\system32\\dps.dll,-501"
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\DPS" -Name "DisplayName" -Type String -Value "Diagnostic Policy Service"
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\DPS" -Name "ErrorControl" -Type Dword -Value 0x00000001
Reg Add "HKLM\SYSTEM\CurrentControlSet\Services\DPS" /v "FailureActions" /t REG_BINARY /d 805101000000000000000000030000001400000001000000c0d4010001000000e09304000000000000000000 /f
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\DPS" -Name "ImagePath" -Type ExpandString -Value "%SystemRoot%\System32\svchost.exe -k LocalServiceNoNetwork -p"
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\DPS" -Name "ObjectName" -Type String -Value "NT AUTHORITY\\LocalService"
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\DPS" -Name "RequiredPrivileges" -Type MultiString -Value SeChangeNotifyPrivilege,SeCreateGlobalPrivilege,SeAssignPrimaryTokenPrivilege,SeImpersonatePrivilege
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\DPS" -Name "ServiceSidType" -Type Dword -Value 0x00000003
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\DPS" -Name "Start" -Type Dword -Value 0x00000003
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\DPS" -Name "Type" -Type Dword -Value 0x00000020
New-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Services\DPS\Parameters" -ErrorAction SilentlyContinue | Out-Null
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\DPS\Parameters" -Name "ServiceDll" -Type ExpandString -Value "%SystemRoot%\system32\dps.dll"
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\DPS\Parameters" -Name "ServiceDllUnloadOnStop" -Type Dword -Value 0x00000001
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\DPS\Parameters" -Name "ServiceMain" -Type String -Value "ServiceMain"
New-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Services\DPS\Security" -ErrorAction SilentlyContinue | Out-Null
Reg Add "HKLM\SYSTEM\CurrentControlSet\Services\DPS\Security" /v "Security" /t REG_BINARY /d 010014808c00000098000000140000003000000002001c000100000002801400ff010f0001010000000000010000000002005c000400000000001400ff010f0001010000000000051200000000001800ff01020001020000000000052000000020020000000014008d010200010100000000000504000000000014008d010200010100000000000506000000010100000000000512000000010100000000000512000000 /f

Registry Editor View:

enter image description here

\$\endgroup\$
1
\$\begingroup\$

I don't use Windows much and have very little experience with the registry, so I have a hard time judging the functional parts. So most of my comments are about the code style.

  • While PowerShell is not very sensitive when it comes to case, convention matters, and so does readability.
    • If a variable has multiple words in it, don't make it all lowercase with no separator. $joinedLines is conventional, but both $joined_lines and ${joined-lines} are at least nicer than $joinedlines
    • Method names, property names and cmdlet flag names generally start with an uppercase letter. Select-String -pattern "...".matches.value works, but Select-String -Pattern "...".Matches.Value is more conventional.
    • But above all else, be consistent in terms of how you refer to each variable. $Commands and $commands are the same, sure, but if you pick one and and stick to it it gets easier to read.
  • You can get rid of some duplication by re-using the same code for all three New-PSDrive command additions thanks to your $hive variable (if you remove the colons):
foreach ($root in $hive) {
    if ((Get-Content -Path $file -Raw) -match $root) {
        if ((Get-PSDrive).Name -notcontains $hive[$root]) {
            $commands += "New-PSDrive -Name $($hive[$root]) -PSProvider Registry -Root $root"
        }
    }
}
  • Though if you do that, you'd have to replace $hive.$hiveName with something more like "$($hive[$hiveName]):" later on.
  • You have a few switch statements where the only cases are $true and $false. It'd probably be clearer to just make those into if statements.
  • You have a couple if ($someVariable -eq $true) and if ($someVariable -eq $false) -- you can just replace those with if ($someVariable) and if ( ! $someVariable)
  • At one point, you go $command+=$Command+="...", which might be intentional, but doesn't look like it. If it is, you should probably add a comment specifying that, and possibly spell the line out like $command = $command + $command + "...".
  • If you have multiple statements on a single line separated by ;, consider splitting it into multiple lines instead (for loop headers not included)
  • Code tends to look a lot more pleasant when statements and operators get some space to breathe. for ($i=0;$i -lt $FileContent.count;$i++){ works exactly the same as for ($i = 0; $i -lt $FileContent.count; $i++) {, but the second is less of a hassle to read.
  • As a Cmdlet, your reg2psi function should probably follow the Verb-Noun naming convention, with a name like ConvertFrom-RegistryFile perhaps?
  • I'm not sure creating the allcommands_reg.ps1 is your script's responsibility, and I'm also not sure it's well-behaved if you're working on a folder that contains a file called allcommands.reg. Might be best to just have your cmdlet create the individual *_reg.ps1 files and making a separate script for combining them.
  • The line joining removes all backslashes from lines that end with a backslash, which could be dangerous unless you know that a backslash will never appear in another position. Maybe that's the case (I don't know much about the Windows registry), but being explicit and replacing \\$ instead is probably best.
  • Also, when doing the line joining, you can just iterate over the lines directly rather than keeping track of an index:
foreach ($line in $fileContent) {
    if ($line.EndsWith("\")) {
        # stuff
    } else {
        # stuff
    }
}
  • Once the lines are joined...
    • I think that first series of matches can be reduced to just one like $joinedLine -match '^\[-?HKEY[^\]]*\]$'. But again, I don't have much registry experience, I might be missing something.
    • If instead of treating the key as starting with - you treat the line as starting with [- you might be able to get rid of some duplication:
$key = $joinedLine -replace '^\[-?|\]$'
$hiveName = $key.Split('\')[0]
$key = '"' + ($key -replace $hiveName, "$($hive[$hiveName]):") + '"'
if ($joinedLine -StartsWith '[-') {
    # Add a Remove-Item command
} else {
    # Maybe add a New-Item command
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.