1

Unfortunately we broke some component rules in our msi setup made with Windows Installer for XML. From one specific point in time our windows service executable needs another file to work properly, but this setup is shared between different versions of our application. So we got an issue.

Service executable (snippet. ServiceInstall part removed)

  <Component Id="WindowsServiceId" Guid="GUID_A">
    <File Id ="file_ServiceEXE" Checksum="yes" Source="$(var.pathToServiceRelease)WindowsService.exe" KeyPath="yes" />
  </Component>
 

This above is our application version 2.0 with service 1.0.

In application version 2.1 we added another file to our setup, the version of all files are increased to 1.1:

<Component Id="AdditionalServiceFile" Guid="GUID_B" >
    <File Id="additionalFile_dll" Source="$(var.pathToServiceRelease)ServiceManager.dll" KeyPath="yes" Checksum="yes"/>
</Component>

So imagine we have both application setups 2.0 and 2.1 installed, so the version of the service executable is 1.1, as of the ServiceManager.dll When we deinstall application setup 2.1, it will remove the ServiceManager.dll since no other installation needs it, but leaves the executable in version 2.1 – so the service can’t be startet, since the dll is missing. Unfortunately, this scenario has been already released.

Our first approach to solve this issue was to install the ServiceManager.dll permanent, not a good approach, but works for most cases. But when we install the ServiceManager.dll Permanent=true, we are going into this:

  1. Install application 2.0, exe is installed in version 1.0
  2. Install application 2.1, exe and dll are installed in version 1.1
  3. Deinstall application 2.0, exe and dll are still installed in version 1.1
  4. Deinstall application 2.1, exe is beeing removed, the dll remains.
  5. Reinstall apllication 2.0: exe is installed in version 1.0, dll is still installed in version 1.1 -> the service is broken.

So we tried this:

<Component Id="WindowsServiceId" Guid="GUID_A">
   <RemoveFile Id=“myId“ Name=“ServiceManager.dll“/>
   <File Id ="file_ServiceEXE" Checksum="yes" Source="$(var.pathToServiceRelease)WindowsService.exe" KeyPath="yes" />
   <File Id="additionalFile_dll" Source="$(var.pathToServiceRelease)ServiceManager.dll" Checksum="yes"/>
</Component>

We simply added the ServiceManager file to he same component as the service executable, so both are always doing the same and are treated equally. Any prior left behinds are removed within the component. We would create an application 2.2 with the same service files like in application version 2.1, but just with changed installation like described above. We know that our application version 2.1 will always have that issue, but we would recommend our customers to delete this and never use it again. And we would address our main issue:

  1. Install application 2.0, exe is installed in version 1.0
  2. Install application 2.2, exe and dll are installed in version 1.1
  3. Deinstall application 2.0, exe and dll are still installed in version 1.1
  4. Deinstall application 2.2, exe is beeing removed, the dll is beeing removed.
  5. Reinstall apllication 2.0: exe is installed in version 1.0, dll is installed in version 1.0 -> should work fine, also in future releases with aplication 2.x and service version 1.y

Before the user can install application version 2.2, they must deinstall application version 2.1. Is this a valid approach? Or do we have to create an new component and a new window service?

  • I don't have time to read this in detail, but the general solution to this problem is to change both the component GUID and the file name of the file in question to decouple it from the past sins. [I think this old answer touches on the issue](https://stackoverflow.com/a/47932742/129130). – Stein Åsmul Apr 19 '23 at 22:37
  • @Stein: Thank you. Unfortunately our service can't be running in parallel at this time, so this would be our preferred solution for a future release. – Torben - TSC Apr 21 '23 at 07:31

1 Answers1

0

it will remove the ServiceManager.dll since no other installation needs it, but leaves the executable in version 2.1

If the executable is installed and running as a service, then it may not uninstall with everything else. I had a similar issue and was able to solve it with a custom action that handles cleanup/installation. So in my Product.wxs, I include CleanupActionRemove last in the install sequence:

<Binary Id="CleanupActionBinary" SourceFile="$(var.CleanupAction.TargetDir)$(var.CleanupAction.TargetName).CA.dll"/>
<CustomAction Id="CleanupActionStart"
  BinaryKey="CleanupActionBinary"
  DllEntry="StartService"
  Return="check"
  Execute="deferred"
  Impersonate="no"/>

<CustomAction Id="CleanupActionRemove"
  BinaryKey="CleanupActionBinary"
  DllEntry="RemoveService"
  Return="check"
  Execute="deferred"
  Impersonate="no"/>

<CustomAction Id="CleanupActionInstall"
  BinaryKey="CleanupActionBinary"
  DllEntry="InstallService"
  Return="check"
  Execute="deferred"
  Impersonate="no"/>

<InstallExecuteSequence>
  <Custom Action="PrepareLaunchApplication" After="InstallFinalize">NOT Installed OR REINSTALL OR UPGRADINGPRODUCTCODE</Custom>
  <Custom Action="LaunchApplication" After="PrepareLaunchApplication">NOT Installed OR REINSTALL OR UPGRADINGPRODUCTCODE</Custom>
  <Custom Action="QtExecAction" After="CustAction">NOT Installed</Custom>
  <Custom Action="CleanupActionStart" After="QtExecAction"/>
  <Custom Action="CleanupActionRemove" After="InstallFiles">Installed AND REMOVE ~= "ALL"</Custom>
</InstallExecuteSequence>

Then in my CleanupAction project:

    [CustomAction]
    public static ActionResult RemoveService(Session session)
    {
        session.Log("Begin Cleanup Action Uninstall Service!");
        ServiceInstaller serviceInstallerObj = new ServiceInstaller();
        InstallContext context = new InstallContext();
        serviceInstallerObj.Context = context;
        serviceInstallerObj.ServiceName = ServiceName;
        try
        {
            serviceInstallerObj.Uninstall(null);
        }
        catch (Exception ex)
        {
             session.Log(ex.Message);
        }

        return ActionResult.Success;
    }

This might help with your next installer version to help remove existing files, regardless of the version. Also, the GUID for each component should be the same across versions.

Mdurks
  • 16
  • 2