# GPOAppLocker.ps1 # AppLocker policy management via GPO. # Uses Set-AppLockerPolicy / Get-AppLockerPolicy with -LDAP parameter. # Depends on: GPOCore.ps1 function ConvertTo-AppLockerXml { <# .SYNOPSIS Converts an AppLockerPolicy hashtable into AppLocker XML format. Generates unique GUIDs per rule and resolves user names to SIDs. #> param( [Parameter(Mandatory)] [hashtable]$AppLockerPolicy ) $esc = [System.Security.SecurityElement] $collectionMap = @{ Exe = 'Exe' Msi = 'Msi' Script = 'Script' Appx = 'Appx' Dll = 'Dll' } $collections = foreach ($collectionName in $AppLockerPolicy.Keys) { $collection = $AppLockerPolicy[$collectionName] $xmlType = $collectionMap[$collectionName] if (-not $xmlType) { Write-Host " [WARN] Unknown AppLocker collection: $collectionName" -ForegroundColor Yellow continue } $enforcement = if ($collection.EnforcementMode -eq 'Enabled') { 'Enabled' } else { 'AuditOnly' } $ruleXml = foreach ($rule in $collection.Rules) { $ruleId = [Guid]::NewGuid().ToString() $ruleName = if ($rule.Name) { $esc::Escape($rule.Name) } else { "$collectionName rule $ruleId" } $ruleDesc = if ($rule.Description) { $esc::Escape($rule.Description) } else { '' } $action = $rule.Action # Allow or Deny # Resolve user to SID $userSid = 'S-1-1-0' # Default: Everyone try { $ntAccount = New-Object System.Security.Principal.NTAccount($rule.User) $userSid = $ntAccount.Translate([System.Security.Principal.SecurityIdentifier]).Value } catch { # Well-known SIDs if ($rule.User -eq 'Everyone') { $userSid = 'S-1-1-0' } else { Write-Host " [WARN] Cannot resolve '$($rule.User)' to SID, using Everyone" -ForegroundColor Yellow } } $conditionXml = switch ($rule.Type) { 'Publisher' { $pub = $esc::Escape($rule.Publisher) $prod = if ($rule.Product) { $esc::Escape($rule.Product) } else { '*' } $bin = if ($rule.Binary) { $esc::Escape($rule.Binary) } else { '*' } $lowVer = if ($rule.LowVersion) { $rule.LowVersion } else { '0.0.0.0' } $highVer = if ($rule.HighVersion) { $rule.HighVersion } else { '*' } @" "@ } 'Path' { $path = $esc::Escape($rule.Path) @" "@ } 'Hash' { $hash = $rule.Hash $fileName = if ($rule.FileName) { $esc::Escape($rule.FileName) } else { '' } $fileLength = if ($rule.SourceFileLength) { $rule.SourceFileLength } else { '0' } @" "@ } default { Write-Host " [WARN] Unknown rule type: $($rule.Type)" -ForegroundColor Yellow '' } } $elementName = switch ($rule.Type) { 'Publisher' { 'FilePublisherRule' } 'Path' { 'FilePathRule' } 'Hash' { 'FileHashRule' } default { 'FilePublisherRule' } } @" <$elementName Id="$ruleId" Name="$ruleName" Description="$ruleDesc" UserOrGroupSid="$userSid" Action="$action"> $conditionXml "@ } @" $($ruleXml -join "`n") "@ } return @" $($collections -join "`n") "@ } function Set-GPOAppLockerPolicy { <# .SYNOPSIS Applies an AppLocker policy to a GPO using Set-AppLockerPolicy. Full overwrite semantics. #> param( [Parameter(Mandatory)] [string]$GPOName, [Parameter(Mandatory)] [hashtable]$AppLockerPolicy, [string]$Domain = (Get-ADDomain).DNSRoot ) Write-Host " Applying AppLocker policy..." -ForegroundColor Yellow $xml = ConvertTo-AppLockerXml -AppLockerPolicy $AppLockerPolicy # Get GPO GUID for LDAP path $gpo = Get-GPO -Name $GPOName -Domain $Domain $gpoGuid = $gpo.Id.ToString('B').ToUpper() $domainDN = (Get-ADDomain -Server $Domain).DistinguishedName $ldapPath = "LDAP://CN=$gpoGuid,CN=Policies,CN=System,$domainDN" # Write a temp file with the XML (Set-AppLockerPolicy requires -XmlPolicy file path or pipeline) $tempFile = [System.IO.Path]::GetTempFileName() try { [System.IO.File]::WriteAllText($tempFile, $xml, [System.Text.UTF8Encoding]::new($true)) Set-AppLockerPolicy -XmlPolicy $tempFile -LDAP $ldapPath Write-Host " [OK] AppLocker policy applied to $GPOName" -ForegroundColor Green } finally { Remove-Item $tempFile -Force -ErrorAction SilentlyContinue } # Report what was set foreach ($coll in $AppLockerPolicy.Keys) { $ruleCount = @($AppLockerPolicy[$coll].Rules).Count $mode = $AppLockerPolicy[$coll].EnforcementMode Write-Host " $coll`: $ruleCount rule(s), $mode" -ForegroundColor Green } } function Compare-GPOAppLockerPolicy { <# .SYNOPSIS Compares desired AppLocker policy against current GPO state. Reports missing/extra collections and rules. #> param( [Parameter(Mandatory)] [string]$GPOName, [Parameter(Mandatory)] [hashtable]$AppLockerPolicy, [string]$Domain = (Get-ADDomain).DNSRoot ) $diffs = @() Write-Host " Comparing AppLocker policy..." -ForegroundColor Yellow # Get GPO GUID for LDAP path $gpo = Get-GPO -Name $GPOName -Domain $Domain $gpoGuid = $gpo.Id.ToString('B').ToUpper() $domainDN = (Get-ADDomain -Server $Domain).DistinguishedName $ldapPath = "LDAP://CN=$gpoGuid,CN=Policies,CN=System,$domainDN" # Get current AppLocker policy $currentXml = $null try { $currentXml = Get-AppLockerPolicy -Domain -LDAP $ldapPath -Xml -ErrorAction Stop } catch { # No policy set } if (-not $currentXml) { Write-Host " [DRIFT] No AppLocker policy configured" -ForegroundColor Red $diffs += [PSCustomObject]@{ Type = 'AppLocker' Collection = '(all)' Status = 'No policy' } return $diffs } # Parse current XML [xml]$currentDoc = $currentXml foreach ($collectionName in $AppLockerPolicy.Keys) { $desired = $AppLockerPolicy[$collectionName] $desiredMode = if ($desired.EnforcementMode -eq 'Enabled') { 'Enabled' } else { 'AuditOnly' } # Find matching collection in current XML $currentCollection = $currentDoc.AppLockerPolicy.RuleCollection | Where-Object { $_.Type -eq $collectionName } if (-not $currentCollection) { Write-Host " [DRIFT] Missing collection: $collectionName" -ForegroundColor Red $diffs += [PSCustomObject]@{ Type = 'AppLocker' Collection = $collectionName Status = 'Missing collection' } continue } # Check enforcement mode if ($currentCollection.EnforcementMode -ne $desiredMode) { Write-Host " [DRIFT] $collectionName enforcement: $($currentCollection.EnforcementMode) -> $desiredMode" -ForegroundColor Red $diffs += [PSCustomObject]@{ Type = 'AppLocker' Collection = $collectionName Status = "EnforcementMode: $($currentCollection.EnforcementMode) -> $desiredMode" } } # Compare rule counts $currentRuleCount = @($currentCollection.ChildNodes | Where-Object { $_.LocalName -like '*Rule' }).Count $desiredRuleCount = @($desired.Rules).Count if ($currentRuleCount -ne $desiredRuleCount) { Write-Host " [DRIFT] $collectionName rule count: $currentRuleCount -> $desiredRuleCount" -ForegroundColor Red $diffs += [PSCustomObject]@{ Type = 'AppLocker' Collection = $collectionName Status = "Rule count: $currentRuleCount -> $desiredRuleCount" } } else { Write-Host " [OK] $collectionName`: $currentRuleCount rule(s), $($currentCollection.EnforcementMode)" -ForegroundColor Green } } if ($diffs.Count -eq 0) { Write-Host " [OK] AppLocker policy matches desired state" -ForegroundColor Green } else { Write-Host " [DRIFT] $($diffs.Count) AppLocker difference(s) found" -ForegroundColor Red } return $diffs }