WiX Service Install with user credentials or LocalSystem depending on UI input - wix

Below is a section of my .wxs installer file which installed my service. I have a WiX UI dialog where the user can either choose to install the service to run under the LocalSystem account or input the credentials of the account they want. The UI has two text boxes (one bound to the ACCOUNT property and one to the PASSWORD property) and a checkbox that is bound to a property called USELOCALSYSTEMACCOUNT.
Individually, these service installers work fine, you can see both ServiceInstall sections below. However I want the installer to install the service with the user input credentials if USELOCALSYSTEMACCOUNT is not set to 1, or to use the LocalSystem account if USELOCALSYSTEMACCOUNT is set to 1, but I am unsure how to do this. the Condition tag doesn't work within a ServiceInstall tag so I'm a bit lost on how to achieve this.
WiX component section below:
<Component Id='MainExe' Guid='*'>
<File Id='MainService'
Name='MyService.exe'
Source='$(var.ServiceRoot2)\SA.MyService.exe'/>
<ServiceInstall Id='MyService'
Type='ownProcess'
Vital='yes' Name='MyService'
Name='$(var.HumanProductName)'
Description='$(var.ProductDescription)'
Start='demand'
Account='[ACCOUNT]'
Password='[PASSWORD]'
ErrorControl='ignore'
Interactive='no'/>
<ServiceInstall Id='MyService'
Type='ownProcess'
Vital='yes'
Name='$(var.HumanProductName)'
Description='$(var.ProductDescription)'
Start='demand'
Account='LocalSystem'
ErrorControl='ignore'
Interactive='no'/>
<ServiceControl Id='MyServiceServiceControl'
Stop='both'
Remove='both'
Name='$(var.HumanProductName)'
Wait='yes'/>
</Component>

You should be able to put the condition around the entire component and just have the LocalSystem version and the User specified version. Wix is smart enough to not package the Myservice.exe twice in the installer. Just make sure your condition has "OR Installed" appended to it because I've had issues in the past with conditional components/features not uninstalling because the property they depended on was not set during uninstall. If it is a state of the machine (VersionNT/VersionVT64 which remain constant) then omitting "OR Installed" should be fine.

Related

Custom action of Wix failed to call vb script

I have one VB script in the package itself. I need to call it using CMD, the default way of calling the script taking too much time, so I am trying to call it with CMD and CSCRIPT but the installer raises the error while installation.
I am using the following code which is not working as expected. I searched a lot but didn't find the solution.
<Binary Id="ServiceInstall" SourceFile="..\..\..\AddVirDir.vbs" />
<CustomAction Id="InstallService" BinaryKey ="ServiceInstall"
ExeCommand="CMD /C "[#ServiceInstall]""
Execute="immediate" Return="check" HideTarget="no" Impersonate="no"/>
WiX IIS Elements: If this is all IIS, I would avoid scripting and custom actions whenever possible and use WiX's built-in IIS elements. Here is a Web-installer sample from Rainer Stropek available on github.com.
Look for <iis:WebVirtualDir ... /> et al. Find the WiX Documentation here. I believe you should be able to accomplish what you need without too many custom actions.
DISM.exe: Stropek himself uses custom actions in another sample source to set up IIS using DISM.exe. Not sure I would do it like this (no other suggestions though), but it is a sample of custom actions and IIS.
Need-For-Speed: As to your installation performance issues. Maybe you need to suppress the creation of a restore point and limit file costing? The Windows Installer engine allows this - see link below. I doubt it will be very effective though. I think there must be something else wrong in your installer. Some timeout issue? It could relate to other custom actions, slow network, or some other issue. Can you elaborate on your deployment scenario?
In any case, here is some documentation on speeding up MSI installations in general. Essentially the MSIFASTINSTALL property is the only one I would recommend. DISABLEROLLBACK can cause genuine problems.
Logging: I normally recommend that setup developers enable default verbose MSI logging - as described in the "Globally for all setups on a machine" section, to always have a log file ready when you need one. It is created with a random name for every MSI operation in the TEMP folder and you sort by modify to get the latest one. The log file might give clues as to why the installation is slow - just determine what is really going on. Apologies if this is just obvious trifles and you have this set up already.
Manual log file creation:
msiexec.exe /i C:\Path\Your.msi /L*v C:\Your.log
Interpreting an MSI log: interpreting a log file can be challenging sometimes. Here is an answer with some links to help with this.
Service Installation & Control: You should not install services with scripts. There are built-in mechanisms in MSI that are vastly superior. You simply use the ServiceInstall and ServiceControl WiX XML Elements and "declare" how the service should be registered and when and how the service should be started and stopped:
<Component>
<File Source="$(var.SourceDir)\WindowsService.exe" />
<ServiceInstall Name="MyService" ErrorControl="normal" Start="auto" Type="ownProcess" />
<ServiceControl Id="MyService" Name="MyService" Start="install" Stop="both" Remove="uninstall" Wait="yes" />
</Component>
Look! No custom actions! :-) - Just MSI auto-magic. There is no need to use any custom actions for this. MSI is full-featured and reliable provided your service executable behaves as it is supposed to.
Let me link to a similar sample on github in case the above is not clear. It is more complete and elaborate.
VBScript: I wrote this before I saw you dealt with services. I'll just throw it in: to call a script that doesn't have a function you can try something like this:
<!-- The VBScript file -->
<Binary Id='Sample.vbs' SourceFile='Sample.vbs' />
<!-- The Custom Action -->
<CustomAction Id='Sample.vbs' VBScriptCall='' BinaryKey='Sample.vbs'
Execute='immediate' Return='ignore'/>
<!-- And Insert Into Installation Sequence -->
<InstallExecuteSequence>
<Custom Action='Sample.vbs' After='AppSearch'/>
</InstallExecuteSequence>
That should work for a script like this (Sample.vbs - no funtions, just an implicit main function):
MsgBox(Session.Property("ProductName"))
There is an answer on the topic of VBScript custom actions here: WIX installer execute vbscript from CustomAction.

Install a MSI based on whether a windows service is disabled or not

I have a Bundle setup such that it installs a combination about 4 msis, like, MSI_1 -> MSI_2 -> MSI_3 -> MSI_4.
I want the MSI_4 to be installed only if a particular windows service is NOT disabled, i.e. Don't install if the windows service (say ABC) is disabled. I can probably use a CustomAction and run the command
"Get-WMIObject win32_service -filter "name='ABC'" -computer "." | select -expand state"
to check for the state. But how do I use that(can I use that?) to determine whether to execute the installer or not. Mind you, all the 4 MSIs have their own project and they are combined under the umbrella of one VS solution. I don't have to go the CustomAction route, so if you have anything else in mind I am open to try that as well.
Thanks for your time and help in advance. Let me know if something is not clear.
Will answer my own question here, since I found the solution. What I didn't know was the services listing was also available in the registry setting and one can use Util:RegistrySearch element in the wxs where we are doing the Chain element. One can modify the state of the service from Running to Disabled to observe the change in the value and can set the InstallCondition accordingly.
<util:RegistrySearch Root="HKLM"
Key="System\CurrentControlSet\Services\ABC"
Win64="yes"
Value="Start"
Variable="State" />
And the Chain element will be like:
<MsiPackage Id="package"
Cache="no" Vital="yes"
DisplayInternalUI="no"
ForcePerMachine="yes"
SourceFile="package.msi"
InstallCondition="(State = <condition>)" />
Hope this helps.

Wix stop service on uninstall/upgrade: prevent "restart popup" (file-in-use situation)

I've got the issue, that when uninstalling (or upgrading) the Restart Manager is complaining about a file in use situation, and so is forcing a reboot:
RESTART MANAGER: Detected that application with id 7000, friendly name 'javaw.exe', of type RmCritical and status 1 holds file[s] in use.
RESTART MANAGER: Did detect that a critical application holds file[s] in use, so a reboot will be necessary.
The service that RESTART MANAGER is complainig about, is a java-based service. The service (here called myservice.exe) is recursively starting java child processes:
myservice.exe --run
↳ javaw.exe --someArguments
↳ someother.exe --someArguments
↳ javaw.exe --someMoreArguments
The wix snippet for the service definition:
<DirectoryRef Id="BINDIR">
<Component Id="myservice.exe" Guid="PUT-GUID-HERE">
<File Id="myservice.exe" KeyPath="yes" Vital="yes"
Source="SourceDir\bin\myservice.exe"/>
<ServiceInstall Id="MyService" Type="ownProcess"
Vital="yes" Name="MyService" DisplayName="My Service"
Description="My Service" Start="auto" Account=".\LocalSystem"
ErrorControl="normal" Interactive="no" Arguments="--run"/>
<ServiceControl Id="MyService" Name="MyService" Wait="yes" Remove="uninstall" Stop="uninstall" Start="install"/>
</Component>
</DirectoryRef>
Now, the interesting part:
The service could be started when installing
on uninstall:
if not running, it will be removed
if running, and just agreeing to make a restart
it is indeed stopped within about 2-3 seconds (i guess by the StopServices action)
and successfully removed (by RemoveServices action)
The entries in the Service* Tables seems good for me so far.
ServiceControl-Table:
ServiceControl Name Event Arguments Wait Component_
MyService MyService 161 1 myservice.exe
ServiceInstall-Table:
ServiceInstall Name DisplayName ServiceType StartType ErrorControl LoadOrderGroup Dependencies StartName Password Arguments Component_ Description
MyService MyService My Service 16 2 32769 .\LocalSystem --run myservice.exe My Service
So, to break down everything:
It seems that the Restart Manager is not recognizing, that the java processes are child processes and will be stopped by the StopServices action.
I found some kind of similar problems here:
https://www.mail-archive.com/wix-users#lists.sourceforge.net/msg57924.html
Wix Installer Problem: Why does RestartManager mark Service as RMCritical and not RMService
Thanks in advance for any help to solve this issue!
You have a couple of options to resolve this issue:
-Disable "Restart Manager" by making use of MSIRESTARTMANAGERCONTROL= "Disable" in the property table. This would kick in the legacy "FilesInUse" dialog box.
In your case, The FilesinUse dialog might also not be displayed (since services do not have a window associated with them)
The "FilesinUse" dialog box does not list processes which do not have a window associated with them. So, in your case, disabling the Restart Manager, might not display any dialogs(neither FilesInUse nor RestartManager).
However, this would also mean that a reboot might be required, not necessarily because of your services but due to some other process which might be holding your files in use. If you think that there could be no other process other than your own services holding files, then go ahead and follow this approach . If you think that there could be other processes, other than your services holding files, then having "Restart Manager" enabled is ideal. Not having "Restart Manager" will either result in one of the things:
-Display the Legacy FilesInUse dialog asking you to shut down the processes listed in the dialog. This might result in you having to shut down these processes via a custom action.
Both the "RestartManager" and "FilesInUse" dialogs are displayed by the "InstallValidate" standard action. If you want to suppress both of these dialogs, then ensure that your custom action is scheduled before "InstallValidate" standard action. There is a catch here. Scheduling such a custom action before InstallValidate would have to be an immediate mode custom action(You cannot have deferred mode custom actions before "IntsallFinalize"). So , in cases where you are not running as an administrator(such as in UAC enabled scenarios), you might not have the necessary privileges to shut down applications. So , a restart might be required.
-You can also shut down applications using the using the WiX util extensions CloseApplication() function.
Evaluate your scenario and do what is right for you.
I think I might be late to the party, but here's the solution. The Installer team blog post explains how the Restart Manager decides whether to pop up the files in use dialog. Specifically (Windows Installer-Restart Manager Interaction in Detail section, item 3.b.):
If the package is authored such that the services detected by RM would be shutdown because of the authoring of the Service* tables then those services will not be displayed in the files-in-use dialogs.
(italics are mine). Helpful but not immediately helpful, because such that is not really elaborated. But since my service caused the same problem as described by the OP with
<ServiceControl Stop="uninstall" ... />
I just changed the value to both
<ServiceControl Stop="both" ... />
which was probably the only remaining thing that could make it “such that,” and boom, fireworks, magic:
MSI (s) (50:A0) [21:50:30:352]: RESTART MANAGER: Detected that application with id 6408, friendly name 'XXXX', service short name 'xxxx', of type RmService and status 1 holds file[s] in use.
MSI (s) (50:A0) [21:50:30:352]: RESTART MANAGER: Detected that the service xxxx will be stopped due to a service control action authored in the package before the files are updated. So, we will not attempt to stop this service using Restart Manager
It appears that both flags msidbServiceControlEventStop (0x002) and msidbServiceControlEventUninstallStop (0x020) need to be set in the ServiceControl table to make the RM happily conclude that the service is going to stop before files are updated.
In retrospect this makes sense. Since the uninstallation part during the upgrade is carried out using the old cached MSI database, RM does not look into it to see what is going to happen when the related product is uninstalled. Strictly speaking, there may be multiple products to uninstall, and the Installer does not require anywhere that these related products (those found by the FindRelatedProducts action, including the old version of the same upgrade code) be in fact related to the service that one is controlling in the current package. So it does not care about the service action on uninstall as scripted in the current package (it does not apply to the install action anyway!). For the sake of consistency, it requires a simple and straightforward evidence that the service is going to be stopped before the files in use are overwritten, gathering such evidence from the current package alone.
So it is quite likely that the RM cares about the msidbServiceControlEventStop (0x002) flag only during the installation.

Set environment variable before running a custom action in WiX

I have to build an MSI-based installer using WiX and I need to set environment MY_HOME before running a command action.
I have a component:
<Component Id="SEMYHOME"
Guid="*my guid*">
<CreateFolder />
<Environment Id="MY_HOME"
Action="set"
Part="all"
Name="MY_HOME"
Permanent="no"
System="yes"
Value="[APPLICATIONPATH]myapp"/>
</Component>
Then I have a custom action:
<CustomAction Id="InstallMyService"
Directory="INSTALLDIR"
ExeCommand='"[INSTALLDIR]myapp\install_service.bat" install'
Execute="immediate"
Return="ignore"/>
<InstallExecuteSequence>
<Custom Action="InstallMyService"
After="InstallFinalize"/>
</InstallExecuteSequence>
NOTE: This action need the MY_HOME variable to be set before running.
When install this MSI, I got a log showing that the MY_HOME variable is set before running the custom action "InstallMyService", but the command to install my service still fails. I found that the cause is when command called, MY_HOME still not set.
After an install is finished, MY_HOME was set as expected, but the custom action fails :(
How can I fix this problem?
Windows Installer and Custom Actions are hosted via the Service Control Manager which has a long history of not respecting broadcast messages that are sent announcing Environment changes. So even if you fix the immeadiate / deferred problem that Yan mentions you'll find that your custom action still doesn't have the environment variable.
Why don't just just pass "[APPLICATIONPATH]myapp" to your .bat file and fetch it in as %2?
BTW I also don't reccomend calling batch files from an installer. It's fragile and embarrassing to see installs that run popping up little black windows.
You CA is immediate. This means that it runs immediately when Windows Installer is processing your MSI package. And this obviously happens before the component containing <Environment/> is installed. Modify it to be deferred (Execute="deferred") and schedule before InstallFinalize.

WiX - trying to figure out install sequences

I'm installing a large app, and part of it is a custom written tool called "DbUpdateManager" to mass execute SQL scripts against our target database.
Right now, the WiX 2.x install works - but it has one flaw: during install, I also install a couple of Windows services, which can be optionally started right away. Those however will fail, if the DbUpdateManager hasn't been run yet.
So what I'm trying to accomplish is this:
Install DbUpdateManager and my services from my MSI
Run DbUpdateManager BEFORE any of the services start up
My current WiX source looks something like this:
<Directory Id='INSTALLDIR' Name='DbUpdMgr' LongName='DbUpdateManager' >
<!-- DbUpdateManager component with the necessary files -->
<Component Id='DbUpdateManagerComponent' Guid='...' DiskId='1'>
<File Id='DbUpdateManagerFile' LongName='DbUpdateManager.Wizard.exe'
Name='DbUmWz.exe' src='DbUpdateManager.Wizard.exe' KeyPath='no' />
</Component>
<!-- Component to install one of my Windows services -->
<Component Id='InstallServiceComponent' Guid='...' DiskId='1'>
<File Id='InstallServiceFile' LongName='MyService.exe'
Name='MyServic.exe' src='MyService.exe' KeyPath='yes'/>
<ServiceInstall Id='InstallMyService' Name='MyService'
Description='My Service' ErrorControl='normal'
Start='auto' Type='ownProcess' Vital='yes' />
<ServiceControl Id='UninstallMyService' Name='MyService'
Remove='uninstall' Wait='yes' />
</Component>
<!-- Feature for the DbUpdateManager referencing the above component -->
<Feature Id='DbUpdateManager' ConfigurableDirectory='INSTALLDIR'
AllowAdvertise='no' Description='DbUpdateManager' Level='1'
Title='Database Update Manager'>
<ComponentRef Id='DbUpdateManagerComponent'/>
</Feature>
<!-- Custom action for running DbUpdateManager -->
<CustomAction Id='RunDbUpdateManagerAction' FileKey='DbUpdateManagerFile'
ExeCommand='' Return='asyncWait' />
<!-- Calling the custom action in the install sequence -->
<InstallExecuteSequence>
<RemoveExistingProducts After='InstallInitialize' />
<Custom Action='RunDbUpdateManagerAction'
After='InstallFinalize'>&DbUpdateManager=3</Custom>
I inherited this WIX, and it works - but as I said - the DbUpdateManager gets called too late in the process (only "After=InstallFinalize") and thus the services will fail to start up properly at first (the run fine the second time around when you restart them manually after DbUpdateManager has run).
I poked around the MSI documentation a bit and found a nice step called "StartServices", so my hunch was to just change my calling the custom action to this:
<InstallExecuteSequence>
<Custom Action='RunDbUpdateManagerAction'
Before='StartServices'>&DbUpdateManager=3</Custom>
Unfortunately, in this case, nothing at all happens - DbUpdateManager NEVER gets called....
Any ideas why? Debugging the MSI/WiX stuff is really really tricky, and I can't seem to see the forest for the trees anymore....
Thanks!
Marc
EDIT: The "RunDbUpdateManagerAction" is placed in the right position in the InstallExecuteSequence table in my MSI - right AFTER InstallServices and just BEFORE StartServices - and yet it doesn't work.... DbUpdateManager (a Winforms utility) does not show up during installation :-(
EDIT 2: now my action appears to be executed and at the right time - unfortunately, I'm just not seeing my wizard :-( What I'm seeing is an error code "return value 1631" which means something like "MSI Service could not be started" - wtf ???
MSI (s) (2C:D8) [20:53:36:383]: Doing action: RunDbUpdateManagerAction
Action 20:53:36: RunDbUpdateManagerAction.
Action started at 20:53:36: RunDbUpdateManagerAction.
MSI (s) (2C:D8) [20:53:36:383]: Doing action: StartServices
Action 20:53:36: StartServices. Services are being started
Action started at 20:53:36: StartServices.
Action finished at 20:53:36: RunDbUpdateManagerAction. Return value 1631.
Well, I finally got it working - with a bit of help from everyone who responded, and by consulting some of the WiX tutorials and help pages out there on the web. MSI installer stuff isn't easy to figure out and learn......
Basically, I changed execution of my custom action to "deferred" (as suggested by Rob) and I moved the point in the sequence where it gets executed to "After=InstallFiles". I also changed the condition in the <Custom> tag to "NOT Installed" which seems to work just fine in my scenario.
Contrary to Rob's fear, the Db Update Manager and its UI come up quite nicely this way, and the process of updating our database is now completed before any of our services (that depend on the database) get started.
Looking forward to a full RTM release of WiX 3.0 (and its future) !
Thanks to everyone - unfortunately, I could only accept one answer - all would have deserved it.
Marc
It appears that your CustomAction depends on the 'DbUpdateManagerFile' being installed. That means that your CustomAction needs to be scheduled after InstallFiles executes. Remember there are two passes to the InstallExecuteSequence. First, the "immediate" (or "scheduled" or "script generation") actions are executed to build the transaction log (aka: "install script"). Second, the "deferred" actions in the transaction log are executed.
Right now your CustomAction is "immediate" (the default) so it is trying to run before the files are actually copied to the machine. The InstallFiles action is in the script before your CustomAction but it hasn't been executed yet.
So, you need to mark your CustomAction "deferred" to get it to run after your files are installed.
Note: you are not probably not able to show UI from a deferred CA. I wasn't sure if this tool of yours was expected to show UI.
PS: sorry if I didn't explain that well, it's been a long day.
Try getting a log file of the Installation, and look for the sequence order in there and the value of the condition to perform the Custom Action
Use this in the command line:
msiexec /i [msiname] /l*v [filename]
EDIT: After reading your comment have a look at this page here you could try to add NOT INSTALLED in the condition
EDIT2: I found this page Search for your error Number 1631
You can open the .msi in Orca and look at the InstallExecuteSequence table to see what order things are actually happening in. This may give you a good idea of what's actually happening when.