# Configures the default 32-bit data source with an extracted compatibility driver. # Existing driver files remain intact; -ReplaceDsn backs up and replaces the DSN. # Run in elevated 64-bit Windows PowerShell after installing the 5.5.3 gateway. param( [Parameter(Mandatory=$true)][string]$Installer, [string]$Directory = 'C:\APPX-ODBC-553-Client', [string]$Dsn = 'AppxODBC', [string]$GatewayHost = 'localhost', [string]$Database = 'appx', [string]$DriverName = 'APPX 5.5.3 Dharma 14 Compatibility', [switch]$ReplaceDsn, [switch]$ReuseDriver ) $ErrorActionPreference = 'Stop' if (-not [Environment]::Is64BitProcess) { throw 'Use elevated 64-bit Windows PowerShell for setup.' } $identity = [Security.Principal.WindowsIdentity]::GetCurrent() if (-not (New-Object Security.Principal.WindowsPrincipal($identity)).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { throw 'Open Windows PowerShell with Run as administrator.' } if ($Dsn -notmatch '^[A-Za-z][A-Za-z0-9_-]{0,63}$') { throw 'Use a simple DSN name.' } if ((Test-Path $Directory) -and -not $ReuseDriver) { throw 'Destination exists. Use a new directory, or explicitly reuse verified files with -ReuseDriver.' } $inst = 'HKLM:\SOFTWARE\WOW6432Node\ODBC\ODBCINST.INI' $ini = 'HKLM:\SOFTWARE\WOW6432Node\ODBC\ODBC.INI' if ((Test-Path "$inst\$DriverName") -and -not $ReuseDriver) { throw 'Driver alias exists. Use a new alias, or explicitly reuse it with -ReuseDriver.' } if ((Test-Path "$ini\$Dsn") -and -not $ReplaceDsn) { throw 'DSN exists. Use -ReplaceDsn to back it up and replace it.' } $packageHash = '0453E2A800C1DA5132C0F6FEAAB55619C941D97B62D68E9724DD0C3BBA86A23D' if ((Get-FileHash -LiteralPath $Installer -Algorithm SHA256).Hash -ne $packageHash) { throw 'Installer is not the verified official 6.2.1 x86 package.' } $expected = @{ 'dhsetup.dll'='68112D36C39DF77E8EB12FE862D71069B1968C87293E45C15AAF6EEA88800C61' 'dhsodbc.dll'='9D7D70F8C9BD23F094CF228061AACA36450543F4646BD2F09510E1A295F2C585' 'msvcp140.dll'='02C7259456EAC8CBADFB460377BA68E98282400C7A4A9D0BF49B3313EF6D554D' 'msvcp140_1.dll'='C5A6507A240A50A20F7D3FDF014D21176F909299F0F381200E389534CF93FABE' 'msvcp140_2.dll'='459CB496AEBF1BFC40405E8EB98F43CC360B9BA7B0FC0D71851A0BDF6487132D' 'vcruntime140.dll'='FF9C1123CFF493A8F5EACB91115611B6C1C808B30C82AF9B6F388C0EF1F6B46D' } # Extract only the verified driver payload, without running the newer installer. # The signed installer has a trailer after the ZIP; the ZIP need not end at EOF. Add-Type -AssemblyName System.IO.Compression.FileSystem $bytes = [IO.File]::ReadAllBytes((Resolve-Path -LiteralPath $Installer)) $end = -1 for ($i=$bytes.Length-22; $i -ge [Math]::Max(0,$bytes.Length-65557); $i--) { if ([BitConverter]::ToUInt32($bytes,$i) -eq 0x06054b50 -and $i+22+[BitConverter]::ToUInt16($bytes,$i+20) -le $bytes.Length) { $end=$i; break } } if ($end -lt 0) { throw 'Verified payload directory not found.' } $start = $end - [BitConverter]::ToUInt32($bytes,$end+12) - [BitConverter]::ToUInt32($bytes,$end+16) if ($start -lt 0 -or [BitConverter]::ToUInt32($bytes,$start) -ne 0x04034b50) { throw 'Verified payload boundary differs.' } if (-not $ReuseDriver) { New-Item -ItemType Directory -Path $Directory | Out-Null $payload = Join-Path $Directory 'driver-payload.zip' $stream = [IO.File]::Create($payload) try { $stream.Write($bytes,$start,$end+22+[BitConverter]::ToUInt16($bytes,$end+20)-$start) } finally { $stream.Dispose() } $zip = [IO.Compression.ZipFile]::OpenRead($payload) try { foreach ($name in $expected.Keys) { $entry=$zip.GetEntry($name) if (-not $entry) { throw "Missing payload: $name" } [IO.Compression.ZipFileExtensions]::ExtractToFile($entry,(Join-Path $Directory $name),$false) if ((Get-FileHash (Join-Path $Directory $name)).Hash -ne $expected[$name]) { throw "Driver file hash differs: $name" } } } finally { $zip.Dispose() } } foreach ($name in $expected.Keys) { if ((Get-FileHash (Join-Path $Directory $name)).Hash -ne $expected[$name]) { throw "Driver file hash differs: $name" } } if ($ReuseDriver -and (Test-Path "$inst\$DriverName")) { $registered=Get-ItemProperty "$inst\$DriverName" if ($registered.Driver -ne (Join-Path $Directory 'dhsodbc.dll') -or $registered.Setup -ne (Join-Path $Directory 'dhsetup.dll')) { throw 'Existing driver alias points elsewhere; it was preserved.' } } New-Item "$inst\$DriverName" -Force | Out-Null $values=@{Driver=(Join-Path $Directory 'dhsodbc.dll');Setup=(Join-Path $Directory 'dhsetup.dll');APILevel='1';ConnectFunctions='YYY';DriverODBCVer='03.00';SQLLevel='1';UsageCount='1'} foreach ($key in $values.Keys) { Set-ItemProperty "$inst\$DriverName" -Name $key -Value $values[$key] } Set-ItemProperty "$inst\ODBC Drivers" -Name $DriverName -Value 'Installed' if (Test-Path "$ini\$Dsn") { $backup=Join-Path $Directory ('previous-dsn-'+(Get-Date -Format 'yyyyMMdd-HHmmss')+'.reg') & reg.exe export ("HKLM\SOFTWARE\WOW6432Node\ODBC\ODBC.INI\"+$Dsn) $backup /y | Out-Null if ($LASTEXITCODE -ne 0) { throw 'DSN backup failed; existing data source was preserved.' } $label=(Get-ItemProperty "$ini\ODBC Data Sources").$Dsn $label | Set-Content ($backup+'.label.txt') Remove-OdbcDsn -Name $Dsn -DsnType System -Platform '32-bit' Write-Host "Previous DSN preserved in $backup" } Add-OdbcDsn -Name $Dsn -DriverName $DriverName -DsnType System -Platform '32-bit' ` -SetPropertyValue @("Host=$GatewayHost","Database=$Database",'Description=APPX 5.5.3 cookbook compatibility driver') # Omit Service when creating the fresh DSN. Add-OdbcDsn rejects an empty Service= property. # The verified client resolves sqlnw and then its measured fallback when Service is absent. # ASQL 5.5.3 filters by this exact enumeration label. Its explicit DLL path stays separate. Set-ItemProperty "$ini\ODBC Data Sources" -Name $Dsn -Value 'AppxODBC Driver' Get-ItemProperty "$ini\$Dsn" | Select-Object Driver,Host,Database,Service Write-Host "Created $Dsn. Next: import COUNTRY with the 5.5.3 ASQL tool."