Private/Registry.ps1

function ConvertFrom-HLRegistryPath {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Path
    )

    if ($Path -match '^(?i)HKLM:\\(.+)$') {
        return [pscustomobject]@{ Hive = 'LocalMachine'; SubKey = $Matches[1] }
    }
    if ($Path -match '^(?i)HKEY_LOCAL_MACHINE\\(.+)$') {
        return [pscustomobject]@{ Hive = 'LocalMachine'; SubKey = $Matches[1] }
    }
    if ($Path -match '^(?i)HKCU:\\(.+)$') {
        return [pscustomobject]@{ Hive = 'CurrentUser'; SubKey = $Matches[1] }
    }
    if ($Path -match '^(?i)HKEY_CURRENT_USER\\(.+)$') {
        return [pscustomobject]@{ Hive = 'CurrentUser'; SubKey = $Matches[1] }
    }

    throw "Unsupported registry path '$Path'. Use HKLM:\\ or HKCU:\\."
}

function Get-HLRegistryValue {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Path,

        [Parameter(Mandatory)]
        [string]$Name
    )

    Assert-HLWindows
    $parsed = ConvertFrom-HLRegistryPath -Path $Path
    $hive = if ($parsed.Hive -eq 'LocalMachine') { [Microsoft.Win32.RegistryHive]::LocalMachine } else { [Microsoft.Win32.RegistryHive]::CurrentUser }
    $view = if ([Environment]::Is64BitOperatingSystem) { [Microsoft.Win32.RegistryView]::Registry64 } else { [Microsoft.Win32.RegistryView]::Default }
    $baseKey = $null
    $key = $null

    try {
        $baseKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey($hive, $view)
        $key = $baseKey.OpenSubKey([string]$parsed.SubKey, $false)
        if ($null -eq $key) {
            return [pscustomobject][ordered]@{ KeyExists = $false; ValueExists = $false; Value = $null; Kind = $null; Path = $Path; Name = $Name }
        }

        $valueNames = @($key.GetValueNames())
        $valueExists = $Name -in $valueNames
        if (-not $valueExists) {
            return [pscustomobject][ordered]@{ KeyExists = $true; ValueExists = $false; Value = $null; Kind = $null; Path = $Path; Name = $Name }
        }

        return [pscustomobject][ordered]@{
            KeyExists   = $true
            ValueExists = $true
            Value       = $key.GetValue($Name, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
            Kind        = $key.GetValueKind($Name).ToString()
            Path        = $Path
            Name        = $Name
        }
    }
    finally {
        if ($null -ne $key) { $key.Dispose() }
        if ($null -ne $baseKey) { $baseKey.Dispose() }
    }
}

function Get-HLRegistryKeyValueMap {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Path
    )

    Assert-HLWindows
    $parsed = ConvertFrom-HLRegistryPath -Path $Path
    $hive = if ($parsed.Hive -eq 'LocalMachine') { [Microsoft.Win32.RegistryHive]::LocalMachine } else { [Microsoft.Win32.RegistryHive]::CurrentUser }
    $view = if ([Environment]::Is64BitOperatingSystem) { [Microsoft.Win32.RegistryView]::Registry64 } else { [Microsoft.Win32.RegistryView]::Default }
    $baseKey = $null
    $key = $null

    try {
        $baseKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey($hive, $view)
        $key = $baseKey.OpenSubKey([string]$parsed.SubKey, $false)
        if ($null -eq $key) {
            return $null
        }

        $values = [ordered]@{}
        foreach ($name in @($key.GetValueNames())) {
            $values[$name] = $key.GetValue($name, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
        }
        return [pscustomobject]$values
    }
    finally {
        if ($null -ne $key) { $key.Dispose() }
        if ($null -ne $baseKey) { $baseKey.Dispose() }
    }
}

function Invoke-HLRegistryValueProbe {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [object]$Control,

        [Parameter(Mandatory)]
        [object]$SystemContext,

        [AllowNull()]
        [object]$CollectionContext
    )

    $parameters = $Control.parameters
    $registryPath = [string]$parameters.path
    $registryName = [string]$parameters.name
    $providerName = 'RegistryValue:{0}:{1}' -f $registryPath, $registryName
    $registry = Get-HLProviderSnapshot -CollectionContext $CollectionContext -Name $providerName -Factory {
        Get-HLRegistryValue -Path $registryPath -Name $registryName
    }
    $expected = $parameters.expected
    $operator = if (Test-HLProperty -InputObject $parameters -Name 'operator') { [string]$parameters.operator } else { 'Equals' }

    if (-not $registry.ValueExists) {
        $missingIsPass = (Test-HLProperty -InputObject $parameters -Name 'missingIsPass') -and [bool]$parameters.missingIsPass
        if (-not $missingIsPass -and (Test-HLProperty -InputObject $parameters -Name 'missingIsPassOnBuildAtLeast')) {
            $build = 0
            [void][int]::TryParse([string]$SystemContext.BuildNumber, [ref]$build)
            if ($build -ge [int]$parameters.missingIsPassOnBuildAtLeast) {
                $missingIsPass = $true
            }
        }

        if ($missingIsPass) {
            return Get-HLProbeResult -Status Pass -Expected $expected -Actual '<not configured; secure operating-system default>' -Message 'The value is not explicitly configured and the secure operating-system default applies.' -Evidence $registry
        }
        return Get-HLProbeResult -Status Fail -Expected $expected -Actual '<not configured>' -Message "Registry value '$($parameters.name)' is not explicitly configured." -Evidence $registry
    }

    $warningValues = if (Test-HLProperty -InputObject $parameters -Name 'warningValues') { @($parameters.warningValues) } else { $null }
    return Get-HLValueProbeResult -Actual $registry.Value -Expected $expected -Operator $operator -WarningValues $warningValues -Evidence $registry
}