2

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?

Stein Åsmul
  • 39,960
  • 25
  • 91
  • 164
David Rogers
  • 2,601
  • 4
  • 39
  • 84

2 Answers2

3

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
Stein Åsmul
  • 39,960
  • 25
  • 91
  • 164
  • The DTF library is the way to go for sure. COM-Interop with the legacy automation interface is a real pain. – Christopher Painter Dec 07 '20 at 19:26
  • Script works great, yeah I think I was running against a corrupted installer just like you said, I also up voted your post on ethics around rebooting, that's super well written, I wish more people would put as much though into what there doing as you have, no disagreement on the DTF either, the only reason I'm going this way is because it's convenient and easy to call in a PostBuildEvents if this was on a bigger scale I'd console app this for sure. – David Rogers Dec 07 '20 at 21:46
  • Reboots should always be avoided. I am not sure why you need a reboot, but there is the **Restart Manager feature** of Windows Installer. Its basic idea is to shut down applications before installation automatically and then restart them after the installation rather than rebooting the OS. Some information here: [**`1)`** Restart Manager](https://stackoverflow.com/a/50935008/129130), [**`2)`** ScheduleReboot action](https://stackoverflow.com/a/48842663/129130), [**`3)`** Supporting Restart Manager in your application](https://www.advancedinstaller.com/user-guide/qa-vista-restart-manager.html). – Stein Åsmul Dec 07 '20 at 21:55
  • Your worst problem with REBOOT = Force will likely be the prompt for reboot in all installation modes. – Stein Åsmul Dec 07 '20 at 21:58
1

WiX has the DTF library that lets you do all this in just managed code. There is a powershell cmdlet that does a lot of stuff but not database editing. It's probably good code to look at though to understand what to do.

https://github.com/heaths/psmsi

It's written my a MSFT employee.

PS- Why edit the MSI? Your going to break it's digital signature if you do. Usually you would just say MSIEXEC /I FOO.MSI REBOOT=F if you want it to always reboot.

Christopher Painter
  • 54,556
  • 6
  • 63
  • 100
  • I sign the file after I edit this property not before, my goal is to make this a powershell script that is then invoked in the PostBuildEvents in my project (this is already the case with the digital signature) that way, I just rebuild everything and my installer is ready to go, no further work needed. – David Rogers Dec 07 '20 at 21:01
  • The point of me producing a MSI is to distribute it to non-technical users, ones who it would be very difficult to train to invoke MSIEXEC commands, this is similar to the [discussion here](https://stackoverflow.com/a/1609329/2912011) while it may fit other's use cases it does not fit mine. – David Rogers Dec 07 '20 at 21:10
  • You could just make a bootstrapper project to wrap with an EXE and in your MsiPackage element specify REBOOT=F – Christopher Painter Dec 08 '20 at 15:02