installationwixwindows-serviceswindows-installerrdp

Why does WiX Toolset MSI installer not start a Windows Service via Terminal Services, but does locally?


Use case

I was asked to write a WiX Toolset MSI installer for a piece of software (let's call it Z from now on).

Z was developed atop of ASP.NET Web API Core 6.0 and it basically exchanges data between two machines (let's call them C and S).

My task is to install Z on C.

For testing purposes, the team and I tried installing Z on our local laptops running Windows 10. This worked like a charm.

One member, however, didn't have a Windows box (let's call it M), so he was using a remote machine running Windows Server 2012 (let's call it W) to install the MSI package.

After two or three weeks of distress between M's owner and the team, we decided to connect to a spare server running Windows Server 2016 from one Windows laptop, and tried installing Z there.

We've finally agreed upon M's complaints.

The MSI install fails when done via RDP (aka Terminal Service or mstsc /admin).


Details of Z

This is the ProductComponents.wxs, where we instruct Z how the Windows Service is deployed.

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  <Fragment>
    <!-- Customizable component group -->
    <ComponentGroup Id="ProductComponents" Directory="APPLICATIONFOLDER">
      <ComponentRef Id="WindowsServiceComponent" />
      <ComponentRef Id="RemoveFolderComponent" />
    </ComponentGroup>

    <!-- Removal of the install folder when uninstalling the product -->
    <!-- and (un)installation of Z as a Windows Service -->
    <DirectoryRef Id="APPLICATIONFOLDER">
        
      <!-- Removal of the folder -->
      <Component Id="RemoveFolderComponent" Guid="{snipped}">
        <RemoveFolder Id="RemoveInstallFolder" Directory="APPLICATIONFOLDER" On="uninstall" />
      </Component>
            
      <!-- (Un)installation of Z's Windows Service -->
      <Component Id="WindowsServiceComponent" Guid="{snipped}">
        <File Id="fil87B519F6CA084815922D1FED42E5B4AD" Source="$(var.PublishDir)\Z.exe" KeyPath="yes" Vital="yes" />
        <ServiceInstall Id="WindowsServiceInstall" Name="Z" DisplayName="!(loc.ProductName_$(var.Platform))" Description="!(loc.WindowsServiceDescription)" Start="auto" Type="ownProcess"
          ErrorControl="normal" />
        <ServiceControl Id="StartService" Name="Z" Start="install" Stop="both" Remove="uninstall" Wait="yes" />
      </Component>

    </DirectoryRef>
  </Fragment>
</Wix>

And this is our Product.wxs, where we tell WiX about Z itself:

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
    
  <!-- Fixed upgrade ID (do *NOT* change it) -->
  <?define UpgradeCode = "{someguid}"?>

  <!-- Declare Z -->
  <Product Id="*" Name="!(loc.ProductName_$(var.Platform))" Language="!(loc.Language)" Version="$(var.BuildVersion)" 
      Manufacturer="!(loc.CompanyName)" UpgradeCode="$(var.UpgradeCode)">
        
    <!-- Provide package details -->
    <Package Compressed="yes" InstallScope="perMachine"
        Platform="$(var.Platform)"
        Manufacturer="!(loc.CompanyName)"
        Description="!(loc.PackageDescription)"
        Keywords="!(loc.PackageKeywords)"
        Comments="!(loc.PackageComments)"
        InstallPrivileges="elevated" />

    <!-- Upgrade -->
    <Property Id="PREVIOUSVERSIONSINSTALLED" Value="0" />
    <Upgrade Id="$(var.UpgradeCode)">
      <UpgradeVersion Minimum="1.0.0.0" Maximum="$(var.BuildVersion)" Property="PREVIOUSVERSIONSINSTALLED" />
    </Upgrade>
    <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeErrorMessage)" AllowDowngrades="no"
      AllowSameVersionUpgrades="yes" IgnoreRemoveFailure="no" Schedule="afterInstallInitialize"  />
        
    <!-- Include .cab file into the .msi file -->
    <MediaTemplate EmbedCab="yes" />
        
    <!-- Use the icon.ico icon for this installer (shows up in Add/Remove Programs) -->
    <Icon Id="MyBeautifulIcon" SourceFile="Assets\icon.ico" />
    <Property Id="ARPPRODUCTICON" Value="MyBeautifulIcon" />
        
    <!-- Help/Support website (shows in the Add/Remove Programs) -->
    <Property Id="ARPURLINFOABOUT">http://www.z-enterprise.co.jp/</Property>

    <!-- Disable Change/Repair buttons -->
    <Property Id="ARPNOREPAIR" Value="yes" Secure="yes" />
    <Property Id="ARPNOMODIFY" Value="yes" Secure="yes" />
        
    <!-- Define components (files, shortcuts, Windows Services, etc.) -->
    <Feature Id="RootFeature" Title="Z" Level="1">
      <ComponentGroupRef Id="ProductComponents" />
      <ComponentGroupRef Id="AutoGeneratedComponents" />
    </Feature>

    <UIRef Id="InstallerUI"/>

    <!-- Custom actions and their order of execution 
    See more at https://learn.microsoft.com/en-us/windows/win32/msi/standard-actions-reference -->
    <InstallUISequence>
      <Custom Action="ReadSettingsAction" After="CostFinalize" />
    </InstallUISequence>
    <InstallExecuteSequence>
      <Custom Action="CheckSettingsAction" After="InstallInitialize" />
      <Custom Action="SetPropertyAction" After="CheckSettingsAction" />
            
      <!-- The condition NOT REMOVE below skips the custom action during uninstall -->
      <Custom Action="SaveSettingsAction" Before="InstallServices"><![CDATA[NOT REMOVE]]></Custom>
    </InstallExecuteSequence>
        
    <!-- Enable verbose log mode for the installer. Logs are saved at %temp%\MSI{random chars}.LOG. -->
    <Property Id="MsiLogging" Value="v" />
  </Product>
</Wix>

The Windows user that is being employed to run the MSI installer is a local Administrator.

This user is able to install the Windows Service, but not to start it.

The MSI log file from an offending execution of Z is shown below,

MSI (s) (04:F8) [07:59:44:887]: Created Custom Action Server with PID 6620 (0x19DC).
MSI (s) (04:5C) [07:59:44:887]: Executing op: ActionStart(Name=StartServices,Description=Starting services,Template=Service: [1])
MSI (s) (04:5C) [07:59:44:887]: Executing op: ProgressTotal(Total=1,Type=1,ByteEquivalent=1300000)
MSI (s) (04:5C) [07:59:44:887]: Executing op: ServiceControl(,Name=Z,Action=1,Wait=1,)
MSI (s) (04:4C) [07:59:44:902]: Running as a service.
MSI (s) (04:4C) [07:59:44:902]: Hello, I'm your 64bit Elevated Non-remapped custom action server.
SFXCA: Extracting custom action to temporary directory: C:\Windows\Installer\MSIBE30.tmp-\
SFXCA: Binding to CLR version v4.0.30319
Calling custom action Z.CustomActions!Z.CustomActions.CustomActions.SaveSettings
Begin SaveSettings().
SaveSettings() has finished.
1: SaveSettingsAction 2: 0 
MSI (s) (04:5C) [08:00:15:014]: Note: 1: 2205 2:  3: Error 
MSI (s) (04:5C) [08:00:15:014]: Note: 1: 2228 2:  3: Error 4: SELECT `Message` FROM `Error` WHERE `Error` = 1920 
MSI (s) (04:94) [08:00:30:142]: I/O on thread 1524 could not be cancelled. Error: 1168
MSI (s) (04:94) [08:00:30:142]: I/O on thread 6288 could not be cancelled. Error: 1168
MSI (s) (04:94) [08:00:30:142]: I/O on thread 7668 could not be cancelled. Error: 1168
MSI (s) (04:94) [08:00:30:142]: I/O on thread 1356 could not be cancelled. Error: 1168
MSI (s) (04:94) [08:00:30:142]: I/O on thread 8044 could not be cancelled. Error: 1168
MSI (s) (04:94) [08:00:30:142]: I/O on thread 8284 could not be cancelled. Error: 1168
MSI (s) (04:94) [08:00:30:142]: I/O on thread 4088 could not be cancelled. Error: 1168
MSI (s) (04:5C) [08:00:30:142]: Note: 1: 2205 2:  3: Error 
MSI (s) (04:5C) [08:00:30:142]: Note: 1: 2228 2:  3: Error 4: SELECT `Message` FROM `Error` WHERE `Error` = 1709 
MSI (s) (04:5C) [08:00:30:142]: Product: Z (64-bit) -- Error 1920. Service 'Z (64-bit)' (Z) failed to start.  Verify that you have sufficient privileges to start system services.

For some reason, these 1168 errors were concerning me. I thought it might be the case that Z was trying to call the .NET Runtime. Then one of my attempts was to install the ASP.NET Core Runtime 6.0, like shown below.


Some other questions

I tried the following:

  1. Checking out some GPOs by doing: Win+R, run gpedit.msc, expand [Computer Configuration] → [Windows Settings] → [Security Settings] → [Local Policy] → [Security Options] in the Local Group Policy Editor interface, find and double-click to open [User Account Control: Run all administrators in administrator approved mode] in the right column, as shown in the figure below.
  2. Checking out the "Windows button > Change User Account Control settings"
  3. Making sure we ran the Terminal Service Console using: mstsc /admin or the equivalent for the platform
  4. Restarting the Z's target machine
  5. Installing the ASP.NET Core Runtime 6.0

to no avail.


Solution

  • Not exactly an answer but a good process to diagnose these failures is to:

    1. Run the install
    2. Wait for the error dialog indicating that the service failed to start
    3. Do NOT dismiss the error dialog
    4. Use cmd (net start <name>) or services.msc to start the service
    5. The service should fail to start, so start diagnosing why your code won't start

    Not clear why TS would introduce problems, but the above should help you isolate the service start issue. 99.999% of the time, there is a problem with the service code.