|
| 1 | +<# |
| 2 | +Migration script for Windows ODBC DSNs |
| 3 | +- Migrates from "Simba ODBC Driver for Google Bigquery" |
| 4 | +- to "Google Bigquery Driver" |
| 5 | +
|
| 6 | +Usage: |
| 7 | + migrate.ps1 -Mode copy -DriverInstaller "GoogleBigQueryODBC.msi" |
| 8 | + migrate.ps1 -Mode replace -DriverInstaller "GoogleBigQueryODBC.msi" |
| 9 | +#> |
| 10 | + |
| 11 | +param( |
| 12 | + [Parameter(Mandatory=$true)] |
| 13 | + [ValidateSet("copy","replace")] |
| 14 | + [string]$Mode, |
| 15 | + |
| 16 | + [Parameter(Mandatory=$true)] |
| 17 | + [string]$DriverInstaller |
| 18 | +) |
| 19 | + |
| 20 | +$SimbaDriverName = "Simba ODBC Driver for Google Bigquery" |
| 21 | +$GoogleDriverName = "Google Bigquery Driver" |
| 22 | + |
| 23 | +Write-Host "=== Installing Google Bigquery Driver ===" |
| 24 | +Start-Process "msiexec.exe" -ArgumentList "/i `"$DriverInstaller`" /quiet /norestart" -Wait |
| 25 | +Write-Host "=== Driver installation complete ===`n" |
| 26 | + |
| 27 | +# Registry paths for DSNs |
| 28 | +$DSNRoots = @( |
| 29 | + "HKLM:\SOFTWARE\ODBC\ODBC.INI", |
| 30 | + "HKCU:\SOFTWARE\ODBC\ODBC.INI" |
| 31 | +) |
| 32 | +$DSNListRoot = "ODBC Data Sources" |
| 33 | + |
| 34 | +function CopyRegistryTree($SourcePath, $TargetPath) { |
| 35 | + if (!(Test-Path $TargetPath)) { |
| 36 | + New-Item -Path $TargetPath | Out-Null |
| 37 | + } |
| 38 | + |
| 39 | + $props = Get-ItemProperty -Path $SourcePath |
| 40 | + foreach ($p in $props.PSObject.Properties) { |
| 41 | + if ($p.Name -notlike "PS*") { |
| 42 | + Set-ItemProperty -Path $TargetPath -Name $p.Name -Value $p.Value |
| 43 | + } |
| 44 | + } |
| 45 | +} |
| 46 | + |
| 47 | +# === DPAPI HEX DECRYPTOR === |
| 48 | +function Decode-DPAPIHex($hex) { |
| 49 | + if ([string]::IsNullOrWhiteSpace($hex)) { return $null } |
| 50 | + |
| 51 | + Add-Type -AssemblyName System.Security |
| 52 | + |
| 53 | + # Convert hexadecimal → byte array |
| 54 | + $bytes = for ($i = 0; $i -lt $hex.Length; $i += 2) { |
| 55 | + [Convert]::ToByte($hex.Substring($i, 2), 16) |
| 56 | + } |
| 57 | + |
| 58 | + # Try LocalMachine first |
| 59 | + try { |
| 60 | + return [System.Security.Cryptography.ProtectedData]::Unprotect( |
| 61 | + $bytes, $null, |
| 62 | + [System.Security.Cryptography.DataProtectionScope]::LocalMachine |
| 63 | + ) |
| 64 | + } |
| 65 | + catch { |
| 66 | + # Try CurrentUser if LocalMachine does not work |
| 67 | + return [System.Security.Cryptography.ProtectedData]::Unprotect( |
| 68 | + $bytes, $null, |
| 69 | + [System.Security.Cryptography.DataProtectionScope]::CurrentUser |
| 70 | + ) |
| 71 | + } |
| 72 | +} |
| 73 | + |
| 74 | +function TestODBCConnection($DSN) { |
| 75 | + Write-Host " → Testing ODBC connection..." |
| 76 | + try { |
| 77 | + $conn = New-Object System.Data.Odbc.OdbcConnection("DSN=$DSN") |
| 78 | + $conn.Open() |
| 79 | + Write-Host " → Connection test: OK" -ForegroundColor Green |
| 80 | + $conn.Close() |
| 81 | + } catch { |
| 82 | + Write-Host " → Connection test: FAILED" -ForegroundColor Red |
| 83 | + Write-Host $_.Exception.Message |
| 84 | + } |
| 85 | +} |
| 86 | +$SimbaDSNs = @() |
| 87 | + |
| 88 | +foreach ($root in $DSNRoots) { |
| 89 | + |
| 90 | + $dsnTablePath = Join-Path $root $DSNListRoot |
| 91 | + if (!(Test-Path $dsnTablePath)) { continue } |
| 92 | + |
| 93 | + $dsns = Get-ItemProperty $dsnTablePath |
| 94 | + foreach ($entry in $dsns.PSObject.Properties) { |
| 95 | + |
| 96 | + $dsnName = $entry.Name |
| 97 | + $driverValue = $entry.Value |
| 98 | + |
| 99 | + if ($driverValue -eq $SimbaDriverName) { |
| 100 | + $SimbaDSNs += [PSCustomObject]@{ |
| 101 | + Root = $root |
| 102 | + DsnName = $dsnName |
| 103 | + DsnTablePath = $dsnTablePath |
| 104 | + } |
| 105 | + } |
| 106 | + } |
| 107 | +} |
| 108 | + |
| 109 | +if ($SimbaDSNs.Count -eq 0) { |
| 110 | + Write-Host "No Simba DSNs found. Nothing to migrate." |
| 111 | + exit |
| 112 | +} |
| 113 | +elseif ($SimbaDSNs.Count -eq 1) { |
| 114 | + $Chosen = $SimbaDSNs[0] |
| 115 | +} |
| 116 | +else { |
| 117 | + Write-Host "`nMultiple Simba DSNs found:" |
| 118 | + for ($i=0; $i -lt $SimbaDSNs.Count; $i++) { |
| 119 | + Write-Host " [$($i+1)] $($SimbaDSNs[$i].DsnName) ($($SimbaDSNs[$i].Root))" |
| 120 | + } |
| 121 | + |
| 122 | + do { |
| 123 | + $sel = Read-Host "Enter the number of the DSN you want to migrate" |
| 124 | + } until ($sel -as [int] -and $sel -ge 1 -and $sel -le $SimbaDSNs.Count) |
| 125 | + |
| 126 | + $Chosen = $SimbaDSNs[$sel - 1] |
| 127 | +} |
| 128 | + |
| 129 | +Write-Host "`nSelected DSN: $($Chosen.DsnName)" |
| 130 | +Write-Host "Location: $($Chosen.Root)`n" |
| 131 | + |
| 132 | +# =============================================================== |
| 133 | +# === Continue the original loop logic ONLY for chosen DSN === |
| 134 | +# =============================================================== |
| 135 | + |
| 136 | +$root = $Chosen.Root |
| 137 | +$dsnName = $Chosen.DsnName |
| 138 | +$dsnTablePath = $Chosen.DsnTablePath |
| 139 | + |
| 140 | +Write-Host "`nFound Simba DSN: $dsnName" |
| 141 | + |
| 142 | +# Source DSN registry key |
| 143 | +$srcDSNKey = Join-Path $root $dsnName |
| 144 | +if (!(Test-Path $srcDSNKey)) { |
| 145 | + Write-Warning "DSN entry exists in table but no registry key: $srcDSNKey" |
| 146 | + exit |
| 147 | +} |
| 148 | + |
| 149 | +# Determine target DSN values based on mode |
| 150 | +if ($Mode -eq "replace") { |
| 151 | + $newDSNName = $dsnName |
| 152 | +} else { |
| 153 | + $newDSNName = "${dsnName}_Google" |
| 154 | +} |
| 155 | + |
| 156 | +Write-Host " → Target DSN: $newDSNName" |
| 157 | +$dstDSNKey = Join-Path $root $newDSNName |
| 158 | + |
| 159 | +# 1. Update ODBC Data Sources table |
| 160 | +Set-ItemProperty -Path $dsnTablePath -Name $newDSNName -Value $GoogleDriverName |
| 161 | +if ($Mode -eq "replace" -and $newDSNName -eq $dsnName) { |
| 162 | + Set-ItemProperty -Path $dsnTablePath -Name $dsnName -Value $GoogleDriverName |
| 163 | +} |
| 164 | + |
| 165 | +# 2. Copy registry values |
| 166 | +CopyRegistryTree -SourcePath $srcDSNKey -TargetPath $dstDSNKey |
| 167 | + |
| 168 | +# 3. Update Driver DLL |
| 169 | +$googleDriverKey = Get-ChildItem "HKLM:\SOFTWARE\ODBC\ODBCINST.INI" | |
| 170 | + Where-Object { $_.PSChildName -eq $GoogleDriverName } |
| 171 | +if (!$googleDriverKey) { Write-Warning "Google driver not found in ODBCINST.INI"; exit } |
| 172 | + |
| 173 | +$googleDLL = (Get-ItemProperty $googleDriverKey.PSPath).Driver |
| 174 | +Set-ItemProperty -Path $dstDSNKey -Name "Driver" -Value $googleDLL |
| 175 | +Write-Host " → Set Driver DLL to: $googleDLL" |
| 176 | + |
| 177 | +# 3b. Fix TrustedCerts |
| 178 | +$driverFolder = Split-Path $googleDLL -Parent |
| 179 | +$rootsCert = Join-Path $driverFolder "Assets\roots.pem" |
| 180 | +if (Test-Path $rootsCert) { |
| 181 | + Set-ItemProperty -Path $dstDSNKey -Name "TrustedCerts" -Value $rootsCert |
| 182 | + Write-Host " → Updated TrustedCerts to: $rootsCert" |
| 183 | +} else { |
| 184 | + Write-Warning " → roots.pem not found in $driverFolder\Assets" |
| 185 | +} |
| 186 | + |
| 187 | +# 4. Decrypt KeyFilePath_Enc |
| 188 | +$encKeyPath = (Get-ItemProperty $srcDSNKey -ErrorAction SilentlyContinue).KeyFilePath_Enc |
| 189 | +if ($encKeyPath) { |
| 190 | + |
| 191 | + Write-Host " → Decrypting KeyFilePath_Enc..." |
| 192 | + |
| 193 | + $rawBytes = Decode-DPAPIHex $encKeyPath |
| 194 | + if ($rawBytes) { |
| 195 | + $plainPath = [System.Text.Encoding]::UTF8.GetString($rawBytes) |
| 196 | + Write-Host " → Decrypted KeyFilePath: $plainPath" |
| 197 | + |
| 198 | + # Write plain KeyFilePath into new DSN |
| 199 | + Set-ItemProperty -Path $dstDSNKey -Name "KeyFilePath" -Value $plainPath |
| 200 | + } else { |
| 201 | + Write-Warning " → Failed to decrypt KeyFilePath_Enc" |
| 202 | + } |
| 203 | + } |
| 204 | + |
| 205 | + # 5. Test DSN connectivity |
| 206 | + TestODBCConnection -DSN $newDSNName |
| 207 | + |
| 208 | +Write-Host "`n=== Migration complete ===" |
0 commit comments