Search code examples
windowsinstallationwixwindows-installeruninstallation

When I try to uninstall my software containing an NSSM service, it fails


For the following installer package, I have the service XXXService successfully installed and running, when I install my package.

But uninstallation fails with an error and reverts. I suspect, that the service is still running blocking removal of main.exe file.

Please, help to make uninstallation work.

The complete minimal source to reproduce this bug is available.

<?xml version="1.0" encoding="UTF-8"?>
<?if $(env.ARCH) = x64 ?>
  <?define ProductName = "Uninstall Bug (64 bit)" ?>
  <?define Win64 = "yes" ?>
  <?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?else ?>
  <?define ProductName = "Uninstall Bug" ?>
  <?define Win64 = "no" ?>
  <?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
<?endif ?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  <Product Id="1edc84cf-3a2b-4be9-ab5d-a1553c37e8ef" Name="Uninstall Bug" Language="1033" Version="1.0.0.0" Manufacturer="Example" UpgradeCode="2a82c6fe-9e93-4ccd-8e6c-c04de9a8289b">
    <Package InstallerVersion="200" Compressed="yes" />

    <MediaTemplate EmbedCab="yes" />

    <Feature Id="ProductFeature" Title="Uninstall Bug" Level="1">
        <ComponentGroupRef Id="MyComponentGroupId" />
    </Feature>

    <Directory Id="TARGETDIR" Name="SourceDir">
        <Directory Id="$(var.PlatformProgramFilesFolder)">
            <Directory Id="INSTALLDIR" Name="Uninstall Bug" />
        </Directory>
    </Directory>

    <ComponentGroup Id="MyComponentGroupId">
        <Component Id="MyComponent" Directory="INSTALLDIR" Guid="dfb1e839-1f62-4613-b323-daa3166caab5" KeyPath="yes">
            <File Id="MainFile" Source="src/main.exe" />
            <?if Win64 = "yes" ?>
            <File Id="NSSMFIle" Source="distrib/nssm/win64/nssm.exe" />
            <?else ?>
            <File Id="NSSMFIle" Source="distrib/nssm/win32/nssm.exe" />
            <?endif ?>
        </Component>
    </ComponentGroup>

    <CustomAction Id="CreateService" Directory="INSTALLDIR" Execute="deferred" Impersonate="no"
    ExeCommand='"[INSTALLDIR]nssm" install XXXService "[INSTALLDIR]main.exe" xxx' />
    <CustomAction Id="SetServiceDirectory" Directory="INSTALLDIR" Execute="deferred" Impersonate="no"
    ExeCommand='"[INSTALLDIR]nssm" set XXXService AppDirectory [INSTALLDIR]' />
    <CustomAction Id="StartService" Directory="INSTALLDIR" Execute="deferred" Impersonate="no"
    ExeCommand='"[INSTALLDIR]nssm" start XXXService' />
    <CustomAction Id="StopService" Directory="INSTALLDIR" Execute="deferred" ExeCommand='"[INSTALLDIR]nssm" stop XXXService' Impersonate="no" Return="ignore" />
    <CustomAction Id="UninstallService" Directory="INSTALLDIR" Execute="deferred" ExeCommand='"[INSTALLDIR]nssm" remove XXXService confirm' Impersonate="no" Return="ignore" />
    <InstallExecuteSequence>
        <Custom Action="CreateService" After="InstallServices" />
        <Custom Action="SetServiceDirectory" After="InstallServices" />
        <Custom Action="StartService" After="StartServices" />
        <Custom Action="UninstallService" Before="DeleteServices" />
        <Custom Action="StopService" Before="StopServices" />
    </InstallExecuteSequence>
  </Product>
</Wix>

Solution

  • Instead of NSSM you can use modified srvany-ng.

    The WiX syntax is as follows:

    <Component Id="ServiceComponent" Guid="XXX" Directory="SrvAnyDIR">
        <File Id="SrvAnyFile" Source="srvany-ng\src\srvany-ng.exe" KeyPath="yes" />
        <ServiceInstall
            Id="MyServiceInstall"
            Type="ownProcess"
            Vital="yes"
            Name="XXX"
            DisplayName="XXX"
            Description="XXX"
            Start="auto"
            Account="LocalSystem"
            ErrorControl="normal"
            Arguments='"[NETHERMIND_DIR]Nethermind.Runner.exe" --config [ConfigDir]config.cfg --datadir [MyAppFolder]data --Init.LogDirectory [MyAppFolder]log --Init.StaticNodesPath [MyAppFolder]static-nodes.json' />
        <ServiceControl
            Id="MyServiceControl"
            Name="XXX"
            Stop="both"
            Remove="uninstall"
            Wait="yes" />
    </Component>
    

    This is the correct way to install executables used as a Windows service, because all (un)installation is done by the installer itself, without calling external commands as custom actions.