Damien Coles f172d00514 Initial release: Declarative AD Framework v2.1.0
Infrastructure-as-code framework for Active Directory objects and Group Policy.
Sanitized from production deployment for public sharing.
2026-02-19 17:02:42 +00:00

304 lines
10 KiB
PowerShell

# GPOFirewall.ps1
# Windows Firewall rule and profile management via GPO.
# Uses Open-NetGPO / New-NetFirewallRule -GPOSession / Save-NetGPO cmdlets.
# Depends on: GPOCore.ps1 (Get-GPOSysvolPath)
function Set-GPOFirewallProfiles {
<#
.SYNOPSIS
Sets Windows Firewall profile settings (Domain/Private/Public) on a GPO
via registry values.
#>
param(
[Parameter(Mandatory)]
[string]$GPOName,
[Parameter(Mandatory)]
[hashtable]$FirewallProfiles,
[string]$Domain = (Get-ADDomain).DNSRoot
)
$profileKeyMap = @{
Domain = 'HKLM\Software\Policies\Microsoft\WindowsFirewall\DomainProfile'
Private = 'HKLM\Software\Policies\Microsoft\WindowsFirewall\PrivateProfile'
Public = 'HKLM\Software\Policies\Microsoft\WindowsFirewall\PublicProfile'
}
$actionMap = @{
'Block' = 1
'Allow' = 0
'NotConfigured' = 0
}
foreach ($profileName in $FirewallProfiles.Keys) {
$profile = $FirewallProfiles[$profileName]
$regKey = $profileKeyMap[$profileName]
if (-not $regKey) {
Write-Host " [WARN] Unknown firewall profile: $profileName" -ForegroundColor Yellow
continue
}
# Enable/Disable firewall for this profile
if ($profile.ContainsKey('Enabled')) {
$enableValue = if ($profile.Enabled) { 1 } else { 0 }
Set-GPRegistryValue -Name $GPOName -Key $regKey -ValueName 'EnableFirewall' `
-Type DWord -Value $enableValue -Domain $Domain | Out-Null
}
# Default inbound action
if ($profile.ContainsKey('DefaultInboundAction')) {
$inValue = $actionMap[$profile.DefaultInboundAction]
Set-GPRegistryValue -Name $GPOName -Key $regKey -ValueName 'DefaultInboundAction' `
-Type DWord -Value $inValue -Domain $Domain | Out-Null
}
# Default outbound action
if ($profile.ContainsKey('DefaultOutboundAction')) {
$outValue = $actionMap[$profile.DefaultOutboundAction]
Set-GPRegistryValue -Name $GPOName -Key $regKey -ValueName 'DefaultOutboundAction' `
-Type DWord -Value $outValue -Domain $Domain | Out-Null
}
Write-Host " [SET] Firewall profile: $profileName" -ForegroundColor Green
}
}
function Compare-GPOFirewallProfiles {
<#
.SYNOPSIS
Compares desired firewall profile settings against current GPO state.
#>
param(
[Parameter(Mandatory)]
[string]$GPOName,
[Parameter(Mandatory)]
[hashtable]$FirewallProfiles,
[string]$Domain = (Get-ADDomain).DNSRoot
)
$profileKeyMap = @{
Domain = 'HKLM\Software\Policies\Microsoft\WindowsFirewall\DomainProfile'
Private = 'HKLM\Software\Policies\Microsoft\WindowsFirewall\PrivateProfile'
Public = 'HKLM\Software\Policies\Microsoft\WindowsFirewall\PublicProfile'
}
$actionMap = @{
'Block' = 1
'Allow' = 0
'NotConfigured' = 0
}
$diffs = @()
Write-Host " Comparing firewall profiles..." -ForegroundColor Yellow
foreach ($profileName in $FirewallProfiles.Keys) {
$profile = $FirewallProfiles[$profileName]
$regKey = $profileKeyMap[$profileName]
if (-not $regKey) { continue }
$settingsToCheck = @()
if ($profile.ContainsKey('Enabled')) {
$desired = if ($profile.Enabled) { 1 } else { 0 }
$settingsToCheck += @{ ValueName = 'EnableFirewall'; Desired = $desired }
}
if ($profile.ContainsKey('DefaultInboundAction')) {
$settingsToCheck += @{ ValueName = 'DefaultInboundAction'; Desired = $actionMap[$profile.DefaultInboundAction] }
}
if ($profile.ContainsKey('DefaultOutboundAction')) {
$settingsToCheck += @{ ValueName = 'DefaultOutboundAction'; Desired = $actionMap[$profile.DefaultOutboundAction] }
}
foreach ($setting in $settingsToCheck) {
$current = $null
try {
$regResult = Get-GPRegistryValue -Name $GPOName -Key $regKey -ValueName $setting.ValueName `
-Domain $Domain -ErrorAction Stop
$current = $regResult.Value
} catch {
$current = $null
}
if ($current -ne $setting.Desired) {
$currentDisplay = if ($null -eq $current) { '(not set)' } else { $current }
Write-Host " [DRIFT] $profileName\$($setting.ValueName): $currentDisplay -> $($setting.Desired)" -ForegroundColor Red
$diffs += [PSCustomObject]@{
Type = 'FirewallProfile'
Profile = $profileName
Setting = $setting.ValueName
Current = $currentDisplay
Desired = $setting.Desired
}
} else {
Write-Host " [OK] $profileName\$($setting.ValueName) = $current" -ForegroundColor Green
}
}
}
if ($diffs.Count -eq 0) {
Write-Host " [OK] All firewall profiles match desired state" -ForegroundColor Green
}
return $diffs
}
function Set-GPOFirewall {
<#
.SYNOPSIS
Writes Windows Firewall rules to a GPO using Open-NetGPO session.
Full overwrite semantics -- removes all existing rules, then creates declared rules.
#>
param(
[Parameter(Mandatory)]
[string]$GPOName,
[Parameter(Mandatory)]
[array]$FirewallRules,
[string]$Domain = (Get-ADDomain).DNSRoot
)
$domainFqdn = $Domain
Write-Host " Applying firewall rules..." -ForegroundColor Yellow
# Open a GPO session
$gpoSession = Open-NetGPO -PolicyStore "$domainFqdn\$GPOName"
# Remove all existing rules in this GPO
try {
$existing = Get-NetFirewallRule -GPOSession $gpoSession -ErrorAction SilentlyContinue
if ($existing) {
Remove-NetFirewallRule -GPOSession $gpoSession -All
Write-Host " Removed $(@($existing).Count) existing rule(s)" -ForegroundColor Yellow
}
} catch {
# No existing rules -- nothing to remove
}
# Create each declared rule
foreach ($rule in $FirewallRules) {
$params = @{
GPOSession = $gpoSession
DisplayName = $rule.DisplayName
Direction = $rule.Direction
Action = $rule.Action
}
if ($rule.Protocol) { $params.Protocol = $rule.Protocol }
if ($rule.LocalPort) { $params.LocalPort = $rule.LocalPort }
if ($rule.RemotePort) { $params.RemotePort = $rule.RemotePort }
if ($rule.LocalAddress) { $params.LocalAddress = $rule.LocalAddress }
if ($rule.RemoteAddress) { $params.RemoteAddress = $rule.RemoteAddress }
if ($rule.Program) { $params.Program = $rule.Program }
if ($rule.Description) { $params.Description = $rule.Description }
if ($rule.ContainsKey('Enabled')) {
$params.Enabled = if ($rule.Enabled) { 'True' } else { 'False' }
}
if ($rule.Profile) {
$params.Profile = $rule.Profile
}
New-NetFirewallRule @params | Out-Null
Write-Host " [CREATED] $($rule.DisplayName)" -ForegroundColor Green
}
# Save the GPO session
Save-NetGPO -GPOSession $gpoSession
Write-Host " [OK] $($FirewallRules.Count) firewall rule(s) applied" -ForegroundColor Green
}
function Compare-GPOFirewall {
<#
.SYNOPSIS
Compares desired firewall rules against current GPO state.
Reports missing, extra, and differing rules.
#>
param(
[Parameter(Mandatory)]
[string]$GPOName,
[Parameter(Mandatory)]
[array]$FirewallRules,
[string]$Domain = (Get-ADDomain).DNSRoot
)
$domainFqdn = $Domain
$diffs = @()
Write-Host " Comparing firewall rules..." -ForegroundColor Yellow
# Read rules via PolicyStore (Get-NetFirewallRule has no -GPOSession parameter)
$policyStore = "$domainFqdn\$GPOName"
$existing = @()
try {
$existing = @(Get-NetFirewallRule -PolicyStore $policyStore -ErrorAction SilentlyContinue)
} catch {
# No rules found
}
$existingByName = @{}
foreach ($r in $existing) {
$existingByName[$r.DisplayName] = $r
}
# Check for missing rules
foreach ($rule in $FirewallRules) {
if ($existingByName.ContainsKey($rule.DisplayName)) {
$current = $existingByName[$rule.DisplayName]
$mismatch = $false
if ($rule.Direction -and $current.Direction -ne $rule.Direction) { $mismatch = $true }
if ($rule.Action -and $current.Action -ne $rule.Action) { $mismatch = $true }
if ($mismatch) {
Write-Host " [DRIFT] Rule differs: $($rule.DisplayName)" -ForegroundColor Red
$diffs += [PSCustomObject]@{
Type = 'FirewallRule'
Rule = $rule.DisplayName
Status = 'Differs'
}
} else {
Write-Host " [OK] $($rule.DisplayName)" -ForegroundColor Green
}
} else {
Write-Host " [DRIFT] Missing rule: $($rule.DisplayName)" -ForegroundColor Red
$diffs += [PSCustomObject]@{
Type = 'FirewallRule'
Rule = $rule.DisplayName
Status = 'Missing'
}
}
}
# Check for extra rules (in GPO but not declared)
$declaredNames = @{}
foreach ($rule in $FirewallRules) { $declaredNames[$rule.DisplayName] = $true }
foreach ($r in $existing) {
if (-not $declaredNames.ContainsKey($r.DisplayName)) {
Write-Host " [DRIFT] Extra rule: $($r.DisplayName)" -ForegroundColor Red
$diffs += [PSCustomObject]@{
Type = 'FirewallRule'
Rule = $r.DisplayName
Status = 'Extra (undeclared)'
}
}
}
if ($diffs.Count -eq 0) {
Write-Host " [OK] All firewall rules match desired state" -ForegroundColor Green
} else {
Write-Host " [DRIFT] $($diffs.Count) firewall rule difference(s) found" -ForegroundColor Red
}
return $diffs
}