Waiting to Install

If you’ve ever tried to install a Windows Installer package and had returned ERROR_INSTALL_ALREADY_RUNNING (1618) it’s because the Windows Installer execution server is already processing another install, administrative install, or advertisement action. That is, Windows Installer is processing the InstallExecuteSequence table, the AdminExecuteSequence table, or the AdvtExecuteSequence table, respectively. This is because right before the action is run in the server context Windows Installer grabs the _MSIExecute mutex. Take a look at the log below:

=== Verbose logging started: 10/3/2005 14:26:48 Build type: SHIP UNICODE 3.01.4000.2435 Calling process: C:\Documents and Settings\User\Local Settings\Temp\SIT12330.tmp\setup.exe ===
MSI (c) (80:00) [14:26:48:285]: Resetting cached policy values
MSI (c) (80:00) [14:26:48:285]: Machine policy value ‘Debug’ is 0
MSI (c) (80:00) [14:26:48:285]: ******* RunEngine:
******* Product: C:\WINDOWS\Installer\3dfeb63f.msi
******* Action:
******* CommandLine: **********
MSI (c) (80:00) [14:26:48:285]: Client-side and UI is none or basic: Running entire install on the server.
MSI (c) (80:00) [14:26:48:285]: Grabbed execution mutex.
MSI (c) (80:00) [14:26:48:295]: Cloaking enabled.
MSI (c) (80:00) [14:26:48:295]: Attempting to enable all disabled priveleges before calling Install on Server
MSI (c) (80:00) [14:26:48:295]: Incrementing counter to disable shutdown. Counter after increment: 0
MSI (s) (3C:1C) [14:26:48:315]: Grabbed execution mutex.
MSI (s) (3C:0C) [14:26:48:315]: Resetting cached policy values
MSI (s) (3C:0C) [14:26:48:315]: Machine policy value ‘Debug’ is 0
MSI (s) (3C:0C) [14:26:48:315]: ******* RunEngine:
******* Product: C:\WINDOWS\Installer\3dfeb63f.msi
******* Action:
******* CommandLine: **********
MSI (s) (3C:0C) [14:26:48:315]: Machine policy value ‘DisableUserInstalls’ is 0

Multiple installer sessions can run in the client context concurrently but only one can enter the server context. If a concurrent installer session attempts to grab the execution mutex when another thread owns it, ERROR_INSTALL_ALREADY_RUNNING is returned. This isn’t always desirable in automated testing. SMS could be installing something at the time, for example, and the install you’re intending to test will fail.

Since mutexes are owned by a single thread, we can wait to own the mutex and then kick off an install successfully since the client will grab the execution mutex on the same thread if we’re making calls to Windows Installer APIs like the MsiInstallProduct function. Let’s take a look at some sample code that waits on the mutex and calls MsiInstallProduct which can do just about everything you need from installing a new package to patching, and administrative installs to advertised installs.

#define WIN32_LEAN_AND_MEAN

#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <msi.h>
#include <string>

typedef std::basic_string<TCHAR> tstring;

#pragma comment(lib, "msi.lib")

int _tmain(int argc, _TCHAR* argv[])
{
  DWORD dwError = NOERROR;
  LPTSTR pszPackage = NULL;
  tstring strArgs;
  HANDLE hMutex = NULL;
  SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES) };

  // Make sure at least a package was specified.
  if (2 > argc)
  {
    _ftprintf(stderr,
      TEXT("Error: you must provide the path to a Windows Installer package.\n"));
    _ftprintf(stderr, TEXT("Usage: %s <package> [Windows Installer options]\n"),
      argv[0]);
    return ERROR_INVALID_PARAMETER;
  }

  // Get the path the specified package.
  pszPackage = argv[1];

  // Build up remaining options as a single string.
  for (int i = 2; i < argc; i++)
  {
    strArgs += argv[i];
    strArgs += TEXT(" ");
  }

  // Grab the execution mutex when made available.
  hMutex = CreateMutex(&sa, FALSE, TEXT("_MSIExecute"));
  if (!hMutex)
  {
    dwError = GetLastError();
    _ftprintf(stderr,
      TEXT("Error: failed to grab execution mutex. GetLastError() = %d\n"),
      dwError);
    return dwError;
  }

  // Wait to grab ownership of the execution mutex.
  _tprintf(TEXT("Waiting for execution mutex...\n"));
  dwError = WaitForSingleObject(hMutex, INFINITE);
  if (WAIT_OBJECT_0 == dwError)
  {
    // Install the package on the current thread owning the execution mutex.
    _tprintf(TEXT("Installing the specified package..."));
    dwError = MsiInstallProduct(pszPackage, strArgs.c_str());
    _tprintf(TEXT("done.\n"));
  }

  if (ERROR_SUCCESS != dwError)
  {
    _ftprintf(stderr, TEXT("Error: an error has occured. GetLastError() = %d\n"),
      dwError);
  }

  CloseHandle(hMutex);
  return static_cast<int>(dwError);
}

Now even if another installation is being processed in the server context we can wait to install whatever product we specify by using a command similar to the following. Assume the code was compiled to MsiWait.exe.

MsiWait.exe Package.msi ACTION=INSTALL