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

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.

Related

Wix bootstrapper conditional installation

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();
}

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

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.

WiX EmbeddedChainer Examples?

With Windows Installer 4.5, there was a new table added for MsiEmbeddedChainer Table. This table was supposed to allow multiple-package installation. WiX added support for the table by creating the EmbeddedChainer element. I've read the wiki, but are there any examples on how to use the element?
I'm attempting to install a JRE before my program.
Embedded chainers only work after the installer that contained them is installed, and can only install raw .msi files (.msi files with their own bootstrap .exe files cannot be used), so I don't think you'll be able to install the JRE the way you want.
Do the following steps:
Changes in WXS file:
...
<Component DiskId="1" Guid="5CE59096-E197-4694-8DC2-E8EB4601C7C5" Id="CHAINERRUN.EXE">
<File Id="CHAINERRUN.EXE" Name="ChainerRun.exe" Source="..\ClinAppChainers\bin\ChainerRun.exe" />
<File Id="MICROSOFT.DEPLOYMENT.WINDOWSINSTALLER.DLL" Name="Microsoft.Deployment.WindowsInstaller.dll" Source="C:\Program Files\Windows Installer XML v3.6\SDK\Microsoft.Deployment.WindowsInstaller.dll" />
<File Id="MICROSOFT.CSHARP.DLL" Name="Microsoft.CSharp.dll" Source="C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\Microsoft.CSharp.dll" />
<File Id="SYSTEM.DLL" Name="System.dll" Source="C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.dll" />
<File Id="SYSTEM.CORE.DLL" Name="System.Core.dll" Source="C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.Core.dll" />
<File Id="SYSTEM.XML.DLL" Name="System.Xml.dll" Source="C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.Xml.dll" />
<File Id="SYSTEM.XML.LINQ.DLL" Name="System.Xml.Linq.dll" Source="C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.Xml.Linq.dll" />
</Component>
...
<EmbeddedChainer Id="ChainerRun" FileSource="CHAINERRUN.EXE" />
The FileSource is the reference to the File element ID defined in the component
Create a C# project, reference to the file Microsoft.Deployment.WindowsInstaller.dll, or create a new WIX "C# Custom action project" then change the output to Console application EXE instead of DLL. The body of the CS file should contain the Main function
ChainerRun.CS
namespace ChainerRun
{
public class CustomActions
{
static void Main(string[] args)
{
System.Diagnostics.Debugger.Launch();
try
{
IntPtr ptr = new IntPtr(Convert.ToInt32(args[0], 16));
//ptr = System.Runtime.InteropServices.Marshal.StringToCoTaskMemAuto(args[0]);
Transaction transaction = Transaction.FromHandle(ptr, true);
transaction.Join(TransactionAttributes.JoinExistingEmbeddedUI);
// Installer.InstallProduct(#"c:\MyOtherApp.msi", argline);
transaction.Commit();
transaction.Close();
}
catch (Exception e)
{
throw e;
}
}
[CustomAction]
public static ActionResult CustomAction1(Session session)
{
System.Diagnostics.Debugger.Launch();
session.Log("My CustomAction1() begins ...");
}
}
There is a bug in the c# code below: In the line "IntPtr ptr = new IntPtr(Convert.ToInt32(args[0], 16));" the "16" must be a "10"!
Otherwise you will get "bad handle" errors when there are more than 10 transactions (e.g. when there are five or more sub msi's called from the embedded chainer).
The approach to embed the JRE as a package in a multi package transaction is an overkill which unnecessarily complicates maintenance.
There are two reasonable solutions with low maintenance.
Use burn and install the JRE as a separate package in the bundle. Advantage of being able to use a prepared installation such as the ones from Oracle.
The JRE by design is self versioned and requires no registrations or special handling, given that it may be best to include the jre in the main application MSI. This is a practice that I've seen in many professional java-based applications and has the added advantage of easily creating application shortcuts by referencing java.exe directly.

How to add net.tcp to the "Enabled protocols" by using a WIX 3.5 project/setup?

We have a few MSI packages (generated by WIX) that install WCF services. Most of these services need net.tcp for their endpoint bindings.
I'd like to make our deployment life easier and automate the process of adding net.tcp.
I already know the WixIisExtension.dll and make use of its useful functions (create web site, virt. directory, etc.).
Can I use the WixIisExtension to enable the net.tcp protocol?
If not, how can I achieve that?
Add a new Project to your Setup Solution (Windows Installer XML -> C# Custom Action Project)
In this Project add a reference to the Assembly Microsoft.Web.Administration, which can be found here: C:\Windows\System32\inetsrv and is required to add the protocols.
My Custom Action looks like this:
using System;
using System.Linq;
using Microsoft.Deployment.WindowsInstaller;
using Microsoft.Web.Administration;
namespace Setup.CustomAction.EnableProtocols
{
public class CustomActions
{
[CustomAction]
public static ActionResult EnableProtocols(Session session)
{
session.Log("Begin EnableProtocols");
var siteName = session["SITE"];
if (string.IsNullOrEmpty(siteName))
{
session.Log("Property [SITE] missing");
return ActionResult.NotExecuted;
}
var alias = session["VIRTUALDIRECTORYALIAS"];
if (string.IsNullOrEmpty(alias))
{
session.Log("Property [VIRTUALDIRECTORYALIAS] missing");
return ActionResult.NotExecuted;
}
var protocols = session["PROTOCOLS"];
if (string.IsNullOrEmpty(protocols))
{
session.Log("Property [PROTOCOLS] missing");
return ActionResult.NotExecuted;
}
try
{
var manager = new ServerManager();
var site = manager.Sites.FirstOrDefault(x => x.Name.ToUpper() == siteName.ToUpper());
if (site == null)
{
session.Log("Site with name {0} not found", siteName);
return ActionResult.NotExecuted;
}
var application = site.Applications.FirstOrDefault(x => x.Path.ToUpper().Contains(alias.ToUpper()));
if (application == null)
{
session.Log("Application with path containing {0} not found", alias);
return ActionResult.NotExecuted;
}
application.EnabledProtocols = protocols;
manager.CommitChanges();
return ActionResult.Success;
}
catch (Exception exception)
{
session.Log("Error setting enabled protocols: {0}", exception.ToString());
return ActionResult.Failure;
}
}
}
}
Please note that I am assuming three properties here: SITE, VIRTUALDIRECTORYALIAS & PROTOCOLS
Build the solution now. In the background, WiX creates two assemblies %Project%.dll and %Project%.CA.dll. The CA.dll includes the depending Microsoft.Web.Administration automatically.
Then in your WiX Setup Project include a reference to the new Custom Action Project. The reference is required for referencing the %Projet%.CA.dll.
Edit the product.wxs
First add the properties somewhere inside the product element:
<!-- Properties -->
<Property Id="SITE" Value="MySite" />
<Property Id="VIRTUALDIRECTORYALIAS" Value="MyVirtDirectoryAlias" />
<Property Id="PROTOCOLS" Value="http,net.tcp" />
Below add the binary element:
<!-- Binaries -->
<Binary Id="CustomAction.EnableProtocols" SourceFile="$(var.Setup.CustomAction.EnableProtocols.TargetDir)Setup.CustomAction.EnableProtocols.CA.dll" />
Note that you have to add the CA.dll.
Below add the Custom Action:
<!-- Custom Actions -->
<CustomAction Id="EnableProtocols" BinaryKey="CustomAction.EnableProtocols" DllEntry="EnableProtocols" Execute="immediate" Return="check" />
And finally the Installation Sequence where you want the execution to take place.
<!-- Installation Sequence -->
<InstallExecuteSequence>
<Custom Action="EnableProtocols" After="InstallFinalize">NOT Installed</Custom>
</InstallExecuteSequence>
that's all. Should work.
Thanks to Darin Dimitrov for providing the links above.
Here is the right way to do this in WIX (assuming you are installing on a 64-bit operating system - if not at a guess I'd say change CAQuietExec64 to CAQuietExec although this is untested):
Get a reference to appcmd.exe:
<Property Id="APPCMD">
<DirectorySearch Id="FindAppCmd" Depth="1" Path="[WindowsFolder]\system32\inetsrv\">
<FileSearch Name="appcmd.exe"/>
</DirectorySearch>
</Property>
Define the following custom actions (the properties [WEB_SITE_NAME] and [WEB_APP_NAME] can be populated elsewhere in your installer; or to test you can hard-code them):
<CustomAction
Id="SetEnableNetTCPCommmand"
Property="EnableNetTCP"
Value=""[APPCMD]" set app "[WEB_SITE_NAME]/[WEB_APP_NAME]" /enabledProtocols:http,net.tcp"/>
<CustomAction
Id="EnableNetTCP"
BinaryKey="WixCA"
DllEntry="CAQuietExec64"
Execute="deferred"
Return="ignore"
Impersonate="no" />
Now in the InstallExecuteSequence add
<InstallExecuteSequence>
...
<Custom Action="SetEnableNetTCPCommmand" After="InstallExecute">APPCMD AND NOT Installed</Custom>
<Custom Action="EnableNetTCP" After="SetEnableNetTCPCommmand">APPCMD AND NOT Installed</Custom>
...
</InstallExecuteSequence>
And if all is well in the world that will now update the protocols.
You may take a look at this article on MSDN. There's a section at the end which illustrates how to use the managed API to achieve configure a WAS enabled service. I am not familiar with Wix but you could probably use and plug this code into some custom deployment step.
This can't be done using the standard WiXIIsExtension, as far as I know. Thus, the only option you have is a custom action.
You can also find this thread interesting - it gives a hint how to achieve the similar thing in MSBuild script, but you should be able to translate it to custom action easily.
Good luck!