|
- <#
- .SYNOPSIS
- Installs or rolls back an immutable release in a dedicated IIS site/app pool.
-
- .DESCRIPTION
- The complete repository is retained in each immutable release and IIS serves
- only <release>\public. The script never discovers or adopts another site.
- A missing dedicated target is created only after package extraction, layout,
- and XML validation succeed. Host preflight is read-only and does not require
- the dedicated target to exist.
- #>
-
- [CmdletBinding(DefaultParameterSetName = 'Deploy')]
- param(
- [ValidatePattern('^[A-Za-z0-9_. -]+$')]
- [string]$SiteName = 'AspClassicUnifiedFramework',
- [ValidatePattern('^[A-Za-z0-9_. -]+$')]
- [string]$AppPoolName = 'AspClassicUnifiedFramework',
- [string]$DeployRoot = 'D:\Deployments\AspClassicUnifiedFramework',
- [string]$BindingIpAddress = '100.97.39.23',
- [ValidateRange(1, 65535)]
- [int]$BindingPort = 8085,
- [AllowEmptyString()]
- [string]$HostHeader = '',
- [string]$InitialWebConfigPath = '',
-
- [Parameter(ParameterSetName = 'Deploy')]
- [string]$PackagePath = '',
- [Parameter(ParameterSetName = 'Deploy')]
- [ValidatePattern('^[A-Za-z0-9._-]+$')]
- [string]$ReleaseId = (Get-Date -Format 'yyyyMMdd-HHmmss'),
- [Parameter(Mandatory = $true, ParameterSetName = 'Rollback')]
- [ValidatePattern('^[A-Za-z0-9._-]+$')]
- [string]$RollbackTo,
-
- [string]$BaseUrl = '',
- [ValidateRange(2, 100)]
- [int]$KeepReleases = 5,
- [string]$ExpectedSha256 = '',
- [switch]$RunMigrations,
- [switch]$SkipSmokeTest,
- [switch]$PreflightOnly,
- [switch]$DryRun
- )
-
- Set-StrictMode -Version 2.0
- $ErrorActionPreference = 'Stop'
-
- function Write-Step { param([string]$Message) Write-Host ('==> ' + $Message) }
-
- function Get-NormalizedPath {
- param([string]$Path)
- return [System.IO.Path]::GetFullPath([Environment]::ExpandEnvironmentVariables($Path)).TrimEnd('\')
- }
-
- function Assert-Administrator {
- $identity = [Security.Principal.WindowsIdentity]::GetCurrent()
- $principal = New-Object Security.Principal.WindowsPrincipal($identity)
- if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
- throw 'An elevated Administrator PowerShell session is required.'
- }
- }
-
- function Assert-SafeNameValue {
- param([string]$Name, [string]$Value)
- if ([string]::IsNullOrWhiteSpace($Value) -or $Value -notmatch '^[A-Za-z0-9_. -]+$') {
- throw "$Name contains unsupported characters."
- }
- if ($Value -match '(?i)schedulicious') {
- throw "$Name must not identify a Schedulicious resource."
- }
- }
-
- function Assert-DeploymentInputs {
- Assert-SafeNameValue -Name 'SiteName' -Value $SiteName
- Assert-SafeNameValue -Name 'AppPoolName' -Value $AppPoolName
- if ($DeployRoot -match '(?i)schedulicious') { throw 'DeployRoot must not reference Schedulicious.' }
- if ($HostHeader -match '(?i)schedulicious') { throw 'HostHeader must not reference Schedulicious.' }
- if ($HostHeader -match '[:/\\]') {
- throw 'HostHeader must be empty or a DNS host name without a scheme, port, slash, or backslash.'
- }
- $parsedAddress = $null
- if (-not [System.Net.IPAddress]::TryParse($BindingIpAddress, [ref]$parsedAddress)) {
- throw "BindingIpAddress is not a valid IP address: $BindingIpAddress"
- }
- }
-
- function Assert-ReleaseLayout {
- param([string]$ReleasePath)
- $required = @(
- 'public\Default.asp',
- 'public\web.config',
- 'core\autoload_core.asp',
- 'app\controllers\autoload_controllers.asp',
- 'scripts\install-iis-release.ps1'
- )
- foreach ($relativePath in $required) {
- if (-not (Test-Path -LiteralPath (Join-Path $ReleasePath $relativePath) -PathType Leaf)) {
- throw "Release is incomplete; missing $relativePath"
- }
- }
- foreach ($xmlFile in Get-ChildItem -LiteralPath $ReleasePath -Recurse -Force -Filter 'web.config') {
- try {
- [xml](Get-Content -LiteralPath $xmlFile.FullName -Raw) | Out-Null
- } catch {
- throw "$($xmlFile.FullName) is not valid XML: $($_.Exception.Message)"
- }
- }
- }
-
- function Assert-ArchiveEntries {
- param([string]$ZipPath)
- Add-Type -AssemblyName System.IO.Compression.FileSystem
- $archive = [System.IO.Compression.ZipFile]::OpenRead($ZipPath)
- try {
- foreach ($entry in $archive.Entries) {
- $name = $entry.FullName.Replace('/', '\')
- if ([System.IO.Path]::IsPathRooted($name) -or $name -match '(^|\\)\.\.(\\|$)') {
- throw "Package contains an unsafe path: $($entry.FullName)"
- }
- }
- } finally {
- $archive.Dispose()
- }
- }
-
- function Get-BindingInformation {
- return $BindingIpAddress + ':' + $BindingPort + ':' + $HostHeader
- }
-
- function Assert-HostCapabilities {
- $getWindowsFeature = Get-Command Get-WindowsFeature -ErrorAction SilentlyContinue
- $getOptionalFeature = Get-Command Get-WindowsOptionalFeature -ErrorAction SilentlyContinue
- if ($null -ne $getWindowsFeature) {
- $aspFeature = Get-WindowsFeature -Name Web-ASP
- if ($null -eq $aspFeature -or -not $aspFeature.Installed) { throw 'The IIS Classic ASP feature (Web-ASP) is not installed.' }
- } elseif ($null -ne $getOptionalFeature) {
- $aspFeature = Get-WindowsOptionalFeature -Online -FeatureName IIS-ASP -ErrorAction SilentlyContinue
- if ($null -eq $aspFeature -or $aspFeature.State -ne 'Enabled') { throw 'The IIS-ASP Windows feature is not enabled.' }
- } else {
- throw 'Classic ASP feature state cannot be verified: no supported Windows feature cmdlet is available.'
- }
- if ($null -eq (Get-WebGlobalModule -Name AspModule -ErrorAction SilentlyContinue)) {
- throw 'The IIS Classic ASP module (AspModule) was not found.'
- }
- if ($null -eq (Get-WebGlobalModule -Name RewriteModule -ErrorAction SilentlyContinue)) {
- throw 'IIS URL Rewrite is not installed (RewriteModule was not found).'
- }
- }
-
- function Assert-DeployRoot {
- param([string]$Path)
- $root = [System.IO.Path]::GetPathRoot($Path)
- if ([string]::IsNullOrWhiteSpace($root) -or -not (Test-Path -LiteralPath $root -PathType Container)) {
- throw "The deployment drive/root is unavailable: $root"
- }
- if ($Path.TrimEnd('\') -eq $root.TrimEnd('\')) { throw 'DeployRoot must not be a drive root.' }
- if ((Test-Path -LiteralPath $Path) -and -not (Test-Path -LiteralPath $Path -PathType Container)) {
- throw "DeployRoot exists but is not a directory: $Path"
- }
- $ancestor = $Path
- while (-not (Test-Path -LiteralPath $ancestor -PathType Container)) {
- $parent = Split-Path -Parent $ancestor
- if ([string]::IsNullOrWhiteSpace($parent) -or $parent -eq $ancestor) { break }
- $ancestor = $parent
- }
- if (-not (Test-Path -LiteralPath $ancestor -PathType Container)) {
- throw "No accessible ancestor exists for DeployRoot: $Path"
- }
- Get-Item -LiteralPath $ancestor -ErrorAction Stop | Out-Null
- }
-
- function Test-PathUnderRoot {
- param([string]$Path, [string]$Root)
- $normalizedPath = Get-NormalizedPath $Path
- $prefix = (Get-NormalizedPath $Root) + '\'
- return $normalizedPath.StartsWith($prefix, [StringComparison]::OrdinalIgnoreCase)
- }
-
- function Get-TargetState {
- $site = Get-Website -Name $SiteName -ErrorAction SilentlyContinue
- $poolExists = Test-Path -LiteralPath ('IIS:\AppPools\' + $AppPoolName)
- if (($null -eq $site) -ne (-not $poolExists)) {
- throw 'Dedicated target is partial: the site and app pool must either both exist or both be absent.'
- }
- if ($null -eq $site) {
- return [pscustomobject]@{
- Exists = $false; Site = $null; PhysicalPath = ''; ParentPaths = $null
- SiteState = ''; PoolState = ''
- }
- }
- if ($site.applicationPool -ne $AppPoolName) {
- throw "Existing target site uses app pool '$($site.applicationPool)', expected '$AppPoolName'. Refusing adoption."
- }
- $otherPoolConsumer = Get-Website |
- Where-Object { $_.Name -ne $SiteName -and $_.applicationPool -eq $AppPoolName } |
- Select-Object -First 1
- if ($null -ne $otherPoolConsumer) {
- throw "App pool '$AppPoolName' is also used by site '$($otherPoolConsumer.Name)'. Refusing to alter a shared pool."
- }
- $bindings = @($site.Bindings.Collection)
- $expectedBinding = Get-BindingInformation
- if ($bindings.Count -ne 1 -or $bindings[0].protocol -ne 'http' -or $bindings[0].bindingInformation -ne $expectedBinding) {
- throw "Existing target binding does not exactly match http/$expectedBinding. Refusing adoption or binding changes."
- }
- $physicalPath = Get-NormalizedPath $site.physicalPath
- $releasesRoot = Join-Path $DeployRoot 'releases'
- if (-not (Test-PathUnderRoot -Path $physicalPath -Root $releasesRoot) -or
- -not $physicalPath.EndsWith('\public', [StringComparison]::OrdinalIgnoreCase)) {
- throw "Existing target physicalPath is outside this pipeline's release public directories: $physicalPath"
- }
- if (-not (Test-Path -LiteralPath $physicalPath -PathType Container)) {
- throw "Existing target physicalPath does not exist: $physicalPath"
- }
- Assert-ReleaseLayout -ReleasePath (Split-Path -Parent $physicalPath)
- $parentPaths = (Get-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' -Location $SiteName -Filter 'system.webServer/asp' -Name 'enableParentPaths').Value
- return [pscustomobject]@{
- Exists = $true; Site = $site; PhysicalPath = $physicalPath; ParentPaths = [bool]$parentPaths
- SiteState = (Get-WebsiteState -Name $SiteName).Value
- PoolState = (Get-WebAppPoolState -Name $AppPoolName).Value
- }
- }
-
- function Assert-NoBindingConflict {
- param($TargetState)
- $expectedBinding = Get-BindingInformation
- foreach ($candidate in Get-Website) {
- if ($TargetState.Exists -and $candidate.Name -eq $SiteName) { continue }
- foreach ($binding in @($candidate.Bindings.Collection)) {
- if ($binding.protocol -ne 'http') { continue }
- if ($binding.bindingInformation -notmatch '^(.*):(\d+):(.*)$') { continue }
- $candidateIp = $Matches[1]
- $candidatePort = [int]$Matches[2]
- $candidateHost = $Matches[3]
- $ipOverlaps = ($candidateIp -eq '*' -or $candidateIp -eq '0.0.0.0' -or $candidateIp -eq $BindingIpAddress)
- if ($candidatePort -eq $BindingPort -and $candidateHost -eq $HostHeader -and $ipOverlaps) {
- throw "Requested binding http/$expectedBinding conflicts with existing site '$($candidate.Name)' binding '$($binding.bindingInformation)'."
- }
- }
- }
- }
-
- function Get-SmokeUrl {
- if (-not [string]::IsNullOrWhiteSpace($BaseUrl)) { return $BaseUrl }
- $hostPart = $BindingIpAddress
- if ($hostPart.Contains(':')) { $hostPart = '[' + $hostPart + ']' }
- return 'http://' + $hostPart + ':' + $BindingPort
- }
-
- function Invoke-SmokeTest {
- if ($SkipSmokeTest) {
- Write-Step 'Smoke test skipped by explicit request'
- return
- }
- $target = (Get-SmokeUrl).TrimEnd('/') + '/'
- Write-Step ('Smoke testing ' + $target)
- $headers = @{}
- if (-not [string]::IsNullOrWhiteSpace($HostHeader)) { $headers['Host'] = $HostHeader }
- $response = Invoke-WebRequest -UseBasicParsing -Uri $target -Headers $headers -TimeoutSec 30
- if ($response.StatusCode -lt 200 -or $response.StatusCode -ge 400) {
- throw "Smoke test returned HTTP $($response.StatusCode)"
- }
- Write-Host ('Smoke test returned HTTP ' + $response.StatusCode)
- }
-
- function Set-IisRelease {
- param([string]$PhysicalPath)
- Set-ItemProperty -Path ('IIS:\Sites\' + $SiteName) -Name physicalPath -Value $PhysicalPath
- $poolState = (Get-WebAppPoolState -Name $AppPoolName).Value
- if ($poolState -eq 'Started') { Restart-WebAppPool -Name $AppPoolName }
- else { Start-WebAppPool -Name $AppPoolName }
- }
-
- function Restore-ExistingTarget {
- param($TargetState)
- if (-not $TargetState.Exists) { return }
- Set-ItemProperty -Path ('IIS:\Sites\' + $SiteName) -Name physicalPath -Value $TargetState.PhysicalPath
- Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' -Location $SiteName -Filter 'system.webServer/asp' -Name 'enableParentPaths' -Value $TargetState.ParentPaths
- if ($TargetState.PoolState -eq 'Started') {
- if ((Get-WebAppPoolState -Name $AppPoolName).Value -eq 'Started') { Restart-WebAppPool -Name $AppPoolName }
- else { Start-WebAppPool -Name $AppPoolName }
- } elseif ((Get-WebAppPoolState -Name $AppPoolName).Value -eq 'Started') {
- Stop-WebAppPool -Name $AppPoolName
- }
- if ($TargetState.SiteState -eq 'Started') {
- if ((Get-WebsiteState -Name $SiteName).Value -ne 'Started') { Start-Website -Name $SiteName }
- } elseif ((Get-WebsiteState -Name $SiteName).Value -eq 'Started') {
- Stop-Website -Name $SiteName
- }
- }
-
- if ($env:OS -ne 'Windows_NT') { throw 'This script must run on Windows.' }
- Assert-Administrator
- Import-Module WebAdministration -ErrorAction Stop
- Assert-DeploymentInputs
- $DeployRoot = Get-NormalizedPath $DeployRoot
- Assert-DeployRoot -Path $DeployRoot
- if (-not [string]::IsNullOrWhiteSpace($InitialWebConfigPath)) {
- $preflightInitialConfig = Get-NormalizedPath $InitialWebConfigPath
- if (-not (Test-Path -LiteralPath $preflightInitialConfig -PathType Leaf)) {
- throw "InitialWebConfigPath not found: $preflightInitialConfig"
- }
- try { [xml](Get-Content -LiteralPath $preflightInitialConfig -Raw) | Out-Null }
- catch { throw "InitialWebConfigPath is not valid XML: $($_.Exception.Message)" }
- }
- Assert-HostCapabilities
- $targetState = Get-TargetState
- Assert-NoBindingConflict -TargetState $targetState
-
- Write-Step "Dedicated site: $SiteName"
- Write-Host "Dedicated app pool: $AppPoolName"
- Write-Host "Binding: http/$(Get-BindingInformation)"
- Write-Host "Deployment root: $DeployRoot"
- if ($targetState.Exists) {
- Write-Host 'Target state: existing and exactly matched'
- } else {
- Write-Host 'Target state: absent; eligible for isolated creation after release validation'
- }
-
- if ($PreflightOnly -or $DryRun) {
- Write-Step 'Host preflight passed; no IIS or filesystem changes were made'
- exit 0
- }
-
- $releasesRoot = Join-Path $DeployRoot 'releases'
- $sharedRoot = Join-Path $DeployRoot 'shared'
- $sharedConfig = Join-Path $sharedRoot 'public.web.config'
- $statePath = Join-Path $DeployRoot 'deployment-state.json'
-
- if ($PSCmdlet.ParameterSetName -eq 'Rollback') {
- if (-not $targetState.Exists) { throw 'Rollback requires the dedicated target site and app pool to exist.' }
- if (-not (Test-Path -LiteralPath $sharedConfig -PathType Leaf)) { throw "Shared configuration is missing: $sharedConfig" }
- $rollbackRoot = Get-NormalizedPath (Join-Path $releasesRoot $RollbackTo)
- if (-not (Test-PathUnderRoot -Path $rollbackRoot -Root $releasesRoot)) { throw 'Rollback target escaped the releases directory.' }
- Assert-ReleaseLayout -ReleasePath $rollbackRoot
- try { [xml](Get-Content -LiteralPath $sharedConfig -Raw) | Out-Null }
- catch { throw "Shared configuration is not valid XML: $($_.Exception.Message)" }
- try {
- Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' -Location $SiteName -Filter 'system.webServer/asp' -Name 'enableParentPaths' -Value $true
- Set-IisRelease -PhysicalPath (Join-Path $rollbackRoot 'public')
- Invoke-SmokeTest
- [ordered]@{
- siteName = $SiteName; appPoolName = $AppPoolName; currentRelease = $RollbackTo
- currentPhysicalPath = (Join-Path $rollbackRoot 'public'); previousPhysicalPath = $targetState.PhysicalPath
- binding = (Get-BindingInformation); switchedAtUtc = (Get-Date).ToUniversalTime().ToString('o'); operation = 'rollback'
- } | ConvertTo-Json | Set-Content -LiteralPath $statePath -Encoding UTF8
- } catch {
- Write-Warning 'Rollback failed; restoring the prior dedicated target path and parent-path setting.'
- Restore-ExistingTarget -TargetState $targetState
- throw
- }
- Write-Step "Rollback complete: $RollbackTo"
- exit 0
- }
-
- if ([string]::IsNullOrWhiteSpace($PackagePath)) { throw '-PackagePath is required for deployment.' }
- $PackagePath = Get-NormalizedPath $PackagePath
- if (-not (Test-Path -LiteralPath $PackagePath -PathType Leaf)) { throw "Package not found: $PackagePath" }
- if ([System.IO.Path]::GetExtension($PackagePath) -ne '.zip') { throw 'PackagePath must name a .zip package.' }
- if (-not [string]::IsNullOrWhiteSpace($ExpectedSha256)) {
- $actualHash = (Get-FileHash -LiteralPath $PackagePath -Algorithm SHA256).Hash
- if ($actualHash -ne $ExpectedSha256) { throw "Package SHA-256 mismatch. Expected $ExpectedSha256; got $actualHash" }
- Write-Host ('Package SHA-256 verified: ' + $actualHash)
- }
- Assert-ArchiveEntries -ZipPath $PackagePath
-
- $releaseRoot = Join-Path $releasesRoot $ReleaseId
- $stagingRoot = $releaseRoot + '.staging'
- if ((Test-Path -LiteralPath $releaseRoot) -or (Test-Path -LiteralPath $stagingRoot)) { throw "Release already exists: $ReleaseId" }
-
- $createdSite = $false
- $createdPool = $false
- $createdSharedConfig = $false
- $iisMutationStarted = $false
- try {
- New-Item -ItemType Directory -Force -Path $releasesRoot, $sharedRoot | Out-Null
- New-Item -ItemType Directory -Force -Path $stagingRoot | Out-Null
- Expand-Archive -LiteralPath $PackagePath -DestinationPath $stagingRoot -Force
- $unsafeExtractedItem = Get-ChildItem -LiteralPath $stagingRoot -Recurse -Force |
- Where-Object { ($_.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0 } |
- Select-Object -First 1
- if ($null -ne $unsafeExtractedItem) {
- throw "Extracted release contains a reparse point: $($unsafeExtractedItem.FullName)"
- }
- Assert-ReleaseLayout -ReleasePath $stagingRoot
-
- if (-not (Test-Path -LiteralPath $sharedConfig -PathType Leaf)) {
- $initialConfig = Join-Path $stagingRoot 'public\web.config'
- if (-not [string]::IsNullOrWhiteSpace($InitialWebConfigPath)) {
- $initialConfig = Get-NormalizedPath $InitialWebConfigPath
- if (-not (Test-Path -LiteralPath $initialConfig -PathType Leaf)) { throw "InitialWebConfigPath not found: $initialConfig" }
- }
- try { [xml](Get-Content -LiteralPath $initialConfig -Raw) | Out-Null }
- catch { throw "Initial web.config is not valid XML: $($_.Exception.Message)" }
- Copy-Item -LiteralPath $initialConfig -Destination $sharedConfig -Force
- $createdSharedConfig = $true
- Write-Step "Initialized shared configuration from $initialConfig"
- }
- try { [xml](Get-Content -LiteralPath $sharedConfig -Raw) | Out-Null }
- catch { throw "Shared configuration is not valid XML: $($_.Exception.Message)" }
- Copy-Item -LiteralPath $sharedConfig -Destination (Join-Path $stagingRoot 'public\web.config') -Force
- Assert-ReleaseLayout -ReleasePath $stagingRoot
-
- if ($RunMigrations) {
- $migrationScript = Join-Path $stagingRoot 'scripts\runMigrations.vbs'
- if (-not (Test-Path -LiteralPath $migrationScript -PathType Leaf)) { throw "Migration script not found: $migrationScript" }
- Write-Warning 'Running migrations by explicit request; IIS rollback cannot undo data changes.'
- Push-Location $stagingRoot
- try {
- & cscript.exe //nologo $migrationScript up
- if ($LASTEXITCODE -ne 0) { throw "Migration command exited with code $LASTEXITCODE" }
- } finally { Pop-Location }
- }
-
- Move-Item -LiteralPath $stagingRoot -Destination $releaseRoot
- $newPublic = Join-Path $releaseRoot 'public'
-
- $iisMutationStarted = $true
- if (-not $targetState.Exists) {
- New-WebAppPool -Name $AppPoolName | Out-Null
- $createdPool = $true
- Set-ItemProperty -Path ('IIS:\AppPools\' + $AppPoolName) -Name managedRuntimeVersion -Value ''
- New-Website -Name $SiteName -PhysicalPath $newPublic -ApplicationPool $AppPoolName -IPAddress $BindingIpAddress -Port $BindingPort -HostHeader $HostHeader | Out-Null
- $createdSite = $true
- } else {
- Set-IisRelease -PhysicalPath $newPublic
- }
-
- Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' -Location $SiteName -Filter 'system.webServer/asp' -Name 'enableParentPaths' -Value $true
- if (-not $targetState.Exists) {
- $poolState = (Get-WebAppPoolState -Name $AppPoolName).Value
- if ($poolState -ne 'Started') { Start-WebAppPool -Name $AppPoolName }
- $siteState = (Get-WebsiteState -Name $SiteName).Value
- if ($siteState -ne 'Started') { Start-Website -Name $SiteName }
- }
- Invoke-SmokeTest
-
- [ordered]@{
- siteName = $SiteName; appPoolName = $AppPoolName; currentRelease = $ReleaseId
- currentPhysicalPath = $newPublic; previousPhysicalPath = $targetState.PhysicalPath
- binding = (Get-BindingInformation); packageSha256 = (Get-FileHash -LiteralPath $PackagePath -Algorithm SHA256).Hash
- switchedAtUtc = (Get-Date).ToUniversalTime().ToString('o'); migrationsRun = [bool]$RunMigrations; operation = 'deploy'
- } | ConvertTo-Json | Set-Content -LiteralPath $statePath -Encoding UTF8
-
- $protectedPaths = @($newPublic, $targetState.PhysicalPath)
- $oldReleases = Get-ChildItem -LiteralPath $releasesRoot -Directory |
- Where-Object { $_.Name -notlike '*.staging' } |
- Sort-Object LastWriteTimeUtc -Descending |
- Select-Object -Skip $KeepReleases
- foreach ($oldRelease in $oldReleases) {
- $oldPublic = Join-Path $oldRelease.FullName 'public'
- if ($protectedPaths -notcontains $oldPublic) { Write-Step ('Retention candidate (not deleted): ' + $oldRelease.FullName) }
- }
- Write-Step "Deployment complete: $ReleaseId"
- } catch {
- $failure = $_
- if ($iisMutationStarted) {
- if ($createdSite -and (Get-Website -Name $SiteName -ErrorAction SilentlyContinue)) {
- Write-Warning "Removing site created by this invocation: $SiteName"
- Remove-Website -Name $SiteName
- }
- if ($createdPool -and (Test-Path -LiteralPath ('IIS:\AppPools\' + $AppPoolName))) {
- Write-Warning "Removing app pool created by this invocation: $AppPoolName"
- Remove-WebAppPool -Name $AppPoolName
- }
- if ($targetState.Exists) {
- Write-Warning 'Restoring the prior dedicated target state.'
- Restore-ExistingTarget -TargetState $targetState
- }
- }
- if ($createdSharedConfig -and (Test-Path -LiteralPath $sharedConfig -PathType Leaf)) {
- Write-Warning 'Removing shared configuration initialized by this failed invocation.'
- Remove-Item -LiteralPath $sharedConfig -Force
- }
- if (Test-Path -LiteralPath $stagingRoot) { Write-Warning "Incomplete staging retained for inspection: $stagingRoot" }
- throw $failure
- }
|