Scripts/Sort-RegistryExport.ps1

<#
    .SYNOPSIS
    Lexically sorts the exported values for each registry key in a Windows Registry export
 
    .DESCRIPTION
    Exporting a registry key to a ".reg" file will typically not result in the exported values being lexically sorted.
 
    This command sorts the exported values under each registry key in a Windows Registry export in lexicographical order.
 
    .PARAMETER Path
    The path to a Windows Registry export which will have the values for each exported registry key sorted lexically.
 
    .EXAMPLE
    Sort-Registryexport -Path Export.reg
 
    Sorts the registry values lexically for each exported registry key in the Export.reg file.
 
    .NOTES
    Windows Registry export are expected to have a first line beginning with "Windows Registry Editor".
 
    Registry keys are not sorted (only their values), however, Windows built-in tools export keys in lexical order.
 
    .LINK
    https://github.com/ralish/PSWinGlue
#>


#Requires -Version 3.0

[CmdletBinding()]
[OutputType([Void])]
Param(
    [Parameter(Mandatory)]
    [String]$Path
)

$RegSignature = 'Windows Registry Editor'

try {
    $RegFile = Get-Item -Path $Path -ErrorAction Stop
} catch {
    throw $_
}

if ($RegFile -isnot [IO.FileInfo]) {
    throw 'Expected a file but received: {0}' -f $RegFile.GetType().Name
}

$RegFileSig = Get-Content -Path $RegFile -TotalCount 1
if ($RegFileSig -notmatch "^$RegSignature") {
    throw 'File does not begin with expected "{0}" signature: {1}' -f $RegSignature, $RegFile.Name
}

# Everything looks good so retrieve the complete file content
$RegFileContent = Get-Content -Path $RegFile

# List to hold the new file content starting with the signature
$RegNewContent = New-Object -TypeName 'Collections.Generic.List[String]'
$RegNewContent.Add($RegFileContent[0])

# List holding entries for the current registry key (INI section)
$RegKeyContent = New-Object -TypeName 'Collections.Generic.List[String]'

for ($Idx = 1; $Idx -lt $RegFileContent.Count; $Idx++) {
    $Line = $RegFileContent[$Idx]

    # Blank line (ignored)
    if ([String]::IsNullOrWhiteSpace($Line)) {
        continue
    }

    # Registry key
    if ($Line -match '^\[') {
        # Sort and append entries from the previous registry key
        if ($RegKeyContent.Count -ne 0) {
            $RegKeyContent.Sort()
            foreach ($Entry in $RegKeyContent) {
                $RegNewContent.Add($Entry)
            }

            $RegKeyContent.Clear()
        }

        $RegNewContent.Add([String]::Empty)
        $RegNewContent.Add($Line)
        continue
    }

    # Registry value
    if ($Line -match '^[@"]') {
        # Handle values where the data is split over multiple lines
        if ($Line -match '\\$') {
            do {
                $Idx++
                $ExtraLine = $RegFileContent[$Idx]
                $Line += '{0}{1}' -f [Environment]::NewLine, $ExtraLine
            } while ($ExtraLine -match '\\$')
        }

        $RegKeyContent.Add($Line)
        continue
    }

    throw 'Unexpected content on line {0} sorting registry file: {1}' -f ($Idx + 1), $RegFile.Name
}

# Add any values from the final registry key
if ($RegKeyContent.Count -ne 0) {
    $RegKeyContent.Sort()
    foreach ($Entry in $RegKeyContent) {
        $RegNewContent.Add($Entry)
    }
}

Set-Content -Path $RegFile -Value $RegNewContent