Wix: Windows Service sometimes uninstalled when upgrading

We install our software with Wix. Our setup also installs a Windows service. To allow users to change the login information for the Windows service we only want to install the service on first installation and only delete it on uninstall. For upgrades we manually stop the service so the files can be upgraded.

We have this working, but recently we found that on some machines the Windows service gets uninstalled during UnpublishFeatures:

This if from a failed upgrade log:

Action 13:41:38: UnpublishFeatures. Unpublishing Product Features
MSI (s) (D8:EC) [13:41:38:346]: Executing op: FeatureUnpublish(Feature=Main,,Absent=2,Component=
UnpublishFeatures: Feature: Main
MSI (s) (D8:EC) [13:41:38:346]: Note: 1: 1402 2: UNKNOWN\Installer\Features\84B659030632F794E93A7CB19A87DB8E 3: 2 
MSI (s) (D8:EC) [13:41:38:346]: Executing op: ActionStart(Name=StopServices,Description=Stopping services,Template=Service: [1])
Action 13:41:38: StopServices. Stopping services
MSI (s) (D8:EC) [13:41:38:362]: Executing op: ProgressTotal(Total=1,Type=1,ByteEquivalent=1300000)
MSI (s) (D8:EC) [13:41:38:362]: Executing op: ServiceControl(,Name=RidderIQWebApi,Action=2,Wait=1,)
StopServices: Service: Ridder iQ Web API
MSI (s) (D8:EC) [13:41:38:393]: Executing op: ActionStart(Name=DeleteServices,Description=Deleting services,Template=Service: [1])
Action 13:41:38: DeleteServices. Deleting services
MSI (s) (D8:EC) [13:41:38:393]: Executing op: ProgressTotal(Total=1,Type=1,ByteEquivalent=1300000)
MSI (s) (D8:EC) [13:41:38:393]: Executing op: ServiceControl(,Name=RidderIQWebApi,Action=8,Wait=1,)
DeleteServices: Service: Ridder iQ Web API

This if from a log from a successfull upgrade:

Action 11:53:24: UnpublishFeatures. Unpublishing Product Features
MSI (s) (CC:3C) [11:53:24:976]: Executing op: FeatureUnpublish(Feature=Main,,Absent=2,Component=
UnpublishFeatures: Feature: Main
MSI (s) (CC:3C) [11:53:24:977]: Note: 1: 1402 2: UNKNOWN\Installer\Features\84B659030632F794E93A7CB19A87DB8E 3: 2 
MSI (s) (CC:3C) [11:53:24:978]: Executing op: ActionStart(Name=RemoveFiles,Description=Removing files,Template=File: [1], Directory: [9])
Action 11:53:24: RemoveFiles. Removing files

As you can see Windows Installer skips the StopServices/DeleteServices actions and starts removing the files. Because the service is deleted on UnpublishFeatures later during the setup it attempts to configure the service, but fails because it's no longer installed:

MSI (s) (D8:68) [13:42:34:772]: Executing op: CustomActionSchedule(Action=ExecServiceConfig,ActionType=3073,Source=BinaryData,Target=ExecServiceConfig,CustomActionData=)
MSI (s) (D8:90) [13:42:34:772]: Invoking remote custom action. DLL: C:\Windows\Installer\MSI170B.tmp, Entrypoint: ExecServiceConfig
ExecServiceConfig:  Error 0x80070424: Service 'RidderIQWebApi' does not exist on this system.
ExecServiceConfig:  Error 0x80070424: Failed to get service: RidderIQWebApi
CustomAction ExecServiceConfig returned actual error code 1603 (note this may not be 100% accurate if translation happened inside sandbox)
Action ended 13:42:35: InstallFinalize. Return value 3.

My guess is that this happens because the action for the component is different for both upgrades, for the failed upgrade these are the component actions:

MSI (s) (D8:68) [13:41:26:400]: Component: cmp.SR.SDKWebAPI.Service.exe; Installed: Absent;   Request: Local;   Action: Local
MSI (s) (D8:EC) [13:41:36:400]: Component: cmp.SR.SDKWebAPI.Service.exe; Installed: Local;   Request: Absent;   Action: Absent

For the successfull upgrade these are the component actions:

MSI (s) (CC:44) [11:53:17:386]: Component: cmp.SR.SDKWebAPI.Service.exe; Installed: Absent;   Request: Local;   Action: Local
MSI (s) (CC:3C) [11:53:22:850]: Component: cmp.SR.SDKWebAPI.Service.exe; Installed: Local;   Request: Absent;   Action: FileAbsent

As you can see the action for the failed upgrade is Absent, and for the successfull upgrade is FileAbsent. From what I've read the FileAbsent means that the feature is reinstalled and Absent means the feature will be removed.

My question is how are the actions for components determined, and why is it on one machine Absent and on another machine FileAbsent. And is there a way to fix this?

The component if configured like this:

  <Component Id="cmp.SR.SDKWebAPI.Service.exe" Guid="">
    <File Id="fil.SDKWebAPI.Service.exe" Source="SDKWebAPI.Service.exe" KeyPath="yes" />
    <File Id="fil.SDKWebAPI.Service.exe.config" Source="SDKWebAPI.Service.exe.config" KeyPath="no" />
    <ServiceInstall Id="SDKWebAPI.Service.exe.Installer"
                    Type="ownProcess"
                    Name="RidderIQWebApi"
                    DisplayName="Ridder iQ Web API"
                    Description="Ridder iQ Web API service"
                    Start="auto"
                    Account="LocalSystem"
                    ErrorControl="ignore">
      <util:ServiceConfig FirstFailureActionType="restart" 
                          SecondFailureActionType="restart"
                          ThirdFailureActionType="restart"
                          RestartServiceDelayInSeconds="60" 
                          ResetPeriodInDays="0" />
    </ServiceInstall>
  </Component>

Solution 1:

Blank Component GUID: Guid="" is this something you set recently? This will, I believe, set a blank GUID for the component, meaning that it will be installed on first install and never touched or upgraded afterwards (unless you have found some trick to reinstall the component on upgrade) - and it won't be uninstalled either as far as I recall.

Late REP: The above (blank GUID) does not seem like what you intend. You just want the component to not uninstall on major upgrade, in which case what you generally would do would be to move RemoveExistingProducts late in the InstallExecuteSequence - something which requires you to follow all component rules to the letter. This is very complicated runtime behavior, but a simple concept. Essentially your new version will install as a patch - overwriting files without uninstalling them first - allowing your service credentials to be preserved since the component hosting the service is never uninstalled.

Early REP: Just for the record, the common way to do major upgrades is to schedule RemoveExistingProducts early in the InstallExecuteSequence meaning that all files are uninstalled, and then reinstalled. This approach is used because it allows sloppy component referencing. It is reknown for wiping out user data such as license keys, service credentials, etc...

Permanent Component: Another approach would be to set the hosting component to be permanent. Then it will never be uninstalled during a major upgrade (even if you user early REP), but not during a regular uninstall either, hence stranding the file(s) in question on the system (unless you add your own, custom cleanup features - which can be very error-prone).

Custom Action Backup Mechanism: Others rely on their own custom actions (example) to back up the data that gets wiped out during upgrade and then reapply them after the upgrade is complete. A very error-prone approach in my view.

Service Only MSI: You can also put the service installation in its own MSI to make its update more controllable for you - or in case the main setup can't be made to respect the component rules. This is also somewhat complicated, but better than custom actions in my view.

Minor Upgrade: If you can use minor upgrades to install upgrades, you can avoid this service credential problem. I will just link to another answer which describes this: Restarting windows service during WIX upgrade.

(Managed) Service Accounts: You could use a regular service account without (about service accounts) credentials to run the service - such as LocalService, LocalSystem or NetworkService (which obviously is not possible for you I would presume). Or the newer concepts of managed service accounts, group managed service accounts or virtual accounts step-by-step info (concepts that I do not know enough about).

Other Approaches: There are no doubt other approaches as well. I suppose you could keep the service configuration out of the MSI and apply it via a script. I wouldn't recommend it. I know that some people toggle between using services and scheduled tasks depending on the nature of the task at hand (essentially switching to scheduled tasks if it is a task that runs only once in a while). Though risky, I suppose you could postpone the service configuration to an elevated EXE that the user launches after installation (the user must be admin in this case, obviously) which could then set up the config with some interactivity (error and status messages directly to the user - and not just hidden in logs) which can sometimes help to get people going. Not my recommended approach though - elevated actions is what a setup is for. Any non-elevated configuration I like to do in the application.


Common Real-World MSI Problems: I wrote about some of the common problems seen in practical application of MSI a while back, and here it is: How do I avoid common design flaws in my WiX / MSI deployment solution? It is not great. I am not very happy with it - it is lacking in more ways than one - but there it is, in case it can help. It was best effort in the time available. Please take it for what it is: an unfinished dump of real-world problems with a few pointers here and there for what you can try to deal with the problem.


Links:

  • Chris Painter on service credential preservation