Maintenance round (May 2026): fix #6, add tooling, license, README rewrite #1
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: e2e | |
| # Real end-to-end test on a windows-latest runner. Drops WHATIF, runs the | |
| # script in real-mode, creates actual Windows Firewall rules, verifies via | |
| # Get-NetFirewallRule, exercises the menu, delete, idempotency, WHATIF, and | |
| # the known-CEF sweep against a custom drive. Manual trigger only — heavy. | |
| # | |
| # To trigger: | |
| # gh workflow run e2e.yml --ref maintenance-2026-05 | |
| # or use the "Run workflow" button on the Actions tab. Set `enable_tmate=true` | |
| # if you want an interactive SSH session opened just before cleanup. | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| enable_tmate: | |
| description: "Open a tmate SSH session before cleanup (interactive)" | |
| type: boolean | |
| default: false | |
| tmate_only: | |
| description: "Skip all tests, just open a tmate session immediately" | |
| type: boolean | |
| default: false | |
| # Auto-run on PRs that touch the script or this workflow. Manual | |
| # triggers via workflow_dispatch only register against the default | |
| # branch, so this is how we run real-mode tests before merging. | |
| pull_request: | |
| branches: [main] | |
| paths: | |
| - "WinMasterBlocker.bat" | |
| - ".github/workflows/e2e.yml" | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: e2e-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| e2e: | |
| name: End-to-end on real Windows | |
| runs-on: windows-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Tmate (immediate, if requested) | |
| if: ${{ inputs.tmate_only }} | |
| uses: mxschmitt/action-tmate@v3 | |
| with: | |
| detached: false | |
| - name: Pre-clean any stale *-block rules from previous runs | |
| shell: pwsh | |
| run: | | |
| $stale = Get-NetFirewallRule -DisplayName '*-block' -ErrorAction SilentlyContinue | |
| if ($stale) { | |
| Write-Host "Removing $($stale.Count) stale rule(s)" | |
| $stale | Remove-NetFirewallRule | |
| } else { | |
| Write-Host "No stale rules" | |
| } | |
| - name: Stage fake install trees | |
| shell: pwsh | |
| run: | | |
| # Empty fake tree for WMB_TEST_ROOT (keeps the main path-table walk | |
| # from picking up unrelated runner binaries). | |
| $fake = Join-Path $env:RUNNER_TEMP 'wmb-fake' | |
| New-Item -ItemType Directory -Force -Path $fake | Out-Null | |
| foreach ($sub in @( | |
| 'Program Files\Adobe', | |
| 'Program Files (x86)\Adobe', | |
| 'Common Files\Adobe', | |
| 'Common Files (x86)\Adobe', | |
| 'ProgramData\Adobe', | |
| 'AppData\Local\Adobe', | |
| 'AppData\Roaming\Adobe' | |
| )) { | |
| New-Item -ItemType Directory -Force -Path (Join-Path $fake $sub) | Out-Null | |
| } | |
| # Custom-drive install: D:\Adobe\ is what the new known-sweep alphabet | |
| # walk catches. We seed acrocef.exe and RdrCEF.exe here so the only | |
| # path that can produce rules is the sweep. | |
| $custom = 'D:\Adobe\Acrobat DC\Acrobat\acrocef_1' | |
| New-Item -ItemType Directory -Force -Path $custom | Out-Null | |
| fsutil file createnew (Join-Path $custom 'acrocef.exe') 1024 | Out-Null | |
| $rdr = 'D:\Adobe\Reader DC\Reader' | |
| New-Item -ItemType Directory -Force -Path $rdr | Out-Null | |
| fsutil file createnew (Join-Path $rdr 'RdrCEF.exe') 1024 | Out-Null | |
| # User-profile install (second known-sweep target). | |
| $up = Join-Path $env:USERPROFILE 'Adobe\AcrobatHome\bin' | |
| New-Item -ItemType Directory -Force -Path $up | Out-Null | |
| fsutil file createnew (Join-Path $up 'AcrobatHome.exe') 1024 | Out-Null | |
| "FAKE_ROOT=$fake" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 | |
| Write-Host "FAKE_ROOT=$fake" | |
| Write-Host "Custom drive seeded: D:\Adobe\..." | |
| Write-Host "User-profile seeded: $up" | |
| # --------------------------------------------------------------------- | |
| # SCENARIO 1 — real block via unattended mode. | |
| # --------------------------------------------------------------------- | |
| - name: "S1: block creates real firewall rules" | |
| if: ${{ !inputs.tmate_only }} | |
| shell: cmd | |
| env: | |
| WMB_VENDOR: "Adobe" | |
| WMB_QUIET: "1" | |
| WMB_TEST_ROOT: ${{ runner.temp }}\wmb-fake | |
| run: WinMasterBlocker.bat | |
| - name: "S1: verify acrocef Adobe-block exists (in + out)" | |
| if: ${{ !inputs.tmate_only }} | |
| shell: pwsh | |
| run: | | |
| $rules = Get-NetFirewallRule -DisplayName 'acrocef Adobe-block' -ErrorAction SilentlyContinue | |
| if ($rules.Count -ne 2) { | |
| Write-Host "::error::expected 2 rules (in+out) for acrocef, got $($rules.Count)" | |
| $all = Get-NetFirewallRule -DisplayName '*-block' -ErrorAction SilentlyContinue | |
| Write-Host "All *-block rules on host:" | |
| $all | Select-Object DisplayName,Direction,Action | Format-Table | |
| exit 1 | |
| } | |
| foreach ($r in $rules) { | |
| if ($r.Action -ne 'Block') { | |
| Write-Host "::error::rule $($r.DisplayName) dir=$($r.Direction) action=$($r.Action), expected Block" | |
| exit 1 | |
| } | |
| } | |
| Write-Host "S1 ok: 2 acrocef rules, both Block" | |
| - name: "S1: verify RdrCEF + AcrobatHome (known-sweep proof)" | |
| if: ${{ !inputs.tmate_only }} | |
| shell: pwsh | |
| run: | | |
| foreach ($name in @('RdrCEF Adobe-block','AcrobatHome Adobe-block')) { | |
| $rules = Get-NetFirewallRule -DisplayName $name -ErrorAction SilentlyContinue | |
| if ($rules.Count -ne 2) { | |
| Write-Host "::error::expected 2 rules for '$name', got $($rules.Count). Known-sweep regression." | |
| exit 1 | |
| } | |
| } | |
| Write-Host "S1 ok: known-sweep caught D:\Adobe\ + %USERPROFILE%\Adobe\" | |
| # --------------------------------------------------------------------- | |
| # SCENARIO 2 — idempotency. Re-running adds zero new rules. | |
| # --------------------------------------------------------------------- | |
| - name: "S2: count rules before re-run" | |
| if: ${{ !inputs.tmate_only }} | |
| shell: pwsh | |
| run: | | |
| $n = (Get-NetFirewallRule -DisplayName '*-block' -ErrorAction SilentlyContinue).Count | |
| "BEFORE_COUNT=$n" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 | |
| Write-Host "BEFORE_COUNT=$n" | |
| - name: "S2: re-run script (should be idempotent)" | |
| if: ${{ !inputs.tmate_only }} | |
| shell: cmd | |
| env: | |
| WMB_VENDOR: "Adobe" | |
| WMB_QUIET: "1" | |
| WMB_TEST_ROOT: ${{ runner.temp }}\wmb-fake | |
| run: WinMasterBlocker.bat | |
| - name: "S2: verify count unchanged" | |
| if: ${{ !inputs.tmate_only }} | |
| shell: pwsh | |
| run: | | |
| $after = (Get-NetFirewallRule -DisplayName '*-block' -ErrorAction SilentlyContinue).Count | |
| if ($after -ne [int]$env:BEFORE_COUNT) { | |
| Write-Host "::error::idempotency broken: before=$env:BEFORE_COUNT after=$after" | |
| exit 1 | |
| } | |
| Write-Host "S2 ok: $after rules (unchanged)" | |
| # --------------------------------------------------------------------- | |
| # SCENARIO 3 — delete via unattended mode. | |
| # --------------------------------------------------------------------- | |
| - name: "S3: delete all via WMB_ACTION=delete" | |
| if: ${{ !inputs.tmate_only }} | |
| shell: cmd | |
| env: | |
| WMB_VENDOR: "Adobe" | |
| WMB_ACTION: "delete" | |
| WMB_QUIET: "1" | |
| run: WinMasterBlocker.bat | |
| - name: "S3: verify zero *-block rules remain" | |
| if: ${{ !inputs.tmate_only }} | |
| shell: pwsh | |
| run: | | |
| $remaining = Get-NetFirewallRule -DisplayName '*-block' -ErrorAction SilentlyContinue | |
| if ($remaining) { | |
| Write-Host "::error::delete left $($remaining.Count) rule(s) behind:" | |
| $remaining | Select-Object DisplayName,Direction | Format-Table | |
| exit 1 | |
| } | |
| Write-Host "S3 ok: all -block rules removed" | |
| # --------------------------------------------------------------------- | |
| # SCENARIO 4 — WHATIF makes zero firewall changes. | |
| # --------------------------------------------------------------------- | |
| - name: "S4: WHATIF mode creates no rules" | |
| if: ${{ !inputs.tmate_only }} | |
| shell: cmd | |
| env: | |
| WHATIF: "1" | |
| WMB_VENDOR: "Adobe" | |
| WMB_QUIET: "1" | |
| WMB_TEST_ROOT: ${{ runner.temp }}\wmb-fake | |
| run: WinMasterBlocker.bat | |
| - name: "S4: verify firewall untouched" | |
| if: ${{ !inputs.tmate_only }} | |
| shell: pwsh | |
| run: | | |
| $rules = Get-NetFirewallRule -DisplayName '*-block' -ErrorAction SilentlyContinue | |
| if ($rules) { | |
| Write-Host "::error::WHATIF created $($rules.Count) real rule(s)!" | |
| exit 1 | |
| } | |
| $log = Get-ChildItem $env:TEMP -Filter 'WinMasterBlocker-*.log' | Sort-Object LastWriteTime -Descending | Select-Object -First 1 | |
| $body = Get-Content $log.FullName -Raw | |
| if ($body -notmatch '\[WHATIF\] add ".*acrocef.*Adobe-block"') { | |
| Write-Host "::error::WHATIF transcript missing acrocef line" | |
| exit 1 | |
| } | |
| Write-Host "S4 ok: no real rules, transcript has [WHATIF] entries" | |
| # --------------------------------------------------------------------- | |
| # Cleanup before tmate / exit. Always runs. | |
| # --------------------------------------------------------------------- | |
| - name: Tmate (debug, before cleanup) | |
| if: ${{ inputs.enable_tmate || failure() }} | |
| uses: mxschmitt/action-tmate@v3 | |
| with: | |
| detached: false | |
| limit-access-to-actor: true | |
| - name: Final cleanup | |
| if: always() | |
| shell: pwsh | |
| run: | | |
| Get-NetFirewallRule -DisplayName '*-block' -ErrorAction SilentlyContinue | Remove-NetFirewallRule -ErrorAction SilentlyContinue | |
| Remove-Item -Recurse -Force -ErrorAction SilentlyContinue D:\Adobe | |
| Remove-Item -Recurse -Force -ErrorAction SilentlyContinue (Join-Path $env:USERPROFILE 'Adobe') | |
| Remove-Item -Recurse -Force -ErrorAction SilentlyContinue (Join-Path $env:RUNNER_TEMP 'wmb-fake') | |
| Write-Host "Cleanup done." | |
| - name: Surface transcript log on failure | |
| if: failure() | |
| shell: pwsh | |
| run: | | |
| Get-ChildItem $env:TEMP -Filter 'WinMasterBlocker-*.log' | Sort-Object LastWriteTime -Descending | ForEach-Object { | |
| Write-Host "=== $($_.FullName) ===" | |
| Get-Content $_.FullName -Raw | |
| } |