Search code examples
powershellinsertcomwixwindows-installer

Powershell - How to use "WindowsInstaller.Installer" to Insert a Property Value in a MSI - REBOOT = Force


I'm am trying to accomplish this in Powershell, I've found it to very difficult to do. This is the closest bit of code I've found that does something similar. I've also found this example which is similar but in VBScript. I've compiled all this plus hours of googling into this:

$com_object = New-Object -com WindowsInstaller.Installer

[int]$msiOpenDatabaseMode = 0
$database = $com_object.GetType().InvokeMember(
    "OpenDatabase",
    "InvokeMethod",
    $Null,
    $com_object,
    @("C:\XXX.msi", $msiOpenDatabaseMode)
)       

$View = $database.GetType().InvokeMember('OpenView', 'InvokeMethod', $null, $database, 
    ("INSERT INTO Property (Property, Value) VALUES ('REBOOT', 'Force')"))    
$View.GetType().InvokeMember('Execute', 'InvokeMethod', $null, $View, $null) | Out-Null
$View.GetType().InvokeMember('Close', 'InvokeMethod', $null, $View, $null) | Out-Null

However I get the following when I run it:

Exception calling "InvokeMember" with "5" argument(s): "Execute,Params" At C:\XXX\Example.PS1:15 char:5 $View.GetType().InvokeMember('Execute', 'InvokeMethod', $null, $V ...

CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
FullyQualifiedErrorId : COMException

And I can't figure out why, does anyone have a idea why this won't run? My real issue with this is that I can't see the root of the issue, all the COMExceptions don't seem to return any real information, it makes figuring out why COM invocations are failing extremely difficult, is there a better way of doing that too?


Solution

  • Read / Write: [int]$msiOpenDatabaseMode = 0 this opens the MSI database read-only - very common glitch. This needs to be corrected, but there could be other glitches as well. I just based myself on an existing script for the sample below.

    Heads-up: Keep in mind that some properties should not be authored into the Property table. The only ones I can think of right now are: FASTOEM, ADDLOCAL - there are others. REBOOT should be OK technically - I think - however read the next point - it is not good practice.

    Warning on Reboot: The ScheduleReboot MSI standard action triggers a spontaneous reboot of the system when an MSI is run in silent mode. I just verified that the same thing happens with this approach (REBOOT = Force in property table). To install MSI silently, run this command from an elevated cmd.exe command prompt: msiexec /i Setup.msi /qn.


    DTF: I also want to mention the Deployment Tools Foundation .NET classes written to interact with the MSI API. Using this dll you don't have to deal with COM Interop. These DLLs come installed with the WiX toolkit. Here is a brief overview of the dll family.


    Powershell is not my thing - you would have to be Egyptian to like those hieroglyphs (as powerful as they might be) - but maybe you can try the script below (update $MsiFilePath). Say no to high line-noise!

    • This script assumes there is no pre-existing REBOOT entry in the property table - running twice will trigger an error.
    • If you have problems - and you have run many tests - try to use a fresh copy of your test MSI or switch to another MSI entirely.
      • You can get into some strange "dirty states" with automation testing.
      • Avoid banging your head on a script when the file is corrupted (just to state the obvious).

    Actual script:

    $Installer = new-object -comobject WindowsInstaller.Installer
    $MSIOpenDatabaseModeTransact = 1
    $MsiFilePath = "C:\Users\UserName\Desktop\MyTest.msi"
    
    $MsiDBCom = $Installer.GetType().InvokeMember(
            "OpenDatabase", 
            "InvokeMethod", 
            $Null, 
            $Installer, 
            @($MsiFilePath, $MSIOpenDatabaseModeTransact)
        )
    
    $Query1 = "INSERT INTO ``Property`` (``Property``,``Value``) VALUES ('REBOOT','Force')"
    
    $Insert = $MsiDBCom.GetType().InvokeMember(
            "OpenView",
            "InvokeMethod",
            $Null,
            $MsiDBCom,
            ($Query1)
        )
    
    $Insert.GetType().InvokeMember("Execute", "InvokeMethod", $Null, $Insert, $Null)        
    $Insert.GetType().InvokeMember("Close", "InvokeMethod", $Null, $Insert, $Null)
    [System.Runtime.Interopservices.Marshal]::ReleaseComObject($Insert) | Out-Null
    
    $MsiDBCom.GetType().InvokeMember("Commit", "InvokeMethod", $Null, $MsiDBCom, $Null)
    [System.Runtime.Interopservices.Marshal]::ReleaseComObject($MsiDBCom) | Out-Null