|
|
|
@@ -25,6 +25,53 @@ param( |
|
|
|
$ErrorActionPreference = 'Stop' |
|
|
|
Import-Module WebAdministration |
|
|
|
|
|
|
|
# Stop-WebAppPool only requests a stop - the w3wp.exe worker process can keep running for a |
|
|
|
# few seconds afterwards (finishing in-flight requests), still holding the site's files open. |
|
|
|
# Wait for the pool to actually report Stopped, then force-kill any worker process that's |
|
|
|
# still lingering past the timeout so the wipe below doesn't hit "Access is denied". |
|
|
|
function Wait-AppPoolFullyStopped { |
|
|
|
param([string]$PoolName, [int]$TimeoutSec = 30) |
|
|
|
|
|
|
|
if([string]::IsNullOrWhiteSpace($PoolName)){ return } |
|
|
|
|
|
|
|
$deadline = (Get-Date).AddSeconds($TimeoutSec) |
|
|
|
while((Get-Date) -lt $deadline){ |
|
|
|
if((Get-WebAppPoolState -Name $PoolName -ErrorAction SilentlyContinue).Value -eq 'Stopped'){ |
|
|
|
return |
|
|
|
} |
|
|
|
Start-Sleep -Milliseconds 500 |
|
|
|
} |
|
|
|
|
|
|
|
Write-Host "App pool $PoolName did not report Stopped within ${TimeoutSec}s - killing any lingering worker process" |
|
|
|
Get-CimInstance Win32_Process -Filter "Name = 'w3wp.exe'" -ErrorAction SilentlyContinue | |
|
|
|
Where-Object { $_.CommandLine -like ('*' + $PoolName + '*') } | |
|
|
|
ForEach-Object { |
|
|
|
try { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue } catch { } |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
# Wipes are attempted a few times with a short pause - file handles (IIS or AV scanning |
|
|
|
# the just-stopped worker process's files) can take a moment to release even after the |
|
|
|
# worker process itself is confirmed gone. |
|
|
|
function Remove-DirectoryContentsWithRetry { |
|
|
|
param([string]$Path, [int]$MaxAttempts = 5, [int]$DelayMs = 2000) |
|
|
|
|
|
|
|
for($attempt = 1; $attempt -le $MaxAttempts; $attempt++){ |
|
|
|
$failure = $null |
|
|
|
try { |
|
|
|
Remove-Item -Recurse -Force $Path -ErrorAction Stop |
|
|
|
return |
|
|
|
} catch { |
|
|
|
$failure = $_ |
|
|
|
if($attempt -lt $MaxAttempts){ |
|
|
|
Write-Host "Wipe attempt $attempt failed (files still locked?) - retrying in $($DelayMs)ms" |
|
|
|
Start-Sleep -Milliseconds $DelayMs |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
throw $failure |
|
|
|
} |
|
|
|
|
|
|
|
if(!(Get-Website -Name $SiteName -ErrorAction SilentlyContinue)){ |
|
|
|
throw "IIS site '$SiteName' does not exist yet. Create the site and app pool once manually (or via a one-time setup script) before running this deploy." |
|
|
|
} |
|
|
|
@@ -35,6 +82,7 @@ if(!(Test-Path ('IIS:\AppPools\' + $AppPool))){ |
|
|
|
Write-Host "Stopping IIS site $SiteName and app pool $AppPool" |
|
|
|
try { Stop-Website -Name $SiteName } catch { } |
|
|
|
try { Stop-WebAppPool -Name $AppPool } catch { } |
|
|
|
Wait-AppPoolFullyStopped -PoolName $AppPool |
|
|
|
|
|
|
|
# The admin site's physical path may already point inside RemoteDir (e.g. RemoteDir\public-admin) |
|
|
|
# from a previous deploy - if it's left running, IIS holds those files open and the wipe below |
|
|
|
@@ -44,12 +92,13 @@ if(![string]::IsNullOrWhiteSpace($AdminSiteName) -and (Get-Website -Name $AdminS |
|
|
|
try { Stop-Website -Name $AdminSiteName } catch { } |
|
|
|
if(![string]::IsNullOrWhiteSpace($AdminAppPool)){ |
|
|
|
try { Stop-WebAppPool -Name $AdminAppPool } catch { } |
|
|
|
Wait-AppPoolFullyStopped -PoolName $AdminAppPool |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if(Test-Path $RemoteDir){ |
|
|
|
Write-Host "Wiping $RemoteDir" |
|
|
|
Remove-Item -Recurse -Force (Join-Path $RemoteDir '*') -ErrorAction SilentlyContinue |
|
|
|
Remove-DirectoryContentsWithRetry -Path (Join-Path $RemoteDir '*') |
|
|
|
} else { |
|
|
|
New-Item -ItemType Directory -Force -Path $RemoteDir | Out-Null |
|
|
|
} |
|
|
|
|