Updating a package with a Windows service resets the service account and password
I am working on an MSI installer with WiX. I try to keep it as simple as possible: it is an internal product and my users are our IT professionals.
The product includes a Windows service that must be configured to run under a different account for each machine.
The workflow I planned for my users (for the initial installation) is as follows:
- Run the installer (The installer installs the service under the default account)
- Stop service via
sc
or local services applet - Update the properties of the service that will run under the appropriate account for the specific machine. (The account is different for each machine, and only the IT staff has access to passwords.)
- Restart the service
Subsequent updates consist of installing the updated MSI files.
Testing the "small" update, I was surprised to find that the installer reset is back to working under the default account. This is a serious problem for me because it is very difficult for users to update their servers. They will have to re-enter the account information on each machine every time there is an update. I expected this to happen with a "major" update, but not a "small" one.
-
Is there a way to configure the installer so that it doesn't change the existing account / password configuration for the service during a "minor" or "minor" upgrade?
-
Will this also happen during the "repair" (I have not tried this)?
This is what my component looks like in the file .wxs
:
<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 expected Remove="uninstall"
to keep the service in place if there were no changes to it. Obviously not. (I'm not too worried if this happens on "major" updates).
I also noticed that the element ServiceConfig
has attributes ( OnReinstall
) that seem to match a vector, but from the candle error messages it is pretty clear that it is OnReinstall
intended to only affect configuration items the element ( PreShutdownDelay
etc.) and not a service setting in the whole.
I looked them over:
- Let the user specify which account the service is running under
- WiX MajorUpgrade for Windows service, save .config and prevent reboots
- How to stop and not uninstall Windows services on major update in wix?
Curiously, this answer assumes that this is a problem for "major" updates only. This was not my experience. Was my experience random?
During the installation process, it would be correct to specify the account and password, but storing the password in the registry or elsewhere is not really an option in this case, and having to re-enter the credentials with every update is as destructive as manually reconfiguring the service.
source to share
I had a phone call with FireGiant about this problem and we came up with a solution.
Background:
- When installing our application, MSI first installs Windows Service using
LocalService
, however, our real desktop software changes it toNetworkService
or even a custom user account, which may be necessary in certain network environments. Our element
<Component> <ServiceInstall>
hadAccount="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>
Performing these repeated steps will unintentionally reset the service registration / configuration:
- Complete the installation using MSI version 1.0.0
- Open
Services.msc
and changeRussianSpyingService
to useNT AUTHORITY\NetworkService
(instead ofNT AUTHORITY\LocalService
) - Create a new MSI using the same files
*.wxs
but with higher file versions and give it a higher version like 1.0.1 (remember MSI only uses the first 3 components of the version number and ignores the 4th version) - After completing this installation, make sure it has
RussianSpyingService
been reset for useNT AUTHORITY\LocalService
.
Also, I asked FireGiant (their consultants previously worked at Microsoft and helped other company teams use MSI) who other software, such as SQL Server, can use MSI to install Windows services that work fine despite configuration changes between updates- They told me that products like SQL Server often use custom actions to configure a Windows service, and despite the general advice to avoid custom actions, it is acceptable because the SQL Server team at Microsoft is large enough to allocate resources for development and testing to make them work.
Decision
- In short: "Use MSI properties!"
- Specifically, define the MSI property that represents the attribute value
Account
, and load that value from the registry at MSI startup time, and if no value is present, use the defaultNT AUTHORITY\LocalService
. - Ideally, the property value should be stored in its own application registry key, and the application should ensure that this value matches the current service configuration.
- This can be done by creating a new registry key in
HKLM
which allowsLocalService
orNetworkService
(or any other service account) to write to it, so when the service starts, it writes the name of its user account there. - But it is difficult. - Don't use
HKCU
values ββfor storing because that won't work:HKCU
Allows completely different registry hives (which may not even be loaded or accessible) for different users.
- This can be done by creating a new registry key in
- The other option is not technically supported by Microsoft because it uses its own
services
Windows registry key valueObjectName
(account name), which is usually in the same format that the attribute usesAccountName=""
. This is also the most pragmatic, and this is what is described below:
Here's what worked for us:
In your element
<Wix> ... <Product>...
add this declaration<Property>
and element<RegistrySearch />
:<?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 the item
<ServiceInstall
to use the new MSI propertySERVICE_ACCOUNT_NAME
forAccount=""
instead of the previous hardcoded oneNT 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 Setup, run the update script, and you will see that the account username of any configured service will persist between update installations.
You can generalize this approach to other properties as well.
Denial of responsibility:
- Microsoft does not officially endorse user-space programs that directly interact with a registry key
HKLM\SYSTEM\CurrentControlSet\Services\
. All operations in Windows Services are designed to execute the documented and supported Win32 Service Control Manager API: https://docs.microsoft.com/en-us/windows/desktop/services/service-control-manager- This means that Microsoft can, at its discretion, change the configuration of the Windows service so that it no longer uses the key
HKLM\SYSTEM\CurrentControlSet\Services\
. - (This can break a lot of third-party software; if Microsoft did, they would probably add some sort of virtualization or remapping system to it, as they do with
SysWow6432Node
).
- This means that Microsoft can, at its discretion, change the configuration of the Windows service so that it no longer uses the key
- I've only tested with
LocalService
andNetworkService
. I haven't seen what happens if you change the service configuration to use a custom user account after installation before running the update. I expect it to preserve the configuration in this case as well, as it will perform string comparison for the valueObjectName
in SCM and it has no access to passwords.
source to share
In the end I got it
<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 and a combination of several other topics with similar answers.
One of the possible reasons why it doesn't work is that WIX also removes the service upon reinstallation. We only want to install the service once, during the initial installation. We also want to make sure that the service is removed after deletion. This is the only combination of conditions that worked for me, allowing the service to save its settings and user account.
source to share