Wix - ServiceControl start takes four minutes to fail, should be 30 sec - wix

My service automatically starts during install...
<ServiceControl Id="StartService" Start="install" Stop="both" Remove="uninstall" Name="HeskaGateway" Wait="yes" />
And it works fine if I provide the service with a valid connection string. If I provide a bad connection string the service starts and stops very quickly... I see this when I go to Services and do a manual start. According to the documentation on MSI ServiceControl Table, a Wait value of "yes" turns into a 1 which means it should wait for 30 seconds and then fail. It takes 4 minutes and 7 seconds. Why so long?
MSI (s) (6C:78) [16:36:41:932]: Executing op: ServiceControl(,Name=HeskaGateway,Action=1,Wait=1,)
StartServices: Service: Heska Gateway
MSI (s) (6C:78) [16:40:48:862]: Note: 1: 2205 2: 3: Error
MSI (s) (6C:78) [16:40:48:862]: Note: 1: 2228 2: 3: Error 4: SELECT `Message` FROM `Error` WHERE `Error` = 1920
Error 1920. Service 'Heska Gateway' (HeskaGateway) failed to start. Verify that you have sufficient privileges to start system services.
EDIT: I never got to find out what my real problem was. I also had an installation sequencing error because my CustomAction (deferred) which would edit the connection string in the JSON file was triggering AFTER the ServiceStart. Trying to move the ServiceStart after that deferred custom action was awful. So I killed off the start from the ServiceControl entry and then added another custom action which silently ran "SC.EXE start HeskaGateway". I'll document that below as a solution.

The installer has a custom UI which asks the user to copy-paste their connection string given to them from support department. The installer edits a JSON file in the app folder using a deferred CustomAction. It is deferred because it needed to be after the files are written to the disk and it also needed to have elevated permissions. This all worked great until I decided to have the service start itself "at the end" of the installation. My first attempt was to use a
<ServiceControl Id="StartService" Start="install" ...>
But that was taking 4 minutes to fail. Troubleshooting showed that the service was being started BEFORE the custom action which writes the connection string into the JSON file. I needed the service start to be delayed till after the custom action. I looked at adding a second ServiceControl entry into its own component that could be scheduled much later but that gave me an uncomfortable feeling that I was going to break uninstall and repair installs. So, I just added another deferred custom action sequenced right after the JSON file edit.
That new action executes "SC.EXE start MyServiceName". SC.EXE is a non-blocking way to start services so succeed or fail, it will finish quickly.
My final solution:
<Component Id="MyCloudSync.exe" Guid="{generate-your-own-guid}">
<File Id="MyCloudSync.exe.file" KeyPath="yes" Source="$(var.RELEASEBINARIES)\MyCloudSync.exe" />
<ServiceInstall Id="MyCloudSync.exe"
Type="ownProcess"
Name="MyGateway"
DisplayName="My Gateway"
Description="Synchronizes laboratory data with Cloud"
Start="auto"
ErrorControl="normal" />
<!--Start is performed by a customer action that calls SC.EXE so it can be delayed after the custom action that writes the JSON file -->
<ServiceControl Id="StartService" Stop="both" Remove="uninstall" Name="MyGateway" Wait="yes" />
</Component>
You should note that the ServiceControl entry above does not have a "start="
The DLL that GetConnectionString and SetConnectionString calls is one of my own making. Wix has its own custom action for running command lines quietly... WixQuietExec
<CustomAction Id= "GetConnectionString"
BinaryKey="MyCustomActions"
DllEntry="GetConnectionString"
Execute="immediate"/>
<CustomAction Id= "SetConnectionString"
BinaryKey="MyCustomActions"
Impersonate="no"
DllEntry="SetConnectionString"
Execute="deferred"/>
<CustomAction Id="SetConnectionStringDeferredParams"
Property="SetConnectionString"
Value=""[INSTALLFOLDER]""[CONNECTIONSTRING]"" />
<Property Id="QtExecStartService" Value=""SC.EXE" start MyGateway"/>
<CustomAction Id="QtExecStartService"
BinaryKey="WixCA"
DllEntry="WixQuietExec"
Impersonate="no"
Execute="deferred"
Return="ignore"/>
Starting the service is just a convenience so for the installer to prevent going to Services.msc to perform the start or requiring a reboot. So I used Return="ignore". Also SC.EXE just puts the service into "Start Pending" so it probably can't return much of an error unless your service doesn't exist.
NOTE: WixQuietExec is documented here. Make sure to quote the EXE and give your property the same Id as your CustomAction that uses WixQuietExec. That info is under "Deferred Execution" but I still got it wrong on my first try.

The service itself might be doing something. The service control protocol includes a service status that's returned from the service itself, and this tells Windows what's going on. One of the items in there is a wait hint. Knowing nothing about the service, it's possible that the service is aware that it might have a slow startup and tells Windows (with a wait hint) that it should wait longer. 30 seconds is really a default, not a fixed value. This post refers to the wait hint for a managed code service:
How to choose value for serviceStatus.dwWaitHint?
You didn't show the ServiceControl used to install the service, but if it's shared with another service in the same process things can get complicated because the process itself can't terminate while it's also hosting another service.

Error 1920: this error would seem to indicate a missing privilege (logon as a service) or access right, or perhaps some sort of interference from an external cause or perhaps an MSI package that is not running elevated (unlikely I think - then you can only write to per-user paths).
Are you running this service with the LocalSystem account or NetworkService or LocalService or with a regular user account? (about the above service accounts).
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).
If you use a regular user account, does it have the "logon as a service" privilege set?
If you create the user in your WiX MSI (or define it), you can set LogonAsService="yes" for the User(element) in question. I believe this adds the privilege for the account (SeServiceLogonRight).
A privilege is different from access rights (ACLs) - for the record: a privilege is a pervasive system-wide access to some sort of function / feature - for example change system time, start / stop services, logon as a service, etc... (see section 13 here for more).
Is there a security software / antivirus on your test box? It could be interfering with your setup's API calls. If so, try to disable it (if possible) during the installation process. I'll mention firewalls too.
Is your MSI set to run elevated? (Package Element => InstallPrivileges).
UPDATE: Just adding the issue it turned out to be: the custom action update of config data needed to run with elevated rights or faulty service configuration data resulted which in turn caused the generic 1920 error message. In this case the configuration was in JSON format, it can obviously be in several formats: XML, registry, INI, etc... See OP comment above for more details.
Timeout: As to the long timeout. I have seen this sometimes with security software (locks the whole setup so the service timeout runs only after some scanning delay), or with setups that trigger the creation of a system restore point prior to the installation kicking off in the first place (this is one of the possible reasons why some MSI installations suddenly take a long time when they ordinarily complete quickly - it is possible to prevent this restore point creation - MSIFASTINSTALL). Also, maybe check this answer from serverfault and the registry value described for whether there is a policy on your network to change the default service start timeout: How do I increase windows service startup timeout (ServicesPipeTimeout). Frankly I am not sure whether MSI uses its own timeout or the system default one - maybe someone can illuminate? One could also speculate that the database connection you initiate has its own timeout? (doesn't match your interactive test experience?) Maybe you can check your code and your call and let us know? Does your service depend on another service? (see symantec link below). Any dependencies to files installed to the GAC or to WinSxS as mentioned by Chris in the first link below?
A lot of speculation. Let's hope some of it helps or that it inspires other ideas that solve the problem. Below some links for safekeeping (trying to write a generic answer that may also help others with the same or similar problems - which makes the answer too long, sorry about that).
Links:
Error 1920 service failed to start. Verify that you have sufficient privileges to start system services
http://blog.iswix.com/2008/09/different-year-same-problem.html
https://support.symantec.com/en_US/article.TECH103676.html (adds issues such as: dependency on other services)
How to debug Windows services (service timeout and more)
windows service startup timeout (I would check this)
Service failed to start error 1920 (generatePublisherEvidence)

Related

How do you make a WiX installer fail as soon as a service fails to start?

Snippet from my wxs file:
<ServiceInstall Id="ServiceInstall" Type="ownProcess" Vital="yes"
Name="service name"
DisplayName="service display name"
Description="service description"
Start="auto" Account="[SERVICEUSERNAME]" Password="[SERVICEPASSWORD]"
ErrorControl="normal" Interactive="no" />
<ServiceControl Id="StartService"
Start="install" Stop="uninstall" Remove="uninstall"
Name="service name" Wait="yes">
<ServiceArgument>arguments for first run</ServiceArgument>
</ServiceControl>
If the service fails to start, the installer waits for several minutes before failing, whereas it ought to be able to detect that it is in the stopped state, and from that point could never reach the started state without manual intervention. Yet, it keeps waiting. Is there any way to fix this?
Service control is a message-based scheme. Windows Installer and the service control mechanism wait for the service to respond from the start message. It's not clear what you mean by "detect it's in the stopped state" because the code might be doing legitimate work, may have crashed but has recovery code, or may be configured to restart after a failure (see ChangeServiceConfig2 Win32 API).
In your case, you have wait=yes which explicitly causes the installer to wait until the service is completely initialized. As the MSDN docs say:
"Leaving this field null or entering a value of 1 causes the installer to wait a maximum of 30 seconds for the service to complete before proceeding. The wait can be used to allow additional time for a critical event to return a failure error. A value of 0 in this field means to wait only until the service control manager (SCM) reports that this service is in a pending state before continuing with the installation."
So changing your Wait value could help. In any event, the normal case when a well-coded service starts normally doesn't cause any of these issues. The fact that the service is broken is not only a rare case (or should be) but when things are broken they are in fact broken and often unpredictable for that reason.

MSI: How a Service is stopped on uninstall during InstallValidate - could that work anyway?

I want to know, how and when in concrete a service is tried to stop if it's marked as Remove="uninstall" Stop="uninstall" in the WiX Project File.
Why I'm asking:
On uninstalling, the service is not recognized or handled correctly by the RESTART MANAGER resulting in the "restart dialog".
I attached the debugger to the service and realized, that it's never been tried to be stopped - at least, not via the registered ControlHandler. I was guessing, that it will be stopped in the same manner as executing "sc stop servicename" or by stopping it via the Services-Dialog. The service itself is written in C++ and the control handler is registerd by RegisterServiceCtrlHandler which works quite normally with the both outlined cases. The handler is invoked when stopping the service. Not so, if it's stopped/uninstalled via the MSI.
By the way, as stated by the answer of jbudreau in Wix Installer Problem: Why does RestartManager mark Service as RMCritical and not RMService can a service ever been stopped in the InstallValidate section if the service is running as LocalSystem? In InstallValidate, the uninstall-process is not elevated yet.
Some explanation of how the process of stopping a LocalSystem service is working on uninstall would be great!
Thanks in advance!
EDIT:
Here is my service definition with ServiceInstall and ServiceControl:
<DirectoryRef Id="BINDIR">
<!-- the service -->
<Component Id="myProgramsvc.exe" Guid="1f64c03f-26ea-47ba-985c-2a566afffffa">
<File Id="myProgramsvc.exe" KeyPath="yes" Vital="yes"
Source="SourceDir\bin\myProgramsvc.exe"/>
<ServiceInstall Id="MyProgramSvc" Type="ownProcess"
Vital="yes" Name="MyProgramSvc" DisplayName="Test MyProgram Service"
Description="Test MyProgram Service" Start="auto" Account=".\LocalSystem"
ErrorControl="normal" Interactive="no" Arguments="--run"/>
<ServiceControl Id="MyProgramSvc" Name="MyProgramSvc" Wait="yes" Remove="uninstall" Stop="uninstall" Start="install"/>
</Component>
</DirectoryRef>
and the process hierarchy, where the RestartManager is complaining about the ----as.exe (PID: 4312):
EDIT 2: (the id 9324 is different from the screenshot, took from the logfile of a differenty try)
MSI (s) (BC:38) [16:46:14:141]: RESTART MANAGER: Detected that application with id 9324, friendly name '---as.exe', of type RmCritical and status 1 holds file[s] in use.
MSI (s) (BC:38) [16:46:14:141]: RESTART MANAGER: Did detect that a critical application holds file[s] in use, so a reboot will be necessary.
EDIT 3:
The Logfile: uninstall.log - the logfile is too large for posting here (30000 chars limitation).
InstallValidate doesn't stop services - that's done by the StopServices action. InstallValidate is what tries to figure out what files are in use (with Restart Manager) and show a files-in-use dialog. Where services are concerned, it ignores any files in use by services that are in the ServiceControl table with a "stop at uninstall" setting. If you want to be sure that the service completely stops, use wait="yes". It's a standard stop service control handler call.
If the service fires off a bunch of processes that remain running then it's just an ordinary files-in-use issue and services are not relevant. Those processes need a shut-down call (from the service?) or they should integrate with Restart Manager, this kind of thing : http://www.codeproject.com/Articles/772868/Restart-Manager-Support-For-Windows-Application
If a process responds to windows messages the WiX util CloseApplication should shut it down.
I'll point out that you may simply have a bug in your WiX where the service named in your ServiceControl is incorrect. Since you can control any service (not just one you're installing/uninstalling) there is no check that the name corresponds to your service or any other. If you never see the service control stop being called this is the simplest explanation.

Updating a package with a Windows Service resets service's account and password

I'm working on an MSI installer with WiX. I'm trying to keep this as simple to develop as possible: this is an internal product, and my users are our IT personnel.
The product includes a Windows Service that must be configured to run under a different account for each machine.
The workflow I was planning for my users (for first-time install) is as follows:
Run the installer
(The installer sets up the service under a default account)
Stop the service via sc or Local Services applet
Update the service properties to run under the correct machine-specific account.
(The account is different for each machine, and only the IT personnel has access to the passwords.)
Restart the service
Subsequent updates would consist of installing from updated MSI files.
Testing a "small" update, I was surprised to find that the installer reset the service back to running under the default account. This is a major problem for me because it makes it really hard for my users to update their servers. They would have to re-enter the account information on each machine every time there is an update. I expected that would happen with a "major" update, but not on a "small" one.
Is there a way to configure the installer so that it does not change the existing account/password configuration for a service during a "small" or a "minor" update?
Will this happen during a "repair" as well (I haven't tried that)?
Here's what my component looks like in the .wxs file:
<Component Id="cmpService" Guid="{MYGUIDHERE}">
<File Id="filService" KeyPath="yes" Name="ServiceApp.exe" />
<ServiceInstall Id="ServiceInstall" Name="ServiceApp" DisplayName="My Service"
Type="ownProcess" Start="auto" ErrorControl="normal"
Account="LocalSystem">
<util:PermissionEx ... attributes here... />
</ServiceInstall>
<ServiceControl Id="StartService" Start="install" Stop="both" Remove="uninstall"
Name="ServiceApp" Wait="yes" />
</Component>
I had expected that Remove="uninstall" would preserve the service in place if there were no changes to it. Apparently not. (I'm not too worried if this happens on "major" updates).
I also noticed that the ServiceConfig element has attributes (OnReinstall) that seem to fit the bill, but based on candle error messages, it's pretty clear that OnReinstall is intended to affect only the configuration members of the element (PreShutdownDelay, etc.) rather than the service installation as a whole.
I've looked into these:
Let the user specify in which account a service runs
WiX MajorUpgrade of Windows Service, preserving .config, and avoiding a reboot
How to only stop and not uninstall windows services when major upgrade in wix?
Curiously, this answer suggests that this is an issue only for "major" upgrades. That wasn't my experience. Was my experience a fluke?
How do I create a custom dialog in WiX for user input?
It would have been OK prompting for an account and password during installation, but storing the password in the registry or elsewhere is a not really an option in this case, and having to reenter the credentials on every update is just as disruptive as having to reconfigure the service by hand.
I had a consultation phone-call with FireGiant today about this exact issue and we came to a solution.
Backstory:
Our application install MSI installs a Windows Service using LocalService initially, however our actual desktop software changes this to NetworkService or even a custom user account as may be necessary in certain network environments.
Our <Component> <ServiceInstall> element had Account="NT AUTHORITY\LocalService" and looked like this:
<Component Id="Comp_File_OurServiceExe" Guid="*">
<File Source="$(var.TargetDir)OurService.exe" id="File_OurServiceExe" KeyPath="yes" />
<ServiceInstall
Id = "ServiceInstall_OurServiceExe"
Vital = "yes"
Name = "RussianSpyingService"
DisplayName = "Russian Spying Service"
Description = "Crawls your network for incriminating files to send to the FSB"
Account = "NT AUTHORITY\LocalService"
Type = "ownProcess"
Arguments = "-mode service"
Interactive = "no"
Start = "auto"
ErrorControl = "normal"
>
<ServiceConfig DelayedAutoStart="yes" OnInstall="yes" OnUninstall="no" OnReinstall="yes" />
<util:ServiceConfig FirstFailureActionType="restart" SecondFailureActionType="restart" ThirdFailureActionType="none" ResetPeriodInDays="1" />
</ServiceInstall>
</Component>
When these repro-steps are followed the service registration/configuration would be unintentionally reset:
Complete an install using the MSI version 1.0.0
Open Services.msc and change the RussianSpyingService to use NT AUTHORITY\NetworkService (instead of NT AUTHORITY\LocalService)
Create a new MSI using the same *.wxs files, but higher file versions and give it a higher version, e.g. 1.0.1 (don't forget MSI only uses the first 3 components of a version number and ignores the 4th version)
After that install has finished, observe the the RussianSpyingService has been reset to use NT AUTHORITY\LocalService.
As an aside, I asked FireGiant (their consultants previously worked at Microsoft and helped other teams at the company use MSI) who other software, like SQL Server are able to use MSI to install Windows Services that work fine despite configuration changes between upgrade-installs. They told me that products like SQL Server often use Custom Actions for windows service configuration and despite the general advice to avoid Custom Actions it's acceptable because the SQL Server team at Microsoft is big enough to devote engineering and test resources to ensure they work.
Solution
In short: "Use MSI properties!"
Specifically, define an MSI property that represents the Account attribute value and load that value from the registry during MSI startup and if the value is not present, use a default value of NT AUTHORITY\LocalService.
Ideally the property value would be stored in the application's own registry key and it is the application's responsibility to ensure that value matches the current service configuration.
This can be done by creating a new registry key in HKLM that lets LocalService or NetworkService (or whatever the service account is) write to it, so when the service starts-up it records its user-account's name there - but this is complex.
Do not use HKCU to store the value because that won't work: HKCU resolves to completely different registry hives (that might not even be loaded or accessible) for different users.
The other option is technically not supported by Microsoft because it uses the Windows registry's own services registration key raw ObjectName (Account name) value - which so-happens to be in the same format used by the AccountName="" attribute. It's also the most pragmatic and it's what is described below:
Here's what worked for us:
Within your <Wix> ... <Product>... element, add this <Property> declaration and <RegistrySearch /> element:
<?xml version="1.0" encoding="UTF-8"?>
<Wix
xmlns = "http://schemas.microsoft.com/wix/2006/wi"
xmlns:netfx = "http://schemas.microsoft.com/wix/NetFxExtension"
xmlns:util = "http://schemas.microsoft.com/wix/UtilExtension"
>
<Product
Id="*"
UpgradeCode="{your_const_GUID}"
otherAttributes="goHere"
>
<!-- [...] -->
<Property Id="SERVICE_ACCOUNT_NAME" Value="NT AUTHORITY\LocalService">
<!-- Properties used in <RegistrySearch /> must be public (ALL_UPPERCASE), not private (AT_LEAST_1_lowercase_CHARACTER) -->
<RegistrySearch Id="DetermineExistingServiceAccountName" Type="raw" Root="HKLM" Key="SYSTEM\CurrentControlSet\Services\RussianSpyingService" Name="ObjectName" />
</Property>
<!-- [...] -->
</Product>
</Wix>
Update your <ServiceInstall element to use the new SERVICE_ACCOUNT_NAME MSI property for Account="" instead of the previous hardcoded NT AUTHORITY\LocalService:
<ServiceInstall
Id = "ServiceInstall_OurServiceExe"
Vital = "yes"
Name = "RussianSpyingService"
DisplayName = "Russian Spying Service"
Description = "Crawls your network for incriminating files to send to the FSB"
Account = "[SERVICE_ACCOUNT_NAME]"
Type = "ownProcess"
Arguments = "-mode service"
Interactive = "no"
Start = "auto"
ErrorControl = "normal"
>
<ServiceConfig DelayedAutoStart="yes" OnInstall="yes" OnUninstall="no" OnReinstall="yes" />
<util:ServiceConfig FirstFailureActionType="restart" SecondFailureActionType="restart" ThirdFailureActionType="none" ResetPeriodInDays="1" />
</ServiceInstall>
Build and run your installer and perform the upgrade scenario and you'll see any customized service Account User Name will be preserved between upgrade installs.
You can generalize this approach for other properties too.
Disclaimer:
Microsoft does not officially endorse user-land programs directly fiddling with the HKLM\SYSTEM\CurrentControlSet\Services\ registry key. All operations on Windows Services are meant to go through the documented and supported Win32 Service Control Manager API: https://learn.microsoft.com/en-us/windows/desktop/services/service-control-manager
This means that Microsoft could at their discretion, change Windows Service configuration so it no-longer uses the HKLM\SYSTEM\CurrentControlSet\Services\ key.
(This would presumably break lots of third-party software, if Microsoft were to do this they would probably add some kind of virtualization or re-mapping system to it like they do with SysWow6432Node).
I only tested it with LocalService and NetworkService. I didn't see what happens if you modify the service configuration to use a custom user account post-install before running an upgrade. I do expect that it will also preserve the configuration in that case as it would be performing a string-comparison on the ObjectName value in SCM and it has no access to passwords.
What finally ended up working for me was
<DeleteServices><![CDATA[REMOVE ~= "ALL" AND (NOT UPGRADINGPRODUCTCODE)]]> </DeleteServices>
<InstallServices><![CDATA[NOT Installed]]> </InstallServices>
I arrived at this answer through a series of trial and error attempts and a combination of a few other threads with similar answers.
One of the possible reasons why only the doesn't work is because WIX also removes the service upon re-install.. we only want to install the service once, during the initial install. We also want to make sure that the service is removed upon uninstall. This is the only combination of conditions which worked for me, allowing the service to keep its settings and user account.

Start service after all custom actions

We tried ServiceControl Start="install" ... but service is trying to start before some custom actions. For example deferred CA that installs DB or CA that modifies some file. But it is completely wrong. So, we use CA to start services. Do we do it in a right way?
The ServiceControl element creates the ServiceControl table is processed by the StartServices standard action. You need to look at your built MSI using ORCA and take a look at the InstallExecute sequence.
At a minimum these custom actions need to be scheduled before StartServices. Otherwise the service would need to be more flexible such as polling for changes to it's config file and reloading and being ok with a database that's not yet available and trying to connect later.

how to add firewall exception and show message if failed, without rollback

This is the case, I want to install an application and add a firewall exception on installation, but when that fails the installation should not rollback, but display a notification to the user/administrator performing the installation.
I use WIX to build the installer.
What I have so far is the following piece to install the firewall exception:
<Component Id="fwException" DiskId="1" Guid="guid">
<fw:FirewallException
Name="new firewall exception"
Id="FirewallException"
Port="1234"
Protocol="tcp"
Scope="any"
IgnoreFailure="yes"
>
</fw:FirewallException>
</Component>
All fine and doesn't rollback, but how do I get the installer to display a notification at end of installation or a dialog box, informing the user that the firewall exception hasn't been added.
As silent failure to add the exception to the firewall can be misleading when troubleshooting.
I am thinking of using ?fwException = INSTALLSTATE_ABSENT in a condition somewhere, but don't know where to use it to get the desired effect.
Any hints/tips much appreciated.
Short of rolling your own custom action that prompts an ignore, retry, cancel dialog when the UILevel property indicates it's an interactive installation, the way I've seen most installers ( SQL Server, TFS ) handle it is to have a prereq check in the UI portion to warn you that a problem may exist.
The problem they typically check for is that the firewall is disabled and that therefore the exception can't be registered. Therefore if you later enable the firewall the application will not work.
Personally, the way I handle it is the firewall exception in the installer is a nice to have for 90% of people. People may be running other firewall products or the firewall configuration may change later so it's best to have the application itself to instrument this.