[CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$ApacheConfig, [Parameter(Mandatory = $true)] [string]$PhpIni, [switch]$Apply, [switch]$SkipApacheSyntaxCheck ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' function Resolve-ConfigFile([string]$Value, [string]$Label) { $resolved = (Resolve-Path -LiteralPath $Value -ErrorAction Stop).Path if (-not (Test-Path -LiteralPath $resolved -PathType Leaf)) { throw "$Label is not a file: $resolved" } return $resolved } function Replace-ManagedBlock([string]$Text, [string]$Begin, [string]$End, [string]$Block) { $normalized = $Text -replace "`r`n?", "`n" $pattern = '(?ms)(?:^|\n)' + [regex]::Escape($Begin) + '.*?' + [regex]::Escape($End) + '(?:\n|$)' $without = [regex]::Replace($normalized, $pattern, "`n").TrimEnd("`n") if ($without.Length -eq 0) { return "$Block`n" } return "$without`n`n$Block`n" } function Write-Utf8NoBom([string]$File, [string]$Text) { [System.IO.File]::WriteAllText($File, $Text, [System.Text.UTF8Encoding]::new($false)) } $apacheFile = Resolve-ConfigFile $ApacheConfig 'Apache configuration' $phpFile = Resolve-ConfigFile $PhpIni 'PHP configuration' $apacheBegin = '# BEGIN LINKFIELD HOST HARDENING' $apacheEnd = '# END LINKFIELD HOST HARDENING' $phpBegin = '; BEGIN LINKFIELD HOST HARDENING' $phpEnd = '; END LINKFIELD HOST HARDENING' $apacheBlock = "$apacheBegin`nServerTokens Prod`nServerSignature Off`n$apacheEnd" $phpBlock = "$phpBegin`nexpose_php = Off`n$phpEnd" $apacheOriginal = [System.IO.File]::ReadAllText($apacheFile) $phpOriginal = [System.IO.File]::ReadAllText($phpFile) $apacheUpdated = Replace-ManagedBlock $apacheOriginal $apacheBegin $apacheEnd $apacheBlock $phpUpdated = Replace-ManagedBlock $phpOriginal $phpBegin $phpEnd $phpBlock $apacheAlreadySafe = $apacheUpdated -eq ($apacheOriginal -replace "`r`n?", "`n") $phpAlreadySafe = $phpUpdated -eq ($phpOriginal -replace "`r`n?", "`n") Write-Output "Apache config: $apacheFile" Write-Output "PHP config: $phpFile" Write-Output "Apache ServerTokens/ServerSignature: $(if ($apacheAlreadySafe) {'already managed'} else {'change required'})" Write-Output "PHP expose_php: $(if ($phpAlreadySafe) {'already managed'} else {'change required'})" if (-not $Apply) { Write-Output 'Dry run only. Re-run with -Apply to create backups and write the managed settings.' exit 0 } $stamp = Get-Date -Format 'yyyyMMdd-HHmmss' $apacheBackup = "$apacheFile.linkfield-backup-$stamp" $phpBackup = "$phpFile.linkfield-backup-$stamp" Copy-Item -LiteralPath $apacheFile -Destination $apacheBackup -Force Copy-Item -LiteralPath $phpFile -Destination $phpBackup -Force try { Write-Utf8NoBom $apacheFile $apacheUpdated Write-Utf8NoBom $phpFile $phpUpdated if (-not $SkipApacheSyntaxCheck) { $apacheRoot = Split-Path -Parent (Split-Path -Parent $apacheFile) $httpd = Join-Path $apacheRoot 'bin\httpd.exe' if (-not (Test-Path -LiteralPath $httpd -PathType Leaf)) { throw "Apache syntax checker was not found at $httpd. Re-run with -SkipApacheSyntaxCheck only after locating another way to run httpd -t." } & $httpd -t -f $apacheFile if ($LASTEXITCODE -ne 0) { throw "Apache rejected the updated configuration (exit $LASTEXITCODE)." } } } catch { Copy-Item -LiteralPath $apacheBackup -Destination $apacheFile -Force Copy-Item -LiteralPath $phpBackup -Destination $phpFile -Force throw "Host hardening failed; both original files were restored. $($_.Exception.Message)" } Write-Output "Applied LinkField host hardening. Backups:" Write-Output " $apacheBackup" Write-Output " $phpBackup" Write-Output 'Restart Apache, then run: npm run smoke:public -- https://2012r2.nishi.boats/~333/link-field/'