param( [Parameter(Mandatory = $true)] [string]$SetupExe, [Parameter(Mandatory = $true)] [string]$OutputMsi, [Parameter(Mandatory = $true)] [string]$Version, [string]$UpgradeCode = "194198A0-0A65-52A3-9E49-CCDE26A5BF65", [string]$ProductId = "F2AECE98-63E9-5A32-9B4B-9D7C6254CBA8" ) $ErrorActionPreference = "Stop" function Get-MsiVersion { param([string]$InputVersion) $parts = $InputVersion.Split(".") while ($parts.Count -lt 4) { $parts += "0" } return ($parts[0..3] -join ".") } function Invoke-InstallerExecute { param( [object]$Database, [string]$Sql ) $null = $Database.GetType().InvokeMember( "Execute", "InvokeMethod", $null, $Database, @($Sql) ) } function Invoke-InstallerViewInsert { param( [object]$Installer, [object]$Database, [string]$Sql, [object[]]$Values ) $view = $Database.GetType().InvokeMember( "OpenView", "InvokeMethod", $null, $Database, @($Sql) ) $record = $Installer.GetType().InvokeMember( "CreateRecord", "InvokeMethod", $null, $Installer, @($Values.Count) ) for ($index = 0; $index -lt $Values.Count; $index++) { $value = $Values[$index] if ($value -is [int]) { $record.GetType().InvokeMember( "IntegerData", "SetProperty", $null, $record, @($index + 1, $value) ) } else { $record.GetType().InvokeMember( "StringData", "SetProperty", $null, $record, @($index + 1, [string]$value) ) } } $view.GetType().InvokeMember("Execute", "InvokeMethod", $null, $view, @($record)) $view.GetType().InvokeMember("Close", "InvokeMethod", $null, $view, $null) } function Set-InstallerBinaryStream { param( [object]$Installer, [object]$Database, [string]$Name, [string]$FilePath ) $deleteSql = "DELETE FROM `Binary` WHERE `Name` = '$Name'" Invoke-InstallerExecute -Database $Database -Sql $deleteSql $insertSql = "INSERT INTO `Binary` (`Name`, `Data`) VALUES (?, ?)" $view = $Database.GetType().InvokeMember( "OpenView", "InvokeMethod", $null, $Database, @($insertSql) ) $record = $Installer.GetType().InvokeMember( "CreateRecord", "InvokeMethod", $null, $Installer, @(2) ) $record.GetType().InvokeMember( "StringData", "SetProperty", $null, $record, @(1, $Name) ) $record.GetType().InvokeMember( "SetStream", "InvokeMethod", $null, $record, @(2, (Resolve-Path -LiteralPath $FilePath).Path) ) $view.GetType().InvokeMember("Execute", "InvokeMethod", $null, $view, @($record)) $view.GetType().InvokeMember("Close", "InvokeMethod", $null, $view, $null) } function New-WrapperMsiDatabase { param( [string]$OutputPath, [string]$SetupExePath, [string]$MsiVersion, [string]$ProductCode, [string]$UpgradeCodeValue ) if (Test-Path $OutputPath) { Remove-Item -LiteralPath $OutputPath -Force } $outputDir = Split-Path $OutputPath -Parent if ($outputDir -and -not (Test-Path $outputDir)) { New-Item -ItemType Directory -Path $outputDir -Force | Out-Null } $installer = New-Object -ComObject WindowsInstaller.Installer $database = $installer.GetType().InvokeMember( "OpenDatabase", "InvokeMethod", $null, $installer, @($OutputPath, 1) ) Invoke-InstallerExecute -Database $database -Sql @" CREATE TABLE `Property` ( `Property` CHAR(72) NOT NULL PRIMARY KEY, `Value` CHAR(0) NOT NULL LOCALIZABLE ) "@ Invoke-InstallerExecute -Database $database -Sql @" CREATE TABLE `Directory` ( `Directory` CHAR(72) NOT NULL PRIMARY KEY, `Directory_Parent` CHAR(72), `DefaultDir` CHAR(255) NOT NULL ) "@ Invoke-InstallerExecute -Database $database -Sql @" CREATE TABLE `Component` ( `Component` CHAR(72) NOT NULL PRIMARY KEY, `ComponentId` CHAR(38) NOT NULL, `Directory_` CHAR(72) NOT NULL, `Attributes` SHORT NOT NULL ) "@ Invoke-InstallerExecute -Database $database -Sql @" CREATE TABLE `Feature` ( `Feature` CHAR(72) NOT NULL PRIMARY KEY, `Feature_Parent` CHAR(72), `Title` CHAR(64) NOT NULL LOCALIZABLE, `Description` CHAR(255) LOCALIZABLE, `Display` SHORT, `Level` SHORT NOT NULL, `Directory_` CHAR(72) ) "@ Invoke-InstallerExecute -Database $database -Sql @" CREATE TABLE `FeatureComponents` ( `Feature_` CHAR(72) NOT NULL, `Component_` CHAR(72) NOT NULL PRIMARY KEY ) "@ Invoke-InstallerExecute -Database $database -Sql @" CREATE TABLE `Binary` ( `Name` CHAR(72) NOT NULL PRIMARY KEY, `Data` OBJECT NOT NULL ) "@ Invoke-InstallerExecute -Database $database -Sql @" CREATE TABLE `CustomAction` ( `Action` CHAR(72) NOT NULL PRIMARY KEY, `Type` SHORT NOT NULL, `Source` CHAR(72), `Target` CHAR(255) ) "@ Invoke-InstallerExecute -Database $database -Sql @" CREATE TABLE `InstallExecuteSequence` ( `Action` CHAR(72) NOT NULL, `Condition` CHAR(255), `Sequence` SHORT ) "@ Invoke-InstallerViewInsert -Installer $installer -Database $database ` -Sql "INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)" ` -Values @("Manufacturer", "Tom Butcher") Invoke-InstallerViewInsert -Installer $installer -Database $database ` -Sql "INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)" ` -Values @("ProductName", "Farm Control Server") Invoke-InstallerViewInsert -Installer $installer -Database $database ` -Sql "INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)" ` -Values @("ProductVersion", $MsiVersion) Invoke-InstallerViewInsert -Installer $installer -Database $database ` -Sql "INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)" ` -Values @("ProductCode", $ProductCode) Invoke-InstallerViewInsert -Installer $installer -Database $database ` -Sql "INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)" ` -Values @("UpgradeCode", $UpgradeCodeValue) Invoke-InstallerViewInsert -Installer $installer -Database $database ` -Sql "INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)" ` -Values @("ProductLanguage", "1033") Invoke-InstallerViewInsert -Installer $installer -Database $database ` -Sql "INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)" ` -Values @("ALLUSERS", "1") Invoke-InstallerViewInsert -Installer $installer -Database $database ` -Sql "INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)" ` -Values @("MSIINSTALLPERUSER", "1") Invoke-InstallerViewInsert -Installer $installer -Database $database ` -Sql "INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)" ` -Values @("DISABLEADVTSHORTCUTS", "1") Invoke-InstallerViewInsert -Installer $installer -Database $database ` -Sql "INSERT INTO `Directory` (`Directory`, `Directory_Parent`, `DefaultDir`) VALUES (?, ?, ?)" ` -Values @("TARGETDIR", "", "SourceDir") Invoke-InstallerViewInsert -Installer $installer -Database $database ` -Sql "INSERT INTO `Directory` (`Directory`, `Directory_Parent`, `DefaultDir`) VALUES (?, ?, ?)" ` -Values @("TempFolder", "TARGETDIR", "Temp") Invoke-InstallerViewInsert -Installer $installer -Database $database ` -Sql "INSERT INTO `Component` (`Component`, `ComponentId`, `Directory_`, `Attributes`) VALUES (?, ?, ?, ?)" ` -Values @( "EmptyComponent", "7145262D-7149-4F1A-9A69-F377E38F04DA", "TempFolder", 256 ) Invoke-InstallerViewInsert -Installer $installer -Database $database ` -Sql "INSERT INTO `Feature` (`Feature`, `Feature_Parent`, `Title`, `Description`, `Display`, `Level`, `Directory_`) VALUES (?, ?, ?, ?, ?, ?, ?)" ` -Values @("EmptyFeature", "", "Empty", "", 0, 0, "") Invoke-InstallerViewInsert -Installer $installer -Database $database ` -Sql "INSERT INTO `FeatureComponents` (`Feature_`, `Component_`) VALUES (?, ?)" ` -Values @("EmptyFeature", "EmptyComponent") Set-InstallerBinaryStream -Installer $installer -Database $database ` -Name "WrappedExe" -FilePath $SetupExePath # EXE from Binary, deferred, no impersonate, check return code. $customActionType = 2 + 64 + 1024 + 2048 Invoke-InstallerViewInsert -Installer $installer -Database $database ` -Sql "INSERT INTO `CustomAction` (`Action`, `Type`, `Source`, `Target`) VALUES (?, ?, ?, ?)" ` -Values @("RunInstaller", $customActionType, "WrappedExe", "/S") Invoke-InstallerViewInsert -Installer $installer -Database $database ` -Sql "INSERT INTO `InstallExecuteSequence` (`Action`, `Condition`, `Sequence`) VALUES (?, ?, ?)" ` -Values @("InstallValidate", "", 1400) Invoke-InstallerViewInsert -Installer $installer -Database $database ` -Sql "INSERT INTO `InstallExecuteSequence` (`Action`, `Condition`, `Sequence`) VALUES (?, ?, ?)" ` -Values @("InstallInitialize", "", 1500) Invoke-InstallerViewInsert -Installer $installer -Database $database ` -Sql "INSERT INTO `InstallExecuteSequence` (`Action`, `Condition`, `Sequence`) VALUES (?, ?, ?)" ` -Values @("ProcessComponents", "", 1600) Invoke-InstallerViewInsert -Installer $installer -Database $database ` -Sql "INSERT INTO `InstallExecuteSequence` (`Action`, `Condition`, `Sequence`) VALUES (?, ?, ?)" ` -Values @("RunInstaller", "", 1601) Invoke-InstallerViewInsert -Installer $installer -Database $database ` -Sql "INSERT INTO `InstallExecuteSequence` (`Action`, `Condition`, `Sequence`) VALUES (?, ?, ?)" ` -Values @("InstallFinalize", "", 6600) $summary = $database.GetType().InvokeMember( "GetSummaryInformation", "InvokeMethod", $null, $database, @(20) ) $summary.GetType().InvokeMember( "Property", "SetProperty", $null, $summary, @(1, ";\1033") ) $summary.GetType().InvokeMember( "Property", "SetProperty", $null, $summary, @(2, "Farm Control Server") ) $summary.GetType().InvokeMember( "Property", "SetProperty", $null, $summary, @(3, [Guid]::NewGuid().ToString().ToUpper()) ) $summary.GetType().InvokeMember( "Property", "SetProperty", $null, $summary, @(4, "Farm Control Server") ) $summary.GetType().InvokeMember( "Property", "SetProperty", $null, $summary, @(7, "x64;1033") ) $summary.GetType().InvokeMember( "Property", "SetProperty", $null, $summary, @(9, "Farm Control Server") ) $summary.GetType().InvokeMember( "Property", "SetProperty", $null, $summary, @(14, "200") ) $summary.GetType().InvokeMember( "Property", "SetProperty", $null, $summary, @(15, "2") ) $summary.GetType().InvokeMember( "Property", "SetProperty", $null, $summary, @(19, "2") ) $summary.GetType().InvokeMember( "Persist", "InvokeMethod", $null, $summary, $null ) $database.GetType().InvokeMember( "Commit", "InvokeMethod", $null, $database, $null ) [System.Runtime.InteropServices.Marshal]::ReleaseComObject($summary) | Out-Null [System.Runtime.InteropServices.Marshal]::ReleaseComObject($database) | Out-Null [System.Runtime.InteropServices.Marshal]::ReleaseComObject($installer) | Out-Null } $setupExePath = (Resolve-Path -LiteralPath $SetupExe).Path $outputMsiPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutputMsi) $msiVersion = Get-MsiVersion $Version New-WrapperMsiDatabase ` -OutputPath $outputMsiPath ` -SetupExePath $setupExePath ` -MsiVersion $msiVersion ` -ProductCode $ProductId ` -UpgradeCodeValue $UpgradeCode if (-not (Test-Path $outputMsiPath)) { throw "MSI installer was not created at $outputMsiPath" } Write-Host "Created MSI installer at $outputMsiPath"