Search code examples
wix

How to use WIX to set the recovery options for a Windows Service


Can I use Wix InstallService to set the recovery options for a Windows Service, and if so how ? I couldn't find any parameters for passing in the recovery option settings.

Alternately how can I run sc.exe to set the service recovery options after the install has completed from the WiX installer.

I found this Wix code elsewhere but it seems to do nothing. Where exactly in the WiX package.wsx should this go to ensure it gets called?

<CustomAction Id="SetRecoveryOptions" 
              Directory="INSTALLFOLDER" 
              Execute="immediate" 
              Impersonate="no" 
              ExeCommand="cmd.exe /c &quot;sc failure MyWindowsPService reset= 0 actions= restart/60000/restart/60000/restart/60000&quot;" 
              Return="asyncNoWait" />

<InstallExecuteSequence>
    <Custom Action="SetRecoveryOptions" 
            After="InstallFinalize" />
</InstallExecuteSequence>

The full Wix package file

<?xml version="1.0" encoding="UTF-8"?>

<!-- Define the variables in "$(var.*) expressions" -->
<?define Name = "My Windows Service" ?>
<?define Manufacturer = "XYZ Pty Ltd" ?>
<?define Version = "1.0.1.0" ?>
<?define UpgradeCode = "9ED3FF33-8718-444E-B44B-69A34433B7E98" ?>

<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
    <Package Name="$(Name)"
             Manufacturer="$(Manufacturer)"
             Version="$(Version)"
             UpgradeCode="$(var.UpgradeCode)"
             Compressed="true">

        <!-- Allow upgrades and prevent downgrades -->
        <MajorUpgrade DowngradeErrorMessage="A later version of [ProductName] is already installed. Setup will now exit." />

        <!-- Define the directory structure -->
        <Directory Id="TARGETDIR" Name="SourceDir">
            <Directory Id="ProgramFiles64Folder">

                <!-- Create a folder inside program files -->
                <Directory Id="ROOTDIRECTORY" Name="$(var.Manufacturer)">

                    <!-- Create a folder within the parent folder given the name -->
                    <Directory Id="INSTALLFOLDER_" Name="$(Name)" />
                </Directory>
            </Directory>
        </Directory>

        <MediaTemplate EmbedCab="yes" />

        <ComponentGroup Id="ProgramFiles" Directory="INSTALLFOLDER_">

            <Component Id="ABC.dll" Guid="*">
                <File Id="ABC.dll" KeyPath="true" Source="$(var.MyWindowsServices.TargetDir)\ABC.dll" />
            </Component>

            ...
            
        </ComponentGroup>

        <!-- The files inside this DirectoryRef are linked to
             the App.MyWindowsService directory via INSTALLFOLDER -->
        <DirectoryRef Id="INSTALLFOLDER_">


            <!-- Create a single component which is the App.MyWindowsService.exe file -->
            <Component Id="ServiceExecutable" Bitness="always64">

                <!-- Copies the MyWindowsServices.exe file using the
                     project reference preprocessor variables -->
                <File Id="MyWindowsServices.exe"
                      Source="$(var.MyWindowsServices.TargetDir)\MyWindowsServices.exe"
                      KeyPath="true" />


                <!-- Remove all files from the INSTALLFOLDER on uninstall -->
                <RemoveFile Id="ALLFILES" Name="*.*" On="both" />

                <!-- Tell WiX to install the Service -->
                <ServiceInstall Id="ServiceInstaller"
                                Type="ownProcess"
                                Name="MyWindowsService"
                                DisplayName="$(Name)"
                                Description="My Windows Service"
                                Account=".\LocalSystem"
                                Start="auto"
                                ErrorControl="normal" />

                <!-- Tell WiX to start the Service -->
                <ServiceControl Id="StartService"
                                Start="install"
                                Stop="both"
                                Remove="uninstall"
                                Name="MyWIndowsServices"
                                Wait="true" />
            </Component>
        </DirectoryRef>

        <CustomAction Id="SetRecoveryOptions" 
                      Directory="INSTALLFOLDER" 
                      Execute="immediate" 
                      Impersonate="no" 
                      ExeCommand="cmd.exe /c &quot;sc failure MyWindowsServices reset= 0 actions= restart/60000/restart/60000/restart/60000&quot;" 
                      Return="asyncNoWait" />

        <InstallExecuteSequence>
            <Custom Action="SetRecoveryOptions" 
                    After="InstallFinalize" />
        </InstallExecuteSequence>

        <!-- Tell WiX to install the files -->
        <Feature Id="Service" Title="MyWindowsServices Setup" Level="1">
            <ComponentRef Id="ServiceExecutable" />
            <ComponentGroupRef Id="ProgramFiles" />
        </Feature>

    </Package>
</Wix>

EDIT:

SO I have half the answer now. This now works as long as I run msiexec from an admin command line. Any idea how I can get the installer to run the custom command with the same installer privileges since it is already running as admin.

<CustomAction Id="SetRecoveryOptions"
                  Directory="INSTALLFOLDER_"
                  Execute="immediate"
                  Impersonate="no"
                  ExeCommand="[SystemFolder]sc.exe failure MyWindowsService reset= 0 actions= restart/60000/restart/60000/restart/60000"
                  Return="ignore" />

    <InstallExecuteSequence>
        <Custom Action="SetRecoveryOptions"
                OnExit="success" />
    </InstallExecuteSequence>

Solution

  • It seems like the util:ServiceConfig element should do what you want. For example, adding to your ServiceExecutable Component:

    <Component Id='ServiceExecutable' ...>
      ...
      <util:ServiceConfig Name='YourServiceName' FirstFailureActionType='restart' 
                          RestartServiceDelayInSeconds='60000' ... />
    </Component>
    

    That is way more robust than shelling out to sc.exe.


    Update: Given access to the OP's full source code, a more complete example might look like this:

    Install the Nuget Wix Util Extensions in Visual Studio Add the WiX extension's namespace following to the package.wxs file (second line)

    <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
         xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
    ...
    

    Added the util:ServiceConfig to the service executable component

        <!-- Create a single component for WindowsService.exe -->
        <Component>
    
            <!-- Installs the WindowsServices.exe file -->
            <File Id="WindowsServices.exe"
         Source="WindowsServices.exe" />
    
            <!-- Tell Windows Installer to install the Service -->
            <ServiceInstall Id="ServiceInstaller"
                            Type="ownProcess"
                            Name="WindowsService"
                            DisplayName="$(Name)"
                            Description="Windows Service"
                            Account=".\LocalSystem"
                            Start="auto"
                            ErrorControl="normal">
                <!-- Update the failure handling -->
                <util:ServiceConfig
                            FirstFailureActionType="restart"
                            RestartServiceDelayInSeconds="10000"
                            SecondFailureActionType="restart"
                            ThirdFailureActionType="restart"
                            />
            </ServiceInstall>
            
    
            <!-- Tell WiX to start the Service -->
            <ServiceControl Id="StartService"
                            Start="install"
                            Stop="both"
                            Remove="uninstall"
                            Name="WindowsService"
                            Wait="true" />
        </Component>