# Hard-coded deploy settings. Fill these in before running. $FtpHost = "xTr1m.com" $FtpUser = "xTr1m" $Password = $null # prompted at runtime $RemoteDir = "/httpdocs/picknplay" $ProjectPath = "..\\GameList.csproj" $Configuration = "Release" $Runtime = "win-x64" $PublishDir = "$env:TEMP\\GameList-publish" $SelfContained = $false $WinScpPath = "C:\\Users\\frank\\AppData\\Local\\Programs\\WinSCP\\WinSCP.com" $RecycleAppPool = $true $AppPoolName = "xTr1m.com(domain)(4.0)(pool)" $WinRmComputer = "xTr1m.com" $WinRmCredentialUser = "Administrator" $UseWinRmHttps = $true # set false if using HTTP + TrustedHosts $RemoteSitePath = "C:\Inetpub\vhosts\xTr1m.com\httpdocs\picknplay" $RunEfMigrations = $false # set to $false to skip remote database update <#! .SYNOPSIS Publish the app and mirror the output to an FTP-deployed IIS site. .DESCRIPTION - Builds with dotnet publish. - Uses WinSCP (ftp) to mirror publish output into $RemoteDir (deletes extraneous remote files). - Optionally recycles the IIS app pool remotely via WinRM (no RDP needed). .PREREQS - WinSCP.com available in PATH or set $WinScpPath. - FTP user must have write/delete rights to $RemoteDir. - WinRM must be enabled for remote app pool recycle (set $RecycleAppPool = $false otherwise). .EXAMPLE pwsh ./scripts/deploy-ftp.ps1 #> Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" function Assert-Tool { param([string]$Name) if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) { throw "Required tool '$Name' not found. Install it or update paths." } } Assert-Tool "dotnet" Assert-Tool $WinScpPath function Read-PlainOrPrompt([object]$Value, [string]$Prompt, [bool]$Secure = $false) { if ($Value -is [string] -and -not [string]::IsNullOrWhiteSpace($Value)) { return $Value } if ($Secure) { $pwd = Read-Host -Prompt $Prompt -AsSecureString $ptr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($pwd) try { return [Runtime.InteropServices.Marshal]::PtrToStringUni($ptr) } finally { if ($ptr -ne [IntPtr]::Zero) { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ptr) } } } return Read-Host -Prompt $Prompt } $Password = Read-PlainOrPrompt $Password "Password" $true $WinRmAuth = "Basic" # Basic for local admin over HTTPS; use Default/Kerberos if joined to domain Write-Host "1) Publishing..." -ForegroundColor Cyan if (Test-Path $PublishDir) { Remove-Item $PublishDir -Recurse -Force -ErrorAction SilentlyContinue } New-Item -ItemType Directory -Force -Path $PublishDir | Out-Null $publishArgs = @("publish", $ProjectPath, "-c", $Configuration, "-r", $Runtime, "-o", $PublishDir) if (-not $SelfContained) { $publishArgs += "--self-contained=false" } dotnet @publishArgs if ($RecycleAppPool) { Write-Host "2) Stopping IIS app pool via WinRM..." -ForegroundColor Cyan $sec = ConvertTo-SecureString $Password -AsPlainText -Force $cred = New-Object pscredential($WinRmCredentialUser, $sec) $invokeParams = @{ ComputerName = $WinRmComputer Credential = $cred ScriptBlock = { Import-Module WebAdministration Stop-WebAppPool -Name $using:AppPoolName -ErrorAction SilentlyContinue Get-Process GameList -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue Get-Process dotnet -ErrorAction SilentlyContinue | Where-Object { $_.Path -like "*picknplay*" } | Stop-Process -Force -ErrorAction SilentlyContinue } } if ($UseWinRmHttps) { $invokeParams["UseSSL"] = $true } if ($WinRmAuth) { $invokeParams["Authentication"] = $WinRmAuth } try { Invoke-Command @invokeParams } catch { Write-Warning "WinRM stop failed: $($_.Exception.Message)." } } Write-Host "3) Syncing via WinSCP (FTP mirror with delete)..." -ForegroundColor Cyan $tempScript = New-TemporaryFile @" option batch continue option confirm off open ftp://$($FtpUser):$($Password.Replace('`n','').Replace('`r',''))@$FtpHost lcd $PublishDir cd $RemoteDir synchronize remote . -delete -filemask="|web.config;App_Data/;logs/" exit "@ | Set-Content -Path $tempScript -Encoding UTF8 & $WinScpPath "/ini=nul" "/script=$tempScript" Remove-Item $tempScript -ErrorAction SilentlyContinue if ($RecycleAppPool) { Write-Host "4) Starting IIS app pool via WinRM..." -ForegroundColor Cyan $sec = ConvertTo-SecureString $Password -AsPlainText -Force $cred = New-Object pscredential($WinRmCredentialUser, $sec) $invokeParams = @{ ComputerName = $WinRmComputer Credential = $cred ScriptBlock = { Import-Module WebAdministration Start-WebAppPool -Name $using:AppPoolName } } if ($UseWinRmHttps) { $invokeParams["UseSSL"] = $true } if ($WinRmAuth) { $invokeParams["Authentication"] = $WinRmAuth } try { Invoke-Command @invokeParams } catch { Write-Warning "WinRM start failed: $($_.Exception.Message)." } } if ($RunEfMigrations) { Write-Host "5) Running EF Core migrations on remote site..." -ForegroundColor Cyan $sec = ConvertTo-SecureString $Password -AsPlainText -Force $cred = New-Object pscredential($WinRmCredentialUser, $sec) $invokeParams = @{ ComputerName = $WinRmComputer Credential = $cred ScriptBlock = { param($sitePath) Set-Location $sitePath if (-not (Get-Command dotnet ef -ErrorAction SilentlyContinue)) { throw "dotnet ef not available on remote host. Install SDK or set `$RunEfMigrations = $false." } dotnet ef database update --no-build } ArgumentList = @($RemoteSitePath) } if ($UseWinRmHttps) { $invokeParams["UseSSL"] = $true } if ($WinRmAuth) { $invokeParams["Authentication"] = $WinRmAuth } try { Invoke-Command @invokeParams } catch { Write-Warning "WinRM migrations failed: $($_.Exception.Message)." } } Write-Host "Done." -ForegroundColor Green