<# .SYNOPSIS Idempotent migration runner that takes the OtOpcUaConfig database from the v1 schema (with ConfigGeneration / ClusterNodeGenerationState) to the v2 hosting-aligned schema (with Deployment / NodeDeploymentState / ConfigEdit / DataProtectionKeys). .DESCRIPTION Backs the database up, applies the idempotent EF migration script, then validates that expected tables exist and legacy tables are gone. Safe to re-run — the EF script itself is idempotent, and the backup picks a unique filename per invocation. .PARAMETER ConnectionString Mandatory. Full ADO.NET connection string with permissions to BACKUP DATABASE and apply DDL on the target ConfigDb. .PARAMETER BackupPath Optional. Full path for the backup file. Defaults to a timestamped path under $env:TEMP. .EXAMPLE .\Migrate-To-V2.ps1 -ConnectionString "Server=sql01;Database=OtOpcUaConfig;Trusted_Connection=True;TrustServerCertificate=True" #> [CmdletBinding()] param( [Parameter(Mandatory)][string] $ConnectionString, [string] $BackupPath = "$env:TEMP\OtOpcUa-V1-Backup-$(Get-Date -Format yyyyMMddHHmmss).bak" ) $ErrorActionPreference = 'Stop' if (-not (Get-Command Invoke-Sqlcmd -ErrorAction SilentlyContinue)) { throw "Invoke-Sqlcmd not available. Install module: Install-Module SqlServer -Scope CurrentUser" } Write-Host "Step 1/4 — Backup ConfigDb to $BackupPath" -ForegroundColor Cyan Invoke-Sqlcmd -ConnectionString $ConnectionString ` -Query "BACKUP DATABASE [OtOpcUaConfig] TO DISK = '$BackupPath' WITH FORMAT, COMPRESSION" Write-Host "Step 2/4 — Row counts (before)" -ForegroundColor Cyan $beforeCounts = Invoke-Sqlcmd -ConnectionString $ConnectionString -InputFile "$PSScriptRoot\count-rows.sql" $beforeCounts | Format-Table Write-Host "Step 3/4 — Apply Migrate-To-V2.sql" -ForegroundColor Cyan Invoke-Sqlcmd -ConnectionString $ConnectionString -InputFile "$PSScriptRoot\Migrate-To-V2.sql" -QueryTimeout 1800 Write-Host "Step 4/4 — Row counts (after) + validation" -ForegroundColor Cyan $afterCounts = Invoke-Sqlcmd -ConnectionString $ConnectionString -InputFile "$PSScriptRoot\count-rows.sql" $afterCounts | Format-Table $tablesNow = (Invoke-Sqlcmd -ConnectionString $ConnectionString ` -Query "SELECT name FROM sys.tables ORDER BY name").name foreach ($t in 'Deployment','NodeDeploymentState','ConfigEdit','DataProtectionKeys') { if ($tablesNow -notcontains $t) { throw "Expected v2 table $t missing." } } foreach ($t in 'ConfigGeneration','ClusterNodeGenerationState') { if ($tablesNow -contains $t) { throw "Legacy v1 table $t still present." } } Write-Host "Migration complete. Backup at $BackupPath" -ForegroundColor Green