6

I am a tinkerer who does many registry hacks and I hate having to click many .reg files one by one; how do I convert .reg files to PowerShell Set-ItemProperty commands automatically?

  • I found a site that does so: Registry to PowerShell Converter. However the output isn't in the format I wanted; I want it to have the exact same syntax as below using Set-ItemProperty/Remove-Item/New-Item and nothing else:

    Windows Registry Editor Version 5.00
    
    [HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\CurrentVersion\PushNotifications]
    "NoToastApplicationNotification"=dword:00000001
    
    • cmd
      Reg Add "HKLM\Software\Policies\Microsoft\Windows\CurrentVersion\PushNotifications" /v "NoToastApplicationNotification" /t REG_DWORD /d 1
      
    • powershell
      Set-ItemProperty -Path "HKLM:\Software\Policies\Microsoft\Windows\CurrentVersion\PushNotifications" -Name "NoToastApplicationNotification" -Type DWord -Value 1
      
  • The command to produce intended result should be:

    "Set-ItemProperty -Path " + $path + "-Name " + $name + "-Value " + $value
    
    • I have created an ASCII table with information found here and uploaded here, managing this [output]:

      $ASCII=import-csv ".\desktop\ascii.csv"
      [array]$AsciiTable=0..255 | foreach-object{
        $Decimal=$ASCII[$_].DEC
        $Hexadecimal=$ASCII[$_].HEX
        $Binary=$ASCII[$_].BIN
        $Octonary=$ASCII[$_].OCT
        $Symbol=$ASCII[$_].Symbol
        $Value=[char]$_
        $Description=$ASCII[$_].Description
        $HTMLName=$ASCII[$_].HTMLName
        $HTMLNumber=$ASCII[$_].HTMLNumber
        [pscustomobject]@{Decimal=$Decimal;Hexadecimal=$Hexadecimal;Binary=$Binary;Octonary=$Octonary;Symbol=$Symbol;Value=$Value;Description=$Description;HTMLName=$HTMLName;HTMLNumber=$HTMLNumber}
      }
      $AsciiTable | Export-csv ".\Desktop\AsciiTable.csv"
      

Currently, I have managed this, which is incomplete, but the idea is to loop through file by index, assigning values to variables via regex match, changing type and hivename to ones used in PowerShell:

$registry=get-content $regfile

for ($i=0;$i -lt $registry.count;$i++){
  $line=$registry | select-object -index $i
  if ($line -match '\[' -and '\]') {
    $path=$line -replace '\[|\]'
    switch ($path)
    {
      {$path -match "HKEY_CLASSES_ROOT"}    {$path=$path -replace "HKEY_CLASSES_ROOT","HKCR:"}
      {$path -match "HKEY_CURRENT_USER"}    {$path=$path -replace "HKEY_CURRENT_USER","HKCU:"}
      {$path -match "HKEY_LOCAL_MACHINE"}   {$path=$path -replace "HKEY_LOCAL_MACHINE","HKLM:"}
      {$path -match "HKEY_USERS"}           {$path=$path -replace "HKEY_USERS","HKU:"}
      {$path -match "HKEY_CURRENT_CONFIG"}  {$path=$path -replace "HKEY_CURRENT_CONFIG","HKCC:"}
    }
  }
  else {
    $name=($line | select-string -pattern "`"([^`"=]+)`"").matches.value | select-object -first 1
    switch ($line)
  {
  {$line -match}
}

There are six registry value types [REG_SZ, REG_BINARY, REG_DWORD, REG_QWORD, REG_MULTI_SZ, REG_EXPAND_SZ] and I have seen only one DWORD value type in .reg files, although I managed to make a registry key containing all types:

  • RegEdit

    enter image description here

  • .reg
    Windows Registry Editor Version 5.00
    
    [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\AarSvc]
    "DependOnService"=hex(7):41,00,75,00,64,00,69,00,6f,00,73,00,72,00,76,00,00,00,\
      00,00
    "Description"="@%SystemRoot%\\system32\\AarSvc.dll,-101"
    "DisplayName"="@%SystemRoot%\\system32\\AarSvc.dll,-100"
    "ErrorControl"=dword:00000001
    "FailureActions"=hex:80,51,01,00,00,00,00,00,00,00,00,00,04,00,00,00,14,00,00,\
      00,01,00,00,00,10,27,00,00,01,00,00,00,10,27,00,00,01,00,00,00,10,27,00,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,73,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,41,00,61,00,72,00,53,00,76,00,63,00,47,00,72,00,6f,00,75,00,70,\
      00,20,00,2d,00,70,00,00,00
    "ObjectName"="NT Authority\\LocalService"
    "RequiredPrivileges"=hex(7):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:00000001
    "Start"=dword:00000003
    "Type"=dword:00000060
    "UserServiceFlags"=dword:00000003
    "New Value #1"=hex(b):00,00,00,00,00,00,00,00
    
    [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\AarSvc\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,53,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,\
      41,00,61,00,72,00,53,00,76,00,63,00,2e,00,64,00,6c,00,6c,00,00,00
    "ServiceDllUnloadOnStop"=dword:00000001
    "ServiceMain"="ServiceMain"
    

How are registry types determined within a .reg, as the intended end-result is a text file/string array/PowerShell script that contains the converted commands?

  • In a .reg, I know values for type REG_DWORD is written as dword, REG_SZ as plain text enclosed in quotes, REG_QWORD as qword (shown here), and have already mapped registry types to their corresponding PowerShell properties:

    Type Written
    REG_SZ String
    REG_EXPAND_SZ ExpandString
    REG_MULTI_SZ MultiString
    REG_BINARY Binary
    REG_DWORD DWord
    REG_QWORD QWord

    With the relations inferred from above:

    switch ($line)
    {
      {$line -match '"="'}      {$type="string"}
      {$line -match "dword"}    {$type="dword"}
      {$line -match "qword"}    {$type="qword"}
      {$line -match "hex\(2\)"} {$type="expandstring";break}
      {$line -match "hex\(7\)"} {$type="multistring";break}
      {$line -match "hex\(b\)"} {$type="qword";break}
      {$line -match "hex"}      {$type="binary"}
    }
    

How can I detect and decode the registry hex babbles and are there other ways to write REG_EXPAND_SZ, REG_MULTI_SZ, and REG_BINARY types in a .reg (i.e. as ExpandString, MultiString and Binary respectively)?

  • Script to parse registry expandable string values to plain text:

    function parse-expandstring {
      PARAM (
        [Parameter(ValueFromPipeline=$true, Mandatory=$true)] [System.String]$expandstring
      )
    
      $AsciiTable=import-csv ".\desktop\AsciiTable.csv"
      [array]$hex=$expandstring -split'[\,\\]' | where {-not ([string]::IsNullOrWhiteSpace($_))} | %{$_.trimstart()}
      $hexadecimal=0..($hex.count-1) | where {$_ % 2 -ne 1} | foreach-object {$hex[$_]}
      $text=@()
      foreach ($hexadecima in $hexadecimal) {
        for ($i=0;$i -le 255;$i++) {
          if ($AsciiTable[$i].hexadecimal -eq $hexadecima) {
            $text+=$AsciiTable[$i].value
          }
        }
      }
      $text=$text -join ""
      $text
    }
    
  • Function to parse REG_QWORD:

    function parse-qword {
      PARAM (
        [Parameter(ValueFromPipeline=$true, Mandatory=$true)] [System.String]$qword
      )
      [array]$qword=$qword -split','
      $qword=for ($i=$qword.count-1;$i -ge 0;$i--) {$qword[$i]}
      $hexvalue=$qword -join ""
      $hexvalue=$hexvalue.trimstart("0")
      $hexvalue
    }
    
  • Function to parse REG_BINARY:

    function parse-binary {
      PARAM (
        [Parameter(ValueFromPipeline=$true, Mandatory=$true)] [System.String]$binary
      )
      [array]$hex=$binary -split'[,\\]' | where {-not ([string]::IsNullOrWhiteSpace($_))} | %{$_.trimstart()}
      $hex=$hex -join ""
        $hex
    }
    
  • Function to parse REG_MULTI_SZ:

    function parse-multistring {
      PARAM (
        [Parameter(ValueFromPipeline=$true, Mandatory=$true)] [System.String]$multistring
        )
    
      $AsciiTable=import-csv ".\desktop\AsciiTable.csv"
      [array]$hex=$multistring -split'[\,\\]' | where {-not ([string]::IsNullOrWhiteSpace($_))} | %{$_.trimstart()}
      $hexadecimal=0..($hex.count-1) | where {$_ % 2 -ne 1} | foreach-object {$hex[$_]}
      $text=@()
      foreach ($hexadecima in $hexadecimal) {
        for ($i=0;$i -le 255;$i++) {
          if ($AsciiTable[$i].hexadecimal -eq $hexadecima) {
            if ($i -ne 0) {$text+=$AsciiTable[$i].value}
            else {$text+="\0"}
          }
        }
      }
      $text=$text -join ""
      $text
    }
    

The script is almost complete, having already created Remove-Item, New-Item, and Remove-ItemProperty switch conditions; now, the final piece of the puzzle is to write a regex that matches the values. When this is done, I will post it as an answer here.

  • Pseudo-code
    if $line match [ and ]->$line match [-HKEY -> Remove-Item
    else $registry[$i+1] eq ""->New-Item
    elseif $line match "=-" -> Remove-ItemProperty
    
  • I created an ASCII hashtable to use as a dictionary:

    $asciihex=@{}
    0..255 | % {
      $number=$_
      [string]$hex=$number.tostring('x')
      if ($hex.length -eq 1) {$hex='{1}{0}' -f $hex,'0'}
      $char=[char]$number
      $asciihex.add($hex,$char)
      }
    
    • To look for a character at a given codepoint:

      # Change:
        $asciihex.'00'
      
      # to:
        $asciihex.'ff'
      
    • To look for a character at any codepoint:

      # Don't use $asciihex to print it
        $asciihex.$codepoint
      

To grep values from lines, use -replace $name+$type to get the value.

11
  • For registry value type determination, please see RegistryValueKind Enum.
    – JW0914
    Commented Jan 3, 2021 at 13:52
  • For the basics on updating registry values in PS, here is an old article from The Scripting Guy: devblogs.microsoft.com/scripting/… Commented Jan 3, 2021 at 14:25
  • you could also just put all the reg files in a directory, and have a script or function that you call that adds all reg files in that location to the registry. probably much easier and more errorproof
    – Balthazar
    Commented Jan 3, 2021 at 16:44
  • That's exactly what I am trying to avoid, because I want to put all commands in a single .ps1 file, and .reg files can be hard to edit, if it is a .ps1 file with one command each line, editting it would be simpler. Commented Jan 3, 2021 at 16:53
  • Does this script do what you want? github.com/milesgratz/Convert-Reg/blob/master/Convert-Reg.ps1
    – SimonS
    Commented Jan 4, 2021 at 5:54

4 Answers 4

3

Final version:

Function reg2ps1 {

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

    Begin {
        $hive = @{
            "HKEY_CLASSES_ROOT" = "HKCR:"
            "HKEY_CURRENT_USER" = "HKCU:"
            "HKEY_LOCAL_MACHINE" = "HKLM:"
            "HKEY_USERS" = "HKU:"
            "HKEY_CURRENT_CONFIG" = "HKCC:"
        }
        [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 = @()
            [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
                    $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 -eq 'hex(2)' -or $hextype -eq 'hex(7)'} {
                                    $value=for ($i=0;$i -lt $value.count;$i+=2) {
                                        switch ($hextype)
                                        {
                                            'hex(2)' {if ($value[$i] -ne '00') {[string][char][int]('0x'+$value[$i])}}
                                            'hex(7)' {if ($value[$i] -ne '00') {[string][char][int]('0x'+$value[$i])} else {"\0"}}
                                        }
                                    }
                                    $value=$value -join ""
                                    switch ($hextype)
                                    {
                                        'hex(2)' {$type="expandstring"}
                                        'hex(7)' {$type="multistring"}
                                    }
                                }
                                'hex(b)' {
                                    $type="qword"
                                    $value=for ($i=$value.count-1;$i -ge 0;$i--) {$value[$i]}
                                    $value='0x'+($value -join "").trimstart('0')
                                }
                                'hex' {
                                    $type="binary"
                                    $value='0x'+($value -join "")
                                }
                            }
                        }
                    }
                    if ($delete -eq $false) {$commands+='Set-ItemProperty -Path {0} -Name {1} -Type {2} -Value {3}' -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 -FilePath "${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 -FilePath "${path}\allcommands_reg.ps1" -encoding $encoding
        }
    }
}
$path = Read-Host "input path"
reg2ps1 $path

This is the final version, based on my previous script and the script provided by SimonS. The script is truly complete, all bugs fixed, it can correctly parse all 6 registry value types: REG_SZ, REG_DWORD, REG_QWORD, REG_BINARY, REG_MULTI_SZ and REG_EXPAND_SZ, converts each [HKEY_* line to a New-Item line, each [-HKEY_* to a Remove-Item line, each "([^"=]+)"=- line to a Remove-ItemProperty line, and each "([^"=]+)"= line to an appropriate Set-ItemProperty line based on the property type. It accepts an inputted path, automatically detect whether the path points to a file or a folder, if a file with extension of .reg, it outputs converted commands to a file at the parent folder of the file with ${filename}_reg.ps1 as its filename; If a folder, converts all .reg files inside that folder, and outputs a ${filename}_reg.ps1 file for each .reg file to that folder, and then put all _reg.ps1 commands into one allcommands.ps1 in the folder.

I made numerous tests and have confirmed it is really really working. The script is now complete. I made major improvements, used better formats, and simplified the code greatly, used better logics and made many other enhancements.

This is truly complete, to use my final version, copy paste the function into an opened powershell window, and invoke it like reg2ps1 "full\path\to\content" or save it as a .ps1 file and run it by cd $scriptdir and .\reg2ps1.ps1, then input full\path\to\content, notice you shouldn't use any quotes, or the path can't be found...


Update

I made a mistake in the code, by specifying the -Force parameter when using New-Item, if the item already exists, it will re-create the item, emptying the item in the process, this is not what I intended, now it's fixed. By removing -Force parameter in the New-Item line, trying to create an item that already exists will generate an error that tells the item exists, and will not reset the item. The error message is hidden by -ErrorAction SilentlyContinue. If the item doesn't exist, it will be created, the item will be empty, the process will prompt a message that tells the item is created, the message is hidden by | Out-Null.

1
  • Thanks, this is a godsend
    – Yorai Levi
    Commented Oct 18, 2022 at 19:21
1

This is pretty good, but you have some errors in your conversion code.

  1. In reg value text that contains escape characters like "cmd.exe /s /k pushd "%V"" your conversion doesn't replace " with `" or similar.
  2. You need New-ItemProperty rather than Set-ItemProperty when using -Type property.
  3. PowerShell cannot work with "HKCR:" etc. without setting a drive first.
  4. Out-File path parameter is -FilePath

Probably worth fixing

1

According to Microsoft reference, there is no -path parameter in the syntax of Out-File command on line 125, so you need to replace it with the correct one -FilePath:

Change this line:

$Commands | Out-File -Path "${parent}\${filename}_reg.ps1" -encoding $encoding

To:

$Commands | Out-File -FilePath "${parent}\${filename}_reg.ps1" -encoding $encoding

Also, you might want to check your code with these sample registry entries before publishing your "Final version":

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\MyNewKey]
"Value01"=dword:00000a00
"Value02"=hex:c4,ff,cc,ff,00,00,00,00,e2,ff,bb,ff,00,00,09,00,01,00,03,00,27,00,\
  55,00,2e,00,e7,03,00,00,03,00,00,00,03,00,c4,00,00,00,30,00,00,00
"Value03"="@tzres.dll,-243"
"Value04"=hex:00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00
"Value05"=dword:ffffff2e
  
[HKEY_CURRENT_USER\SOFTWARE\MyNewKey]
"Value06"="1445855232"

[HKEY_USERS\S-1-5-18\Software\MyNewKey]
"Value07"=dword:00000000

It will be a good idea to test the code performance with these cases. I have found many errors on converting...

The error codes appear when try to run test_reg.ps1 file which converted from test.reg

4
  • Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
    – Community Bot
    Commented May 21, 2023 at 11:51
  • Actually this is not a bug of my program. I have tested your example and it works fine on both Windows PowerShell and PowerShell 7. I tested on 64 bit Windows 10 though, and I haven't bothered to test it on 32 bit older Windows virtual machines, so I can't ascertain the cause of the error. So either you used an older version of Windows or a 32 bit Windows or both, and according to the error, it seems extremely likely you are using a 32-bit Windows. The error is not caused by a bug in my program but rather your system's limitation. Does it work when you double click the registry file? Commented May 21, 2023 at 13:15
  • I have tried to convert that sample reg code on Windows 10x64 Version 22H2 (10.0.19045.2364) and in Windows PowerShell by using your code which I faced on those red error messages. How might your test result be good!
    – Bobbiz
    Commented May 22, 2023 at 12:25
  • @ΞένηΓήινος For your convenience for investigation, I share the content of the converted ps1 here: https://paste.ee/d/gaEoo
    – Bobbiz
    Commented May 22, 2023 at 12:37
-1

As github.com/rzander/Reg2CI/blob/master/README.md explains, https://reg2ps.azurewebsites.net provides an input form where the user can enter .reg code. A button converts it to .PS1 code.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .