Skip to content

PowerShell Comparing Registry Keys

How to detect registry configuration drift, audit security compliance, and compare registry key differences in PowerShell using Compare-Object and custom scripts.

Comparing registry keys in PowerShell involves evaluating differences in subkey structures, property names, and data values between two registry paths or point-in-time snapshots. The Compare-Object cmdlet provides an automated mechanism to detect configuration drift, audit security compliance against a baseline, and verify registry changes after software deployments. Since Get-ItemProperty returns registry values as object properties, the two work naturally together. However, direct comparisons need careful handling to avoid interference from PowerShell's built-in metadata properties.

Comparing Property Names with Compare-Object

Every registry key object retrieved via Get-Item exposes a .Property collection containing the names of all registry values defined within that key. You can pass these collections directly into Compare-Object to identify which value names exist in one key but not another.

compare-registry-properties.ps1powershell
$baselineKey = "HKCU:\Software\CorpApp\Baseline"
$targetKey   = "HKCU:\Software\CorpApp\Current"

New-Item -Path $baselineKey, $targetKey -Force | Out-Null
Set-ItemProperty -Path $baselineKey -Name "Timeout" -Value 30
Set-ItemProperty -Path $baselineKey -Name "Retries" -Value 3
Set-ItemProperty -Path $baselineKey -Name "LegacyMode" -Value 0

Set-ItemProperty -Path $targetKey -Name "Timeout" -Value 60
Set-ItemProperty -Path $targetKey -Name "Retries" -Value 3
Set-ItemProperty -Path $targetKey -Name "DebugLevel" -Value 2

Compare-Object -ReferenceObject (Get-Item $baselineKey).Property `
               -DifferenceObject (Get-Item $targetKey).Property

where:

  • Compare-Object compares two collections of objects and outputs the differences.
  • -ReferenceObject is the baseline or source collection against which comparison is made.
  • -DifferenceObject is the current or target collection being evaluated.
  • (Get-Item $baselineKey).Property is a string array containing the names of all registry values present in the baseline key.
  • (Get-Item $targetKey).Property is a string array containing the names of all registry values present in the target key.
Consolepowershell
./compare-registry-properties.ps1
Outputplaintext
InputObject SideIndicator
----------- -------------
DebugLevel  =>
LegacyMode  <=

Comparing Specific Properties and Values

Comparing two full Get-ItemProperty results directly often yields no useful output. This happens because Compare-Object compares entire objects by default. However, the built-in metadata properties like PSPath and PSParentPath will always differ between two distinct registry paths. As a result, these differences obscure the actual value variations you're trying to identify.

To get meaningful results, compare only the relevant value names, excluding PowerShell's registry metadata properties.

compare-registry-values.ps1powershell
$current = Get-ItemProperty -Path "HKCU:\Software\MyApp" | Select-Object -Property * -ExcludeProperty PS*
$backup  = Get-ItemProperty -Path "HKCU:\Software\MyApp_Backup" | Select-Object -Property * -ExcludeProperty PS*

$currentProps = $current.PSObject.Properties.Name
$backupProps  = $backup.PSObject.Properties.Name

foreach ($prop in $currentProps) {
    if ($current.$prop -ne $backup.$prop) {
        [PSCustomObject]@{
            Property     = $prop
            CurrentValue = $current.$prop
            BackupValue  = $backup.$prop
        }
    }
}

where:

  • -ExcludeProperty PS* strips out PowerShell's added metadata properties (PSPath, PSParentPath, PSChildName, PSProvider), leaving only actual registry values.
  • .PSObject.Properties.Name retrieves the list of property names dynamically from the object.
  • The foreach loop manually compares each matching property and reports only the ones that differ.
Consolepowershell
./compare-registry-values.ps1
Outputplaintext
Property     CurrentValue                 BackupValue
--------     ------------                 -----------
MaxRetries   5                            3

Comparing Value Data and Types

Comparing .Property collections checks only whether value names match. In administration workflows, you must also compare the underlying data payloads and data types (e.g., verifying whether a DWORD value changed from 0 to 1).

To compare both names and data simultaneously, construct custom objects containing the value name, data payload, and registry type for each entry.

compare-registry-values-advanced.ps1powershell
function Get-RegistryValueDetails {
    param([string]$KeyPath)

    $key = Get-Item -Path $KeyPath
    foreach ($valName in $key.Property) {
        [PSCustomObject]@{
            Name  = $valName
            Value = $key.GetValue($valName)
            Type  = $key.GetValueKind($valName)
        }
    }
}

$refValues  = Get-RegistryValueDetails -KeyPath "HKCU:\Software\CorpApp\Baseline"
$diffValues = Get-RegistryValueDetails -KeyPath "HKCU:\Software\CorpApp\Current"

Compare-Object -ReferenceObject $refValues `
               -DifferenceObject $diffValues `
               -Property Name, Value `
               -PassThru | Select-Object -Property Name, Value, SideIndicator

where:

  • Get-RegistryValueDetails is a helper function extracting names, data payloads, and types from a key.
  • $key.GetValueKind($valName) is a .NET method that retrieves the specific registry type (such as DWord, String, or Binary).
  • -Property Name, Value directs Compare-Object to evaluate differences based on both the value name and its contents.
  • -PassThru passes the original custom objects through to the pipeline with the appended SideIndicator property.
Consolepowershell
./compare-registry-values-advanced.ps1
Outputplaintext
Name       Value SideIndicator
----       ----- -------------
Timeout       60 =>
DebugLevel     2 =>
Timeout       30 <=
LegacyMode     0 <=

Comparing Just the Value Names Present

Sometimes the goal isn't comparing values, but checking whether the same set of value names exists in both keys (e.g., detecting a missing setting after an update).

Consolepowershell
Compare-Object -ReferenceObject $currentProps -DifferenceObject $backupProps
Outputplaintext
InputObject   SideIndicator
-----------   -------------
Debug         <=

where the SideIndicator <= means the property exists only in the reference (current) object and => means it exists only in the difference (backup) object.

Practical Usage: Auditing Configuration Drift

A common admin scenario is verifying that a registry key on a workstation still matches an approved baseline, flagging any unauthorized changes.

audit-registry-drift-simple.ps1powershell
param(
    [string]$LiveKeyPath,
    [string]$BaselineKeyPath
)

$live     = Get-ItemProperty -Path $LiveKeyPath | Select-Object -Property * -ExcludeProperty PS*
$baseline = Get-ItemProperty -Path $BaselineKeyPath | Select-Object -Property * -ExcludeProperty PS*

$allProps = ($live.PSObject.Properties.Name + $baseline.PSObject.Properties.Name) | Select-Object -Unique

$drift = foreach ($prop in $allProps) {
    $liveValue     = $live.$prop
    $baselineValue = $baseline.$prop

    if ($liveValue -ne $baselineValue) {
        [PSCustomObject]@{
            Property = $prop
            Live     = $liveValue
            Baseline = $baselineValue
        }
    }
}

if ($drift) {
    Write-Output "Configuration drift detected:"
    $drift | Format-Table -AutoSize
} else {
    Write-Output "No drift detected. Configuration matches baseline."
}

where:

  • $allProps a combined, de-duplicated list of value names from both keys, ensuring properties unique to either side are checked too.
  • $drift collects each mismatched property into a report; remains $null if no differences are found.
Consolepowershell
./audit-registry-drift-simple.ps1 -LiveKeyPath "HKCU:\Software\MyApp" -BaselineKeyPath "HKCU:\Software\MyApp_Backup"
Outputplaintext
Configuration drift detected:

Property     Live    Baseline
--------     ----    --------
MaxRetries   5       3

Advanced Drift Detection with Missing/Extra/Modified Classification

In enterprise compliance, servers must adhere to standardized security baselines. The following script audits a production registry key against a reference baseline and exports an audit report detailing missing, modified, and extraneous settings.

audit-registry-drift-advanced.ps1powershell
param (
    [string]$BaselineKey = "HKCU:\Software\CorpApp\Baseline",
    [string]$TargetKey   = "HKCU:\Software\CorpApp\Current"
)

if (-not (Test-Path $BaselineKey) -or -not (Test-Path $TargetKey)) {
    Write-Error "One or both registry paths do not exist. Audit aborted."
    return
}

function Get-KeySnapshot ($Path) {
    $item = Get-Item -Path $Path
    $hash = @{}
    foreach ($prop in $item.Property) {
        $hash[$prop] = [string]$item.GetValue($prop)
    }
    return $hash
}

$baseline = Get-KeySnapshot -Path $BaselineKey
$current  = Get-KeySnapshot -Path $TargetKey

$auditResults = [System.Collections.Generic.List[PSCustomObject]]::new()

foreach ($name in $baseline.Keys) {
    if (-not $current.ContainsKey($name)) {
        $auditResults.Add([PSCustomObject]@{
            Setting = $name
            Status  = "Missing from Current"
            Expected= $baseline[$name]
            Actual  = "N/A"
        })
    }
    elseif ($baseline[$name] -ne $current[$name]) {
        $auditResults.Add([PSCustomObject]@{
            Setting = $name
            Status  = "Value Mismatch / Drift"
            Expected= $baseline[$name]
            Actual  = $current[$name]
        })
    }
}

foreach ($name in $current.Keys) {
    if (-not $baseline.ContainsKey($name)) {
        $auditResults.Add([PSCustomObject]@{
            Setting = $name
            Status  = "Unauthorized Extra Setting"
            Expected= "N/A"
            Actual  = $current[$name]
        })
    }
}

$auditResults | Format-Table -AutoSize

where:

  • Get-KeySnapshot converts the values and data of a registry key into a hash table for fast key-based lookups.
  • [System.Collections.Generic.List[PSCustomObject]]::new() instantiates a generic list for accumulating audit records.
  • $current.ContainsKey($name) validates whether a specific baseline property exists in the active registry key.
  • Format-Table -AutoSize formats the drift report with dynamic column widths.
Consolepowershell
./audit-registry-drift-advanced.ps1
Outputplaintext
Setting    Status                     Expected Actual
-------    ------                     -------- ------
LegacyMode Missing from Current       0        N/A
Timeout    Value Mismatch / Drift     30       60
DebugLevel Unauthorized Extra Setting N/A      2
Navigation