From aeee210cf61ca1e9c00d328178b155edb39dd903 Mon Sep 17 00:00:00 2001 From: cproudlock Date: Tue, 4 Aug 2026 20:06:47 -0400 Subject: [PATCH] Repair a web.config that an earlier build made unusable Stage 4 deliberately leaves an existing web.config alone, because operators put real changes in it: extra MIME maps, a /installers location, bindings, a proxy-specific rule. Overwriting reverts those silently. That rule had no exception, and earlier builds of this installer wrote an block which is fatal on its own: the section is Deny by default, so IIS rejects the entire file with 500.52 before httpPlatformHandler runs. Any server already installed would therefore keep the broken file forever, with re-running the fixed installer powerless to help, since the first thing stage 4 does is decline to touch it. Strip just that element, keeping every other edit, and only when it contains nothing besides the variable this installer adds. A block holding anything else is somebody's deliberate change and is left alone with a warning. The previous file is copied to web.config.before-xff-fix first. Exercised against four inputs: the file earlier builds wrote, which is repaired and still parses as XML with the rewrite rule intact; a block with an operator-added variable, which is left unchanged; an empty block, which is the $null.Count trap under Set-StrictMode 2.0 and is why the filter is wrapped in @(); and an already-correct file, which is a no-op. --- deploy/windows/installer/shopdb-install.ps1 | 34 +++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/deploy/windows/installer/shopdb-install.ps1 b/deploy/windows/installer/shopdb-install.ps1 index 16b1b43..a1e060a 100644 --- a/deploy/windows/installer/shopdb-install.ps1 +++ b/deploy/windows/installer/shopdb-install.ps1 @@ -1699,6 +1699,40 @@ has to come from the bundle either way. if (Test-Path $dstCfg) { Write-Log 'web.config already exists; leaving it alone' 'OK' $existing = Get-Content $dstCfg -Raw + + # ONE EXCEPTION to leaving it alone. An block is + # fatal by itself: that section is Deny by default, so IIS rejects the + # whole file with 500.52 (0x80070021) before httpPlatformHandler runs. + # The app never starts and its stdout log stays empty, which reads as a + # broken application and is not one. + # + # Earlier builds of THIS installer wrote exactly that block, so honouring + # "leave it alone" without qualification would leave every server already + # installed permanently unable to start, with re-running powerless to fix + # it. Strip just that element and keep every other edit - and only when it + # contains nothing but the variable this installer puts there, because + # anything else is somebody's deliberate change. + if ($existing -match '(?s)(.*?)') { + $inner = $Matches[1] + $names = [regex]::Matches($inner, 'name\s*=\s*"([^"]+)"') | + ForEach-Object { $_.Groups[1].Value } + # @() around the pipeline: a zero-match filter returns $null, and + # $null.Count is a terminating error under Set-StrictMode 2.0. + $foreign = @($names | Where-Object { $_ -ne 'HTTP_X_FORWARDED_FOR' }).Count + if ($foreign -gt 0) { + Write-Log 'web.config declares holding variables this installer did not add' 'WARN' + Write-Log ' IIS returns 500.52 unless that section is unlocked server-wide; left unchanged' 'WARN' + } elseif (-not $WhatIfOnly) { + $backup = "$dstCfg.before-xff-fix" + Copy-Item $dstCfg $backup -Force + $fixed = $existing -replace '(?s)[ \t]*.*?\r?\n?', '' + Set-Content -Path $dstCfg -Value $fixed -Encoding UTF8 + Write-Log 'removed from web.config; it made IIS return 500.52' 'OK' + Write-Log " the previous file is kept at $backup" + $existing = $fixed + } + } + $hasRule = $existing -match '' -and $existing -notmatch 'SHOPDB-CLIENTIP-BEGIN' if ($ClientIpSource -eq 'direct' -and -not $hasRule) { Write-Log 'this web.config does NOT set X-Forwarded-For; client IPs will read as 127.0.0.1' 'WARN'