WiX XmlConfig change values on multiple lines? - wix

Related: WiX XmlFile setValue on multiple lines
I'd like to update attributes on multiple xml nodes with one statement using XmlConfig. Is there an equivalent to XmlFile - bulkSetValue ?
This will update the first instance.
<util:XmlConfig Id="ServerWCFCertClientSettings" File="[#APP_CONFIG]" On="install" PreserveModifiedDate="yes"
Action="create"
Node="value"
ElementPath="//configuration/system.serviceModel/client/endpoint/identity/certificateReference"
Name="findValue"
Value="[SELECTED_WCF_CERTIFICATE]"
Sequence="200" />
I could use XmlFile for this, but we're trying to standardize on XmlConfig for all operations.

#Doug Though it's a more than a year old post but just in case this helps someone: Setting a value XmlConfig

Related

ExecuteOnInstall and RollbackOnUninstall attributes on SqlScript in WiX

I have the following piece of code in my wxs file for DB.
<Binary Id="binCreateTbl" SourceFile="CREATE_TABLE.sql" />
<sql:SqlScript BinaryKey="binCreateTbl" Id="script_CreateTbl" ExecuteOnInstall="yes" Sequence="2"/>
<sql:SqlScript BinaryKey="binCreateTbl" Id="script_CreateTbl1" RollbackOnUninstall="yes" Sequence="1" />
The Create_Table.sql just contains a create table statement with just two simple columns namely ID and Name. When installing the MSI, the table is getting created in the DB, but while uninstalling, the table is not getting removed / dropped. Any idea how to achieve this? I know that including the following piece of code instead of the line with RollbackOnUninstall=yes, then it works. But I want to avoid it. Please help.
<Binary Id="binDropTbl" SourceFile="DROP_TABLE.sql" />
<sql:SqlScript BinaryKey="binDropTbl" Id="script_DropTbl" ExecuteOnUninstall="yes" />
Looks like this is not possible. Since MSI has no way of knowing what is written inside of the sql file, it cannot be rolled back simply using RollbackOnUninstall=yes. There has to be a separate script which will include the code to delete / remove the table during uninstall and it has to be called using ExecuteOnUninstall=yes.

Set File Name to Property

I have a Merge Module which installs a file. I would like to use a property passed to the Merge Module during MSI creation. Something like:
<Configuration Name='FileNameProperty'
Format='Text'
DefaultValue='[FileNameProperty]' />
<Substitution Table='CustomAction'
Row='SetFileName'
Column='Target'
Value='[=FileNameProperty]' />
<CustomAction Id='SetFileName'
Property='MYFILENAME'
Value='[MYFILENAME]' />
<InstallExecuteSequence>
<Custom Action='SetFileName'
Before='LaunchConditions'>1</Custom>
</InstallExecuteSequence>
...
<File Name="[MYFILENAME]"
Source="my-file.exe" />
Currently I am using a Custom Action, namely Type 51, which works when substituting property values for other element attributes, such as ServiceInstall DisplayName. However, in this instance the file is deployed as [MYFILENAME].
I've looked through the other Custom Actions provided by MSDN, but can't find anything that fits this situation. Any suggestions or idea if this is even possible?
My last ditch option is to include File elements for each variation of file name and select the desired file based off condition, but I would like to avoid that.
File names aren't formatted so properties can't be used. If you use multiple files, WiX's smart cabbing ensures the copies don't take up extra space in your cabinet.

Wix installer - copy attribute value to another attribute

What I want to do is copy the value of a single property, and place it as the value of another property. Is this possible with Wix?
<Property Id="PROP1" Value="default" Secure="yes"/>
I want PROP2 to have the same value some where during the UI sequence.
You can use Custom action to accomplish it. Define action as
<CustomAction Id="SetMyProperty" Execute="immediate" Property="PROP2" Value="[PROP1]" />
Then call it in <InstallUISequence> or <InstallExecuteSequence> when you need it set, something like
<Custom Action="SetMyProperty" After="FileCost"></Custom>
Although #Mischo5500 answer is correct, there is a more elegant way to achieve the same. You can use a special element, <SetProperty>, which was designed exactly for that purpose.
The benefits of using it is:
It's just one line of WiX code
It is scheduled properly by default into both sequences
The name of the element is more natural and self-explanatory
Thus, the above example can be transformed into the following:
<SetProperty Id="PROP2" Value="[PROP1]" After="FileCost" />

WiX: Does setting MultiInstance attribute to yes generate unique component IDs for each instance transform, even when static guids are used?

I have two msi packages that gets triggered by a bootstrapper and together install a product. I have multiple instance transforms defined for each msi, and I want to set the MultiInstance attribute to 'yes' for all the components in the harvested fragment such that a new component GUID will be generated per instance transform. (It seems that for now, there isn't a heat parameter that you can set to do this, and it has to be accomplished via an xslt transformation.)
I'd want to use -gg flag for Heat to auto-generate static GUIDs because the install directory is set during run-time as a parameter and is not necessarily a standard directory.
Basically, the output should look like:
<Fragment>
<DirectoryRef Id="TARGETDIR">
<Component Id="cmp32EAD7F5A154CBFA668F294AEEE77B45" Guid="{6529235A-EE06-47EB-A56B-1D016B2396CF}" MultiInstance="yes" >
<File Id="fil3F2F6C0F947339E1ED2CF4459569CC5A" KeyPath="yes" Source="$(var.BIN)\File1.txt" />
</Component>
</DirectoryRef>
... Etc.
I'm wondering, even if the GUID is hard-coded such that the linker does not generate it (like above, instead of Guid="*"), will the MultiInstance attribute being set to 'yes' generate unique guids for each instance transforms' components? I sot of became confused about this when I was test calling the MsiGetProductCode by passing in a component guid for a file, which was defined like below:
<Component Id="ProductComponent" Guid="{1C149757-1E1D-424D-AF77-A156CB87F0BF}" MultiInstance="yes">
<!-- TODO: Insert files, registry keys, and other resources here. -->
<File Id="Picture1" Source="C:\Users\Public\Pictures\Sample Pictures\Desert.jpg" ProcessorArchitecture="x64" />
</Component>
* This is a test file that gets installed for all instance transforms defined.
I had two instances from the msi installed (Instance1, Instance2) and the MsiGetProductCode function ran as a part of a custom action that executes during uninstall. On the first execution of MsiGetProductCode, I got the ProductCode of Instance1. On the second execution of MsiGetProductCode (after Instance1 was removed), I got the ProductCode of Instance2. It seemed like that static component id had been used for both instance transforms.
Is the unique component ID generated by the MultiInstance attribute being set to 'yes' not supposed to replace that visible component guid? I haven't had any issues certain files or registry values not being removed due to a component still being used. Basically, I want to confirm that unique guids are being generated per instance and that it's safe to use the MultiInstance attribute to guarantee that component ID collisions will not occur, even when static guids are in use. Could someone kindly elaborate how this works in the background?
Thanks a lot in advance!
It's pretty easy to confirm WiX behavior just by logging the install. Consider the following code:
<Component Id="test" Guid="{EAF11690-2396-4EBE-A74D-37FA1751BBC3}" MultiInstance="yes">
<File Id="test" Source="C:\windows\notepad.exe" KeyPath="yes" />
</Component>
<InstanceTransforms Property="INSTANCEID">
<Instance Id="I01" ProductCode="{7474D99A-B56C-4767-B437-52F56746274A}" ProductName="ProductName2-1" UpgradeCode="{7C2BE622-7543-4F22-A0ED-A9FD28C78C8A}"/>
</InstanceTransforms>
Logging the base and secondary installation reveals that the GUID is unique / transformed.
Another thought would be to extract the instance transform from the MSI and apply it using ORCA to see the differences.
MSI (s) (E4:A4) [10:36:37:021]: Executing op:
ComponentRegister(ComponentId={EAF11690-2396-4EBE-A74D-37FA1751BBC3},KeyPath=C:\Program
Files
(x86)\MyCompany\ProductName2\notepad.exe,State=3,,Disk=1,SharedDllRefCount=0,BinaryType=0)
MSI (s) (E4:DC) [10:37:04:234]: Executing op:
ComponentRegister(ComponentId={BEC4E6A5-9CFB-5C77-A854-CC0179CFEDCE},KeyPath=C:\Program
Files (x86)\My
Company\ProductName2\notepad.exe,State=3,,Disk=1,SharedDllRefCount=0,BinaryType=0)

How can multiple elements be added to an XML config file with wix?

I am trying to edit an XML file with Wix. I am using the WixUtilExtension bundled with Wix 3.7. The xml file is a settings file created in Visual Studio 2010 for a C# application. In this file, I am using an element which is used to store multiple string values in an array. This is the content of the unaltered settings file:
<configuration>
<applicationSettings>
<AppName.Properties.Settings>
<setting name="StringArray" serializeAs="Xml">
<value>
<ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
</ArrayOfString>
</value>
</setting>
</AppName.Properties.Settings>
</applicationSettings>
</configuration>
I want to add <string> elements to the <ArrayOfString> element in this file. One way to do this is by using an <XmlConfig> element from the wix/UtilExtension namespace. I have added this element to the component which holds the config file like this:
<Component Id="ProductComponent" Guid="$(var.ConfigGuid)">
<File Source="SettingsFile.exe.config" KeyPath="yes" Id="FILE_config" />
<util:XmlConfig
Name="string"
Value="My value"
File="[INSTALLFOLDER]SettingsFile.exe.config"
Id="String1"
On="install"
Action="create"
Node="element"
ElementPath="/configuration/applicationSettings/AppName.Properties.Settings/setting[\[]#name='StringArray'[\]]/value/ArrayOfString"
Sequence="100"
/>
</Component>
This results in the addition of one <string> element to the <ArrayOfString> element. To add another <string> element to the settings file, another XmlConfig element has to be added to the <Component> element of the setup project with a different Id attribute and a higher value for the Sequence attribute like this:
<util:XmlConfig
Name="string"
Value="My second value"
File="[INSTALLFOLDER]SettingsFile.exe.config"
Id="String2"
On="install"
Action="create"
Node="element"
ElementPath="/configuration/applicationSettings/AppName.Properties.Settings/setting[\[]#name='StringArray'[\]]/value/ArrayOfString"
Sequence="101"
/>
After installation of the msi, the <ArrayOfString> element in the settings file looks like this:
<ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<string>My value</string><string>My second value</string></ArrayOfString>
I have found out that it is possible to set the Value attribute of an <XmlConfig> attribute to the value of a property like this:
<Property Id="STRING1VALUE" Value="My value" />
<util:XmlConfig Value="[STRING1VALUE]" ... />
This is good. I would like the user to be able to add multiple values in the installation process dynamically so that a variable amount of <string> elements can be added to the settings file.
My first approach was to use a <?foreach?> statement like this:
<?define values="My value;My second value"?>
<?foreach value in $(var.values)?>
<util:XmlConfig
Name="string"
Value="$(var.value)"
File="[INSTALLFOLDER]SettingsFile.exe.config"
Id="String$(var.value)"
On="install"
Action="create"
Node="element"
ElementPath="/configuration/applicationSettings/AppName.Properties.Settings/setting[\[]#name='StringArray'[\]]/value/ArrayOfString"
Sequence="101"
/>
<?endforeach?>
There are a few problems with this approach:
The foreach statement uses a preprocessor variable which cannot be set to the value of a property.
The value of the Sequence attribute stays the same.
I would like the user to store the values for the string elements in a Property which separates the values by semicolons and then parse them in a foreach statement like this:
<Property Id="VALUES" Value="My value;My second value" />
<?foreach value in [VALUES]?>
<util:XmlConfig
Name="string"
Value="$(var.value)"
File="[INSTALLFOLDER]SettingsFile.exe.config"
Id="String$(var.value)"
On="install"
Action="create"
Node="element"
ElementPath="/configuration/applicationSettings/AppName.Properties.Settings/setting[\[]#name='StringArray'[\]]/value/ArrayOfString"
Sequence="101"
/>
<?endforeach?>
This throws the following error:
The util:XmlConfig/#Id attribute's value, 'String[VALUES]', is not a legal identifier.
Identifiers may contain ASCII characters A-Z, a-z, digits, underscores (_), or periods (.).
Every identifier must begin with either a letter or an underscore.
Is there any way I can create a variable amount of elements with the XmlFile or the XmlConfig element? Is the only solution to this problem a CustomAction?
Based on Rob's answer, here is my new approach to adding multiple elements to an XML config file with Wix. I did not want to write C++ code, that is why I used DTF in my CustomAction.
I am going to describe how to turn a string containing multiple elements using a delimiter into multiple XML elements.
First there needs to be a property in the setup file containing the delimited string.
<Property Id="STRINGARRAY" Value="string1;string2;string3" />
This property could be populated by the user in a dialog, of course.
Next, a CustomAction has to be written. To make use of the DTF, a reference to the Microsoft.Deployment.WindowsInstaller.dll has to be added to the C# CustomAction project. The namespace Microsoft.Deployment.WindowsInstaller should be included with a using directive in that project. My CustomAction looks like this:
[CustomAction]
public static ActionResult Insert(Session session)
{
string strings = session["STRINGARRAY"];
string[] stringArray = strings.Split(';');
Database db = session.Database;
View view = db.OpenView("select * from `XmlConfig`");
string xpath = "/configuration/applicationSettings/AppName.Properties.Settings/setting[\\[]#name='StringArray'[\\]]/value/ArrayOfString";
for (int i = 0; i < stringArray.Length; i++)
{
string id = String.Format("String{0}", i);
int sequence = 100 + i;
string value = stringArray[i].Trim();
Record rec = new Record(
id,
"[INSTALLFOLDER]SettingsFile.exe.config",
xpath,
null,
"string",
value,
273,
"ProductComponent",
sequence);
view.InsertTemporary(rec);
}
db.Close();
return ActionResult.Success;
}
Here, at first the Property StringArray is read into a local variable which is converted to a string array. The following line establishes a connection to the current database used by the installer. A handle on the table XmlConfig is created, which is the table where the XML elements are added to. To insert the right values into that table, it is best to create an installer file which contains such a table and then take a look at that table in an editor like orca or InstEd.
In the xpath, backslashes have to be escaped by using double backslashes. The id variable holds the name of the temporary record, using a simple string and a number works flawlessly. The sequence has to be incremented for each element. I could not find any documentation on the values of the flags column, but I have found out that its value is set to 273 for elements that are created and 289 for elements that get deleted.
Once the record is filled with the correct values, it gets added to the XmlConfig table by using the InsertTemporary method of the view object. This is done for each element found in the delimited string.
A problem I have come across is that this CustomAction fails, if the XmlConfig table does not exist. To counter this problem I have added the following code to the setup file, which adds an element to the XML file and immediately deletes that element. I guess there could be a cleaner solution, but this was the easiest one for me.
<util:XmlConfig
Name="string"
Value="Dummy"
File="[INSTALLFOLDER]SettingsFile.exe.config"
Id="DummyEntry"
On="install"
Action="create"
Node="element"
ElementPath="/configuration/applicationSettings/AppName.Properties.Settings/setting[\[]#name='StringArray'[\]]/value/ArrayOfString"
Sequence="1" />
<util:XmlConfig
On="install"
Action="delete"
Id="DeleteDummyEntry"
Node="element"
File="[INSTALLFOLDER]SettingsFile.exe.config"
VerifyPath="/configuration/applicationSettings/AppName.Properties.Settings/setting[\[]#name='StringArray'[\]]/value/ArrayOfString/string"
ElementPath="/configuration/applicationSettings/AppName.Properties.Settings/setting[\[]#name='StringArray'[\]]/value/ArrayOfString"
Sequence="2" />
Finally, the CustomAction has to be added to the setup project. By adding a reference to the CustomAction project in the setup project, the location of the binary can be specified like this:
<Binary Id="XmlCustomActionDLL" SourceFile="$(var.XmlCustomAction.TargetDir)XmlCustomAction.CA.dll" />
The CustomAction has to be executed immediately, otherwise it won't be able to access the session variable:
<CustomAction Id="CA_XmlCustomAction" BinaryKey="XmlCustomActionDLL" DllEntry="Insert" Execute="immediate" Return="check" />
<InstallExecuteSequence>
<Custom Action="CA_XmlCustomAction" Before="RemoveRegistryValues" />
</InstallExecuteSequence>
To determine the right position for the CustomAction in the installation sequence, I relied on this article by Bob Arnson.
Yes, this is possible but if you want to have this determined at install time then the preprocessor is not an option. The preprocessor executes during the build process.
To get what you want, you'll need to write another custom action that takes the arbitrarily long set of user data and adds temporary rows to the XmlConfig table. The WcaAddTempRecord() function in src\ca\wcautil\wcawrap.cpp can do the work. The src\ca\wixca\dll\RemoveFoldersEx.cpp is a pretty good example of using WcaAddTempRecord() to add rows to the RemoveFile table. You'll want to do similarly.