WiX/MSI dynamically add CopyFile elements - wix

I am a noob in Windows programming, but I have to create a tricky MSI installer that installs a plugin via WiX toolset.
The installer should detect upon running all installed versions of specific software and their plug-ins directories via Windows Registry API. After that it should show all of them on a separate page (dialog) with appropriare checkboxes. A user should choose what versions they want the plugin install into.
I've created a custom action (in C++ and put it into a DLL that is in the MSI database) that interacts registry API then loops over the results and adds temporary records to the database tables:
adds path properites to persist the plugin paths;
adds records to the CheckBox table;
adds properties for them to hold their states;
adds conditions for them than checks their property states and enables/disables them;
adds events to reset path properties according to the state properies.
It runs after AppSearch
<InstallUISequence>
<Custom Action="PopulateVersions" After="AppSearch">Not Installed</Custom>
</InstallUISequence>
Then clicking Next button (I know that it's a wrong place to do such things) performs the custom action that filters off active property paths takes the first of them and performs the SetTargetPath action (it works fine). For the rest of them the action inserts appropriate temporary records into:
the DuplicateFile table, where DestFolder is the property name;
the Component table, copying all field values from the original component, setting the Component_Parent field value to the original;
the Directory table. One record per path property, Direcory_Parent is TARGETDIR;
It installed the plugin only to the first property path refers to (which was passed to the SetTargetPath action).
Fine... I added a few CopyFile elements that refer to my custom properties (I declared a few properies to prevent MSI build error due to unknown properties) to the WiX markup just for testing:
<Property Id="PathProperty0" Value="{}"/>
<Property Id="PathProperty1" Value="{}"/>
<Property Id="PathProperty2" Value="{}"/>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFiles64Folder">
<Directory Id="INSTALLLOCATION" Name="MyPluginDir">
<Component Id="PluginExt" Guid="C112184A-307C-5E15-994F-0DFDA9DD427E">
<File Id="MyPlugin" Name="MyPlugin.dll" Source="MyPlugin.dll" Vital="yes" />
<CopyFile Id="MyPlugin_Copy1" FileId="MyPlugin" DestinationProperty="PathProperty1"/>
<CopyFile Id="MyPlugin_Copy2" FileId="MyPlugin" DestinationProperty="PathProperty2"/>
</Component>
</Directory>
</Directory>
</Directory>
Now the ProgressDialog is saying that "{}" is invalid path, but all of the properties were set to valid paths (I've checked this in debug)! Seems like it forgets or ignores all temporary database changes/property changes after showing elevating UAC prompt that asks for access to the same MSI file. The prompt is raised after clicking the Install button (with a shield icon). Probably it reads the database again and does not find any changes as they were in memory or cache, I don't know.
What am I doing wrong or how to make that installer properly? IMHO my implementation is overcomplicated. I do need your assistance.

I see at least two mistakes:
The properties for paths that you want to change at run time should be PUBLIC properties (that is, their names must not include lower case letters. Furthermore they should probably be listed in the SecureCustomProperties property, which you can ensure by declaring them in your source with or without a value, and specifying the Secure attribute.
The value you are specifying for your directory-related property is literally {}. This is an unusual value, and is only rarely correct. Typically you want an empty string instead. The exception is when you are in a Formatted context and need to specify a non-empty string that results in an empty string value, such as a property-setting ControlEvent. The Property element of your wix source is not such a context.
I'm not certain about the full complexity of the approach you described (I haven't been able to fully think it through), but I do want to encourage the general approach you're taking of programmatically creating table data and letting Windows Installer do the rest. This approach is important enough to have been given a name of sorts: semi-custom action.
Per the guidance on semi-custom actions, you will likely want to schedule your PopulateVersions action in both the InstallUISequence and InstallExecuteSequence (especially once it uses RemoveFile to perform cleanup) in order to ensure that installs, maintenance, and silent removals all get the proper table data added.

I think you are making this over complicated. You could just create an AddIns feature with a bunch of AddInVersionXXXX sub features and use the results of the AppSearch in Feature Conditions then show the Custom Setup dialog.
In my IsWiX I do a similar thing except I always install to every version detected. The trick is CopyFile will gracefully skip over the copy if the directory property doesn't have a value. It's really simple and can be see here:
http://iswix.codeplex.com/SourceControl/latest#main/Source/Installer/IsWiXNewAddInMM/IsWiXNewAddInMM.wxs

Related

Get target file path from custom action

How can I get a path to file being installed from a WiX custom action?
I'm creating a WIX Extension, which has a custom element which can be nested under <File> component like this:
<Component Id="FooComponent">
<File Id="filekey" Name="foo.txt">
<myextension:Stuff />
</File>
</Component>
The extension has its custom table which has a foreign key columns pointing to "Component" and "File" tables, and it is executed when a file's component is being installed/uninstalled (like the built-in IIS extension or SQL extension for example)
What I want to achieve is, in my deferred (sheduled) custom action, figure out the target path of the file the extension isbound to. I.e. basically in the differed custom action, I want to get value of [!filekey] (in terms of MSI formatted string). How can I go about that?
I have found a somewhat similar topic here
One of the solutions suggested was to use MsiFormatRecord from a custom action and pass that [#filekey] to that function. It resolves properly then.
I've found examples of using this approach in WiX sources, in gaming extension and NetFX extensions; they use code like this:
StrAllocFormatted(&pwzFormattedFile, L"[#%s]", pwzFileId);
WcaGetFormattedString(pwzFormattedFile, &pwzGamePath);
Here WcaGetFormattedString is basically a wrapper for MsiFormatRecord
Still unanswered, is this a right approach to the issue?
Basically you pass that [!filekey] to the deferred custom action in that format, using the CustomActionData indirect way of doing that. Then in your custom action you get the property value [CustomActionData] . This might help:
How to pass CustomActionData to a CustomAction using WiX?
To spell it out, if your type 51 custom action that is used to prepare CustomActionData has a Target of [!filekey] then when you retrieve the value of the CustomActionData property inside your custom action then it will contain the full path to the file, the entire path and filename. This demonstrably works, although it wouldn't be practical if you were doing this for many files.

Dynamically assigning name to shortcut at run time in WIX

I want to assign different names to a shortcut based on user input. Is this feasible using the WiX toolset? I have tried assigning value of property to Name attribute of Shortcut element but it does not work.
The Name column of the Shortcut Table isn't of type Formatted therefore it isn't possible to use [PROPERTY] to mutate it at runtime.
The only ways I know how to do it are:
Skip the shortcut table and write a custom action to handle it yourself.
Write a custom action to dynamically author the ShortCut table at runtime using SQL TEMPORARY syntax.
There might be an extension out there that has implemented #2.
I use this workaround:
<CustomAction Id="SetShortcutFolder" Return="check" Property="ProgramMenuDir" Value="[ProgramMenuFolder][PRODUCTNAME]"></CustomAction>
<!--Setting up the shortcuts for the product-->
<Directory Id="ProgramMenuFolder" Name="Programs">
<Directory Id="ProgramMenuDir" Name="TempName"/>
</Directory>
<!--Set the install path to match the path of the product already installed-->
<Custom Action="SetInstallPath" Before="CostFinalize"/>
Hope this helps..:)
If you are doing lots of translations and say lots of permutations, then this is a real drawback. Instead of using WIX (which is one of the most obfuscated API's I've ever used) you may prefer to simply write the shortcut using the Windows API.

Customising the WiX Burn theme with additional inputs

I'm looking at using Burn as a bootstrapper for an installer and I need to pass in a couple of arguments into the MSI.
I know that the way to do this is to use MsiProperty elements, the issue I am having is with displaying the UI to capture those properties. I'm aware that I can create a completely custom UI via the managed bootstrapper application host, however this is turning out to be a lot of work to implement for a relatively minor tweak to the bootstrapper.
I've found this blog article with describes how to do basic UI customisations and wondered if its possible to modify the Burn UI to include a simple checkbox / textbox (whose value is then use to set a Burn variable so I can pass it into my MSI) in a similar way, or do I need to use the managed bootstrapper application host after all?
I cant find any documentation on this anywhere, but a little bit of experimentation + reading through the source code reveals that this is fairly straightforward - just set the Name of the control (e.g. Checkbox) to the name of a Burn variable (not a WiX variable - they are different), like so (see Burn UI Customisations for more info on where to put this)
<Checkbox Name="MyCheckBox" ...>Hello, checkbox</Checkbox>
If you like you can define a burn variable beneath your bundle to initialise it to some value (use 1 for "ticked" and 0 for "unticked" with checkboxes)
<Variable Name="MyCheckBox" Value="1" />
However its not required - the variable will be created automatically for you anyway. Note that it needs to be a Variable, not a WixVariable - these are different.
Finally to set an MSI property based on this variable add a MsiProperty element as a child of your MsiPackage element, like so
<MsiPackage Name="MyMsi.msi" ...>
<MsiProperty Name="SOMEPROPERTY" Value="[MyCheckBox]" />
</MsiPackage>
The value of the MSI property "SOMEPROPERTY" will then be set to 0 or 1 based on the checked state of your checkbox.

Set default values in registry, if there are none

I am maintaining an old application in which the users configuration is stored in registry. It is left behind when uninstalling. I am now re-writing the installer in WiX.
The installer should add a default value in the registry if there are none there, otherwise
the existing value should be left alone.
I was thinking on how to do this in WiX. And the solution I came up with is somewhat cumbersome:
<Property Id="MY_PROPERTY">
<RegistrySearch Root="HKLM" Key="SOFTWARE\MyProduct" Name="MyProperty" Type="raw" />
</Property>
<CustomAction Id="ca.SetDefaultValue" Property="MY_PROPERTY" Value="DefaultValue" />
<InstallExecuteSequence>
<Custom After="RegistrySearch" Action="ca.SetDefaultValue">Not MY_PROPERTY</Custom>
</InstallExecuteSequence>
<Component Id="c.Registry">
<RegistryValue Root="HKLM" Key="SOFTWARE\MyProduct" Name="MyProperty" Type="string" Value="[MY_PROPERTY]" />
</Component>
So do a registry search to find old value. If not set, set to default value using a scheduled custom action. Then create the value "as usual".
Anyone who can think up a smoother way to do this?
Note that I cannot use convenient variables like Installed since values might be there, left behind by a previous, now uninstalled, version.
Start with the Wix Remember Property pattern but take it one step farther. After AppSearch runs and the REMEMBERME property does or doesn't get a value, use a SetProperty custom action to assign a default value if REMEMBERME="".
I take it one step farther though. I have a concept I call "property precedence". Basically it's a list of priorities for how a property should get it's value.
Highest to lowest:
properties inputted during the UI
public properties passed in at the command line
Properties found during AppSearch
Default values defined in the Property table
In other words, during a first time silent install with no properties passed at the command line the default value in the property table should be used.
During a second time silent install with no properties passed at the command line, the remembered value should take priority over the default value. (If different)
During a first of second silent install, a property passed at the command line should be considered an override value and take priority over both the default and remembered value.
During an interactive install the above rules take place and the UI should display that value. If the user changes the value then this is the ultimate value.
I'll leave it up to you of how to implement the various custom actions to do this. It generally involves a temp prop and a real prop and a series of Set Property CA's with the right execution scheduling and conditions to do what you want it to do.
You haven't explicitly set keypath=yes on the registry value in your component. However, in that case wix will select the first child item which can serve as the keypath. From the wix component element documentation:
If KeyPath is not set to 'yes' for the Component or for a child Registry value or File, WiX will look at the child elements under the Component in sequential order and try to automatically select one of them as a key path.
Therefore, your registry value is the keypath of the component which installs it. This means that the component will not be installed if the registry value already exists. As far as I can tell, that's the behavior you want.

Set Wix property only if certain condition is met

What I would like to do is this:
<Property Id="LICENSEKEYPATH">
REMOVE~="ALL" AND NOT UPGRADINGPRODUCTCODE
<DirectorySearch Id="ProgramDataSearch" AssignToProperty="yes" Depth="4" Path="[#ProductDirInAppData]">
<FileSearch Id="LicenseFileSearch" Name="lic-conf.enp"/>
</DirectorySearch>
</Property>
When my application is being uninstalled, only then, do I want to search for the license file and get its path. Currently, although the code doesnt give any errors, it still searches for the license file path even when I am installing the file. Because of this, the setup gets delayed by a long time. And more importantly, the wix setup displays in the first screen to the effect that its searching for this property and then it continues with the other screens.
So, how do I search for a file or set the value of a property only during uninstallation?
You can control the setting of a property using the SetProperty element. That is just a shortcut for registering a custom action. You can control when the SetProperty executes using a Conditoin in the text element.
As for AppSearch (XxxSearch elements), you can add a condition like the one above to the AppSearch element so that it only runs during uninstall. Note that the conditioning the AppSearch element will affect all XxxSearch elements. So if you need to have a search working during install and another search only during uninstall, that is not possible.
PS: The condition you want will look something like:
Installed AND REMOVE="ALL"