選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

198 行
8.3KB

  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. function Remove-DirectoryContentsWithRetry {
  49. param([string]$Path, [int]$MaxAttempts = 5, [int]$DelayMs = 2000)
  50. for($attempt = 1; $attempt -le $MaxAttempts; $attempt++){
  51. $failure = $null
  52. try {
  53. Remove-Item -Recurse -Force $Path -ErrorAction Stop
  54. return
  55. } catch {
  56. $failure = $_
  57. if($attempt -lt $MaxAttempts){
  58. Write-Host "Wipe attempt $attempt failed (files still locked?) - retrying in $($DelayMs)ms"
  59. Start-Sleep -Milliseconds $DelayMs
  60. }
  61. }
  62. }
  63. throw $failure
  64. }
  65. if(!(Get-Website -Name $SiteName -ErrorAction SilentlyContinue)){
  66. 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."
  67. }
  68. if(!(Test-Path ('IIS:\AppPools\' + $AppPool))){
  69. throw "App pool '$AppPool' does not exist yet. Create it once manually before running this deploy."
  70. }
  71. if(![string]::IsNullOrWhiteSpace($AdminSiteName)){
  72. if(!(Get-Website -Name $AdminSiteName -ErrorAction SilentlyContinue)){
  73. throw "IIS admin site '$AdminSiteName' does not exist yet. Create the site and app pool once manually before running this deploy."
  74. }
  75. if(![string]::IsNullOrWhiteSpace($AdminAppPool) -and !(Test-Path ('IIS:\AppPools\' + $AdminAppPool))){
  76. throw "Admin app pool '$AdminAppPool' does not exist yet. Create it once manually before running this deploy."
  77. }
  78. }
  79. Write-Host "Stopping IIS site $SiteName and app pool $AppPool"
  80. try { Stop-Website -Name $SiteName } catch { }
  81. try { Stop-WebAppPool -Name $AppPool } catch { }
  82. Wait-AppPoolFullyStopped -PoolName $AppPool
  83. # The admin site's physical path may already point inside RemoteDir (e.g. RemoteDir\public-admin)
  84. # from a previous deploy - if it's left running, IIS holds those files open and the wipe below
  85. # fails with "Access is denied". Stop it up front, alongside the public site, not after.
  86. if(![string]::IsNullOrWhiteSpace($AdminSiteName) -and (Get-Website -Name $AdminSiteName -ErrorAction SilentlyContinue)){
  87. Write-Host "Stopping IIS admin site $AdminSiteName"
  88. try { Stop-Website -Name $AdminSiteName } catch { }
  89. if(![string]::IsNullOrWhiteSpace($AdminAppPool)){
  90. try { Stop-WebAppPool -Name $AdminAppPool } catch { }
  91. Wait-AppPoolFullyStopped -PoolName $AdminAppPool
  92. }
  93. }
  94. if(Test-Path $RemoteDir){
  95. Write-Host "Wiping $RemoteDir"
  96. Remove-DirectoryContentsWithRetry -Path (Join-Path $RemoteDir '*')
  97. } else {
  98. New-Item -ItemType Directory -Force -Path $RemoteDir | Out-Null
  99. }
  100. Write-Host "Extracting $ZipPath -> $RemoteDir"
  101. Expand-Archive -Path $ZipPath -DestinationPath $RemoteDir -Force
  102. Remove-Item $ZipPath -ErrorAction SilentlyContinue
  103. $publicDir = Join-Path $RemoteDir 'public'
  104. if(!(Test-Path $publicDir)){
  105. throw "No public\ folder in the extracted release - deploy aborted, site left stopped for inspection."
  106. }
  107. # Persistent data folder (DB + error log) - lives outside RemoteDir so it survives the wipe
  108. # above. Create it and grant the app pool identity modify rights.
  109. $dataDir = Split-Path $DbPath -Parent
  110. if(!(Test-Path $dataDir)){
  111. New-Item -ItemType Directory -Force -Path $dataDir | Out-Null
  112. }
  113. $logDir = Join-Path $dataDir 'logs'
  114. if(!(Test-Path $logDir)){
  115. New-Item -ItemType Directory -Force -Path $logDir | Out-Null
  116. }
  117. icacls $dataDir /grant ("IIS AppPool\" + $AppPool + ":(OI)(CI)(M)") /T | Out-Null
  118. Set-ItemProperty ('IIS:\Sites\' + $SiteName) -Name physicalPath -Value $publicDir
  119. Set-ItemProperty ('IIS:\Sites\' + $SiteName) -Name applicationPool -Value $AppPool
  120. # This server has the 64-bit Access Database Engine (ACE OLEDB) installed, unlike the local
  121. # dev machine, which needs the 32-bit one - so the app pool stays 64-bit here.
  122. Set-ItemProperty ('IIS:\AppPools\' + $AppPool) -Name enable32BitAppOnWin64 -Value $false
  123. # --- Admin site ---
  124. $adminPublicDir = Join-Path $RemoteDir 'public-admin'
  125. if(![string]::IsNullOrWhiteSpace($AdminSiteName) -and (Test-Path $adminPublicDir)){
  126. Write-Host "Configuring admin site $AdminSiteName"
  127. if(![string]::IsNullOrWhiteSpace($AdminAppPool)){
  128. Set-ItemProperty ('IIS:\Sites\' + $AdminSiteName) -Name applicationPool -Value $AdminAppPool
  129. Set-ItemProperty ('IIS:\AppPools\' + $AdminAppPool) -Name enable32BitAppOnWin64 -Value $false
  130. icacls $dataDir /grant ("IIS AppPool\" + $AdminAppPool + ":(OI)(CI)(M)") /T | Out-Null
  131. }
  132. Set-ItemProperty ('IIS:\Sites\' + $AdminSiteName) -Name physicalPath -Value $adminPublicDir
  133. }
  134. if($RunMigrations){
  135. Write-Host 'Running pending migrations'
  136. Push-Location $RemoteDir
  137. try {
  138. & cscript.exe //nologo scripts\runMigrations.vbs up
  139. if($LASTEXITCODE -ne 0){ throw 'runMigrations.vbs up failed - see output above' }
  140. } finally {
  141. Pop-Location
  142. }
  143. }
  144. # Start-WebAppPool/Start-Website are no-ops (no error) if already started, so there's no
  145. # need to check current state first - and checking first (via Get-WebAppPoolState) is what
  146. # was silently killing this whole block: with $ErrorActionPreference = 'Stop', a transient
  147. # error from that check (e.g. right after Wait-AppPoolFullyStopped had to force-kill a
  148. # lingering worker process) aborted the script before the admin site/pool were ever started.
  149. # Each start below is independently wrapped so a problem with one site doesn't prevent the
  150. # other from starting, and every failure is reported instead of aborting silently.
  151. function Start-SiteAndPool {
  152. param([string]$PoolName, [string]$WebsiteName)
  153. if(![string]::IsNullOrWhiteSpace($PoolName)){
  154. try {
  155. Start-WebAppPool -Name $PoolName -ErrorAction Stop
  156. Write-Host "Started app pool $PoolName"
  157. } catch {
  158. Write-Host "WARNING: failed to start app pool $PoolName : $($_.Exception.Message)"
  159. }
  160. }
  161. if(![string]::IsNullOrWhiteSpace($WebsiteName)){
  162. try {
  163. Start-Website -Name $WebsiteName -ErrorAction Stop
  164. Write-Host "Started site $WebsiteName"
  165. } catch {
  166. Write-Host "WARNING: failed to start site $WebsiteName : $($_.Exception.Message)"
  167. }
  168. }
  169. }
  170. Start-SiteAndPool -PoolName $AppPool -WebsiteName $SiteName
  171. Start-SiteAndPool -PoolName $AdminAppPool -WebsiteName $AdminSiteName
  172. Write-Host 'Remote apply complete.'

Powered by TurnKey Linux.