# Performance West Document Conversion Worker — Windows Installation # Run as Administrator in PowerShell on the Windows VM # # What this does: # 1. Verifies Python + Microsoft Word are installed # 2. Installs Python dependencies (pywin32, minio) # 3. Copies docserver_worker.py + config to C:\docserver\ # 4. Creates a Task Scheduler task that starts the worker at system boot # (runs as the installing user, "Run whether user is logged on or not" # — Word COM works in session 0 on Server 2019 with desktop interaction) # # Prerequisites: # - Windows 10/11 Pro or Windows Server 2022 # - Microsoft Word installed (only Word needed, not full Office) # - Python 3.12+ (python.org — check "Add to PATH") # - Outbound HTTPS to minio.performancewest.net (or wherever MinIO lives) # - No inbound ports required — the VM connects OUT to MinIO only # # Usage: # .\install.ps1 -MinioEndpoint "minio.performancewest.net" ` # -MinioPort 443 ` # -MinioSecure $true ` # -MinioAccessKey "your_access_key" ` # -MinioSecretKey "your_secret_key" param( [Parameter(Mandatory=$true)] [string]$MinioEndpoint, [int] $MinioPort = 9000, [bool] $MinioSecure = $false, [Parameter(Mandatory=$true)] [string]$MinioAccessKey, [Parameter(Mandatory=$true)] [string]$MinioSecretKey, [string]$MinioBucket = "performancewest", [int] $PollInterval = 3, [string]$AppDir = "C:\docserver" ) $ErrorActionPreference = "Stop" Write-Host "" Write-Host "=== Performance West Document Conversion Worker Setup ===" -ForegroundColor Cyan Write-Host "" # ── 0. Admin check ──────────────────────────────────────────────────────────── if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]"Administrator")) { Write-Host "ERROR: Run this script as Administrator!" -ForegroundColor Red exit 1 } # ── 1. Python check ─────────────────────────────────────────────────────────── Write-Host "Checking Python..." -ForegroundColor Yellow $python = Get-Command python -ErrorAction SilentlyContinue if (-not $python) { Write-Host "ERROR: Python not found." -ForegroundColor Red Write-Host " Download from https://python.org/downloads" -ForegroundColor Red Write-Host " Install with 'Add Python to PATH' checked, then re-run." -ForegroundColor Red exit 1 } $pyVersion = python --version 2>&1 Write-Host " Found: $pyVersion" -ForegroundColor Green # ── 2. Word check ───────────────────────────────────────────────────────────── Write-Host "Checking Microsoft Word..." -ForegroundColor Yellow try { $word = New-Object -ComObject Word.Application $wordVersion = $word.Version $word.Quit() [System.Runtime.Interopservices.Marshal]::ReleaseComObject($word) | Out-Null Write-Host " Found: Microsoft Word $wordVersion" -ForegroundColor Green } catch { Write-Host "ERROR: Microsoft Word not found or COM registration broken." -ForegroundColor Red Write-Host " Install Microsoft Word and retry." -ForegroundColor Red exit 1 } # ── 3. Create application directory ────────────────────────────────────────── Write-Host "Creating $AppDir..." -ForegroundColor Yellow New-Item -ItemType Directory -Path $AppDir -Force | Out-Null New-Item -ItemType Directory -Path "$AppDir\logs" -Force | Out-Null New-Item -ItemType Directory -Path "$AppDir\temp" -Force | Out-Null Copy-Item -Path "$PSScriptRoot\docserver_worker.py" -Destination $AppDir -Force Copy-Item -Path "$PSScriptRoot\requirements.txt" -Destination $AppDir -Force Write-Host " Files copied." -ForegroundColor Green # ── 4. Install Python dependencies ─────────────────────────────────────────── Write-Host "Installing Python dependencies..." -ForegroundColor Yellow python -m pip install --upgrade pip --quiet python -m pip install -r "$AppDir\requirements.txt" --quiet # pywin32 post-install COM registration $pyPrefix = python -c "import sys; print(sys.prefix)" $postInstall = "$pyPrefix\Scripts\pywin32_postinstall.py" if (Test-Path $postInstall) { python $postInstall -install 2>$null Write-Host " pywin32 COM registration done." -ForegroundColor Green } Write-Host " Dependencies installed." -ForegroundColor Green # ── 5. Write environment config ────────────────────────────────────────────── $envContent = @" MINIO_ENDPOINT=$MinioEndpoint MINIO_PORT=$MinioPort MINIO_SECURE=$($MinioSecure.ToString().ToLower()) MINIO_ACCESS_KEY=$MinioAccessKey MINIO_SECRET_KEY=$MinioSecretKey MINIO_BUCKET=$MinioBucket POLL_INTERVAL=$PollInterval LOG_DIR=$AppDir\logs TEMP_DIR=$AppDir\temp "@ Set-Content -Path "$AppDir\docserver.env" -Value $envContent -Encoding UTF8 Write-Host " Config written to $AppDir\docserver.env" -ForegroundColor Green # ── 6. Write startup batch script ──────────────────────────────────────────── # Reads .env, sets env vars, then starts the worker $startScript = @' @echo off setlocal cd /d C:\docserver :: Load environment variables from docserver.env for /f "usebackq tokens=1,* delims==" %%a in ("C:\docserver\docserver.env") do ( if not "%%a"=="" ( set "line=%%a" if not "!line:~0,1!"=="#" set "%%a=%%b" ) ) :: Start the worker python C:\docserver\docserver_worker.py >> C:\docserver\logs\worker.log 2>&1 endlocal '@ # Note: the batch script uses delayed expansion so we write it separately $startScriptFull = @' @echo off setlocal enabledelayedexpansion cd /d C:\docserver echo [%date% %time%] Starting Performance West Docserver Worker... for /f "usebackq tokens=1,* delims==" %%a in ("C:\docserver\docserver.env") do ( set "ln=%%a" if not "!ln:~0,1!"=="#" ( if not "%%a"=="" set "%%a=%%b" ) ) python C:\docserver\docserver_worker.py echo [%date% %time%] Worker exited with code %errorlevel%. endlocal '@ Set-Content -Path "$AppDir\start_worker.bat" -Value $startScriptFull -Encoding ASCII # ── 7. Register Task Scheduler task ────────────────────────────────────────── Write-Host "Registering Task Scheduler task..." -ForegroundColor Yellow $taskName = "PW-DocserverWorker" $currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue $action = New-ScheduledTaskAction ` -Execute "cmd.exe" ` -Argument "/c `"$AppDir\start_worker.bat`"" ` -WorkingDirectory $AppDir $trigger = New-ScheduledTaskTrigger -AtStartup $settings = New-ScheduledTaskSettingsSet ` -ExecutionTimeLimit (New-TimeSpan -Hours 0) ` -RestartCount 10 ` -RestartInterval (New-TimeSpan -Minutes 1) ` -StartWhenAvailable ` -MultipleInstances IgnoreNew ` -AllowStartIfOnBatteries ` -DontStopIfGoingOnBatteries # Register as the current user with "Run whether user is logged on or not" # This allows the task to start at boot without requiring an interactive login. # Word COM works in session 0 on Windows Server 2019. $password = Read-Host -Prompt "Enter password for $currentUser (needed for 'Run whether logged on or not')" -AsSecureString $plainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto( [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password) ) Register-ScheduledTask ` -TaskName $taskName ` -Action $action ` -Trigger $trigger ` -Settings $settings ` -User $currentUser ` -Password $plainPassword ` -RunLevel Highest ` -Description "Performance West DOCX-to-PDF worker (MinIO + Word COM)" | Out-Null Write-Host " Task '$taskName' registered (runs at boot, restarts on failure)." -ForegroundColor Green # ── 8. Start the task now ───────────────────────────────────────────────────── Write-Host "Starting worker task..." -ForegroundColor Yellow Start-ScheduledTask -TaskName $taskName Start-Sleep -Seconds 5 # ── 9. Verify ──────────────────────────────────────────────────────────────── $taskInfo = Get-ScheduledTask -TaskName $taskName Write-Host "" Write-Host "═══════════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host " Setup Complete" -ForegroundColor Green Write-Host "═══════════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host "" Write-Host " Task state: $($taskInfo.State)" Write-Host " App dir: $AppDir" Write-Host " Logs: $AppDir\logs\worker.log" Write-Host " Config: $AppDir\docserver.env" Write-Host "" Write-Host " MinIO endpoint: $MinioEndpoint`:$MinioPort" Write-Host " MinIO bucket: $MinioBucket" Write-Host " Poll interval: ${PollInterval}s" Write-Host "" Write-Host " The worker polls minio://$MinioBucket/to-convert/" -ForegroundColor White Write-Host " Converted PDFs appear in minio://$MinioBucket/converted/" -ForegroundColor White Write-Host "" Write-Host " To verify manually, place a .docx in to-convert/ and watch" -ForegroundColor White Write-Host " converted/ for the resulting .pdf (should appear within a few seconds)." -ForegroundColor White Write-Host "" Write-Host "═══════════════════════════════════════════════════════════" -ForegroundColor Cyan