How to select features from an MsiPackage contained in a Bundle element? - wix

I have a exe I created using WiX Burn v3.8. Let's call it Bundle.exe. In this bundle, I have an MSI with 3 features, two are nested under the one. I want to call Bundle.exe from the command line and pass the selected features into my MSI. I should note that I have an existing UI that let's the user select which features to install, so I don't want to use the WiX Bootstrapper app.
//Fragment of my MSI.wxs
<Feature Id="One">
//ComponentRefs
<Feature Id='A' >
//ComponentRefs
</Feature>
<Feature Id="B" >
//ComponentRefs
</Feature>
</Feature>
//Fragment of my Bundle.wxs
<Bundle>
<Chain>
//List of MsiPackages
</Chain>
</Bundle>

In the default bootstrapper this is how we pass the arguments from command line:
<Wix>
<Bundle>
<Variable Name="CommandLineArgument" bal:Overridable="yes"/>
<Chain>
<MsiPackage>
<MsiProperty Name="CommandLineArgument" Value="[CommandLineArgument]"/>
</MsiPackage>
</Chain>
</Bundle>
</Wix>
Make a note of the BAL:Overridable. That's how I was able to make sure that we can pass the property value from command line. Don't forget to add the namespace declaration to the Wix element xmlns:bal="http://schemas.microsoft.com/wix/BalExtension", and link to the WixBalExtension.dll.
In your case, the MSI property that you should be assigning the feature value[s] should be "ADDLOCAL".
<MsiProperty Name="ADDLOCAL" Value="[CommandLineArgument]"/>
To execute the bundle from commandline do the following:
bundle.exe CommandLineArgument=One,A,B

Related

SqlLocalDB 2019 MSI ignores ARPSYSTEMCOMPONENT from Wix bootstrapper

I have a Wix Bootstrapper application which launches the SqlLocalDB 2019 MSI installer. It works great and everything is installed properly.
In the MsiPackage I'm passing ARPSYSTEMCOMPONET=0 to ensure that the "Microsoft Sql Server 2019 LocalDB" entry appears in Add/Remove Programs on Windows 10.
<?xml version="1.0" encoding="UTF-8"?>
<WixVariable Id="WixUILicenseRtf" Value="$(var.ProjectDir)\Resources\eula_en-us.rtf" />
<BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense">
<bal:WixStandardBootstrapperApplication LogoFile="$(var.ProjectDir)\Resources\cs.png"
LicenseFile="$(var.ProjectDir)\Resources\eula_en-us.rtf"
ThemeFile="$(var.ProjectDir)\Resources\customtheme.xml"
SuppressOptionsUI="yes"
SuppressRepair="yes" />
</BootstrapperApplicationRef>
<Chain>
<MsiPackage Id="SqlLocalDBPackage"
DisplayName="Installing Prerequisites... :)" Vital="yes" Visible="yes" Permanent="yes"
SourceFile="$(var.ProjectDir)\Resources\SqlLocalDB2019.msi" >
<MsiProperty Name="ARPSYSTEMCOMPONENT" Value="0"/>
<MsiProperty Name="IACCEPTSQLLOCALDBLICENSETERMS" Value="YES" />
</MsiPackage>
</Chain>
the Logfile for the Sql Installer does show that ARPSYSTEMCOMPONENT=0 and SYSTEMCOMPONENT=0 within the SqlLocalDB2019 MSI installer.
MSI (s) (9C:EC) [10:13:12:576]: Command Line: MSIFASTINSTALL=7 ARPSYSTEMCOMPONENT=0 IACCEPTSQLLOCALDBLICENSETERMS=YES REBOOT=ReallySuppress CLIENTUILEVEL=3 MSICLIENTUSESEXTERNALUI=1 CLIENTPROCESSID=25032
MSI (s) (9C:EC) [10:13:24:346]: Executing op: ProductRegister(UpgradeCode={F0176A51-908A-4240-8853-E229D0AE3F39},VersionString=15.0.2000.5,HelpLink=https://go.microsoft.com/fwlink/?LinkId=230480,,,InstallSource=C:\ProgramData\Package Cache\{F4F4157C-0951-4F00-8530-E3A6B2BE8606}v15.0.2000.5\,Publisher=Microsoft Corporation,,,,,,,,,,,SystemComponent=0,EstimatedSize=264308,,,,)
however, in the Registry, it will always be set to 1 and the package does not appear in ARP. However, if I manually reset it to 0, it does appear.
Note that I have also tried setting ARPSYSTEMCOMPONENT to "YES", "NO", "1" and "0" and they all result in SystemComponent being 1 in the registry.
I'm stumped on this one....any ideas would be greatly appreciated.
TIA!
Remove:
Remove this whole property from your source:
<MsiProperty Name="ARPSYSTEMCOMPONENT" Value="0" />
Visible Attribute:
Ensure the Visible attibute is set to "yes" in the MsiElement:
<MsiPackage Id="MyMSI" Visible="yes" SourceFile="Test.msi" />
This setting will auto-magically handle the ARPSYSTEMCOMPONENT property writing. It seems to properly override anything specifically defined inside the third party MSI involved. Just did a smoke test.
Custom Actions: It is possible for custom actions to do something weird and set this property or write to this registry location.

How to pass AddLocal-Parameter from wix standard bootstrapper to a MsiPackage

With WIX Tools v3.10 I used to add a variable AddLocal to the bundle which I passed to the MsiProperty with the Name="ADDLOCAL" as described by BryanJ in "Pass parameters from bootstrapper to msi bundle package".
<Bundle>
...
<Variable Name="InstallLevel" Type="numeric" bal:Overridable="yes" Value="1"/>
<Variable Name="AddLocal" Type="string" bal:Overridable="yes" Value=""/>
<Chain>
<MsiPackage Id="Addin64bit_loc" Vital="yes" DisplayInternalUI="yes" ...
EnableFeatureSelection="yes" >
...
<MsiProperty Name="INSTALLLEVEL" Value="[InstallLevel]"/>
<MsiProperty Name="ADDLOCAL" Value="[AddLocal]"/>
</MsiPackage>
</Chain>
</Bundle>
Now after switching to v3.11 I get this warning which will turn to an error in v4:
Warning CNDL1149: The 'ADDLOCAL' MsiProperty is controlled by the bootstrapper and cannot be authored.
(Illegal properties are: 'ACTION', 'ADDLOCAL', 'ADDSOURCE', 'ADDDEFAULT', 'ADVERTISE', 'ALLUSERS', 'REBOOT', 'REINSTALL', 'REINSTALLMODE', or 'REMOVE'.)
Remove the MsiProperty element.
This restriction will be enforced as an error in WiX v4.0.
So what is the adequate way in v3.11 to pass an ADDLOCAL parameter from bootstrapper command line to the Msi?
you can influence on ADDLOCAL parameter in your custom bootstrapper project something like that:
_bootstrapper.PlanMsiFeature += (_, ea) =>
{
ea.State = (needToInstall) ? FeatureState.Local : FeatureState.Absent;
};
I ran into the same issue and ended up using a transform with only the ADDLOCAL property set.
Rather than using AddLocal, I found adding code like:
<Feature Id="FeatureB" Level="0">
<Condition Level="1">INSTALL_FEATUREB="1" OR FEATUREB_INSTALLED="1"
</Condition>
</Feature>
to my msi file behaved in a reasonable manner, allowing me to add/remove a feature.
Modified from:
using https://support.firegiant.com/hc/en-us/articles/230912227-Control-feature-states-for-silent-install-
I like using the 'Variable=X' syntax because I find it easier to read. Without the FEATUREB_INSTALLED test I found uninstall did not act as I wanted.
Well, you can always pass a value to a Variable from the Code behind like this.
Bootstrapper.Engine.StringVariables["AddLocal"] = "your value";

WiX - Does Burn support dual-purpose msi packages?

Does Burn support dual-purpose (per-user or per-machine) MSI packages which were prepared according to these Microsoft guidelines?
I tried to prepare such a package, but it looks like bootstrapper created with Burn doesn't uninstall MSI package, which was installed per-machine after raising UAC privileges by End-User.
The source code for Burn is:
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension"
xmlns:bal="http://schemas.microsoft.com/wix/BalExtension">
<Bundle Version="1.0"
Name="AppNameHere"
UpgradeCode="GuidHere">
<BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.HyperlinkLicense" >
<bal:WixStandardBootstrapperApplication LicenseUrl=""
SuppressOptionsUI="yes"
ThemeFile="Customization\Theme.xml"
LocalizationFile="Customization\LangHere.wxl"/>
</BootstrapperApplicationRef>
<Chain>
<PackageGroupRef Id="WindowsInstaller45"/>
<PackageGroupRef Id="NetFx40ClientRedist"/> <!-- Uzywa rozszerzenia WixNetfxExtension do zainstalowania .net -->
<PackageGroupRef Id="vcredist"/>
<MsiPackage Compressed="yes"
SourceFile="MsiFileNameHere"
DisplayInternalUI="yes">
<MsiProperty Name="UPDATEDIR" Value="[UninstallPath]"/>
<MsiProperty Name="WIXBUNDLEKEY" Value="[WixBundleProviderKey]"/>
</MsiPackage>
</Chain>
</Bundle>
<Fragment>
<util:RegistrySearch Root="HKLM" Key="SOFTWARE\Microsoft\VisualStudio\10.0\VC\VCRedist\x86" Value="Installed" Variable="vcredistkeyx86" />
<util:RegistrySearch Root="HKLM" Key="SOFTWARE\Wow6432Node\Microsoft\VisualStudio\10.0\VC\VCRedist\x86" Value="Installed" Variable="vcredistkeyx64" />
<PackageGroup Id="vcredist">
<ExePackage Id="vcredist_x86"
Cache="no"
Compressed="yes"
PerMachine="yes"
Permanent="yes"
Vital="yes"
SourceFile="Components\vcredist_x86.exe"
DetectCondition="(vcredistkeyx86 AND (vcredistkeyx86 >= 1)) OR (vcredistkeyx64 AND (vcredistkeyx64 >= 1))" />
</PackageGroup>
<PackageGroup Id="WindowsInstaller45">
<ExePackage Cache="no"
Compressed="yes"
PerMachine="yes"
Permanent="yes"
Vital="yes"
SourceFile="Components\WindowsXP-KB942288-v3-x86.exe"
InstallCondition="VersionNT=v5.1 AND NOT VersionNT64 AND VersionMsi < v4.5"
InstallCommand="/quiet /norestart">
<ExitCode Behavior="forceReboot"/>
</ExePackage>
</PackageGroup>
</Fragment>
</Wix>
As of WIX V3.9 the answer is a qualified "No" - Burn doesn't currently support dual-purpose per-user or per-machine MSI package.
A dual-purpose MSI package has the ALLUSERS property set to "2". When you build a WIX bootstrapper project referencing this type of MSI package you should see this type of warning:
2>D:\Robert\Documents\Visual Studio 2013\Projects\BurnTest\Bootstrapper\Bundle.wxs(18,0): warning LGHT1133: Bundles require a package to be either per-machine or per-user. The MSI 'D:\Robert\Documents\Visual Studio 2013\Projects\BurnTest\SetupProject\bin\Release\SetupProject.msi' ALLUSERS Property is set to '2' which may change from per-user to per-machine at install time. The Bundle will assume the package is per-machine and will not work correctly if that changes. If possible, remove the Property with Id='ALLUSERS' and use Package/#InstallScope attribute instead.
The build process for a WIX bootstrapper project will try and work out from the chained packages what type of burn installation to create (per-user or per-machine). The logic is convoluted because of the different places you can declare a preference for per-user or per-machine, and the potential conflicts between chained packages. But the general idea is the burn compiler will generate a per-machine installation, unless one of the chained packages is per-user, which will flip the burn installation into per-user mode. The key point is the decision to create a per-user or per-machine package is made at build time. To properly support dual-purpose MSI packages that decision would need to be moved to install time.

Create msi wrapper for InstallShield installer using WiX

We've prepared installer for our client using InstallShield technology. Installer works fine but client wants this installer in msi technology. We decided to create wrapper using WiX. Msi installer should do few things:
Extract IS installer into temp directory.
Run InstallShield setup.exe.
Remove temporary directory after IS finish.
We haven't done installer in WiX technology before, those are our problems:
We don't know how to embed directory with all it's files (installer) into msi using WiX.
We don't know how to ONLY run msi (it shouldn't install data, only run embeded IS setup.exe during installation and remove after).
We don't know how to run exe during installation.
This is *.wxs file which I've created so far:
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Name="ISSetupPackeger" Language="1033" Version="1.0.0.0" Manufacturer="MyCompany" UpgradeCode="8804d459-2ea5-4bbc-85f7-dfc8419cafe4">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" Id="*" InstallPrivileges="elevated" />
<!-- TODO setup.exe starts but we don't want to run it using cmd -->
<CustomAction Id="LaunchInstaller" Directory="InstallerDir" ExeCommand="cmd /C setup.exe" Impersonate="yes" Return="ignore" />
<InstallExecuteSequence>
<Custom Action="LaunchInstaller" After="InstallFinalize" />
</InstallExecuteSequence>
<Media Id='1' Cabinet='data.cab' EmbedCab='yes'/>
<Feature Id="ProductFeature" Title="WixInstallerProject" Level="1">
<ComponentGroupRef Id="ProductComponents" />
</Feature>
<!-- TODO How to extract files to temp dir? Is there some TEMP constant? -->
<Directory Id="TARGETDIR" Name="SourceDir" >
<Directory Id="TempDir" Name="inst">
<Directory Id="InstallerDir" Name="Installer"/>
</Directory>
</Directory>
<!-- component group which will be installed into tempdir -->
<ComponentGroup Id="ProductComponents" Directory="InstallerDir">
<Component Id="Installer" Guid="7b8bd37f-7eda-4c3a-8155-9dae1a6bbf98">
<!-- TODO How to embed directory with all its files/subdirectories? -->
<File Id="_Setup.dll" Name="_Setup.dll" DiskId="1" Source="installer\_Setup.dll"/>
<File Id="data1.cab" Name="data1.cab" DiskId="1" Source="installer\data1.cab"/>
<File Id="data1.hdr" Name="data1.hdr" DiskId="1" Source="installer\data1.hdr"/>
<File Id="data2.cab" Name="data2.cab" DiskId="1" Source="installer\data2.cab"/>
<File Id="data2.hdr" Name="data2.hdr" DiskId="1" Source="installer\data2.hdr"/>
<File Id="ISSetup.dll" Name="ISSetup.dll" DiskId="1" Source="installer\ISSetup.dll"/>
<File Id="layout.bin" Name="layout.bin" DiskId="1" Source="installer\layout.bin"/>
<File Id="setup.exe" Name="setup.exe" DiskId="1" Source="installer\setup.exe"/>
<File Id="setup.ini" Name="setup.ini" DiskId="1" Source="installer\setup.ini"/>
</Component>
</ComponentGroup>
</Product>
</Wix>
Some alternatives:
From the files, it looks like the installer you have created with InstallShield is an InstallScript non-MSI installer. You might be able to convert it to an InstallScript MSI installer with an InstallShield converter. See this question and answer.
I read the requirement to convert to MSI differently than you do. For any kind of wrapping or converting to be worthwhile, it should take advantage of Windows Installer managing the installation of the files that are actually installed. To do that, you'd have to rewrite the installer from scratch if a conversion is not feasible. Your approach simply uses an MSI as a bundle. You should get clarification on what you want to do.
If you due go with the bundling route, WiX now offers a bootstrapper/downloader/chainer/bundler called Burn. With it, you can create a single .exe that will extract and run your existing installer. And, if you want, you can create an InstallShield response file so the existing install can be run silently. (See the InstallShield documentation for that.)
You can use a WiX Bootstrapper to embed your existing InstallShield executable. No conversion needed.
In our case the Setup.exe as built from InstallShield actually has an MSI inside. (I think this is usually the case). So then you have two options:
- you can either extract that MSI (google "extract MSI from InstallShield") and embedded it in the bootstapper using MsiPackage
- or you can embed the whole Setup.exe, using ExePackage
Passing Parameters to an MSI: easy
<MsiPackage Id="bla" SourceFile="path-to-msi>
<MsiProperty Name="PUBLIC_PROPERTY_IN_UPPERCASE" value="yourvalue"/>
</MsiPackage>
Passing parameters to an InstallShield Setup.exe: tricky
In our case Setup.exe ends up passing the parameters to the MSI anyway, use /v"the-parameters-go-here-but-you-have-to-escape-properly"
Sounds easy, but it was complicated to properly escape slashes (\), single-quotes ('), double quotes ("). Also, WiX escapes \ differently if the whole InstallArgument is enclosed in ' (single quotes) or " (double quotes). I started with single quotes because it looked easier, but ended up using double quotes. Use the install log to see what gets actually passed through each layer.
<Wix>
<Bundle>
<BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.HyperlinkLicense">
<bal:WixStandardBootstrapperApplication
xmlns:bal="http://schemas.microsoft.com/wix/BalExtension"
LicenseUrl=""/>
</BootstrapperApplicationRef>
<!-- rumor has it Windows Installer needs the trailing slash for paths -->
<Variable Name="InstallPath" Value="C:\path\to\destination\on\target\machine\" Type="string" bal:Overridable="yes"/>
<Chain>
<MsiPackage Id="package_your_msi"
SourceFile="path-to-msi"
DisplayInternalUI="no"
ForcePerMachine="yes"
Compressed="yes"
After="package_some_other_msi">
<MsiProperty Name="TEST_PROPERTY_5" Value="5"/>
</MsiPackage>
<ExePackage Id="package_installshield_setup_exe"
SourceFile="C:\path\to\Setup.exe"
Compressed="yes"
PerMachine="yes">
<CommandLine InstallArgument="/s /v"/qn INSTALLDIR=\"[InstallPath] \" HOST_ADDRESS=[HostAddress] HOST_PORT=[HostPort] SCANNER_MODEL=[ScannerType]""
Condition="use_registry_values=1 AND other_cond=1"/>
</ExePackage>
</Bundle>
</Wix>
Sources:
https://social.msdn.microsoft.com/Forums/windows/en-US/1a113abd-dca1-482b-ac91-ddb0dcc5465c/is-it-possible-to-pass-commandline-arguments-to-an-installshield-executable?forum=winformssetup
http://helpnet.installshield.com/installshield22helplib/helplibrary/IHelpSetup_EXECmdLine.htm

Passing command line args to MSI from WiX bundle

I’m on Wix 3.7. I have an MSI that I would like to set a registry key (perhaps via a Custom Action, as he will have to check if the key already exists).
I understand that a Bundle in a bootstrapper project can't change the machine state (such as setting the registry). Therefore, I'm attempting to pass a command line argument via <MsiProperty>, but doesn't appear to show up as a command line argument in my log file for the bootstrapper.
Is it possible to set a registry key up in a Bundle?
If not, how can I add a command line argument (or some other piece of custom data)
to be passed to the MSI.
How can the MSI read whatever it is I pass to it (whether It ends up being a command line arg or something
else).
Bundle:
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Bundle
Name="MyInstallerBootstrapperLocalDb"
Version="1.0.0.0"
Manufacturer="some company"
UpgradeCode="PUT-GUID-HERE">
<BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense" />
<Chain>
<MsiPackage Id="MyInstallerInstaller"
SourceFile="$(var.MyInstallerInstaller.TargetPath)"
Compressed="no">
<!-- TODO - if this is being set correctly, the MSI needs to
interpret it and set up the key-->
<MsiProperty Name="SetLocalDb" Value="yes"/>
</MsiPackage>
</Chain>
</Bundle>
</Wix>
Your MSI needs to define a property like so:
<Property Id="SOMEPROPERTY" Value="Default"/>
You can then set this property from the bundle:
<MsiPackage SourceFile="<package>.msi" Id="SomeId">
<MsiProperty Name="SOMEPROPERTY" Value="[SomeProperty]" />
</MsiPackage>
After this you can set the property in the Bootstrapper as described here: WiX Bootstrapper: How do I set burn variables from the command line?
Notice that SomeProperty is a Burn variable which you have to define:
<Variable Name="SomeProperty" Type="string" Value="DefaultValue" />
Update:
In the MSI you are then able to do a registry search based on this property:
<RegistrySearch Id="GetSomeValue" Root="HKLM" Key="SOFTWARE\<Manufacturer>\[SOMEPROPERTY]" Name="<ValueName>" Type="raw" />
Just to add an extra bit of information. To alter the variable values with command line, I actually had to set it as overriable.
<Variable Name="SomeProperty" Type="string" Value="true" bal:Overridable="yes" />