Windows Guest Initialization &
Database Migration
From first boot to a fully configured, migrated environment — automatically. Covers AWS EC2, Azure VM, and on-premises Windows Server deployments.
End-to-end automation: Windows VM first-boot configuration (hostname, timezone, NTP, WinRM, volumes, service account) → prerequisite software installation (Python, ODBC Driver, pip packages) → database migration execution (schema, data, constraints) → post-migration validation and reporting. A single PowerShell script handles the entire flow unattended.
Manual Setup vs. Endrias Bridge Automated Init
| Task | Manual Process | Endrias Bridge Automated |
|---|---|---|
| Hostname configuration | RDP → System Properties → rename | ✔ Auto from cloud metadata |
| Timezone / NTP | Control Panel → Date/Time | ✔ tzutil + w32tm configured |
| WinRM / PS Remoting | winrm quickconfig manually | ✔ Enabled & hardened automatically |
| Disk volume extension | Disk Management GUI | ✔ Resize-Partition to max |
| Python + ODBC install | Download, run installer, configure PATH | ✔ Silent MSI install, verified |
| Migration config | Manually edit JSON / set up GUI | ✔ Read from JSON or cloud user-data |
| Run migration | Launch GUI, step through wizard | ✔ Headless CLI, streamed log |
| Idempotency | Risk of running twice → duplicates | ✔ Sentinel file prevents re-run |
| Total time (human) | 2–4 hours | ✔ ~15 min unattended |
Architecture Diagram
Prerequisites
| Category | Requirement | Notes |
|---|---|---|
| OS | Windows Server 2019 or 2022 | Desktop Experience or Server Core |
| RAM | 8 GB minimum · 16 GB recommended | 16 GB+ for 1 TB+ database migrations |
| Disk | 50 GB free on C:\ | For Python, ODBC driver, logs, and migration temp space |
| Network outbound | HTTPS (443) to python.org, Microsoft download | Also outbound to source/target DB ports |
| PowerShell | 5.1 minimum · 7.x recommended | Script uses #Requires -RunAsAdministrator |
| Source DB permission | db_datareader + VIEW DEFINITION | Required to reflect schema and read rows |
| Target DB permission | db_owner or db_ddladmin + db_datawriter | Required to create tables and insert rows |
| Execution Policy | RemoteSigned or Bypass | Set-ExecutionPolicy RemoteSigned -Force |
Required Firewall / Security Group Ports
| Target Engine | Port | Protocol | Direction |
|---|---|---|---|
| SQL Server / Azure SQL / AWS RDS SQL | 1433 | TCP | Outbound from migration VM |
| PostgreSQL / RDS PG / Aurora PG | 5432 | TCP | Outbound from migration VM |
| MySQL / RDS MySQL / Aurora MySQL | 3306 | TCP | Outbound from migration VM |
| WinRM HTTP (init phase) | 5985 | TCP | Inbound (management) |
| WinRM HTTPS | 5986 | TCP | Inbound (management) |
| Python / pip install | 443 | TCP | Outbound to pypi.org, python.org |
First-Boot Trigger Methods
AWS EC2 — User Data
Paste the PowerShell script (or a bootstrap stub) into the EC2 instance User Data field at launch. Runs as NT AUTHORITY\SYSTEM on first boot.
<powershell> # Option A: run the script directly from S3 $url = "https://your-bucket.s3.amazonaws.com/Endrias Bridge_FirstBoot_Setup.ps1" Invoke-WebRequest -Uri $url -OutFile "C:\Endrias Bridge_FirstBoot_Setup.ps1" powershell.exe -ExecutionPolicy Bypass -File "C:\Endrias Bridge_FirstBoot_Setup.ps1" </powershell>
Azure VM — Custom Script Extension
Deploy via ARM template or Azure Portal → Extensions → Custom Script Extension. The script runs as SYSTEM on first boot or on-demand.
{
"type": "Microsoft.Compute/virtualMachines/extensions",
"name": "Endrias BridgeInit",
"properties": {
"publisher": "Microsoft.Compute",
"type": "CustomScriptExtension",
"typeHandlerVersion": "1.10",
"settings": {
"fileUris": ["https://yourstorage.blob.core.windows.net/scripts/Endrias Bridge_FirstBoot_Setup.ps1"],
"commandToExecute": "powershell.exe -ExecutionPolicy Bypass -File Endrias Bridge_FirstBoot_Setup.ps1"
}
}
}
On-Premises — Sysprep + SetupComplete.cmd
Add the PowerShell call to C:\Windows\Setup\Scripts\SetupComplete.cmd in your Windows golden image before Sysprep. Runs once after OOBE completes.
:: SetupComplete.cmd powershell.exe -ExecutionPolicy Bypass -File "C:\Endrias Bridge\Endrias Bridge_FirstBoot_Setup.ps1" -HeadlessMode $true
Scheduled Task (One-Time, Any Platform)
Create a one-time scheduled task that runs at next boot, executes the script, then deletes itself. Works on any Windows Server.
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-ExecutionPolicy Bypass -File C:\Endrias Bridge\Endrias Bridge_FirstBoot_Setup.ps1"
$trigger = New-ScheduledTaskTrigger -AtStartup
$settings = New-ScheduledTaskSettingsSet -RunOnlyIfNetworkAvailable
Register-ScheduledTask -TaskName "Endrias BridgeInit" -Action $action `
-Trigger $trigger -Settings $settings -RunLevel Highest -User "SYSTEM"
10 Plugin Phases — Detail
Set Hostname
Reads the desired hostname from cloud metadata (EC2: /meta-data/local-hostname, Azure: /metadata/instance/compute/name) or from the migration config. Renames the computer using Rename-Computer. Skip if already matching. A reboot is deferred to end of init run.
Set Timezone & NTP
Sets timezone via tzutil /s "UTC" (or from $TimeZone param). Configures NTP: w32tm /config /manualpeerlist:"time.windows.com,0x1" /syncfromflags:manual /reliable:YES /update then forces sync with w32tm /resync /force.
Network Config & DNS
Reads preferred DNS from metadata or config. Falls back to 8.8.8.8, 8.8.4.4 if not specified. Sets via Set-DnsClientServerAddress. Validates connectivity with Test-NetConnection to source and target DB hosts.
Enable WinRM
Runs winrm quickconfig -quiet if $EnableWinRM = $true. Sets WinRM service to auto-start. Adds Windows Firewall exception for port 5985. Optionally configures HTTPS listener on 5986 with a self-signed certificate.
Extend Volumes
If $ResizeVolumes = $true, uses Get-Partition | Resize-Partition -Size (Get-PartitionSupportedSize).SizeMax for each volume. Safe on cloud VMs where the EBS/Azure disk was expanded after provisioning. Skips if already at max size.
Install Windows Features
Installs .NET Framework 4.8 (if not present), optionally MSMQ. Uses Install-WindowsFeature (Server) or Enable-WindowsOptionalFeature (Desktop). Checks current state before installing — fully idempotent.
Create Service Account
Creates local user Endrias BridgeSvc with a cryptographically random password (never stored in log). Adds to Users group only — not Administrators. The account is used to run Endrias Bridge in scheduled/service mode.
Install Prerequisites
Downloads and silently installs in order:
- Python 3.11.x —
python-3.11.x-amd64.exe /quiet InstallAllUsers=1 PrependPath=1 - Microsoft ODBC Driver 18 for SQL Server — official MSI, silent install
- Visual C++ 2015–2022 Redistributable — required by pyodbc
- pip packages —
pip install -r requirements.txtwith 3-attempt retry
Each install checks "already present" via registry or command test before downloading.
Read Migration Config
Reads migration_config.json from $MigrationConfigPath. Falls back to JSON decoded from cloud user-data (saved in Phase 4). Validates required fields: source host/database/username, target host/database/username. Exits with error code 3 on missing critical fields.
Run Migration & Finalize
Executes python -m migrationbridge.headless --config migration_config.json in headless mode, streaming output to the log in real time. Parses exit code and log for FATAL/ERROR/MISMATCH lines. Writes .init_complete sentinel and init_summary.json. Tags EC2 instance with MigrationStatus tag via AWS CLI.
Migration Config Reference
{
"source": {
"db_type": "SQL Server 2019",
"host": "sqlserver01.corp.yourcompany.com",
"port": 1433,
"database": "Enterprise_Prod",
"username": "migration_reader",
"password": "READ_FROM_SECRETS_MANAGER"
},
"target": {
"db_type": "Azure SQL Database",
"host": "prod.database.windows.net",
"port": 1433,
"database": "Enterprise_Migrated",
"username": "migration_writer",
"password": "READ_FROM_SECRETS_MANAGER"
},
"tables": [],
"sync_mode": "full",
"do_schema": true,
"do_data": true,
"migration_path": "SQL Server 2019 → Azure SQL Database"
}
| Field | Type | Required | Description |
|---|---|---|---|
source.db_type | string | ✔ | Full engine name e.g. "SQL Server 2019", "PostgreSQL (VM / On-Prem)" |
source.host | string | ✔ | Hostname or IP of source database server |
source.port | int | ~ | Default: 1433 SQL, 5432 PG, 3306 MySQL |
source.database | string | ✔ | Source database name |
source.username | string | ✔ | Least-privilege read account |
source.password | string | ✔ | Use Secrets Manager reference — never hardcode |
tables | array | ✗ | Empty array = migrate all tables. Or list specific table names. |
sync_mode | string | ✗ | full · incremental · cdc. Default: full |
do_schema | bool | ✗ | Create tables + constraints on target. Default: true |
do_data | bool | ✗ | Copy rows. Default: true |
migration_path | string | ✗ | Human-readable label e.g. "SQL Server 2019 → Azure SQL Database" |
Example: SQL Server 2019 → AWS RDS for PostgreSQL
{
"source": { "db_type": "SQL Server 2019", "host": "10.0.1.50", "port": 1433,
"database": "SalesDB", "username": "sa_readonly", "password": "..." },
"target": { "db_type": "AWS RDS for PostgreSQL", "host": "sales.abc123.us-east-1.rds.amazonaws.com",
"port": 5432, "database": "salesdb_pg", "username": "pgadmin", "password": "..." },
"tables": [], "sync_mode": "full", "do_schema": true, "do_data": true
}
Example: Azure SQL Database → AWS RDS for SQL Server (Reverse)
{
"source": { "db_type": "Azure SQL Database", "host": "he-source.database.windows.net", "port": 1433,
"database": "HE_Azure", "username": "reader", "password": "..." },
"target": { "db_type": "AWS RDS for SQL Server", "host": "he-rds.xyz.us-west-2.rds.amazonaws.com",
"port": 1433, "database": "HE_RDS", "username": "admin", "password": "..." },
"sync_mode": "full", "do_schema": true, "do_data": true
}
Running Headless Migration
python -m migrationbridge.headless `
--config "C:\Endrias Bridge\migration_config.json" `
--log "C:\Endrias Bridge\Logs\migration_run.log" `
--tables "" `
--mode full
| Flag | Default | Description |
|---|---|---|
--config | migration_config.json | Path to JSON migration config |
--log | migration_run.log | Path for detailed migration log output |
--tables | (all) | Comma-separated table list; empty = all tables |
--mode | full | full · incremental · cdc · verify |
--no-schema | false | Skip schema creation (data only) |
--no-data | false | Skip data copy (schema only) |
Exit Codes
| Code | Meaning |
|---|---|
0 | SUCCESS — migration completed with no errors |
1 | MIGRATION ERRORS — completed with row/constraint mismatches |
2 | CONNECTION FAILURE — could not connect to source or target |
3 | CONFIG ERROR — required config fields missing or invalid |
Post-Migration Validation
Written at end of successful init run. Contains timestamp, platform, and migration status. If this file exists and $SkipIfAlreadyRun = $true, the init script exits immediately on next run — prevents duplicate migrations.
init_summary.json
{
"timestamp": "2026-06-13T08:32:11Z",
"platform": "AWS",
"instance_id": "i-0abc123456789def0",
"region": "us-east-1",
"python_version": "3.11.9",
"odbc_driver": "ODBC Driver 18 for SQL Server",
"migration_status": "SUCCESS",
"tables_migrated": 42,
"rows_copied": 18473921,
"log_file": "C:\\Endrias Bridge\\Logs\\migration_run.log"
}
Manual Validation Queries (T-SQL on Target)
-- Row count spot check
SELECT TABLE_SCHEMA, TABLE_NAME,
SUM(p.rows) AS row_count
FROM INFORMATION_SCHEMA.TABLES t
JOIN sys.partitions p ON OBJECT_ID(t.TABLE_SCHEMA+'.'+t.TABLE_NAME) = p.object_id
WHERE TABLE_TYPE = 'BASE TABLE' AND p.index_id IN (0,1)
GROUP BY TABLE_SCHEMA, TABLE_NAME
ORDER BY row_count DESC;
-- Constraint count check
SELECT TABLE_NAME,
SUM(CASE WHEN CONSTRAINT_TYPE='PRIMARY KEY' THEN 1 ELSE 0 END) AS pk,
SUM(CASE WHEN CONSTRAINT_TYPE='UNIQUE' THEN 1 ELSE 0 END) AS uq,
SUM(CASE WHEN CONSTRAINT_TYPE='FOREIGN KEY' THEN 1 ELSE 0 END) AS fk,
SUM(CASE WHEN CONSTRAINT_TYPE='CHECK' THEN 1 ELSE 0 END) AS ck
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
GROUP BY TABLE_NAME
ORDER BY TABLE_NAME;
AWS EC2 — Specific Notes
Attach an IAM role to the EC2 instance with: ec2:CreateTags (to tag instance with MigrationStatus), s3:GetObject (to download scripts/config from S3), secretsmanager:GetSecretValue (to read DB passwords). No AWS access keys in the script — use the instance role exclusively.
- Place migration_config.json in S3 and reference it in user-data — never embed DB passwords in user-data
- Use VPC endpoints for S3 and Secrets Manager to avoid internet traversal
- RDS security group must allow inbound TCP 1433/5432/3306 from the EC2 instance security group
- Enable Multi-AZ on RDS before migration — do not migrate to a single-AZ instance in production
- Use IMDSv2 (script already uses token-based IMDS calls)
Azure VM — Specific Notes
Assign a system-managed identity to the VM. Grant the identity Key Vault Secrets User role on the Key Vault containing DB credentials. In the init script, retrieve secrets via az keyvault secret show — never store passwords in the custom data field.
- NSG rule: outbound TCP 1433 to Azure SQL Database service tag
- Azure SQL Database firewall: add the VM's public IP or use Private Endpoint + VNet integration
- Custom data field is base64-encoded — the script auto-decodes and parses JSON
- Use
az vm run-command invoketo re-run the init script remotely if needed
On-Premises — Specific Notes
- Place
Endrias Bridge_FirstBoot_Setup.ps1andmigration_config.jsoninC:\Endrias Bridge\before Sysprep - Windows Firewall: allow outbound 1433 to source SQL Server; allow outbound 5432/3306 if migrating to PostgreSQL/MySQL
- Run
Set-ExecutionPolicy RemoteSigned -Forcebefore first boot or pass-ExecutionPolicy Bypassin the trigger command - For domain-joined machines, ensure the computer account has DNS registration rights post-rename
Security & Least Privilege
EC2 user-data is visible to anyone with ec2:DescribeInstanceAttribute. Azure custom data is stored in plain text in the extension logs. Always reference AWS Secrets Manager or Azure Key Vault and retrieve at runtime.
Retrieve from AWS Secrets Manager (PowerShell)
$secret = aws secretsmanager get-secret-value --secret-id "prod/migrationbridge/source-db" `
--query SecretString --output text | ConvertFrom-Json
$sourcePassword = $secret.password
Service Account Minimum Permissions
| Account | Permission | Scope |
|---|---|---|
| Source DB account | db_datareader + VIEW DEFINITION | Source database only |
| Target DB account | db_ddladmin + db_datawriter | Target database only |
| Endrias BridgeSvc (local) | Users group only | Local machine |
| EC2 IAM Role | ec2:CreateTags · s3:GetObject · secretsmanager:GetSecretValue | Specific resources only |
| Azure Managed Identity | Key Vault Secrets User | Specific Key Vault |
Troubleshooting
| Symptom | Cause | Resolution |
|---|---|---|
| Python install fails silently | TLS 1.2 disabled or proxy blocking downloads | Enable TLS 1.2: [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 · configure $env:HTTPS_PROXY |
| ODBC driver not found after install | Registry not refreshed; reboot needed | Restart the WinRM service or reboot. Verify: Get-OdbcDriver -Name "ODBC Driver 18*" |
| Connection refused to RDS/Azure SQL | Security group / NSG / firewall blocking port 1433 | Add outbound rule for port 1433 from migration VM to RDS endpoint. Check VPC route table. |
| Migration hangs on large table | Network timeout or chunk size too large | Set MIGRATIONBRIDGE_CHUNK_SIZE=5000 env var. Check source query timeout settings. |
| Constraint mismatch in verify log | Expected for cross-engine (MySQL CHECK pre-8.0.16) | Review log — if only CHECK counts differ on MySQL < 8.0.16, this is expected. Constraints are stored, not enforced. |
| Script runs again on reboot | Sentinel file missing or SkipIfAlreadyRun = $false | Check C:\Endrias Bridge\.init_complete exists. Set -SkipIfAlreadyRun $true. |
| pip install fails: "No module named pip" | Python installed but PATH not refreshed | Restart shell / open new PowerShell window. Or call python -m ensurepip --upgrade |
| WinRM access denied | LocalAccountTokenFilterPolicy not set | Set-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System -Name LocalAccountTokenFilterPolicy -Value 1 |
Appendix — Quick Reference
All File Paths Used
| Path | Purpose |
|---|---|
C:\Endrias Bridge\ | Root install directory |
C:\Endrias Bridge\migration_config.json | Migration source/target/options config |
C:\Endrias Bridge\user_data.json | Decoded cloud user-data (if present) |
C:\Endrias Bridge\.init_complete | Sentinel file — prevents re-run |
C:\Endrias Bridge\Logs\Endrias Bridge_Init_YYYYMMDD.log | Init phase log |
C:\Endrias Bridge\Logs\migration_run.log | Database migration engine log |
C:\Endrias Bridge\Logs\init_summary.json | Final status summary |
C:\Endrias Bridge\Endrias Bridge_FirstBoot_Setup.ps1 | The init + migration script |
Environment Variable Overrides
| Variable | Default | Description |
|---|---|---|
MIGRATIONBRIDGE_CHUNK_SIZE | 10000 | Rows per batch during full copy |
MIGRATIONBRIDGE_CONFIG | C:\Endrias Bridge\migration_config.json | Override config file path |
HTTPS_PROXY | (none) | Proxy for Python/ODBC downloads |
NO_PROXY | (none) | Bypass proxy for specific hosts |
Official Download URLs
| Package | Official URL |
|---|---|
| Python 3.11.x (64-bit) | https://www.python.org/ftp/python/3.11.9/python-3.11.9-amd64.exe |
| ODBC Driver 18 for SQL Server | https://aka.ms/odbc18 (Microsoft official redirect) |
| Visual C++ Redistributable 2022 | https://aka.ms/vs/17/release/vc_redist.x64.exe |
| Git for Windows | https://git-scm.com/download/win |