|
- <#
- Deploy asp-territory to an existing IIS site, locally or over SSH.
-
- Remote mode:
- - Copies this script to the remote Windows host with scp
- - Executes it remotely via ssh in -RunRemoteCore mode
- - Preserves the remote site's current DB path unless -DbPath is passed
- - Can run standard migrations and an optional legacy migration script
-
- Local / remote core behavior:
- - Infers IIS site/app pool/work dir from the existing site when possible
- - Stops the site/app pool while deploying
- - Clones/pulls and hard-resets to origin/<Branch>
- - Points IIS at <WorkDir>\public
- - Reapplies the effective DB path in public\web.config
- - Grants IIS AppPool rights to the DB folder
- - Runs migrations
- - Restarts the site/app pool and smoke tests key routes
- #>
-
- param(
- [string]$Repo = 'git@onefortheroadgit.sytes.net:dcovington/asp-classic-unified-framework.git',
- [string]$Branch = 'main',
- [string]$SiteName = 'ttasp',
- [string]$AppPool = '',
- [string]$WorkDir = '',
- [string]$PublicDir = '',
- [string]$BaseUrl = '',
- [string]$DbPath = '',
-
- [switch]$RunMigrations = $true,
- [switch]$SkipLegacyIsBusinessMigration,
- [string]$LegacyMigrationScript = 'scripts\migrate_isbusiness_to_households.vbs',
-
- [switch]$UseRemoteSsh,
- [string]$RemoteTarget = '',
- [int]$RemotePort = 22,
- [string]$SshExe = 'ssh',
- [string]$ScpExe = 'scp',
- [switch]$RunRemoteCore
- )
-
- $ErrorActionPreference = 'Stop'
-
- function Ensure-Dir {
- param([string]$Path)
- if([string]::IsNullOrWhiteSpace($Path)){ return }
- if(!(Test-Path $Path)){
- New-Item -ItemType Directory -Force -Path $Path | Out-Null
- }
- }
-
- function Ensure-Command {
- param([string]$Name)
- if(!(Get-Command $Name -ErrorAction SilentlyContinue)){
- throw "$Name not found on PATH"
- }
- }
-
- function Get-DefaultRemoteTargetFromInfo {
- $infoPath = Join-Path $PSScriptRoot 'depolyinfo.txt'
- if(!(Test-Path $infoPath)){ return '' }
-
- $sshLine = Get-Content $infoPath | Where-Object { $_ -match '^\s*ssh\s+' } | Select-Object -First 1
- if([string]::IsNullOrWhiteSpace($sshLine)){ return '' }
-
- return ($sshLine -replace '^\s*ssh\s+', '').Trim()
- }
-
- function ConvertTo-PowerShellLiteral {
- param([AllowNull()][string]$Value)
- if($null -eq $Value){ return "''" }
- return "'" + ($Value -replace "'", "''") + "'"
- }
-
- function ConvertTo-CmdDoubleQuoted {
- param([AllowNull()][string]$Value)
- if($null -eq $Value){ return '""' }
- return '"' + ($Value -replace '"', '""') + '"'
- }
-
- function Get-DataSourceFromConfig {
- param([string]$ConfigPath)
- if(!(Test-Path $ConfigPath)){ return '' }
-
- $raw = Get-Content $ConfigPath -Raw
- $match = [regex]::Match($raw, 'Data Source=([^;]+);', [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
- if($match.Success){
- return $match.Groups[1].Value.Trim()
- }
-
- return ''
- }
-
- function Set-DataSourceInConfig {
- param(
- [string]$ConfigPath,
- [string]$EffectiveDbPath
- )
-
- if(!(Test-Path $ConfigPath)){ return }
-
- $raw = Get-Content $ConfigPath -Raw
- $updated = [regex]::Replace(
- $raw,
- 'Data Source=[^;]*;',
- ('Data Source=' + $EffectiveDbPath + ';'),
- [System.Text.RegularExpressions.RegexOptions]::IgnoreCase
- )
-
- if($updated -ne $raw){
- Set-Content -Path $ConfigPath -Value $updated -Encoding UTF8
- Write-Host "Updated ConnectionString Data Source to $EffectiveDbPath"
- }
- }
-
- function Get-BaseUrlFromSite {
- param($Site)
-
- $httpBind = $Site.Bindings.Collection | Where-Object { $_.protocol -eq 'http' } | Select-Object -First 1
- if($httpBind){
- $parts = $httpBind.bindingInformation.Split(':')
- $port = $parts[1]
- if([string]::IsNullOrWhiteSpace($port)){ $port = '80' }
- return ('http://127.0.0.1:' + $port)
- }
-
- return 'http://127.0.0.1'
- }
-
- function Invoke-DeployCore {
- Ensure-Command git
- Import-Module WebAdministration
-
- $site = Get-Website -Name $SiteName
- if(!$site){ throw "IIS site not found: $SiteName" }
-
- if([string]::IsNullOrWhiteSpace($AppPool)){
- $AppPool = $site.applicationPool
- }
-
- if([string]::IsNullOrWhiteSpace($PublicDir)){
- $PublicDir = $site.physicalPath
- }
-
- if([string]::IsNullOrWhiteSpace($WorkDir)){
- $pd = [Environment]::ExpandEnvironmentVariables($PublicDir)
- $pd = $pd.Trim().Trim('"')
- $pd = $pd.TrimEnd('\','/')
-
- if((Split-Path $pd -Leaf).ToLower() -eq 'public'){
- $WorkDir = Split-Path $pd -Parent
- } else {
- $WorkDir = $pd
- }
- }
-
- if([string]::IsNullOrWhiteSpace($BaseUrl)){
- $BaseUrl = Get-BaseUrlFromSite -Site $site
- }
-
- $currentPublicDir = $PublicDir
- $currentConfigPath = Join-Path $currentPublicDir 'web.config'
- $effectiveDbPath = $DbPath
- if([string]::IsNullOrWhiteSpace($effectiveDbPath)){
- $effectiveDbPath = Get-DataSourceFromConfig -ConfigPath $currentConfigPath
- }
-
- if([string]::IsNullOrWhiteSpace($effectiveDbPath)){
- throw 'No database path was provided and no existing Data Source could be read from the current web.config'
- }
-
- Write-Host "Stopping IIS site $SiteName and app pool $AppPool"
- try { Stop-Website -Name $SiteName } catch { }
- try { Stop-WebAppPool -Name $AppPool } catch { }
-
- Ensure-Dir (Split-Path $WorkDir -Parent)
- if((Test-Path $WorkDir) -and !(Test-Path (Join-Path $WorkDir '.git'))){
- $bak = ($WorkDir.TrimEnd('\') + '_pre_git_' + (Get-Date -Format 'yyyyMMdd_HHmmss'))
- Write-Host "Existing non-git folder detected. Moving to $bak"
- Move-Item -Force $WorkDir $bak
- }
-
- if(!(Test-Path $WorkDir)){
- Write-Host "Cloning $Repo -> $WorkDir"
- git clone $Repo $WorkDir
- }
-
- Push-Location $WorkDir
- try {
- Write-Host "Updating to origin/$Branch"
- git fetch origin
- git checkout $Branch
- & git reset --hard ("origin/" + $Branch)
- } finally {
- Pop-Location
- }
-
- if((Split-Path $WorkDir -Leaf).ToLower() -eq 'public'){
- $WorkDir = Split-Path $WorkDir -Parent
- }
-
- $PublicDir = Join-Path $WorkDir 'public'
- $cfg = Join-Path $PublicDir 'web.config'
-
- Set-ItemProperty ('IIS:\Sites\' + $SiteName) -Name physicalPath -Value $PublicDir
- Set-ItemProperty ('IIS:\Sites\' + $SiteName) -Name applicationPool -Value $AppPool
- Set-ItemProperty ('IIS:\AppPools\' + $AppPool) -Name processModel.identityType -Value NetworkService
-
- Set-DataSourceInConfig -ConfigPath $cfg -EffectiveDbPath $effectiveDbPath
-
- $dbFolder = Split-Path $effectiveDbPath -Parent
- if(!(Test-Path $dbFolder)){
- Ensure-Dir $dbFolder
- }
- icacls $dbFolder /grant ("IIS AppPool\" + $AppPool + ":(OI)(CI)(M)") /T | Out-Null
-
- Push-Location $WorkDir
- try {
- if($RunMigrations){
- Write-Host 'Running standard migrations'
- cscript //nologo scripts\runMigrations.vbs up
- }
-
- if(-not $SkipLegacyIsBusinessMigration){
- $legacyPath = Join-Path $WorkDir $LegacyMigrationScript
- if(!(Test-Path $legacyPath)){
- throw "Legacy migration script not found: $legacyPath"
- }
-
- Write-Host 'Running legacy IsBusiness migration'
- cscript //nologo $legacyPath $effectiveDbPath
- }
- } finally {
- Pop-Location
- }
-
- if((Get-WebAppPoolState -Name $AppPool).Value -eq 'Started'){
- Restart-WebAppPool -Name $AppPool
- } else {
- Start-WebAppPool -Name $AppPool
- }
- Start-Website $SiteName
-
- Start-Sleep -Seconds 1
-
- $paths = @('/','/territories','/households','/householder-names')
- foreach($path in $paths){
- $url = $BaseUrl + $path
- $response = Invoke-WebRequest -UseBasicParsing -Uri $url -TimeoutSec 30
- Write-Host ("OK " + $path + ' -> ' + $response.StatusCode)
- }
-
- Write-Host 'Deploy complete.'
- }
-
- if($UseRemoteSsh -and !$RunRemoteCore -and [string]::IsNullOrWhiteSpace($RemoteTarget)){
- $RemoteTarget = Get-DefaultRemoteTargetFromInfo
- }
-
- if($UseRemoteSsh -and !$RunRemoteCore -and -not [string]::IsNullOrWhiteSpace($RemoteTarget)){
- Ensure-Command $SshExe
- Ensure-Command $ScpExe
-
- $remoteScriptPath = 'C:\Windows\Temp\deploy-test-territory-git.ps1'
- $scpDestination = "${RemoteTarget}:C:/Windows/Temp/deploy-test-territory-git.ps1"
-
- Write-Host "Copying deploy script to $RemoteTarget"
- & $ScpExe -P $RemotePort $PSCommandPath $scpDestination
- if($LASTEXITCODE -ne 0){ throw 'scp failed' }
-
- $remoteCommand = New-Object System.Collections.Generic.List[string]
- @(
- 'powershell',
- '-NoProfile',
- '-ExecutionPolicy', 'Bypass',
- '-File', (ConvertTo-CmdDoubleQuoted $remoteScriptPath),
- '-RunRemoteCore',
- '-Repo', (ConvertTo-CmdDoubleQuoted $Repo),
- '-Branch', (ConvertTo-CmdDoubleQuoted $Branch),
- '-SiteName', (ConvertTo-CmdDoubleQuoted $SiteName)
- ) | ForEach-Object { [void]$remoteCommand.Add($_) }
-
- if(-not [string]::IsNullOrWhiteSpace($AppPool)){
- [void]$remoteCommand.Add('-AppPool')
- [void]$remoteCommand.Add((ConvertTo-CmdDoubleQuoted $AppPool))
- }
-
- if(-not [string]::IsNullOrWhiteSpace($WorkDir)){
- [void]$remoteCommand.Add('-WorkDir')
- [void]$remoteCommand.Add((ConvertTo-CmdDoubleQuoted $WorkDir))
- }
-
- if(-not [string]::IsNullOrWhiteSpace($PublicDir)){
- [void]$remoteCommand.Add('-PublicDir')
- [void]$remoteCommand.Add((ConvertTo-CmdDoubleQuoted $PublicDir))
- }
-
- if(-not [string]::IsNullOrWhiteSpace($BaseUrl)){
- [void]$remoteCommand.Add('-BaseUrl')
- [void]$remoteCommand.Add((ConvertTo-CmdDoubleQuoted $BaseUrl))
- }
-
- if(-not [string]::IsNullOrWhiteSpace($DbPath)){
- [void]$remoteCommand.Add('-DbPath')
- [void]$remoteCommand.Add((ConvertTo-CmdDoubleQuoted $DbPath))
- }
-
- if(-not [string]::IsNullOrWhiteSpace($LegacyMigrationScript)){
- [void]$remoteCommand.Add('-LegacyMigrationScript')
- [void]$remoteCommand.Add((ConvertTo-CmdDoubleQuoted $LegacyMigrationScript))
- }
-
- if($RunMigrations){ $remoteCommand += '-RunMigrations' }
- if($SkipLegacyIsBusinessMigration){ $remoteCommand += '-SkipLegacyIsBusinessMigration' }
-
- Write-Host "Executing remote deploy on $RemoteTarget"
- & $SshExe -p $RemotePort $RemoteTarget ($remoteCommand -join ' ')
- if($LASTEXITCODE -ne 0){ throw 'remote deploy failed' }
-
- exit 0
- }
-
- Invoke-DeployCore
|