4

I have a customized setup application which installs several MSI files. today I tried to implement the external UI to implement my own progress bar using this article. everything looks working (progressbar receives data and updates) but after about 60% when starting to update the components I receive an exception: 'object not set to ...' and digging further got this: _COMPlusExceptionCode "-532462766"

checked the process monitor and suddenly realized the msiexec is running in 32 bit mode.

funny is when calling the msiexe directly it starts in 64 bit but using MsiInstallProduct() method it starts in 32 bit.

I believe the exception raises when the msiexec tries to configure the registry keys and as the MSI files are 64 bit it crashes.

any help appreciated.

Cheers, Afshin

Update 1: enabled the log using MsiEnableLog and this error showed up:

"MSI (c) (94:F8) [07:50:29:395]: Internal Exception during install operation: 0xc0000005 at 0x000007FE9827F768."

Update 2: digging further based on suggestion by @marceln, used Process Monitor and noticed there are two msiexec processes in the memory. one in 64 bit mode which is in session 0 and the other starts by the first one when I call MsiInstallProduct. the second one starts from 'c:\windows\syswow64\msiexec.exe' which is 32 bit version. I tried to set the lookup path using SetDllDirectory but still getting the same result.

Update 3: the main process is definitely running in 64 bit mode: both prccess monitor proves this and the powershell command result:

[reflection.assemblyname]::GetAssemblyName("setup.exe") | fl

Name                  : Setup
Version               : 5.0.0.0
CultureInfo           :
CultureName           :
CodeBase              : file:///...../Setup.exe
EscapedCodeBase       : file:///Setup.exe
ProcessorArchitecture : **MSIL**
ContentType           : Default
Flags                 : None
HashAlgorithm         : SHA1
VersionCompatibility  : SameMachine
KeyPair               :
FullName              : Setup, Version=5.0.0.0, Culture=neutral, PublicKeyToken=null

Update 4: I'm using this method to import MSI.DLL:

[DllImport("msi.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern int MsiInstallProduct(string packagePath, string commandLine);

Update 5: Tried the process explorer, the application is 64 bit, MSI.DLL file under the application is running from system32. but the msiexec.exe process is still running from syswow64 which is 32 bit. msi file is built as a 64 bit msi.

Update 6: I just found out that this line is the source of the problem:

oldHandler = MSIIntrop.MsiSetExternalUI(
    new MSIIntrop.InstallUIHandler(OnExternalUI),
    32735,
    IntPtr.Zero);

Update 7 [FINAL UPDATE]: To whom it may concern: After hours and hours of wasting time, I finally managed to overcome the issue. looks like there's some sort of internal memory management leak in the MSI API which causes the external UI handler crash in a completely random behaviour. To fix the issue, I implemented IDisposable interface and tried to use the class in a "using" block to make sure the class is completely disposed. The MsiSetExternalUI() and MsiInstallProduct() are now safely called within this block. do not forget to call the MsiSetExternalUI() to revert the UI to it's original state:

IntPtr prevWindow = IntPtr.Zero;
MSIIntrop.INSTALLUILEVEL prevUILevel = MSIIntrop.MsiSetInternalUI(MSIIntrop.INSTALLUILEVEL.INSTALLUILEVEL_NONE, ref prevWindow);

using (MSIContext context = new MSIContext(progressChanged, messageRaised))
{
    MSIIntrop.INSTALLUI_HANDLER prevHandlre = MSIIntrop.MsiSetExternalUI(context.Handler,
        MSIIntrop.INSTALLLOGMODE.INSTALLLOGMODE_FATALEXIT |
        MSIIntrop.INSTALLLOGMODE.INSTALLLOGMODE_ERROR |
        MSIIntrop.INSTALLLOGMODE.INSTALLLOGMODE_WARNING |
        MSIIntrop.INSTALLLOGMODE.INSTALLLOGMODE_ACTIONDATA |
        MSIIntrop.INSTALLLOGMODE.INSTALLLOGMODE_PROGRESS,
        IntPtr.Zero);
    try
    {
        int ret = MSIIntrop.MsiInstallProduct(runningPath, commandLine);
    }
    catch (Exception ex)
    {
        messageRaised("Error: " + ex.Message);
    }
    finally
    {
        MSIIntrop.MsiSetExternalUI(prevHandlre, 0, IntPtr.Zero);
    }
}

PS 1: I have't put this in the answer since I'm not %100 sure that this was the source of the error. This is just a workaround. PS 2: Thanks to Marcel and Jacob :)

Afshin
  • 1,222
  • 11
  • 29
  • Then your host process must be 32 bit (the one that calls `MsiInstallProduct`). Have you checked that? – Marcel N. May 30 '13 at 09:08
  • Yes @marceln, I have! the host process is 64 bit, as I mentioned, if I start the msiexec.exe using Process class and pass the MSI file as argument, it starts in 64 bit mode. but using MSI API and MsiInstallProduct starts in 32 bit – Afshin May 30 '13 at 21:21
  • Then use Dependency Walker to check which version of msi.dll (and it's location) msiexec.exe is using. Then make sure you're using the same one from your managed process. Also, make sure your managed process is built with the `Any CPU` target. – Marcel N. May 30 '13 at 21:28
  • I will try dependency walker right away. it is built with Any CPU. – Afshin May 30 '13 at 21:56
  • @marceln: tried your approach but no luck... please see Update 2... – Afshin May 30 '13 at 23:45
  • Then there's something wrong with your managed app. See this in order to check the actual platform of your assembly: http://stackoverflow.com/questions/270531/how-to-determine-if-a-net-assembly-was-built-for-x86-or-x64 + comments – Marcel N. May 30 '13 at 23:49
  • I'm pretty sure it is Any CPU (unless visual studio lies), btw I tried the powershell method to get the metadata info (see update 3) – Afshin May 31 '13 at 00:08
  • What is the type of msi.dll loaded by your executable: 32-bit or 64-bit? Msi is the dll that hosts MsiInstallProduct. – Jacob Seleznev May 31 '13 at 00:30
  • @JacobSeleznev: I'm importing msi.dll using DLLIMPORT (see update 4) and before calling the method I use SetDllDirectory (update 3) to set the lookup path to system32 (which the msi.dll x64 resist). – Afshin May 31 '13 at 00:36
  • To be sure you need to confirm it. [Get the list of dlls currently loaded in the process](http://kb.froglogic.com/display/KB/Getting+a+list+of+DLLs+currently+loaded+in+a+process) – Jacob Seleznev May 31 '13 at 00:54
  • Thanks Jacob and sorry for the late answer... Just tried the process explorer and I can confirm that the MSI.DLL is 64 bit. but the msiexec.exe (in the task manager) is definitely 32 bit – Afshin Jun 02 '13 at 21:23

2 Answers2

0

I think after two years of the code running with no issues it would be proper to conclude the workflow proposed in the Update 7 above is an answer to the question:

looks like there's some sort of internal memory management leak in the MSI API which causes the external UI handler crash in a completely random behaviour. To fix the issue, I implemented IDisposable interface and tried to use the class in a "using" block to make sure the class is completely disposed. The MsiSetExternalUI() and MsiInstallProduct() are now safely called within this block. do not forget to call the MsiSetExternalUI() to revert the UI to it's original state

Afshin
  • 1,222
  • 11
  • 29
0

The reason is the garbage collector. Please, try to use GC.KeepAlive() method to prevent the garbage collector from collecting the external UI handler.

For example:

// create ui handler
MSIIntrop.UIHandlerDelegate externalUIHandler = new MSIIntrop.UIHandlerDelegate(context.Handler);

// execute MsiSetExternalUI with this handler
MSIIntrop.INSTALLUI_HANDLER prevHandlre = MSIIntrop.MsiSetExternalUI(externalUIHandler,
    MSIIntrop.INSTALLLOGMODE.INSTALLLOGMODE_FATALEXIT |
    MSIIntrop.INSTALLLOGMODE.INSTALLLOGMODE_ERROR |
    MSIIntrop.INSTALLLOGMODE.INSTALLLOGMODE_WARNING |
    MSIIntrop.INSTALLLOGMODE.INSTALLLOGMODE_ACTIONDATA |
    MSIIntrop.INSTALLLOGMODE.INSTALLLOGMODE_PROGRESS,
    IntPtr.Zero);

// install product
int ret = MSIIntrop.MsiInstallProduct(runningPath, commandLine);

// restore the previous ui handler
MSIIntrop.MsiSetExternalUI(prevHandlre, 0, IntPtr.Zero);

// prevent GC from collecting ExternalUIHandler during the product installation
GC.KeepAlive(externalUIHandler);
IPoddubnyi
  • 11
  • 2