<# .SYNOPSIS ROA2WEB - Auto-Deploy Monitor (Server-Side) - Ultrathin Monolith .DESCRIPTION Server-side script that monitors C:\Temp\ for new deployment packages and automatically deploys them using ROA2WEB-Console.ps1. Designed for ULTRATHIN MONOLITH architecture: - Single unified backend service (ROA2WEB-Backend) - All modules deployed together (Reports, Data Entry, Telegram) - Automatic detection and deployment Can run: - Via Scheduled Task (automated, silent) - Interactive mode (manual execution with menu) - Non-interactive mode (command-line automation) .PARAMETER Interactive Run in interactive mode with menu .PARAMETER WatchPath Path to monitor for deployment packages (default: C:\Temp) .PARAMETER StateFile Path to state file tracking last deployment (default: C:\Temp\ROA2WEB-Scripts\last-deploy.json) .PARAMETER ConsoleScriptPath Path to ROA2WEB-Console.ps1 script .PARAMETER LogPath Path to log file directory (default: C:\Temp\ROA2WEB-Scripts\Logs) .PARAMETER CheckOnly Check for updates without deploying .EXAMPLE .\Check-And-Deploy.ps1 Check for new packages and deploy automatically (silent) .EXAMPLE .\Check-And-Deploy.ps1 -Interactive Show interactive menu with deployment options .EXAMPLE .\Check-And-Deploy.ps1 -CheckOnly Check for new packages without deploying .NOTES Author: ROA2WEB Team Version: 1.0 (Auto-Deploy Monitor) Requires: Administrator privileges, PowerShell 5.1+ Designed to run via Scheduled Task for automated deployments. #> [CmdletBinding()] param( [switch]$Interactive, [string]$WatchPath = "C:\Temp", [string]$StateFile = "C:\Temp\ROA2WEB-Scripts\last-deploy.json", [string]$ConsoleScriptPath = "", [string]$LogPath = "C:\Temp\ROA2WEB-Scripts\Logs", [switch]$CheckOnly ) $ErrorActionPreference = "Stop" # ============================================================================= # CONFIGURATION # ============================================================================= $script:Config = @{ WatchPath = $WatchPath StateFile = $StateFile LogPath = $LogPath ConsoleScriptPath = if ($ConsoleScriptPath) { $ConsoleScriptPath } else { # Auto-detect ROA2WEB-Console.ps1 $possiblePaths = @( "C:\inetpub\wwwroot\roa2web\scripts\ROA2WEB-Console.ps1", (Join-Path $PSScriptRoot "ROA2WEB-Console.ps1") ) $found = $possiblePaths | Where-Object { Test-Path $_ } | Select-Object -First 1 if ($found) { $found } else { "" } } } # ============================================================================= # HELPER FUNCTIONS # ============================================================================= function Write-Log { param( [string]$Message, [ValidateSet("INFO", "SUCCESS", "WARNING", "ERROR")] [string]$Level = "INFO" ) $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $logMessage = "[$timestamp] [$Level] $Message" # Ensure log directory exists if (-not (Test-Path $script:Config.LogPath)) { New-Item -ItemType Directory -Path $script:Config.LogPath -Force | Out-Null } $logFile = Join-Path $script:Config.LogPath "check-and-deploy.log" Add-Content -Path $logFile -Value $logMessage # Also write to console if interactive if ($Interactive) { $color = switch ($Level) { "SUCCESS" { "Green" } "WARNING" { "Yellow" } "ERROR" { "Red" } default { "White" } } Write-Host $logMessage -ForegroundColor $color } } function Write-Step { param([string]$Message) Write-Host "`n[*] $Message" -ForegroundColor Cyan Write-Log -Message $Message -Level "INFO" } function Write-Success { param([string]$Message) Write-Host " [OK] $Message" -ForegroundColor Green Write-Log -Message $Message -Level "SUCCESS" } function Write-Error { param([string]$Message) Write-Host " [ERROR] $Message" -ForegroundColor Red Write-Log -Message $Message -Level "ERROR" } function Write-Warning { param([string]$Message) Write-Host " [WARN] $Message" -ForegroundColor Yellow Write-Log -Message $Message -Level "WARNING" } function Wait-ForKeyPress { Write-Host "`nPress any key to continue..." -ForegroundColor Gray $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") } # ============================================================================= # STATE MANAGEMENT # ============================================================================= function Get-DeploymentState { if (Test-Path $script:Config.StateFile) { try { return Get-Content -Path $script:Config.StateFile -Raw | ConvertFrom-Json } catch { Write-Warning "Failed to load state file, starting fresh" return $null } } return $null } function Save-DeploymentState { param( [Parameter(Mandatory)] [hashtable]$DeploymentInfo ) try { # Load existing state $state = Get-DeploymentState $newEntry = @{ packageName = $DeploymentInfo.PackageName component = $DeploymentInfo.Component timestamp = (Get-Date).ToUniversalTime().ToString("o") status = $DeploymentInfo.Status services = $DeploymentInfo.Services } if ($state) { # Add current deployment to history if (-not $state.history) { $state.history = @() } if ($state.lastDeployment) { $state.history = @($state.lastDeployment) + $state.history } # Keep only last 10 entries in history if ($state.history.Count -gt 10) { $state.history = $state.history | Select-Object -First 10 } $state.lastDeployment = $newEntry } else { $state = @{ lastDeployment = $newEntry history = @() } } # Ensure directory exists $stateDir = Split-Path $script:Config.StateFile -Parent if (-not (Test-Path $stateDir)) { New-Item -ItemType Directory -Path $stateDir -Force | Out-Null } # Save state $state | ConvertTo-Json -Depth 10 | Set-Content -Path $script:Config.StateFile Write-Success "Deployment state saved" } catch { Write-Error "Failed to save deployment state: $_" } } # ============================================================================= # PACKAGE DETECTION # ============================================================================= function Get-LatestDeploymentPackage { Write-Step "Scanning for deployment packages..." if (-not (Test-Path $script:Config.WatchPath)) { Write-Warning "Watch path does not exist: $($script:Config.WatchPath)" return $null } # Find all deploy-* folders $packages = Get-ChildItem -Path $script:Config.WatchPath -Directory | Where-Object { $_.Name -match "^deploy-\d{8}-\d{6}$" } | Sort-Object LastWriteTime -Descending if ($packages.Count -eq 0) { Write-Warning "No deployment packages found" return $null } $latest = $packages | Select-Object -First 1 Write-Success "Found $($packages.Count) package(s), latest: $($latest.Name)" return $latest } function Test-IsNewPackage { param([Parameter(Mandatory)]$Package) $state = Get-DeploymentState if (-not $state -or -not $state.lastDeployment) { Write-Success "No previous deployment found - this is a new package" return $true } $lastDeployed = $state.lastDeployment.packageName if ($Package.Name -ne $lastDeployed) { Write-Success "New package detected (last deployed: $lastDeployed)" return $true } Write-Log -Message "Package already deployed: $($Package.Name)" -Level "INFO" return $false } # ============================================================================= # DEPLOYMENT EXECUTION # ============================================================================= function Invoke-Deployment { param([Parameter(Mandatory)]$Package) Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan Write-Host " STARTING DEPLOYMENT" -ForegroundColor Cyan Write-Host ("=" * 70) -ForegroundColor Cyan Write-Host "`nPackage: $($Package.Name)" -ForegroundColor Yellow Write-Host "Path: $($Package.FullName)" -ForegroundColor Gray # Find ROA2WEB-Console.ps1 in package $consoleScript = Join-Path $Package.FullName "scripts\ROA2WEB-Console.ps1" if (-not (Test-Path $consoleScript)) { Write-Error "ROA2WEB-Console.ps1 not found in package: $consoleScript" return $false } try { Write-Step "Executing deployment via ROA2WEB-Console.ps1..." # Execute deployment (deploy all components) Push-Location (Split-Path $consoleScript -Parent) & $consoleScript -NonInteractive -Action DeployAll -PackagePath $Package.FullName # Capture exit code IMMEDIATELY (before any other command that might reset it) $exitCode = $LASTEXITCODE # Run OCR dependency check with auto-install Write-Log -Message "Checking and installing OCR dependencies..." -Level "INFO" & $consoleScript -NonInteractive -Action InstallOCR 2>&1 | ForEach-Object { Write-Log -Message $_ -Level "INFO" } Pop-Location # Check if exit code indicates success (0 = success) $deploySuccess = ($exitCode -eq 0) if ($deploySuccess) { Write-Success "Deployment completed successfully (exit code: $exitCode)" # Save deployment state Save-DeploymentState -DeploymentInfo @{ PackageName = $Package.Name Component = "All" Status = "Success" Services = @{ backend = "Running (Ultrathin Monolith - All modules)" } } Write-Host "`n" + ("=" * 70) -ForegroundColor Green Write-Host " DEPLOYMENT SUCCESSFUL" -ForegroundColor Green Write-Host ("=" * 70) -ForegroundColor Green return $true } else { Write-Error "Deployment failed (exit code: $exitCode)" # Save failure state Save-DeploymentState -DeploymentInfo @{ PackageName = $Package.Name Component = "All" Status = "Failed" Services = @{} } Write-Host "`n" + ("=" * 70) -ForegroundColor Red Write-Host " DEPLOYMENT FAILED" -ForegroundColor Red Write-Host ("=" * 70) -ForegroundColor Red return $false } } catch { Write-Error "Deployment exception: $_" Write-Log -Message $_.ScriptStackTrace -Level "ERROR" # Save failure state Save-DeploymentState -DeploymentInfo @{ PackageName = $Package.Name Component = "All" Status = "Failed" Services = @{} } return $false } } # ============================================================================= # INTERACTIVE MENU # ============================================================================= function Show-MainMenu { Clear-Host Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan Write-Host " ROA2WEB - Auto-Deploy Monitor (Interactive)" -ForegroundColor Cyan Write-Host ("=" * 70) -ForegroundColor Cyan Write-Host "" Write-Host " [1] Check for Updates (No Deploy)" -ForegroundColor White Write-Host " (Scan for new packages without deploying)" -ForegroundColor Gray Write-Host "" Write-Host " [2] Check and Deploy Now" -ForegroundColor White Write-Host " (Find latest package and deploy if new)" -ForegroundColor Gray Write-Host "" Write-Host " [3] View Deployment History" -ForegroundColor White Write-Host " (Show previous deployments)" -ForegroundColor Gray Write-Host "" Write-Host " [4] View Current Configuration" -ForegroundColor White Write-Host " (Display monitor settings)" -ForegroundColor Gray Write-Host "" Write-Host " [Q] Quit" -ForegroundColor Red Write-Host "" Write-Host ("=" * 70) -ForegroundColor Cyan do { Write-Host "`nYour choice: " -ForegroundColor Yellow -NoNewline $choice = Read-Host switch ($choice.ToUpper()) { "1" { return "CheckOnly" } "2" { return "CheckAndDeploy" } "3" { return "History" } "4" { return "ViewConfig" } "Q" { return "Quit" } default { Write-Host "Invalid choice. Please select 1-4 or Q." -ForegroundColor Red } } } while ($true) } function Show-DeploymentHistory { Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan Write-Host " Deployment History" -ForegroundColor Cyan Write-Host ("=" * 70) -ForegroundColor Cyan $state = Get-DeploymentState if (-not $state) { Write-Host "`nNo deployment history found" -ForegroundColor Yellow return } if ($state.lastDeployment) { Write-Host "`nLast Deployment:" -ForegroundColor Yellow Write-Host " Package: $($state.lastDeployment.packageName)" -ForegroundColor Gray Write-Host " Component: $($state.lastDeployment.component)" -ForegroundColor Gray Write-Host " Timestamp: $($state.lastDeployment.timestamp)" -ForegroundColor Gray Write-Host " Status: $($state.lastDeployment.status)" -ForegroundColor $(if ($state.lastDeployment.status -eq "Success") { "Green" } else { "Red" }) } if ($state.history -and $state.history.Count -gt 0) { Write-Host "`nPrevious Deployments:" -ForegroundColor Yellow $i = 1 foreach ($entry in $state.history) { Write-Host " [$i] $($entry.packageName) - $($entry.timestamp) - $($entry.status)" -ForegroundColor Gray $i++ } } Write-Host ("=" * 70) -ForegroundColor Cyan } function Show-Configuration { Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan Write-Host " Current Configuration" -ForegroundColor Cyan Write-Host ("=" * 70) -ForegroundColor Cyan Write-Host "" Write-Host " Watch Path: $($script:Config.WatchPath)" -ForegroundColor Gray Write-Host " State File: $($script:Config.StateFile)" -ForegroundColor Gray Write-Host " Log Path: $($script:Config.LogPath)" -ForegroundColor Gray Write-Host " Console Script: $($script:Config.ConsoleScriptPath)" -ForegroundColor Gray Write-Host "" Write-Host ("=" * 70) -ForegroundColor Cyan } # ============================================================================= # MAIN CHECK & DEPLOY LOGIC # ============================================================================= function Invoke-CheckAndDeploy { param([switch]$CheckOnly) Write-Log -Message "=== Check and Deploy Started ===" -Level "INFO" # Get latest package $latestPackage = Get-LatestDeploymentPackage if (-not $latestPackage) { Write-Log -Message "No deployment packages found" -Level "INFO" return $false } # Check if it's a new package $isNew = Test-IsNewPackage -Package $latestPackage if (-not $isNew) { Write-Log -Message "No new packages to deploy" -Level "INFO" return $false } if ($CheckOnly) { Write-Success "New package available: $($latestPackage.Name)" Write-Host "`nRun without -CheckOnly to deploy" -ForegroundColor Yellow return $false } # Deploy the package $deploySuccess = Invoke-Deployment -Package $latestPackage Write-Log -Message "=== Check and Deploy Completed (Success: $deploySuccess) ===" -Level "INFO" return $deploySuccess } # ============================================================================= # MAIN EXECUTION FLOW # ============================================================================= function Main { # Check if running as Administrator $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object Security.Principal.WindowsPrincipal($currentUser) $isAdmin = $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) if (-not $isAdmin) { Write-Host "`n[ERROR] This script requires Administrator privileges" -ForegroundColor Red Write-Host "Please run PowerShell as Administrator and try again.`n" -ForegroundColor Yellow exit 1 } # Interactive mode if ($Interactive) { do { $mainChoice = Show-MainMenu switch ($mainChoice) { "CheckOnly" { Invoke-CheckAndDeploy -CheckOnly Wait-ForKeyPress } "CheckAndDeploy" { Invoke-CheckAndDeploy Wait-ForKeyPress } "History" { Show-DeploymentHistory Wait-ForKeyPress } "ViewConfig" { Show-Configuration Wait-ForKeyPress } "Quit" { Write-Host "`nGoodbye!`n" -ForegroundColor Cyan return } } } while ($true) } # Non-interactive mode (for Scheduled Task) else { Invoke-CheckAndDeploy -CheckOnly:$CheckOnly | Out-Null } } # Run main Main