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

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.

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.

Failing condition wix

I am trying to skip installations in the following cases:
Windows OS type is = desktop OS
if HKLM\SYSTEM\CurrentControlSet\Services\MyService MYKEY= myValue
if REG HKLM\SYSTEM\CurrentControlSet\Services\MyService = DisplayName is present
AND HKLM\SYSTEM\CurrentControlSet\Services\MyService MYKEY is not present
while installation it went well, but while installation my feature is missing from installation.
Am I doing anything wrong in putting condition?
<Property Id="MYKEY" Secure="yes">
<RegistrySearch Id="MyKey"
Root="HKLM"
Key="SYSTEM\CurrentControlSet\Services\MyService"
Name="mykey"
Type="raw" />
</Property>
<Property Id="MYSERVICE" Secure="yes">
<RegistrySearch Id="MYSERVICE"
Root="HKLM"
Key="SYSTEM\CurrentControlSet\Services\MyService"
Name="DisplayName"
Type="raw" />
</Property>
<Feature Id="MyFeature" Level="" Display="" Title="" Description="" AllowAdvertise="no" ConfigurableDirectory="INSTALLDIR">
<MergeRef Id="MyFeature" Primary="yes"/>
<Condition Level="0">((MsiNTProductType=1) OR
(MYKEY="MyValue") OR
(MYSERVICE="MyService" AND MYKEY=""))</Condition>
</Condition>
</Feature>
NOTE: The below has not been tested extensively - conditions are notorious for being difficult to get right. Testing conditions requires real-world testing. A few more links:
Condition debugging
WiX launch conditions
Wix Tools update uses old custom actions (example of complex conditions)
How to add a WiX custom action that happens only on uninstall (via MSI)?
When either of those three "sub"-conditions are true, what do you want to achieve?
Abort Setup: Abort the whole setup? (LaunchConditions)
Configure Features: Prevent or enable installation of specific feature(s)? (Feature conditions)
This difference is obviously crucial - and we must know to be able to answer. Your WiX source snippet currently shows the conditions used as feature conditions. I have a feeling this is not what you want.
LaunchConditions: In order to abort the whole setup if one of these conditions are true, you could try to use LaunchCondition entries. Instead of making one complicated condition, you can just split them in three different entries that each check if the setup should be aborted - each entry for a different and specific reason. I suggest you add these LaunchCondition entries after your Package element in your WiX source file:
<Condition Message="Aborting setup: Server OS required for installation.">Installed OR MsiNTProductType=1</Condition>
<Condition Message="Aborting setup: State reason for abortion.">Installed OR MYKEY="MyValue"</Condition>
<Condition Message="Aborting setup: State reason for abortion.">Installed OR (MYSERVICE="MyService" AND MYKEY="")</Condition>
These entries will make it into the LaunchCondition table of your compiled MSI file.
LaunchConditions must always evaluate to true for the setup to be able to install / run.
Accordingly, the Installed parts of the conditions above are there to ensure that the condition is always true after installation - so you don't get the situation that the setup will not allow itself to be uninstalled or repaired because a launch condition is not met. The condition: Installed - will always be true except for a fresh install and major upgrades.
NOTE: I am not sure whether launch conditions can cause trouble during administrative installations. I don't think they do (an administrative installation features its own installation sequences). I will test and verify tomorrow. Adding OR ACTION="ADMIN" should make the launch condition true on any box when administrative installation is run.
Feature Conditions: If you don't want to abort the setup, but rather want to control feature installation status based on evaluating these conditions, you need to use the feature conditions concept instead of the launch condition concept.
When you set the Feature level to 0 in your WiX source, the feature is not show in the setup GUI and it is not going to be installed by default either. A feature condition can change this and set the feature to install if the condition evaluates to true.
You can also go the other way around and set the feature level to 1 as default (this should install the feature) and then use a feature condition to set its Feature level to 0 - if you don't want the feature installed - when the condition is true.
There are some further details under "Quick Mockup" here: WIX If...else condition using registry.
In the below WiX snippet we set a feature to install by default (Level="1") and then we use a feature condition to set the feature to not install if its associated condition evaluates to true (this is a multi-part condition). So once the condition evaluates to true we assign feature Level="0" (which means do not install feature and hide it from the setup GUI):
<Feature Id="MyFeature" Level="1"> <!--Default to install feature-->
<Condition Level="0"> <!--Do not install feature if condition is true-->
((MsiNTProductType=1) OR (MYKEY="MyValue") OR (MYSERVICE="MyService" AND MYKEY="") AND (NOT ACTION="ADMIN"))
</Condition>
</Feature>
The AND (NOT ACTION="ADMIN") part is to force the feature to be installed in an administrative installation. It effectively shuts off the other conditions from evaluating to true if the setup is run in administrative install mode - which would cause the feature to not be installed during the admin install. This last part I have to test tomorrow.
UPDATE: Testing indicates that any feature set to Level=0 as default will not be extracted during an administrative install at all,
regardless of any feature conditions setting the feature to install. I
guess the tentative conclusion is to not set any features to
Level=0, but set Level=1 and then set them to Level=0 with a
feature condition that evaluates to true. This way the feature may be
hidden in a regular installation, but all features - with associated
files - are extracted during admin installation. The AND (NOT ACTION="ADMIN") part of the condition appears to not be needed.
Leaving the sample above as it is for now.
Links:
WIX If...else condition using registry
How to execute custom action only in install (not uninstall)
How to add new framework version in Installment Requirement page of InstallShield?

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

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.

WiX: conditionally register application to start when Windows launches

I want to register my application to start as windows launches based on a check box in the exit dialog.
I followed this but it seem that the writing to the registry is done before the end dialog (and the relevant check box) even appear.
My code is:
in product.wxs:
<Property Id="APP_AUTOMATIC_START_UP">1</Property>
....
<Component Id="AppAutoStartUp" Guid="{MyGuid}">
<RegistryValue Id="App.rst" Root="HKCU" Action="write" Key="Software\Microsoft\Windows\CurrentVersion\Run" Name="App" Value="[#MyApp.exe]" Type="string" />
<Condition><![CDATA[Installed OR APP_AUTOMATIC_START_UP]]></Condition>
in MyExitDialog.wxs:
<Control Id="AutomaticStartup" Type="CheckBox" Height="18" Width="295" X="135" Y="190" Text="Run App upon windows startUp" Property="APP_AUTOMATIC_START_UP" CheckBoxValue="1">
<Condition Action="hide" >Installed</Condition>
<Condition Action="show" >NOT Installed</Condition>
EDIT:
I tried adding the key to the registry and in case the user un-check the check box delete it using a custom action. my code:
[CustomAction]
public static ActionResult NotRunOnStartUp(Session session)
{
session.Log("Begin NotRunOnStartUp");
RegistryKey rk = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true);
rk.DeleteValue("MyApp");
return ActionResult.Success;
}
<Binary Id="NotRunOnStartUpBinary" src="..\CustomActions\NotRunOnStartUp\bin\$(var.Configuration)\NotRunOnStartUp.CA.dll" />
...
<CustomAction Id="NotRunOnStartUpCA"
Return="check"
Execute="immediate"
BinaryKey="NotRunOnStartUpBinary"
DllEntry="NotRunOnStartUp" />
...
<Publish Dialog="MyExitDialog" Control="Finish" Event="DoAction" Value="NotRunOnStartUpCA">APP_AUTOMATIC_START_UP= 0 and NOT Installed</Publish>
The result is, during instalation I write the key to the registry, but when I uncheck the check box and press finish, the key in not removed from the registry.
Any Ideas why?
The other choice is to write a custom action that runs at the end of the install based on the value of the checkbox, and again you'd need to remove that registry entry at uninstall.
As suggested, it is better to make it a configuration of the app and not the install. If the user changes his mind, what's he supposed to do? Fiddle in the registry (if he can figure out where it is)? Uninstall and re-install to change the setting?
By the way, the program does not start "as windows launches". It will start when the user logs on, and that's not the same thing. If you want it to launch when the user logs on I would describe it to your users as "when you logon". If you want it launch when Windows starts it would need to be a service.
Sounds like you have a conditional feature. Remove the check box and instead make a sub-feature.
Another take...
User choices like that are not an installation issue; They are a configuration issue. There is a fine and wavy line between the two. The application's programs should manage its own configuration.
After the installation is complete and your user has made a configuration choice, your installer can launch a configuration program with command-line arguments to add or modify the Run key.
The installer could remove the registry entry upon uninstallation since it would be better to clean up the Run key than to preserve user's choice for when they install your application again.

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.