Private/New-StandaloneDetectShim.ps1

<#
.SYNOPSIS
    Generates an enterprise detection script using dual 64/32-bit registry view enumeration and SemVer evaluation.
.DESCRIPTION
    Enumerates subkeys in both 64-bit and 32-bit registry hives via OpenBaseKey, matching ProductCode GUIDs
    or DisplayName regexes, and evaluates versions using segment-by-segment integer comparison.
#>

function New-StandaloneDetectShim {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$PackageId,

        [Parameter()]
        [string]$DisplayName = '',

        [Parameter()]
        [string]$ProductCode = '',

        [Parameter()]
        [string]$MinVersion = '',

        [Parameter()]
        [string]$DetectionStrategy = 'RegistryDisplayVersion'
    )

    $scriptTemplate = @'
<#
.SYNOPSIS
    Intune Win32 Detection Script for __PACKAGE_ID__
    Generated by WingetIntune (Dual-View Registry Enumeration & SemVer Evaluator)
#>
$packageId = '__PACKAGE_ID__'
$displayName = '__DISPLAY_NAME__'
$productCode = '__PRODUCT_CODE__'
$minVersion = '__MIN_VERSION__'
 
$installed = $false
$detectedVersion = $null
 
# Helper: Compare Version Strings Segment-by-Segment (Avoids "1.10.0" < "1.9.0" string bugs)
function Compare-SemVer {
    param([string]$Installed, [string]$Required)
    if (-not $Installed -or -not $Required) { return $true }
     
    # Strip leading 'v' or build tags
    $cleanInst = ($Installed -replace '^[vV]', '') -replace '-.+$', ''
    $cleanReq = ($Required -replace '^[vV]', '') -replace '-.+$', ''
 
    $instParts = @($cleanInst.Split('.') | ForEach-Object { $val = 0; [int]::TryParse($_, [ref]$val) | Out-Null; $val })
    $reqParts = @($cleanReq.Split('.') | ForEach-Object { $val = 0; [int]::TryParse($_, [ref]$val) | Out-Null; $val })
 
    $maxLen = [Math]::Max($instParts.Count, $reqParts.Count)
    for ($i = 0; $i -lt $maxLen; $i++) {
        $iVal = if ($i -lt $instParts.Count) { $instParts[$i] } else { 0 }
        $rVal = if ($i -lt $reqParts.Count) { $reqParts[$i] } else { 0 }
        if ($iVal -gt $rVal) { return $true }
        if ($iVal -lt $rVal) { return $false }
    }
    return $true
}
 
# 1. Dual-View Registry Hive Enumeration (Bypasses 32-bit IME WOW64 Redirection)
$views = @([Microsoft.Win32.RegistryView]::Registry64, [Microsoft.Win32.RegistryView]::Registry32)
 
foreach ($view in $views) {
    try {
        $base = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $view)
        $uninstallKey = $base.OpenSubKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall')
        if ($uninstallKey) {
            foreach ($subName in $uninstallKey.GetSubKeyNames()) {
                $sub = $uninstallKey.OpenSubKey($subName)
                if ($sub) {
                    $dName = [string]$sub.GetValue('DisplayName')
                    $dVer = [string]$sub.GetValue('DisplayVersion')
                     
                    $matched = $false
                    if ($productCode -and $subName -eq $productCode) {
                        $matched = $true
                    } elseif ($displayName -and $dName -match [regex]::Escape($displayName)) {
                        $matched = $true
                    } elseif ($packageId -and $dName -match [regex]::Escape($packageId)) {
                        $matched = $true
                    }
 
                    if ($matched) {
                        $installed = $true
                        $detectedVersion = $dVer
                        $sub.Close()
                        break
                    }
                    $sub.Close()
                }
            }
            $uninstallKey.Close()
        }
        $base.Close()
    } catch { }
 
    if ($installed) { break }
}
 
# 2. Local Winget List Fallback
if (-not $installed) {
    try {
        $wingetCmd = Get-Command 'winget.exe' -ErrorAction SilentlyContinue
        if ($wingetCmd) {
            $output = & $wingetCmd.Source list --exact --id $packageId --source winget --accept-source-agreements 2>$null
            if ($output -match [regex]::Escape($packageId)) {
                $installed = $true
            }
        }
    } catch { }
}
 
# 3. Final Version Compliance Evaluation
if ($installed) {
    if ($minVersion -and $detectedVersion) {
        $isCompliant = Compare-SemVer -Installed $detectedVersion -Required $minVersion
        if (-not $isCompliant) {
            Write-Output "Non-Compliant: Detected version $detectedVersion is below required $minVersion."
            exit 1
        }
    }
    Write-Output "Compliant: $packageId detected (Version: $detectedVersion)."
    exit 0
} else {
    exit 1
}
'@


    $content = $scriptTemplate.Replace('__PACKAGE_ID__', $PackageId).Replace('__DISPLAY_NAME__', $DisplayName).Replace('__PRODUCT_CODE__', $ProductCode).Replace('__MIN_VERSION__', $MinVersion).Replace('__STRATEGY__', $DetectionStrategy)
    return $content
}