$ErrorActionPreference = 'Stop' $project = Split-Path $PSScriptRoot -Parent $exe = [IO.Path]::GetFullPath((Join-Path $project 'dist\MusicBridge-win-x64\app\MusicBridge.Agent.exe')) $script = Join-Path $PSScriptRoot 'Firewall.ps1' $rules = @(& $script -Action Preview -ProgramPath $exe -Port 18765) if ($rules.Count -ne 2) { throw 'Expected two rules.' } foreach ($rule in $rules) { if ($rule.Profile -ne 'Private' -or $rule.RemoteAddress -ne 'LocalSubnet' -or $rule.Program -ne $exe -or $rule.Direction -ne 'Inbound' -or $rule.Action -ne 'Allow' -or $rule.EdgeTraversalPolicy -ne 'Block') { throw 'Rule scope is too broad.' } } if (($rules | Where-Object Protocol -eq TCP).LocalPort -ne 18765) { throw 'HTTPS port mismatch.' } if (($rules | Where-Object Protocol -eq UDP).LocalPort -ne 5353) { throw 'mDNS port mismatch.' } $again = @(& $script -Action Preview -ProgramPath $exe -Port 8765) if (($rules.Name -join ',') -ne ($again.Name -join ',')) { throw 'Changing port would leave old rule names behind.' } & $script -Action Apply -ProgramPath $exe -WhatIf $domain = @(& $script -Action Preview -ProgramPath $exe -Profile Domain -InterfaceAlias Ethernet) foreach ($rule in $domain) { if ($rule.Profile -ne 'Domain' -or $rule.InterfaceAlias -ne 'Ethernet' -or $rule.RemoteAddress -ne 'LocalSubnet') { throw 'Domain scope mismatch.' } } if (($domain.Name -join ',') -ne ($rules.Name -join ',')) { throw 'Changing profile would leave old rules behind.' } foreach ($badAlias in @('', 'Any', 'Ether*', 'Ether?et')) { $rejected = $false try { & $script -Action Preview -ProgramPath $exe -Profile Domain -InterfaceAlias $badAlias | Out-Null } catch { $rejected = $true } if (!$rejected) { throw 'Domain scope accepted an unrestricted interface.' } } & $script -Action Apply -ProgramPath $exe -Profile Domain -InterfaceAlias Ethernet -WhatIf Write-Host 'PASS: private profile, local subnet, executable binding, ports, stable names and dry-run. No firewall changes.'