WIX v4 Unable to reference action state of a feature in the custom element property condition - properties

When trying to add a custom action condition to the Install Execute Sequence of that action. The build will fail when the action state of a feature is referenced via the '&'.
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Package Name="New App" Manufacturer="Coder" Version="1.0.0.0" Scope="perMachine" UpgradeCode="56858172-dbce-452f-855c-2c24cc44a192">
<MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
<MediaTemplate EmbedCab="true"/>
<Property Id="POWERSHELL" Value="powershell.exe"/>
<CustomAction Id="PsInstallSkdTasks" Property="POWERSHELL" Return="asyncNoWait" ExeCommand="-File "[INSTALLFOLDER]\InstallScheduledTasks.ps1"" />
<InstallExecuteSequence>
<Custom Action="PsInstallSkdTasks" OnExit="success" Condition="(?CompPsIns = 3) AND ((!Main = 3) OR (&Main = 3))" />
</InstallExecuteSequence>
<Feature Id="Main" Level="1" AllowAdvertise="false" Display="expand" Title="Main App" Description="Descr...">
<ComponentGroupRef Id="CompGrpMain" />
</Feature>
</Package>
</Wix>
When I attempt to build this I get the following error:
error WIX0104: Not a valid source file; detail: ' ' is an unexpected token. The expected token is ';'.
But then '&Main;' is not a defined entity.
I believe it wants a semicolon after &Main in the condition because the condition has been moved to a property and I believe the XML parser is expecting it. So I'm not sure if this is a bug in version 4 or if there is meant to be a new notation to match this functionality that is documented here:
FireGiant Expression Syntax
I've been unable to find any documentation of the implementation of the condition property for version 4 of the Wixtoolset, as the existing documentation provides no explanations at this time.
Wixtoolset Custom Element

You can't use a naked & in XML. Use & instead.

Related

Registering SharpShell extension using SRM via WIX installer

Firstly I should clarify that I am a novice and have been struggling to understand the WIX formatting, but by cobbling together examples found on-line, I now have the files installing fine so I next need to register my DLL.
I used the example here as a starting point: How to deploy a SharpShell-based shell extension via WiX? but it seems that the SharpShell tool srm.exe may not be getting called at installation.
If I manually call srm.exe as follows, it works as hoped i.e. the DLL is registered and my shell extension works.
srm install MyExtension.dll -codebase
I can also see that the registration has been successful via the Server Manager application that comes with SharpShell.
I can also manually uninstall with the following - not that this is particularly relevant to my problem but it at least confirms that the manual methods work:
srm uninstall MyExtension.dll
Here is a fragment of my WXS file. When I run the resultant MSI, the files are installed but the DLL is not being registered; confirmed via SharpShell's Server Manager. Where am I going wrong?
</Component>
<Component Id="SRMexe" Guid="C17BB61F-6471-46F9-AA87-2D14D2456632">
<File Id='srm' Name='srm.exe' DiskId='1' Source='..\MyExtension\packages\SharpShellTools.2.2.0.0\lib\srm.exe' KeyPath='yes'>
</File>
</Component>
<!-- TODO: Insert files, registry keys, and other resources here. -->
<!-- </Component> -->
</ComponentGroup>
</Fragment>
<Fragment>
<CustomAction Id="InstallShell" FileKey="srm"
ExeCommand='install "[INSTALLFOLDER]\MyExtension.dll" -codebase'
Execute="deferred" Return="check" Impersonate="no" />
<CustomAction Id="UninstallShell" FileKey="srm"
ExeCommand='uninstall "[INSTALLFOLDER]\MyExtension.dll"'
Execute="deferred" Return="check" Impersonate="no" />
<InstallExecuteSequence>
<Custom Action="InstallShell"
After="InstallFiles">
NOT Installed
</Custom>
<Custom Action="UninstallShell"
Before="RemoveFiles">
(NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")
</Custom>
</InstallExecuteSequence>
</Fragment>
It doesn't look like you have any references to the Fragment with the CustomAction definitions so they are not linked into your final output MSI.
Add a CustomActionRef from your Product element to create the reference.

Custom Action for Feature in WIX

I am trying to execute an EXE file through my installation and this file should be installed if the related feature will be installed in the feature tree.
I have two questions :
1-How to relate the Custom Action to this feature."The condition"
2- How I can include this exe file in the generated file. "This EXE File is a SQL Installation which I already made in WIX BOOTSTRAPPER "
http://apprize.info/web/wix/13.html
and my code is
<Feature Id="SubFeature1" Title="SQL Installation" Level="1" >
<ComponentRef Id="SubComponent1"/>
</Feature>
<Feature Id="SubFeature2" Title="Second Subfeature" Level="1" >
<ComponentRef Id="SubComponent2"/>
<!-- <Condition Level="0">IISMAJORVERSION=""</Condition> -->
</Feature>
</Feature>
<CustomAction Id="CreateSQLINSTALLER" Directory="BMSS4_Installer"
Execute= "deferred" Impersonate="no" Return="ignore"
ExeCommand="[BMSS4_Installer]Sql_Installation_Test1.exe -install" />
<InstallExecuteSequence>
<Custom Action="CreateSQLINSTALLER" Before="InstallFinalize"><![CDATA[(&SubFeature1)]]></Custom>
</InstallExecuteSequence>
the Sql_Installation_Test1.exe is included in the main folder so BMSS4_Installer..
But is it right to use it direclty like that in Directory tag om CustomAction !!
Feature conditions are documented here:
https://msdn.microsoft.com/en-us/library/aa368012(v=vs.85).aspx
in the action state of the feature. Basically you use a condition such as:
&featurename=3
where 3 is INSTALLSTATE_LOCAL, as there in the documentation. There are limits on where the condition can be used, the main one being after CostFinalize.
It's not clear if you are installing some version of SQL itself, but that would have its own install and wouldn't need repackaging, and it would be a prerequisite installed with Burn, for example. If it's a separate MSI setup of yours, again a Burn package would probably be the best way to install it and your other MSI.

Copying files to different directory during WIX Upgrade

I have been working on a WIX installer for an application and am stuck on a small part of the upgrade: there are two XML configuration files in the install directory that I would like to copy to the new ProgramData directory (since they will not be in ...\Program Files... going forward).
I have tried several solutions, including different brackets/apostrophes/", to no avail. When I compile the WIX installer, I receive several warnings from CANDLE about the Property containing [CommonAppDataProduct] and [PRODUCTNAMEFOLDER], but I am unsure if there needs to be some reference / PropertyRef from those directories defined in the Product.wxs to each custom action.
Snippets of Product.wxs:
<Product Id="*" Name="$(var.ProductName)" Language="0" Version="$(var.Version)" Manufacturer="$(var.Manufacturer)" UpgradeCode="$(var.UpgradeCode)">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<InstallExecuteSequence>
<SelfUnregModules/>
<SelfRegModules/>
<Custom Action="CopyConfigFilesToTemp" After="InstallValidate" />
<Custom Action="LaunchDPInstActionx86" Before="InstallFinalize">NOT Installed OR MaintenanceMode="Modify"</Custom>
<Custom Action="CopyConfigFilesFromTemp" After="LaunchDPInstActionx86" />
</InstallExecuteSequence>
</Product>
...
<Fragment>
<Directory Id="$(var.PlatformProgramFilesFolder)">
<Directory Id="PRODUCTNAMEFOLDER" Name="$(var.ProductName)"/>
</Directory>
</Fragment>
Custom action CopyConfigFilesToTemp
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<Property Id="QuietExec2" Value='"xcopy.exe [PRODUCTNAMEFOLDER]*.xml" %TEMP% /I /Y'/>
<CustomAction Id="CopyConfigFilesToTemp" BinaryKey="WixCA" DllEntry="WixQuietExec" Execute="immediate" Return="ignore"/>
</Fragment>
</Wix>
Custom action CopyConfigFilesFromTemp
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<Property Id="QuietExec3" Value='"xcopy.exe %TEMP%\*.xml [CommonAppDataProduct]" /I /Y /R'/>
<CustomAction Id="CopyConfigFilesFromTemp" BinaryKey="WixCA" DllEntry="WixQuietExec" Execute="immediate" Return="ignore"/>
</Fragment>
</Wix>
These custom actions must be deferred custom actions because they are trying to modify something in the program files path which requires elevated privileges. The only portion of the install that has elevated privileges is the server server portion of the install when it is copying over files to the install directory.
Custom actions that are deferred have a special requirement on them if you intend to use one of the msi properties within the action.
As per microsoft's website on Deferred Actions
Because the installation script can be executed outside of the installation session in which it was written, the session may no longer exist during execution of the installation script. This means that the original session handle and property data set during the installation sequence is not available to a deferred execution custom action.
This essentially means that you need to put the values of your properties in a special location that is guaranteed to exist while the elevated portion of the install is happening and must be formatted in such a way that it knows exactly where to look to get that value.
So to run these actions they must be scheduled between InstallInitialize and InstallFinalize and must also be able to grab the property values from a special location.
To use a deferred custom action you just need to change the execution to deferred however, we must add this special property with a formatted value so that you can get the values of QuietExec and QuietExec2 from within your custom actions.
You need to declare a custom action as follows for each of the deferred actions:
<CustomAction Id="CustomActionNameHere" Property="CopyConfigFilesToTemp" Value="QuietExec2="xcopy.exe [PRODUCTNAMEFOLDER]*.xml" %TEMP% /I /Y" />
<CustomAction Id="CustomActionNameHere" Property="CopyConfigFilesFromTemp" Value="QuietExec3="xcopy.exe %TEMP%\*.xml [CommonAppDataProduct]" /I /Y /R" />
Generally I call these the same name as the custom action they're setting a property for with "Set" prefixed to the name. IE: SetCopyConfigFilesFromTemp and SetCopyConfigFilesToTemp so that they are easy to locate.
You also must schedule these custom actions and you can't go wrong scheduling them before the action they set properties for and match the conditions.
<Custom Action="SetCopyConfigFilesToTemp" Before="CopyConfigFilesToTemp">
<Custom Action="SetCopyConfigFilesFromTemp" Before="CopyConfigFilesFromTemp">
In the custom action code, you need to use session.CustomActionData["PropertyName"] instead of just session["PropertyName"]
I would also consider the situations when you want to run these copy commands since I don't think you want to do them when uninstalling the product or if its a fresh install and not an upgrade.

Understanding PackageCode, ProductCode and UpgradeCode of WIX with multi instances

My multi instance msi setup work only if I use the same msi file. Then the user
can change the directory
install a new instance if it a new directory
update the instance if it an existing directory
every instance has its own uninstaller entry
If I use a new build of the setup and run an update then
a second uninstaller with the same display name is registered
A second of the new setup start in the maintenance mode. There is no
directory selection possible. An update of the other instances is
not possible.
After reading follow blog entry
http://blogs.msdn.com/b/pusu/archive/2009/06/10/understanding-msi.aspx
I have hard codes the PackageCode, ProductCode (per instance) and
UpgradeCode. And it work like expected. But I receive a big warning on
building. Can I ignore this? Is this the right solution?
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<Product Id="*" Language="1033" Manufacturer="i-net software GmbH" Name="i-net Test"
UpgradeCode="02c7fa01-5143-38ed-b923-2c2aaff301ae" Version="8.0.0.507">
<Package Comments="i-net Test Server" Compressed="yes" Id="c748d2f0-9ca5-3cbc-be9a-730c6d621f00"
InstallScope="perUser" />
<Media Cabinet="media1.cab" EmbedCab="yes" Id="1" />
<InstanceTransforms Property="INSTANCE_ID">
<Instance Id="Instance_0" ProductCode="c748d2f0-9ca5-3cbc-be9a-730c6d621f00" UpgradeCode="c748d2f0-9ca5-3cbc-be9a-730c6d621ff3" />
<Instance Id="Instance_1" ProductCode="c748d2f0-9ca5-3cbc-be9a-730c6d621f01" UpgradeCode="4c8e1670-9d04-3dce-b88a-1a4dbbbc976d" />
<Instance Id="Instance_2" ProductCode="c748d2f0-9ca5-3cbc-be9a-730c6d621f02" UpgradeCode="b76f160d-9395-3eda-a13d-d0566379ca8f" />
</InstanceTransforms>
<MajorUpgrade AllowDowngrades="yes" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLDIR" Name="i-net Test" />
</Directory>
</Directory>
<DirectoryRef Id="INSTALLDIR">
<Directory Id="Server" Name="Server">
<Component Guid="d62d7bcb-9242-39da-a43a-015df0f965af" Id="Server_Comp" MultiInstance="yes">
<CreateFolder />
<File Id="Server_testBuilds.jar" Name="testBuilds.jar" Source="..\testBuilds.jar" />
</Component>
</Directory>
<Component Guid="1543477d-59fc-3ec3-bb67-a541abd9cfba" Id="instance_path" MultiInstance="yes">
<RegistryKey ForceDeleteOnUninstall="yes" Id="instance_path_reg"
Key="Software\i-net software GmbH\i-net Test\Instances\[INSTANCE_NUMBER]" Root="HKLM">
<RegistryValue Type="string" Value="[INSTALLDIR]" />
</RegistryKey>
</Component>
</DirectoryRef>
<Property Id="INSTANCE_ID" Value="NotSet" />
<Property Id="InstancesCount" Value="3" />
<CustomAction Id="SetInstanceID" Script="vbscript">...</CustomAction>
<InstallUISequence>
<Custom Action="SetInstanceID" Before="ExecuteAction" />
<Custom Action="SetTransforms" After="SetInstanceID">ACTION = "INSTALL"</Custom>
</InstallUISequence>
<InstallExecuteSequence>
<Custom Action="SetInstanceID" Before="ValidateProductID" />
<Custom Action="SetProductName" After="SetInstanceID">PRODUCT_NAME</Custom>
</InstallExecuteSequence>
<CustomAction Id="SetTransforms" Property="TRANSFORMS" Value="{:[INSTANCE_ID];}[TRANSFORMS]" />
<CustomAction Id="SetProductName" Property="ProductName" Value="[PRODUCT_NAME]" />
<Feature Id="MainApplication">
<ComponentRef Id="Server_Comp" />
<ComponentRef Id="instance_path" />
</Feature>
</Product>
</Wix>
The SetInstanceID action can you find at https://github.com/i-net-software/SetupBuilder/blob/master/src/com/inet/gradle/setup/msi/MultiInstance.vbs
Before I have use "*" for the ProductCode (global and per instance) and
have hard coded only the UpgradeCode (global and per instance).
The solution was wrong. The PackageCode and ProductCode must be change for every build. Else the update does not work right. This is valid for a single instance but also for multiple instances msi files.
The problem with multiple instances is that the MajorUpgrade element does not work with multiple instances. If you inspect the resulting msi file with Orca you see only one entry with the UpgradeCode from the Product Element. The UpgradeCodes from your instances are not listen in this table. To solve this you must add the UpgradeCodes manually to this table:
<Property Id="InstancesCount" Value="3"/>
<InstanceTransforms Property="INSTANCE_ID">
<Instance Id="Instance_0" ProductCode="*" UpgradeCode="GUID_0"/>
<Instance Id="Instance_1" ProductCode="*" UpgradeCode="GUID_1"/>
<Instance Id="Instance_2" ProductCode="*" UpgradeCode="GUID_2"/>
</InstanceTransforms>
<Upgrade Id="GUID_0">
<UpgradeVersion MigrateFeatures="yes" Minimum="0.0.0.0" Property="WIX_UPGRADE_DETECTED_0"/>
</Upgrade>
<Upgrade Id="GUID_1">
<UpgradeVersion MigrateFeatures="yes" Minimum="0.0.0.0" Property="WIX_UPGRADE_DETECTED_1"/>
</Upgrade>
<Upgrade Id="GUID_2">
<UpgradeVersion MigrateFeatures="yes" Minimum="0.0.0.0" Property="WIX_UPGRADE_DETECTED_2"/>
</Upgrade>
If you have remove the MajorUpgrade element then you must also add:
<InstallExecuteSequence>
<RemoveExistingProducts After="InstallValidate"/>
</InstallExecuteSequence>
This can only add once and will be automatically added form MajorUpgrade. The default property name in the update table is WIX_UPGRADE_DETECTED. We need to use an individual name for every instance. I have suffix it with "_". But also every other is possible.
If we look into the update table with Orca we can see all our Update codes now. But there is a new problem. If we install the second instance then MSIEXEC find an installation of one of this UpgradeCodes in the action FindRelatedProducts and uninstall it in the RemoveExistingProducts. We can install only one instance. All previous instances are removed.
Between the actions FindRelatedProducts and RemoveExistingProducts we need to clear the WIX_UPGRADE_DETECTED_xx properties of the other instances. Only the UpdateCode of the current instance must exists. I have do this with a small vbscript action:
<CustomAction Id="SetInstanceID" Script="vbscript">
InstancesCount = Session.Property( "InstancesCount" )
For i = 0 To InstancesCount - 1
If CStr(i) <> instanceNumber Then
Session.Property( "WIX_UPGRADE_DETECTED_" & i ) = ""
End If
Next
</CustomAction>
Now only the the current instance is removed in RemoveExistingProducts if already exists.
Actually, the MajorUpgrade element does work with multiple instances. The generated instance transforms replace all rows of the upgrade table with new rows that are identical except for the UpgradeCode, which is replaced by that instance's own UpgradeCode. If you extract the instance transforms from your MSI (use the MsiDb.exe tool from the SDK to extract the substorages, which are named the same as the Instance/#Id values, in order to see them in action by applying them to the MSI in Orca).

Why isn't my Wix Property being evaluated?

I'm trying to simulate the InstallURL property of a VS.net install MSI... I've got to the ponit where the WIX MSI will open a browser to the download page that I want it to go to. I thought things were going great because on my test machine, the web page opened when I didn't have the MSXML6 component installed. However things went downhill when I discovered that the web page opened even when I DID have the component installed.
I'm searching for the MSXML6 component using a Property w/ a RegistrySearch. However, as best as I can tell, the registry value isn't even being evaluated, and thus it "always" looks like it isn't installed.
Here's the relevant portion of my WXS:
<Property Id="MSXML6">
<RegistrySearch Id="MSXML6Search" Root="HKCR" Key="Msxml2.DOMDocument.6.0" Type="raw" />
</Property>
<Property Id="TEST">
<RegistrySearch Id="TESTSearch" Root="HKLM" Type="raw" Name="Version" Key="SOFTWARE\Microsoft\DirectX" />
</Property>
<Property Id="cmd.exe" Value="cmd.exe" />
<CustomAction Id="OpenMSXML6Download" Property="cmd.exe" ExeCommand="/c start http://www.microsoft.com/downloads/details.aspx?FamilyID=993c0bcf-3bcf-4009-be21-27e85e1857b1" Execute="immediate" Return="check" />
<CustomAction Id="OpenMSXML6DownloadError" Error="This component requires MSXML6. =[MSXML6]=[cmd.exe]=[TEST]= A web browser has been opened to the download page. Please install MSXML6 and then re-install the connector." />
<!-- installation execution sequence -->
<InstallExecuteSequence>
<!-- wires the error dialog to the downgrade event -->
<Custom Action="PreventDowngrading" After="FindRelatedProducts">NEWPRODUCTFOUND</Custom>
<!-- execution to delete old install info after upgrade-->
<RemoveExistingProducts After="InstallValidate" />
<!-- Forces MSXML6 to be pre-installed -->
<!-- <Custom Action="OpenMSXML6Download" Before="FindRelatedProducts">NOT MSXML6</Custom> -->
<Custom Action="OpenMSXML6Download" Before="FindRelatedProducts">NOT MSXML6</Custom>
<Custom Action="OpenMSXML6DownloadError" After="OpenMSXML6Download">NOT MSXML6</Custom>
</InstallExecuteSequence>
<!-- ui information for the custom actions above. -->
<InstallUISequence>
<Custom Action="PreventDowngrading" After="FindRelatedProducts">NEWPRODUCTFOUND</Custom>
<Custom Action="OpenMSXML6Download" Before="FindRelatedProducts">NOT MSXML6</Custom>
<Custom Action="OpenMSXML6DownloadError" After="OpenMSXML6Download">NOT MSXML6</Custom>
</InstallUISequence>
What this does is if MSXML6 isn't defined then it opens the web page and then prints the custom error message. Note that I'm trying to print the value of the property in the error message (I'm not sure if this is valid or not, but it seems to be.) The text that I see says "This component requires MSXML6. ==[cmd.exe]==..." so it is printing the value of the 'cmd.exe' property but not the other two... maybe that's because I define the property explicitly, I'm not sure... Anyway, I also ran the MSI with debugging on, and in the log file, I see absolutely no reference at all to the MSXML6 or the TEST properties ever being set. I've confirmed that the registry values are indeed set, although I'm not 100% sure how to handle the Msxml2 registry key, since it doesn't have any real values, only a default value. (I'm assuming that leaving off the 'Name' parameter is the right way to handle this.)
Help??
I managed to figure this one out... it was quite a simple answer. Basic issue was that the Custom Actions were executing before AppSearch, which is where the RegistrySearch properties get evaluated. See my blog post at CTICoder for details.