Below is a quick example of how we can insert a VBScript custom action into a Windows Installer (MSI) using PowerShell. Remember that this example does not add the entry into the InstallExecuteSequence table, which will be required to actually run the custom action during a Windows Installer session!
$msiOpenDatabaseModeReadOnly = 0
$msiOpenDatabaseModeTransact = 1
$windowsInstaller = New-Object -ComObject windowsInstaller.Installer
$pathToMSI = "C:\Temp\Alkane.msi"
$database2 = $windowsInstaller.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $null, $windowsInstaller, @($pathToMSI, $msiOpenDatabaseModeTransact))
$query = "INSERT INTO `CustomAction` (`Action`,`Type`,`Target`) Values(?,?,?)"
$View = $database2.GetType().InvokeMember("OpenView","InvokeMethod",$Null,$database2,($query))
$binaryrecord = $windowsInstaller.GetType().InvokeMember("CreateRecord", "InvokeMethod", $null, $windowsInstaller, 3)
$binaryrecord.GetType().InvokeMember("StringData", "SetProperty", $null, $binaryrecord, @(1, "AlkaneCustomAction"))
$binaryrecord.GetType().InvokeMember("IntegerData", "SetProperty", $null, $binaryrecord, @(2, 38))
$vbscriptCode = "Msgbox('hello')`r`nMsgbox('again')"
$binaryrecord.GetType().InvokeMember("StringData", "SetProperty", $null, $binaryrecord, @(3, $vbscriptCode))
$View.GetType().InvokeMember("Execute", "InvokeMethod", $Null, $View, $binaryrecord)
$database2.GetType().InvokeMember("Commit", "InvokeMethod", $null, $database2, $null)
$View.GetType().InvokeMember("Close", "InvokeMethod", $Null, $View, $Null)
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($View) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($binaryrecord) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($database2) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($windowsInstaller) | Out-Null