Wix bootstrapper conditional installation - wix

I created a wix bootstrapper app with a user interface that has 2 checkboxes. The user can choose what to install. This is part of my bootstrapper app:
<MsiPackage
InstallCondition="ClientCondition"
DisplayInternalUI='yes'
Visible="yes"
SourceFile="D:\Project\FirstInstaller.msi"
/>
<MsiPackage
InstallCondition="ServerCondition"
DisplayInternalUI='yes'
Visible="yes"
SourceFile="D:\Project\SecondInstaller.msi"
/>
Problem :
For example, I already have FirstInstaller installed, and I'm trying to install the second one. Due to a false condition, my FirstInstaller will be uninstalled. But this is not what I expected. How do I fix this and have some "ignore" value for the Msi package in the chain ?

I don't know wich language your bootstrapper is written on, but, as option, you can control your msi installation directly from your code via cmd and msiexec command.
Code example for C#:
var command = #"/c msiexec /i c:\path\to\package.msi";
//for silent: /quiet /qn /norestart
//for log: /log c:\path\to\install.log
//with properties: PROPERTY1=value1 PROPERTY2=value2";
var output = string.Empty;
using (var p = new Process())
{
p.StartInfo = new ProcessStartInfo()
{
FileName = "cmd.exe",
Arguments = command,
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
CreateNoWindow = true,
};
p.Start();
while (!p.StandardOutput.EndOfStream)
{
output += $"{p.StandardOutput.ReadLine()}{Environment.NewLine}";
}
p.WaitForExit();
if (p.ExitCode != 0)
{
throw new Exception($"{p.ExitCode}:{ p.StandardError.ReadToEnd()}");
}
Console.WriteLine(output);
Console.ReadKey();
}

Related

Launch Condition, custom action and property

I have this problem with wix installer not installing our application IISversion >=10. It works on IISVersion <10.
I found this link on github. https://github.com/wixtoolset/issues/issues/5276
This link suggests adding a custom action which returns ActionResult.Success if IISRegistryversion is >= IISRequiredVersion.
But I am getting the following error. The error happens after this in the log
Doing action: LaunchConditions
Action start 12:46:02: LaunchConditions.
Either the variable is not being set or custom action is not being called.
I have some logging in the custom action but its not logging anything even with verbose on.
How do make sure the launch condition/ custom action is being called before this condition is evaluated? Can anyone suggest please?
This is how Product.wxs looks
<InstallExecuteSequence>
<Custom Action="CA.DS.CreateScriptDirCommand" Before="InstallFinalize">
<![CDATA[NOT Installed AND (&Feature.DatabaseServer.Database = 3)]]>
</Custom>
<Custom Action="Iis.CheckInstalledVersion.SetProperty" Before="LaunchConditions" >
<![CDATA[NOT Installed AND &Feature.WebServer.WebServices = 3]]>
</Custom>
<Custom Action="Iis.CheckInstalledVersion" After="Iis.CheckInstalledVersion.SetProperty" >
<![CDATA[NOT Installed AND &Feature.WebServer.WebServices = 3]]>
</Custom>
</InstallExecuteSequence>
<Condition Message="This application requires IIS [Iis.RequiredVersion] or higher. Please run this installer again on a server with the correct IIS version.">
<![CDATA[Iis.IsRequiredVersion > 0]]>
</Condition>
<Fragment>
<CustomAction Id='Iis.CheckInstalledVersion.SetProperty' Property='Iis.CheckInstalledVersion' Execute='immediate' Value='' />
<!--Note: Changed "Execute" from "deferred" to "immediate", to avoid error "LGHT0204: ICE77: Iis.CheckInstalledVersion is a in-script custom action. It must be sequenced in between the InstallInitialize action and the InstallFinalize action in the InstallExecuteSequence table"-->
<!--Note: Changed "Impersonate" from "no" to "yes", to avoid warning "LGHT1076: ICE68: Even though custom action 'Iis.CheckInstalledVersion' is marked to be elevated (with attribute msidbCustomActionTypeNoImpersonate), it will not be run with elevated privileges because it's not deferred (with attribute msidbCustomActionTypeInScript)"-->
<CustomAction Id='Iis.CheckInstalledVersion' BinaryKey='B.WixCA' DllEntry='CheckInstalledIISVersion' Execute='immediate' Return='check' Impersonate='yes' />
<Component
</Component>
</Fragment>
[CustomAction]
public static ActionResult CheckInstalledIISVersion(Session session)
{
try
{
session.Log("* Starting to check installed IIS version");
const int IisRequiredVersion = 7;
string IISMajorVersionFromRegistry = session["IISMAJORVERSION"];
session.Log(string.Format("*!*! DEBUG; CheckInstalledIisVersion; IIS major version: {0}", IISMajorVersionFromRegistry));
string iisMajorVersionNumeric = IISMajorVersionFromRegistry.Replace("#", string.Empty);
int iisMajorVersion = int.Parse(iisMajorVersionNumeric, CultureInfo.InvariantCulture);
bool isRequiredVersion = iisMajorVersion >= IisRequiredVersion;
// Setting the required version as a custom property, so that it can be used in the condition message
session["IIs.RequiredVersion"] = IisRequiredVersion.ToString(CultureInfo.InvariantCulture);
// Setting the results of the check as "bool"
session["Iis.IsRequiredVersion"] = isRequiredVersion ? "1" : "0";
return ActionResult.Success;
}
catch (Exception ex)
{
session.Log(string.Format("CheckInstalledIisVersion; Error occured SC: {0}", ex.Message));
return ActionResult.Failure;
}
}
It works without the condition. The condition gets executed before
The feature check Feature.WebServer.WebServices = 3 isn't going to work because the feature "to be installed" state isn't set until after costing (and often choosing the feature in the feature dialogs). So the CA isn't being called.
You probably need to rethink this and force the check for IIS after CostFinalize, and then perhaps warn then that IIS is not installed/running etc. So you'd do the search for IIS unconditionally to set the property and not use it as a launch condition. Then give the warning if &Feature.WebServer.WebServices = 3 and the IIS version is too low.
See feature action condition documentation and the reference to CostFinalize:
https://msdn.microsoft.com/en-us/library/windows/desktop/aa368012(v=vs.85).aspx

Rollback to previous version of WiX bundle installer

I have WiX bundle with two msi packages: A and B. At first, I successfully installed bundle version 1.0.0.0.
Then I am installing MajorUpgrade version 2.0.0.0. Package A successfully upgraded. Package B upgrade fails and rollback started.
I defined msi package upgrade as:
<MajorUpgrade AllowSameVersionUpgrades="yes" Schedule="afterInstallInitialize" DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
Package B reverted to version 1.0.0.0. Package A roll backed by removing. So, the bundle remains in inconsistent state.
I need to revert entire bundle to version 1.0.0.0 if update fails. Is it possible?
There is no standard way to accomplish it, because multi-MSI transactions are not supported by WiX.
I found a workaround that works for me. I use Custom Bootstrapper Application, so I can handle failure event in C# code. If you use WiX Standard Bootstrapper Application (WiXStdBA) it will not help you.
If update failed, I call previous bundle installer from Windows Package Cache in silent repair mode. It restores previous state.
Next code expresses the idea:
Bootstrapper.PlanRelatedBundle += (o, e) => { PreviousBundleId = e.BundleId; };
Bootstrapper.ApplyComplete += OnApplyComplete;
private void OnApplyComplete(object sender, ApplyCompleteEventArgs e)
{
bool updateFailed = e.Status != 0 && _model.InstallationMode == InstallationMode.Update;
if (updateFailed)
{
var registryKey = string.Format("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{0}", VersionManager.PreviousBundleId);
RegistryKey key = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey(registryKey)
?? RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64).OpenSubKey(registryKey);
if (key != null)
{
string path = key.GetValue("BundleCachePath").ToString();
var proc = new Process();
proc.StartInfo.FileName = path;
proc.StartInfo.Arguments = "-silent -repair";
proc.Start();
proc.WaitForExit();
}
}
}

WIX enable Windows feature

I have to check if some windows features are enabled beore installing my software.
I can check it or install it using dism command line tool.
I create a custom action to do this, but is there a way to do it in a "WIX native way" ?
<Property Id="dism" Value="dism.exe" />
<CustomAction Id="InstallMSMQContainer" Property="dism" ExeCommand=" /online /enable-feature /featurename:MSMQ-Container /featurename:MSMQ-Server /featurename:MSMQ-ADIntegration" Return="check" Impersonate="yes" Execute="oncePerProcess"/>
<InstallUISequence>
<Custom Action="InstallMSMQContainer" After="CostFinalize" Overridable="yes">NOT Installed</Custom>
</InstallUISequence>
The problem is that command launch a command prompt, which is very ugly for end user.
How can I make it nicer? I don't know if i need a bootstraper to do this (like installing the .NET Framework).
Is there any extention to manage that things ?
I'm now using WIX 3.7.
David Gardiner's answer hinted at the correct solution in my case. Creating your own custom action is not necessary. Here is how to do it for a 64 bit installation of Windows:
First determine if MSMQ is installed:
<Property Id="MSMQINSTALLED">
<RegistrySearch Id="MSMQVersion" Root="HKLM" Key="SOFTWARE\Microsoft\MSMQ\Parameters" Type="raw" Name="CurrentBuild" />
</Property>
Declare your custom actions. You need two. One to set a property to the path to dism, and another to execute it:
<CustomAction Id="InstallMsmq_Set" Property="InstallMsmq" Value=""[System64Folder]dism.exe" /online /enable-feature /featurename:msmq-server /all" Execute="immediate"/>
<CustomAction Id="InstallMsmq" BinaryKey="WixCA" DllEntry="CAQuietExec64" Execute="deferred" Return="check"/>
Finally specify the custom actions in the install sequence:
<InstallExecuteSequence>
<Custom Action="InstallMsmq_Set" After="CostFinalize"/>
<Custom Action="InstallMsmq" After="InstallInitialize">NOT REMOVE AND NOT MSMQINSTALLED</Custom>
</InstallExecuteSequence>
Because this can take a little bit of time I've added the following to update the installer status text:
<UI>
<ProgressText Action="InstallMsmq">Installing MSMQ</ProgressText>
</UI>
You can also specify a rollback action if you want to remove MSMQ on installation failure.
You might consider the Quiet Execution Custom Action
The way I do it is by creating a DTF custom action that calls the dism.exe process. You get the same result and no command prompt is launched.
[CustomAction]
public static ActionResult RunDism(Session session)
{
session.Log("Begin RunDism");
string arguments = session["CustomActionData"];
try
{
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = "dism.exe";
session.Log("DEBUG: Trying to run {0}", info.FileName);
info.Arguments = arguments;
session.Log("DEBUG: Passing the following parameters: {0}", info.Arguments);
info.UseShellExecute = false;
info.RedirectStandardOutput = true;
info.CreateNoWindow = true;
Process deployProcess = new Process();
deployProcess.StartInfo = info;
deployProcess.Start();
StreamReader outputReader = deployProcess.StandardOutput;
deployProcess.WaitForExit();
if (deployProcess.HasExited)
{
string output = outputReader.ReadToEnd();
session.Log(output);
}
if (deployProcess.ExitCode != 0)
{
session.Log("ERROR: Exit code is {0}", deployProcess.ExitCode);
return ActionResult.Failure;
}
}
catch (Exception ex)
{
session.Log("ERROR: An error occurred when trying to start the process.");
session.Log(ex.ToString());
return ActionResult.Failure;
}
return ActionResult.Success;
}
DISM parameters are set via the custom action data.

Is there any way to create symbolic link in WIX installer?

I need to create a symbolic-link for a particular folder; that folder is created by a WIX installer. Is there any way to create a symbolic-link from WIX installer? I have read about mklink, but I do not know how to use that in WIX (v3)?
You can use Custom actions to run the mklink. Run the custom actions after InstallFinalize.
Or you can use Short cut instead of symbolic links.
In Custom Action file:
[CustomAction]
public static ActionResult symboliclink(Session session)
{
string filePath = session["FilePath"];
string symboliclink = session["symboliclink"];
Process p = new Process();
p.StartInfo.FileName = "mklink.exe";
p.StartInfo.Arguments = "/d" + symboliclink + " " + filePath;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.UseShellExecute = false;
p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
Environment.CurrentDirectory = Path.GetDirectoryName(p.StartInfo.FileName);
p.Start();
p.WaitForExit();
return ActionResult.Success;
}
Wix File:
<Binary Id="Symboliclink" SourceFile="Symboliclink.CA.dll" /> <CustomAction Id="SymbolicLink" BinaryKey="Symboliclink" DllEntry="symboliclink" Return="ignore" Execute="immediate" />
Include the Custom Action in InstallExecuteSequence
<Custom Action="SymbolicLink" Sequence="6703"/>
I had crated a link by using Shortcut keyword. And I found it is the easiest way to resolve this. Please find this code.
<Component Id="XXXX" Guid="E4920A35-13E1-4949-BD3A-7DCC8A70C647">
<File Id="xxXX" Name="xxXX.yyy" Source="..\Installer\Miscellaneous\xxXX.yyy" DiskId="1" Vital="yes" />
<Shortcut Id="xxXX_link" Directory="Dir1" Name="xxXX.yyy" Target="[INSTALLLOCATION]xxXX.yyy" WorkingDirectory="INSTALLLOCATION" />
</Component>
But this is not equivalent to symbolic link.

Launch an EXE from binary file

Hi I have these two binary files:
<Binary Id="Sentinel" SourceFile="sentinel_setup.exe"/>
<Binary Id="Hasp" SourceFile="HASPUserSetup.exe"/>
And I would like to start them on a button click like so:
<CustomAction Id="LaunchHasp" BinaryKey="Hasp" ExeCommand="" Return="asyncWait" />
<CustomAction Id="LaunchSentinel" BinaryKey="Sentinel" ExeCommand="" Return="asyncWait"/>
<Publish Event="DoAction" Value="LaunchHasp">1</Publish>
But it doesn't work, It only works when I run the installer from the command line with elevated privileges. What am I doing wrong? Thanks
Or can someone tell me how I could extract the file from the binary table using a c++ custom action as I cannot get it working at all..:(
Immediate custom actions doesn't have elevated privileges. You should use deffered custom actions for such needs. Any action that make changes to the destimation environment should be deffered. For more details read this article: http://bonemanblog.blogspot.com/2005/10/custom-action-tutorial-part-i-custom.html
<CustomAction Id="LaunchHasp" Impersonate="no" Execute="deferred" BinaryKey="Hasp" ExeCommand="" Return="asyncWait" />
Though deffered custom actions are executed during installation phase, not on button click. Revise your installer logic. As I understand, your exe file "sentinel_setup.exe" changes the system, so should be scheduled between InstallInitialize and InstallFinalize events in InstallExecuteSequence
I would recommend adding a checkbox, that user should mark to install your "Hasp" (or installer Feature which user should select in the feature tree). And add deffered custom action with condition on this checkbox state.
Sometimes it is really required to launch admin actions during or before installer UI sequence. In this case you need to create a setup bootstrapper which asks for permission elevation and does required actions before running MSI process. To ask for permissions you need to add application manifest to your bootstrapper project. My bootstrapper is quite simple, but works in many cases. It is Windows application (though without any Windows forms - it allows to hide console window) which contains only icon, application manifest and small code file:
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace SetupBootstrapper
{
class Program
{
[STAThread]
static void Main(string[] args)
{
var currentDir = AppDomain.CurrentDomain.BaseDirectory;
var parameters = string.Empty;
if (args.Length > 0)
{
var sb = new StringBuilder();
foreach (var arg in args)
{
if (arg != "/i" && arg != "/x" && arg != "/u")
{
sb.Append(" ");
sb.Append(arg);
}
}
parameters = sb.ToString();
}
bool isUninstall = args.Contains("/x") || args.Contains("/u");
string msiPath = Path.Combine(currentDir, "MyMsiName.msi");
if(!File.Exists(msiPath))
{
MessageBox.Show(string.Format("File '{0}' doesn't exist", msiPath), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
string installerParameters = (isUninstall ? "/x" : "/i ") + "\"" + msiPath + "\"" + parameters;
var installerProcess = new Process { StartInfo = new ProcessStartInfo("msiexec", installerParameters) { Verb = "runas" } };
installerProcess.Start();
installerProcess.WaitForExit();
}
}
}