Private/Rules/Find-IslArchitectureIssue.ps1

function Find-IslArchitectureIssue {
    <#
    .SYNOPSIS
        Flags paths that WOW64 redirects when the script runs in the 32-bit host.
 
    .DESCRIPTION
        Remediations and platform scripts default to the 32-bit host in the portal (Win32
        detection defaults to 64-bit). In a 32-bit process on 64-bit Windows, HKLM:\SOFTWARE
        silently becomes HKLM:\SOFTWARE\WOW6432Node, "Program Files" becomes "Program Files
        (x86)" and System32 becomes SysWOW64. The classic symptom: the remediation "fixes" a value
        in WOW6432Node, and a 64-bit detection never sees it.
 
    .PARAMETER Context
        The IntuneScriptLab.ScriptContext from Get-IslScriptContext: AST, tokens, bytes and the
        effective ScriptType, Context and Architecture.
 
    .EXAMPLE
        Find-IslArchitectureIssue -Context (Get-IslScriptContext -Path .\Detect.ps1)
 
        The findings this rule produces for one script, as IntuneScriptLab.Finding objects.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [pscustomobject]$Context
    )

    $rule = 'IslArchitectureIssue'
    $ast = $Context.Ast
    $text = $ast.Extent.Text
    $guarded = $text -match ('(?i)Is64BitProcess|Is64BitOperatingSystem|sysnative|PROCESSOR_ARCHITEW6432|' +
        'RegistryView\]::Registry64|OpenBaseKey')

    if ($Context.Architecture -eq 'x86') {
        $evidence = ('runAs32Bit launched C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe with ' +
            'Is64BitProcess=False (PS-PROBE-SYS32, REM-PROBE-SYS32, W32-DET-32)')
        $severity = if ($guarded) { 'Information' } else { 'Warning' }
        $suffix = if ($guarded) { ' (the script checks bitness, so this may be intended)' } else { '' }

        foreach ($literal in (Get-IslStringLiteral -Ast $ast)) {
            $value = $literal.Value
            if ($value -match ('(?i)^(HKLM:|Registry::HKEY_LOCAL_MACHINE|' +
                'HKEY_LOCAL_MACHINE)\\SOFTWARE\\(?!WOW6432Node)')) {
                $findingSplat = @{
                    RuleName = $rule
                    Severity = $severity
                    Context  = $Context
                    Extent   = $literal.Extent
                    Message  = ('HKLM:\SOFTWARE is redirected to HKLM:\SOFTWARE\WOW6432Node in the 32-bit ' +
                        "host$suffix. Run the script in 64-bit or open the key with RegistryView.Registry64")
                    Evidence = $evidence
                }
                New-IslFinding @findingSplat
            }
            elseif ($value -match '(?i)\\Program Files\\|^C:\\Program Files$') {
                $findingSplat = @{
                    RuleName = $rule
                    Severity = $severity
                    Context  = $Context
                    Extent   = $literal.Extent
                    Message  = ("'Program Files' resolves to 'Program Files (x86)' via the 32-bit " +
                        "environment$suffix. Use `${env:ProgramW6432} or run in 64-bit")
                    Evidence = $evidence
                }
                New-IslFinding @findingSplat
            }
            elseif ($value -match '(?i)\\System32\\' -and $value -notmatch '(?i)SysWOW64|sysnative') {
                $findingSplat = @{
                    RuleName = $rule
                    Severity = $severity
                    Context  = $Context
                    Extent   = $literal.Extent
                    Message  = ("System32 is redirected to SysWOW64 in the 32-bit host$suffix. " +
                        'Use Sysnative or run in 64-bit')
                    Evidence = $evidence
                }
                New-IslFinding @findingSplat
            }
        }
        $programFiles = Find-IslAstNode -Ast $ast -TypeName VariableExpressionAst -Where {
            param($node) $node.VariablePath.UserPath -match '(?i)^env:ProgramFiles$'
        }
        foreach ($variable in $programFiles) {
            $findingSplat = @{
                RuleName = $rule
                Severity = $severity
                Context  = $Context
                Extent   = $variable.Extent
                Message  = ("`$env:ProgramFiles is 'Program Files (x86)' in the 32-bit host$suffix. Use " +
                    "`$env:ProgramW6432 for the 64-bit folder")
                Evidence = $evidence
            }
            New-IslFinding @findingSplat
        }
    }

    # x64 and arm64 are both the native 64-bit host (System32); Sysnative is a WOW64-only alias
    if ($Context.Architecture -in 'x64', 'arm64' -and $text -match '(?i)sysnative') {
        $node = (Get-IslStringLiteral -Ast $ast | Where-Object { $_.Value -match '(?i)sysnative' } |
            Select-Object -First 1)
        $findingSplat = @{
            RuleName = $rule
            Severity = 'Warning'
            Context  = $Context
            Extent   = $(if ($node) { $node.Extent } else { $ast.Extent })
            Message  = 'Sysnative only exists for 32-bit processes; in the 64-bit host the path does not exist'
            Evidence = ('64-bit host observed: System32\WindowsPowerShell\v1.0\powershell.exe, ' +
                'Is64BitProcess=True ' +
                '(PS-PROBE-SYS64); on ARM64 the native host has no Sysnative either (local survey)')
        }
        New-IslFinding @findingSplat
    }
}