|
| 1 | +name: Mask Visual Studio |
| 2 | +description: > |
| 3 | + Make every MSVC discovery strategy of msvc.cppm fail on a runner that ships |
| 4 | + Visual Studio, and assert that they do. The runner is disposable, so this is |
| 5 | + both safe and closer to "absent" than any env-only trick. |
| 6 | +
|
| 7 | +inputs: |
| 8 | + attempts: |
| 9 | + description: How many times one rename is retried before it counts as denied |
| 10 | + required: false |
| 11 | + default: "5" |
| 12 | + retry-delay-ms: |
| 13 | + description: Milliseconds between two attempts at the same rename |
| 14 | + required: false |
| 15 | + default: "2000" |
| 16 | + |
| 17 | +runs: |
| 18 | + using: composite |
| 19 | + steps: |
| 20 | + - name: Mask Visual Studio |
| 21 | + shell: pwsh |
| 22 | + env: |
| 23 | + MASK_ATTEMPTS: ${{ inputs.attempts }} |
| 24 | + MASK_RETRY_DELAY_MS: ${{ inputs.retry-delay-ms }} |
| 25 | + run: | |
| 26 | + $ErrorActionPreference = 'Continue' |
| 27 | +
|
| 28 | + # All three of msvc.cppm's discovery strategies converge on |
| 29 | + # <vsRoot>\VC\Tools\MSVC — vswhere returns an installationPath that |
| 30 | + # find_latest_msvc_tools then resolves through it, the env strategy |
| 31 | + # checks it explicitly, and the well-known-path scan tests for it — so |
| 32 | + # masking that directory is what makes this runner answer "no Visual |
| 33 | + # Studio" to all three. |
| 34 | + # |
| 35 | + # The rename is refused outright on some hosts: "Access to the path |
| 36 | + # '...\18\Enterprise\VC' is denied", while the same script on the same |
| 37 | + # runner image commits the same rename minutes earlier or later (image |
| 38 | + # windows-2025-vs2026 20260925.250 renamed it in one run and was |
| 39 | + # refused in another). A refusal is therefore retried, and then |
| 40 | + # answered one level deeper: a handle into VC does not block renaming |
| 41 | + # VC\Tools, and the postcondition below only needs VC\Tools\MSVC gone. |
| 42 | + # What outlasts both is reported, never swallowed. |
| 43 | + $attempts = [int]$env:MASK_ATTEMPTS |
| 44 | + $delay = [int]$env:MASK_RETRY_DELAY_MS |
| 45 | +
|
| 46 | + function Mask-Path([string]$Path) { |
| 47 | + for ($i = 1; $i -le $attempts; $i++) { |
| 48 | + try { |
| 49 | + Rename-Item -LiteralPath $Path -NewName "$(Split-Path $Path -Leaf).masked" -ErrorAction Stop |
| 50 | + Write-Host "masked $Path" |
| 51 | + return $true |
| 52 | + } catch { |
| 53 | + if ($i -lt $attempts) { |
| 54 | + Start-Sleep -Milliseconds $delay |
| 55 | + } else { |
| 56 | + Write-Host " denied after $attempts attempts: $Path — $($_.Exception.Message)" |
| 57 | + } |
| 58 | + } |
| 59 | + } |
| 60 | + return $false |
| 61 | + } |
| 62 | +
|
| 63 | + $vswhere = "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" |
| 64 | + if (Test-Path $vswhere) { [void](Mask-Path $vswhere) } |
| 65 | +
|
| 66 | + # -Path with a trailing wildcard segment lists the CONTENTS of the |
| 67 | + # matches, not the matches themselves, so `…\*\*\VC` would hand back |
| 68 | + # VC's children. Resolve-Path returns the directories themselves. |
| 69 | + $vsRoots = @(Resolve-Path "C:\Program Files*\Microsoft Visual Studio\*\*\VC" ` |
| 70 | + -ErrorAction SilentlyContinue) |
| 71 | + foreach ($p in $vsRoots) { [void](Mask-Path $p.Path) } |
| 72 | +
|
| 73 | + # The fallback, and the reason it is enough: vswhere's resolution and |
| 74 | + # the well-known-path scan both end at VC\Tools\MSVC, so masking Tools |
| 75 | + # answers "no Visual Studio" as well as masking VC does. |
| 76 | + $tools = @(Resolve-Path "C:\Program Files*\Microsoft Visual Studio\*\*\VC\Tools" ` |
| 77 | + -ErrorAction SilentlyContinue) |
| 78 | + foreach ($p in $tools) { [void](Mask-Path $p.Path) } |
| 79 | +
|
| 80 | + foreach ($v in @('VSINSTALLDIR','VCINSTALLDIR','VCToolsInstallDir', |
| 81 | + 'VS170COMNTOOLS','VS160COMNTOOLS','VS150COMNTOOLS')) { |
| 82 | + "$v=" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8 |
| 83 | + } |
| 84 | +
|
| 85 | + # Check the mask's own postcondition here, where the cause is obvious, |
| 86 | + # instead of letting it surface three steps later as a confusing pass. |
| 87 | + $remaining = Get-ChildItem "C:\Program Files*\Microsoft Visual Studio\*\*\VC\Tools\MSVC" ` |
| 88 | + -Directory -ErrorAction SilentlyContinue |
| 89 | + if ($remaining) { |
| 90 | + Write-Host "FAIL: VC tools still present after masking:" |
| 91 | + $remaining | ForEach-Object { Write-Host " $($_.FullName)" } |
| 92 | + exit 1 |
| 93 | + } |
| 94 | + Write-Host "Visual Studio masked: no VC\Tools\MSVC remains." |
0 commit comments