Public/Invoke-LGPolicyCheck.ps1

function Invoke-LGPolicyCheck {
    <#
    .SYNOPSIS
        Checks installed software against the policy rules in lg-policy.json.
    .EXAMPLE
        Invoke-LGPolicyCheck -PolicyPath .\lg-policy.json
    .EXAMPLE
        $sw = Get-LGInstalledSoftware
        Invoke-LGPolicyCheck -PolicyPath .\lg-policy.json -SoftwareCache $sw
    #>

    [CmdletBinding()]
    param(
        [string]$PolicyPath   = '.\lg-policy.json',
        [PSCustomObject[]]$SoftwareCache = $null
    )

    $L   = Get-LGEffectiveStrings
    $cfg = Get-LGEffectiveConfig
    Write-LGHeader $L['policySection']

    if (-not (Test-Path $PolicyPath)) {
        Write-Warning "Policy file not found: $PolicyPath"
        return @()
    }
    try {
        $policy = Get-Content $PolicyPath -Raw | ConvertFrom-Json
    } catch {
        Write-Warning "Could not read policy: $($_.Exception.Message)"
        return @()
    }

    $swRows = if ($SoftwareCache) {
        $SoftwareCache
    } else {
        Get-LGSoftwareRegistryRows -WarnDays $cfg.WarnDaysBeforeExpiry |
            ForEach-Object { [PSCustomObject]@{ Name=$_.Name; Version=$_.Version; Publisher=$_.Publisher } }
    }

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

    foreach ($sw in $swRows) {
        # Whitelist
        $whitelisted = $false
        if ($cfg.Whitelist -and $cfg.Whitelist.Count -gt 0) {
            foreach ($wl in $cfg.Whitelist) {
                if ($sw.Name -like "*$wl*") { $whitelisted = $true; break }
            }
        }
        if ($whitelisted) {
            $findings.Add([PSCustomObject]@{
                Module       = 'PolicyCheck'; RuleId = 'WL'; Category = 'Whitelist'
                Name         = $sw.Name; Version = $sw.Version; Publisher = $sw.Publisher
                PolicyStatus = 'ALLOWED'; Status = 'OK'; Detail = $L['whitelist']
                Alternative  = ''; Reference = ''; Severity = 'LOW'
            })
            continue
        }

        # Rule matching
        $matched = $false
        foreach ($rule in $policy.rules) {
            $match = switch ($rule.matchType) {
                'contains'   { $sw.Name -like "*$($rule.pattern)*" }
                'startsWith' { $sw.Name -like "$($rule.pattern)*"  }
                'exact'      { $sw.Name -eq $rule.pattern           }
                'regex'      { $sw.Name -match $rule.pattern        }
                default      { $false }
            }
            if ($match) {
                $statusMap = @{ 'PROHIBITED'='EXPIRED'; 'REQUIRES_LICENSE'='WARN'; 'ALLOWED'='OK' }
                $sevProp   = $rule.PSObject.Properties['severity']
                $severity  = if ($sevProp -and $sevProp.Value) { $sevProp.Value } else {
                    switch ($rule.status) { 'PROHIBITED' { 'HIGH' } 'REQUIRES_LICENSE' { 'MEDIUM' } default { 'LOW' } }
                }
                $altProp   = $rule.PSObject.Properties['alternative']
                $refProp   = $rule.PSObject.Properties['referenceUrl']
                $findings.Add([PSCustomObject]@{
                    Module       = 'PolicyCheck'; RuleId = $rule.id; Category = $rule.category
                    Name         = $sw.Name; Version = $sw.Version; Publisher = $sw.Publisher
                    PolicyStatus = $rule.status; Status = $statusMap[$rule.status]; Detail = $rule.reason
                    Alternative  = if ($altProp) { $altProp.Value } else { '' }
                    Reference    = if ($refProp) { $refProp.Value } else { '' }
                    Severity     = $severity
                })
                $matched = $true; break
            }
        }

        if (-not $matched) {
            $findings.Add([PSCustomObject]@{
                Module       = 'PolicyCheck'; RuleId = 'N/A'; Category = 'N/A'
                Name         = $sw.Name; Version = $sw.Version; Publisher = $sw.Publisher
                PolicyStatus = 'ALLOWED'; Status = 'OK'; Detail = $L['noRule']
                Alternative  = ''; Reference = ''; Severity = 'LOW'
            })
        }
    }

    $proh = @($findings | Where-Object { $_.PolicyStatus -eq 'PROHIBITED' })
    $lic  = @($findings | Where-Object { $_.PolicyStatus -eq 'REQUIRES_LICENSE' })
    $ok   = @($findings | Where-Object { $_.PolicyStatus -eq 'ALLOWED' })

    foreach ($f in $findings) {
        $suffix = if ($f.RuleId -notin @('N/A','WL')) { " [$($f.RuleId)]" } else { '' }
        Write-LGStatus ($f.Name + $suffix) $f.Detail $f.Status
        if ($f.Status -ne 'OK') {
            if ($f.Detail)      { Write-Host " Reason: $($f.Detail)"      -ForegroundColor DarkGray }
            if ($f.Alternative) { Write-Host " Suggestion: $($f.Alternative)" -ForegroundColor DarkCyan }
        }
    }

    Write-Host ("`n $($findings.Count) matched -- $($proh.Count) $($L['prohibited']) $($lic.Count) $($L['requiresLicense']) $($ok.Count) $($L['allowed'])") -ForegroundColor Cyan
    $findings
}