Infrastructure-as-code framework for Active Directory objects and Group Policy. Sanitized from production deployment for public sharing.
304 lines
10 KiB
PowerShell
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
|
|
}
|