Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

213 linhas
9.1KB

  1. <#
  2. Runs ON the IIS server (invoked over SSH by scripts\deploy-iis.ps1). Not meant to be run
  3. by hand except for troubleshooting a stuck deploy.
  4. - Stops the site/app pool
  5. - Wipes RemoteDir and re-extracts the release zip into it (safe: the DB and error log
  6. live outside RemoteDir, at DbPath/ErrorLogDir, so a full wipe never touches them)
  7. - Points IIS at RemoteDir\public
  8. - Ensures the app pool identity has modify rights on the persistent data folder
  9. - Runs pending migrations (32-bit cscript - ACE OLEDB is x86-only)
  10. - Restarts the site/app pool
  11. #>
  12. param(
  13. [Parameter(Mandatory = $true)][string]$ZipPath,
  14. [Parameter(Mandatory = $true)][string]$RemoteDir,
  15. [Parameter(Mandatory = $true)][string]$SiteName,
  16. [Parameter(Mandatory = $true)][string]$AppPool,
  17. [Parameter(Mandatory = $true)][string]$DbPath,
  18. [string]$AdminSiteName = '',
  19. [string]$AdminAppPool = '',
  20. [switch]$RunMigrations = $true
  21. )
  22. $ErrorActionPreference = 'Stop'
  23. Import-Module WebAdministration
  24. # Stop-WebAppPool only requests a stop - the w3wp.exe worker process can keep running for a
  25. # few seconds afterwards (finishing in-flight requests), still holding the site's files open.
  26. # Wait for the pool to actually report Stopped, then force-kill any worker process that's
  27. # still lingering past the timeout so the wipe below doesn't hit "Access is denied".
  28. function Wait-AppPoolFullyStopped {
  29. param([string]$PoolName, [int]$TimeoutSec = 30)
  30. if([string]::IsNullOrWhiteSpace($PoolName)){ return }
  31. $deadline = (Get-Date).AddSeconds($TimeoutSec)
  32. while((Get-Date) -lt $deadline){
  33. if((Get-WebAppPoolState -Name $PoolName -ErrorAction SilentlyContinue).Value -eq 'Stopped'){
  34. return
  35. }
  36. Start-Sleep -Milliseconds 500
  37. }
  38. Write-Host "App pool $PoolName did not report Stopped within ${TimeoutSec}s - killing any lingering worker process"
  39. Get-CimInstance Win32_Process -Filter "Name = 'w3wp.exe'" -ErrorAction SilentlyContinue |
  40. Where-Object { $_.CommandLine -like ('*' + $PoolName + '*') } |
  41. ForEach-Object {
  42. try { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue } catch { }
  43. }
  44. }
  45. # Wipes are attempted a few times with a short pause - file handles (IIS or AV scanning
  46. # the just-stopped worker process's files) can take a moment to release even after the
  47. # worker process itself is confirmed gone.
  48. #
  49. # Remove-Item returning without an error is not proof the directory is actually empty yet -
  50. # NTFS can finish a large recursive delete asynchronously, so a Get-ChildItem right after can
  51. # still show remnants. If Expand-Archive -Force then runs against those remnants, it queues
  52. # them for removal itself and can lose the race against our own delete finishing, failing with
  53. # "Cannot find path ... because it does not exist". So after Remove-Item reports success, poll
  54. # until the directory is verifiably empty before trusting the wipe is done.
  55. function Remove-DirectoryContentsWithRetry {
  56. param([string]$DirPath, [int]$MaxAttempts = 5, [int]$DelayMs = 2000)
  57. for($attempt = 1; $attempt -le $MaxAttempts; $attempt++){
  58. $problem = $null
  59. try {
  60. Remove-Item -Recurse -Force (Join-Path $DirPath '*') -ErrorAction Stop
  61. } catch {
  62. $problem = "delete failed: $($_.Exception.Message)"
  63. }
  64. if(!$problem){
  65. if(!(Get-ChildItem -Force -LiteralPath $DirPath -ErrorAction SilentlyContinue)){
  66. return
  67. }
  68. $problem = 'residual files still present after delete (async NTFS delete still finishing?)'
  69. }
  70. if($attempt -lt $MaxAttempts){
  71. Write-Host "Wipe attempt $attempt failed ($problem) - retrying in $($DelayMs)ms"
  72. Start-Sleep -Milliseconds $DelayMs
  73. } else {
  74. throw "Wipe of $DirPath did not complete after $MaxAttempts attempts - $problem"
  75. }
  76. }
  77. }
  78. if(!(Get-Website -Name $SiteName -ErrorAction SilentlyContinue)){
  79. 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."
  80. }
  81. if(!(Test-Path ('IIS:\AppPools\' + $AppPool))){
  82. throw "App pool '$AppPool' does not exist yet. Create it once manually before running this deploy."
  83. }
  84. if(![string]::IsNullOrWhiteSpace($AdminSiteName)){
  85. if(!(Get-Website -Name $AdminSiteName -ErrorAction SilentlyContinue)){
  86. throw "IIS admin site '$AdminSiteName' does not exist yet. Create the site and app pool once manually before running this deploy."
  87. }
  88. if(![string]::IsNullOrWhiteSpace($AdminAppPool) -and !(Test-Path ('IIS:\AppPools\' + $AdminAppPool))){
  89. throw "Admin app pool '$AdminAppPool' does not exist yet. Create it once manually before running this deploy."
  90. }
  91. }
  92. Write-Host "Stopping IIS site $SiteName and app pool $AppPool"
  93. try { Stop-Website -Name $SiteName } catch { }
  94. try { Stop-WebAppPool -Name $AppPool } catch { }
  95. Wait-AppPoolFullyStopped -PoolName $AppPool
  96. # The admin site's physical path may already point inside RemoteDir (e.g. RemoteDir\public-admin)
  97. # from a previous deploy - if it's left running, IIS holds those files open and the wipe below
  98. # fails with "Access is denied". Stop it up front, alongside the public site, not after.
  99. if(![string]::IsNullOrWhiteSpace($AdminSiteName) -and (Get-Website -Name $AdminSiteName -ErrorAction SilentlyContinue)){
  100. Write-Host "Stopping IIS admin site $AdminSiteName"
  101. try { Stop-Website -Name $AdminSiteName } catch { }
  102. if(![string]::IsNullOrWhiteSpace($AdminAppPool)){
  103. try { Stop-WebAppPool -Name $AdminAppPool } catch { }
  104. Wait-AppPoolFullyStopped -PoolName $AdminAppPool
  105. }
  106. }
  107. if(Test-Path $RemoteDir){
  108. Write-Host "Wiping $RemoteDir"
  109. Remove-DirectoryContentsWithRetry -DirPath $RemoteDir
  110. } else {
  111. New-Item -ItemType Directory -Force -Path $RemoteDir | Out-Null
  112. }
  113. Write-Host "Extracting $ZipPath -> $RemoteDir"
  114. Expand-Archive -Path $ZipPath -DestinationPath $RemoteDir -Force
  115. Remove-Item $ZipPath -ErrorAction SilentlyContinue
  116. $publicDir = Join-Path $RemoteDir 'public'
  117. if(!(Test-Path $publicDir)){
  118. throw "No public\ folder in the extracted release - deploy aborted, site left stopped for inspection."
  119. }
  120. # Persistent data folder (DB + error log) - lives outside RemoteDir so it survives the wipe
  121. # above. Create it and grant the app pool identity modify rights.
  122. $dataDir = Split-Path $DbPath -Parent
  123. if(!(Test-Path $dataDir)){
  124. New-Item -ItemType Directory -Force -Path $dataDir | Out-Null
  125. }
  126. $logDir = Join-Path $dataDir 'logs'
  127. if(!(Test-Path $logDir)){
  128. New-Item -ItemType Directory -Force -Path $logDir | Out-Null
  129. }
  130. icacls $dataDir /grant ("IIS AppPool\" + $AppPool + ":(OI)(CI)(M)") /T | Out-Null
  131. Set-ItemProperty ('IIS:\Sites\' + $SiteName) -Name physicalPath -Value $publicDir
  132. Set-ItemProperty ('IIS:\Sites\' + $SiteName) -Name applicationPool -Value $AppPool
  133. # This server has the 64-bit Access Database Engine (ACE OLEDB) installed, unlike the local
  134. # dev machine, which needs the 32-bit one - so the app pool stays 64-bit here.
  135. Set-ItemProperty ('IIS:\AppPools\' + $AppPool) -Name enable32BitAppOnWin64 -Value $false
  136. # --- Admin site ---
  137. $adminPublicDir = Join-Path $RemoteDir 'public-admin'
  138. if(![string]::IsNullOrWhiteSpace($AdminSiteName) -and (Test-Path $adminPublicDir)){
  139. Write-Host "Configuring admin site $AdminSiteName"
  140. if(![string]::IsNullOrWhiteSpace($AdminAppPool)){
  141. Set-ItemProperty ('IIS:\Sites\' + $AdminSiteName) -Name applicationPool -Value $AdminAppPool
  142. Set-ItemProperty ('IIS:\AppPools\' + $AdminAppPool) -Name enable32BitAppOnWin64 -Value $false
  143. icacls $dataDir /grant ("IIS AppPool\" + $AdminAppPool + ":(OI)(CI)(M)") /T | Out-Null
  144. }
  145. Set-ItemProperty ('IIS:\Sites\' + $AdminSiteName) -Name physicalPath -Value $adminPublicDir
  146. }
  147. if($RunMigrations){
  148. Write-Host 'Running pending migrations'
  149. Push-Location $RemoteDir
  150. try {
  151. & cscript.exe //nologo scripts\runMigrations.vbs up
  152. if($LASTEXITCODE -ne 0){ throw 'runMigrations.vbs up failed - see output above' }
  153. } finally {
  154. Pop-Location
  155. }
  156. }
  157. # Start-WebAppPool/Start-Website are no-ops (no error) if already started, so there's no
  158. # need to check current state first - and checking first (via Get-WebAppPoolState) is what
  159. # was silently killing this whole block: with $ErrorActionPreference = 'Stop', a transient
  160. # error from that check (e.g. right after Wait-AppPoolFullyStopped had to force-kill a
  161. # lingering worker process) aborted the script before the admin site/pool were ever started.
  162. # Each start below is independently wrapped so a problem with one site doesn't prevent the
  163. # other from starting, and every failure is reported instead of aborting silently.
  164. function Start-SiteAndPool {
  165. param([string]$PoolName, [string]$WebsiteName)
  166. if(![string]::IsNullOrWhiteSpace($PoolName)){
  167. try {
  168. Start-WebAppPool -Name $PoolName -ErrorAction Stop
  169. Write-Host "Started app pool $PoolName"
  170. } catch {
  171. Write-Host "WARNING: failed to start app pool $PoolName : $($_.Exception.Message)"
  172. }
  173. }
  174. if(![string]::IsNullOrWhiteSpace($WebsiteName)){
  175. try {
  176. Start-Website -Name $WebsiteName -ErrorAction Stop
  177. Write-Host "Started site $WebsiteName"
  178. } catch {
  179. Write-Host "WARNING: failed to start site $WebsiteName : $($_.Exception.Message)"
  180. }
  181. }
  182. }
  183. Start-SiteAndPool -PoolName $AppPool -WebsiteName $SiteName
  184. Start-SiteAndPool -PoolName $AdminAppPool -WebsiteName $AdminSiteName
  185. Write-Host 'Remote apply complete.'

Powered by TurnKey Linux.