Insert a VBScript Custom Action into a Windows Installer

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

Access a Windows Installer property in a Deferred Custom Action

Description:

This post describes how to access a Windows Installer property in a Deferred Custom Action.  Deferred, commit, and rollback custom actions can only access a limited number of built-in Windows Installer properties – CustomActionData, ProductCode, and UserSID.  In brief (since this has been discussed plenty of times before elsewhere) this is due to them being executed in a separate process (they spawn another MSIEXEC.exe process which is run in a System Account context – check Task Manager during an installation to see this).  To pass any Windows Installer property to a deferred Custom Action, we must pass it via the CustomActionData property.  In this example, we’ll pass the ProductName property during the installation of our product.

Step 1
Create a property. Call it ‘AlkaneCustomProperty’ and give it a default value of anything (we’re going to set this to our directory name in Step 2….).

Step 2
Create a SetProperty custom action (Type 51), call it ‘setAlkaneCustomProperty’, select your ‘AlkaneCustomProperty’ property, and under property value write ‘[ProductName]’. Execute this action as Immediate, before InstallInitialize with a condition of ‘NOT Installed’.

Step 3
Create another CA – this time a ‘Call VBScript from Embedded code’ (Type 38). It is IMPORTANT you call this the same name as your property you made earlier, so call it ‘AlkaneCustomProperty’. In your script, to retrieve the directory name use:

Dim ProductName : ProductName = Session.Property("CustomActionData")
MsgBox ProductName

Schedule this CA as ‘Deferred in a System Context’ and put it anywhere between the standard actions ‘InstallInitialize’ and ‘InstallFinalize’.  Use a condition of ‘NOT Installed’.