Search code examples
windowswixwindows-installer

MSI/WIX: How to (self-)update a running service


I have to write an auto update service that updates our companies application(s) on our client PCs. One of the applications to update is the updater itself. I deploy all applications with a MSI packages created with WIX.

The service then spanws a process with "msiexec.exe /q /i " to start a silent install.

This works fine for the other products, but when I want to update the running service, the service is the one which started the process calling the installer. Hence I am trying to update a running process.

How would I go about this? "Fork" the installer process and exit the service? Use some clever Windows built-in method?


Solution

  • Thanks for the input, here is what I came up with:

    I am using a WIX installer with MajorUpgrade support and a ServiceInstall element to install the new service. This will cause MSI to stop the service and upgrade the installation.

    Now, to update the service from within I need to start the installer asynchronously and then allow the running service to be stopped.

    Basically we need to call:

    msiexec /package path_to_msi /quiet
    

    Since CreateProcess needs the full path the executable, I use SHGetKnownFolderPath to retrieve the SYSTEM32 path on the system

    // note: FOLDERID_SystemX86 will return 32 bit version of system32 regardless of application type
    PWSTR str = nullptr;
    if (SHGetKnownFolderPath(FOLDERID_SystemX86, KF_FLAG_DEFAULT, NULL, &str) != S_OK)
      throw std::runtime_error("failed to retrieve FOLDERID_SystemX86");
    std::string exe = ...path to msiexec...;
    std::string options = " /package \"path_to_msi\" /quiet";
    

    Now, we start the process:

    // start process
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));
    
    if (!CreateProcess(exe.c_str(),        // application name
                     options.c_str(),      // Command line options
                     NULL,                 // Process handle not inheritable
                     NULL,                 // Thread handle not inheritable
                     FALSE,                // Set handle inheritance to FALSE
                     0,                    // No creation flags
                     NULL,                 // Use parent's environment block
                     NULL,                 // Use parent's starting directory 
                     &si,                  // Pointer to STARTUPINFO structure
                     &pi))                 // Pointer to PROCESS_INFORMATION structure
      throw std::runtime_error("CreateProcess failed");
    

    And we're done.

    The installer will now signal the service to stop, make sure this is handled properly!

    The new service will be installed and hopefully be back in action in matter of seconds.

    Done ;-)

    If anyone needs more details, just ask away.