How do I pass msiexec properties to a WiX C# custom action? - wix

I have an MSI file being created with Wxs 3.0. My MSI references a C# custom action, written using the new C# Custom Action project.
I want to pass an argument to msiexec that gets routed to my custom action - for example:
msiexec /i MyApp.msi ENVIRONMENT=TEST#
In my .wxs file, I refer to my custom action like this:
<Property Id="ENVIRONMENT"/>
<Binary Id="WixCustomAction.dll" SourceFile="$(var.WixCustomAction.Path)" />
<CustomAction Id="WixCustomAction" BinaryKey="WixCustomAction.dll" DllEntry="ConfigureSettings"/>
<InstallExecuteSequence>
<Custom Action="WixCustomAction" After="InstallFiles"></Custom>
</InstallExecuteSequence>
My C# custom action is set up like this:
[CustomAction]
public static ActionResult ConfigureSettings(Session session)
{
}
I was expecting to be able to access the property like this:
string environmentName = session.Property["ENVIRONMENT"];
but this doesn't seem to work.
How do I access the property I passed to msiexec in my custom action?

If instead of
<CustomAction Id="SetCustomActionDataValue"
Return="check"
Property="Itp.Configurator.WixCustomAction"
Value="[ENVIRONMENT],G2,[CONFIGFILE],[TARGETDIR]ITP_v$(var.VERSION_MAJOR)" />
you write this:
<CustomAction Id="SetCustomActionDataValue"
Return="check"
Property="Itp.Configurator.WixCustomAction"
Value="Environment=[ENVIRONMENT];G=G2;ConfigFile=[CONFIGFILE];TargetDir=[TARGETDIR]ITP_v$(var.VERSION_MAJOR)" />
then you will be able to reference your variables like this:
string env=session.CustomActionData["Environment"];

Just for completeness; using the method described by Jeremy Lew, in the blog above allows for the following:
Calling:
msiexec /i ITP.Platform.2.msi ENVIRONMENT=QA CONFIGFILE=EnvironmentConfig.xml
With this in the .wxs file:
<Property Id="ENVIRONMENT" Secure="yes" />
<Property Id="CONFIGFILE" Secure="yes" />
<Binary Id="Itp.Configurator.WixCustomAction.dll"
SourceFile="$(var.Itp.Configurator.WixCustomAction.Path)" />
<CustomAction Id="SetCustomActionDataValue"
Return="check"
Property="Itp.Configurator.WixCustomAction"
Value="[ENVIRONMENT],G2,[CONFIGFILE],[TARGETDIR]ITP_v$(var.VERSION_MAJOR)" />
<CustomAction Id="Itp.Configurator.WixCustomAction"
Return="check"
Execute="deferred"
BinaryKey="Itp.Configurator.WixCustomAction.dll"
DllEntry="ConfigureItpBrandSettings" />
<InstallExecuteSequence>
<Custom Action="SetCustomActionDataValue" After="InstallFiles"></Custom>
<Custom Action="Itp.Configurator.WixCustomAction" After="SetCustomActionDataValue"></Custom>
</InstallExecuteSequence>
With a custom action:
/// <summary>
/// CustomAction keys should be Environment,BrandId,ConfigPath,itpBasePath
/// </summary>
/// <param name="session"></param>
/// <returns></returns>
[CustomAction]
public static ActionResult ConfigureItpBrandSettings(Session session)
{
string[] arguments = GetCustomActionDataArguments(session);
string environmentName = arguments[0];
string brandId = arguments[1];
string configPath = arguments[2];
string itpBasePath = arguments[3];
//Do stuff
return ActionResult.Success;
}
private static string[] GetCustomActionDataArguments(Session session)
{
string[] keys = new string[session.CustomActionData.Keys.Count];
session.CustomActionData.Keys.CopyTo(keys,0);
return keys[0].Split(',');
}
works.
Parsing the CustomActionData arguments is pretty ugly, but it does work. Hopefully someone knows a more elegant way to do this.

Here is my working code:
<Binary Id="MyCA" SourceFile="..\bin\ChainerRun.CA.exe" />
<CustomAction Id="SetCustomActionDataValue" Return="check" Property="CustomActionData" Value="TARGETDIR=[TARGETDIR];AA=Description;" />
<CustomAction Id="ReadAndSet"
BinaryKey="MyCA"
DllEntry="ReadAndSet"
Execute="immediate"
HideTarget="no"
Return="check" />
<InstallExecuteSequence>
<Custom Action="SetCustomActionDataValue" Before="InstallFiles" />
<Custom Action="ReadAndSet" After="SetCustomActionDataValue" />
</InstallExecuteSequence>
In the C# custom action function:
[CustomAction]
public static ActionResult ReadAndSet(Session session)
{
ActionResult retCode = ActionResult.NotExecuted;
System.Diagnostics.Debug.Assert(false);
session.Log("ReadAndSet() begins ...");
string installLocation = session.CustomActionData["TARGETDIR"];
string hostName = session.CustomActionData["AA"];
...
}

Your custom action needs to be a deferred custom action in order to run after InstallFiles. Deferred custom actions do not have access to properties, but they do have access to CustomActionData. See this blog post for a discussion on how to get what to do about it. (This example is a VBScript custom action, but you will be able to retrieve the value through the session.CustomActionData collection.)

If we're talking about Wix Sharp (and not plain Wix with its XML stuff), adding a custom property is a piece of cake. All you have to do is to set UsesProperties property for your managed action.
For example, if you want to add a custom property named "MYPROP", just define your action like this:
new ElevatedManagedAction(nameof(CustomActions.MyCustomAction))
{
Condition = Condition.Installed,
When = When.Before,
Step = Step.RemoveFiles,
Return = Return.check,
Execute = Execute.deferred,
UsesProperties = "MYPROP"
}
Set the property value via msiexec command line:
msiexec /i my.msi MYPROP=MYVALUE
And then you'll be able to access it from your custom action:
[CustomAction]
public static ActionResult MyCustomAction(Session session)
{
session.Log("MYPROP VALUE: " + session.CustomActionData["MYPROP"]);
return ActionResult.Success;
}
When property is not set via command line the default value will be an empty string.

Related

Custom Action to set INSTALLFOLDER path

So I created this customAction that gets me the path of another app using Registries
string value = Registry.GetValue(userRoot, key, -1).ToString();
session["INSTALLLOCATION"] = value;
And it works. The problem is how I send it back to the .wxs file and set the installation path of something to be that string value. I have this thing in the Wix file:
<CustomAction Id="CustomAction" Property="CustomAction2" Value="path=[INSTALLLOCATION]" />
<Binary Id="CustomActionBinary" SourceFile="$(var.ProAdmin_TargetDir)ExtractRegistryPath\bin\Debug\ExtractRegistryPath.CA.dll"/>
<CustomAction Id="CustomAction2" Impersonate="no" BinaryKey="CustomActionBinary" DllEntry="CustomAction1" Return="check" Execute="deferred"/>
<InstallUISequence>
<Custom Action="CustomAction" Before="CustomAction2" />
<Custom Action='CustomAction2' Before="ExecuteAction" />
</InstallUISequence>
This thing throws an 2762 error code.
You're getting error 2762 because you can't have deferred actions in the InstallUISequence. The custom action that populates the INSTALLLOCATION property should be called at the beginning of both the InstallUISequence and InstallExecuteSequence as an 'immediate' action - and - INSTALLLOCATION should be a folder in the Directory table.

Custom Actions not been called

I have some problems with managed code custom actions. I have 3 custom actions but only one of them is working. They are called at different times in InstallExecuteSequence but moving them makes no difference. I know there not getting very far because if I place a message box at the beginning of the routine(for debugging) it never get called . Where am I going wrong ? The actions are created like so.
<Binary Id="CA" SourceFile="$(var.ca.Custom.Actions.TargetDir)$(var.ca.Custom.Actions.TargetName).CA.dll" />
<CustomAction Id="WriteRemoveArpEntry" Property="CustomActionData" Value="PNAME=$(var.ProductName)" HideTarget="yes" />
<CustomAction Id="RemoveArpEntry" BinaryKey="CA" DllEntry="RemoveProductFromARP" Return="ignore" />
<CustomAction Id="SetValueforProductFolder" Property="CustomActionData" Value="SDIR=[INSTALLDIR];TDIR=[MANUDIR]\backup\$(var.ProductName)\$(var.VersionNumber)" HideTarget="yes" />
<CustomAction Id="Backup_Product_DIR" BinaryKey="CA" DllEntry="BackupDIR" Return="ignore" />
<CustomAction Id="WriteInstallAttemptData" Property="CustomActionData" Value="PRODUCTNAME=$(var.ProductName);APPVERSION=$(var.VersionNumber)" HideTarget="yes" />
<CustomAction Id="WriteInstallAttempt" BinaryKey="CA" DllEntry="WriteXMLServer" Return="ignore" />
I then call them here
<Custom Action="SetValueforProductFolder" Before="Backup_Product_DIR">NOT Installed AND NOT REMOVE</Custom>
<Custom Action="Backup_Product_DIR" Before="InstallFinalize">NOT Installed AND NOT REMOVE</Custom>
<Custom Action="WriteRemoveArpEntry" Before="InstallFinalize">NOT Installed AND NOT REMOVE</Custom>
<Custom Action="RemoveArpEntry" After="WriteRemoveArpEntry">NOT Installed AND NOT REMOVE</Custom>
<Custom Action="WriteInstallAttemptData" After="InstallFinalize">NOT Installed AND NOT REMOVE</Custom>
<Custom Action="WriteInstallAttempt" After="WriteInstallAttemptData">NOT Installed AND NOT REMOVE</Custom>
</InstallExecuteSequence>
The headers for the routines look like this
public static ActionResult BackupDIR(Session session)
{
public static ActionResult RemoveProductFromARP(Session session)
{
Public static ActionResult WriteXMLServer(Session session)
{
However only WriteXMLServer works. In the log file I get the following .
MSI (s) (BC:9C) [07:23:45:562]: Invoking remote custom action. DLL:
C:\Windows\Installer\MSI2E2A.tmp, Entrypoint: BackupDIR CustomAction
Backup_Product_DIR returned actual error code 1154 but will be
translated to success due to continue marking
In the one that works I get
MSI (s) (BC:A0) [07:24:25:994]: Invoking remote custom action. DLL:
C:\Windows\Installer\MSICC20.tmp, Entrypoint: WriteXMLServer SFXCA:
Extracting custom action to temporary directory:
C:\Windows\Installer\MSICC20.tmp-\ SFXCA: Binding to CLR version
v4.0.30319
The answer was very simple I forgot the
[CustomAction]
For the other two routines.

Custom property is empty

I have custom action which stores the file location into property at immediate phase.
Then I have another action at deffered phase, which reads this property and creates the file:
String configFilePath = session.CustomActionData["configPath"];
String configFileName = session.CustomActionData["configFile"];
...
Everything works so far.
At uninstall I would like to remove this file, so I have another deffered action, which executes after removing files. Action works, the problem is that property is empty(session.CustomActionData is empty).
Why? I have setted it at immediate phase also when uninstall is happening. And if I can read it at custom setUpConfig action, why I can't read it at custom removeConfig action?
I guess one option is to store this value into registry at installing phase and then read it from there when uninstalling. But I would like to know why i can set the property when installing the program, and why it is not setted at uninstalling. The action, which sets the proeprty is executed in both cases.
<CustomAction Id="ConfigFileLocation" Property="setUpConfig" Execute="immediate"
Value="configPath=[WEBSITE];configFile=config_template.asp" />
<CustomAction Id="setUpConfig" BinaryKey="MyCustomAction" DllEntry="configFile"
Execute="deferred" Impersonate="no" Return="check" />
<CustomAction Id="removeConfig" BinaryKey="MyCustomAction"
DllEntry="removeCustomFile" Execute="deferred" Impersonate="no" Return="check" />
<InstallExecuteSequence>
<Custom Action="ConfigFileLocation" After="LaunchConditions" />
<Custom Action="setUpConfig" Before="InstallFinalize"><![CDATA[NOT REMOVE="ALL"]] />
<Custom Action="removeConfig" After="RemoveFiles"><![CDATA[REMOVE="ALL"]] />
</InstallExecuteSequence>
To pass data to a deferred custom action you must set a Property with a name that matches the CustomAction/#Id. You are doing that correctly for the setUpConfig custom action via the immediate ConfigFileLocation custom action.
However, the removeConfig custom action does not have an immediate custom action setting a Property named removeConfig.
Ergo the removeConfig custom action's CustomActionData property is blank.

WiX unable to check property that is set using CustomAction

I want to include different dll in the installation based on certain values. So, I am trying to load a component based on a property that is set using custom action.
In wxs file:
...
<Property Id="PropDllVersion" Value="0" />
...
<CustomAction Id="CheckPropDllVersion" BinaryKey="CustomAction1.dll" DllEntry="GetPropVersion" Return="ignore" Execute="immediate"/>
...
<InstallExecuteSequence>
<Custom Action="CheckPropDllVersion" After="ValidateProductID" />
</InstallExecuteSequence>
...
<Component Id="Test"
Guid="B81F832D-2D96-4169-9BD0-8D77098FEC60">
<Condition><![CDATA[PropDllVersion = "19"]]></Condition>
<File Id="File15"
Name="xyz.dll"
Vital="yes"
KeyPath="yes"
AssemblyManifest="File5"
AssemblyApplication="File5"
Assembly=".net"
DiskId="1"
/>
</Component>
...
Then in the custom action file:
[CustomAction]
public static ActionResult GetPropVersion(Session session)
{
session["PropDllVersion"] = "19";
}
I can see in the msi log file that this property is changed to 19, however the xyz.dll is not included in the installation. It looks like the PropDllVersion is not set at the condition level or am I doing something wrong... I tried to sequence it at many other places still it is not working...
If I use a global property in the condition instead of my property it works!
Please note that private properties (its name contains lowercase letters) use their default values in InstallExecuteSequence. So you should use a public property, for example PROP_DLL_VERSION.
Where else have you tried to sequence your custom action? You need to sequence it before the CostInitialize action.
Also, call the FileCost action following CostInitialize and the CostFinalize action then.
You can check out the documentation of the CostInitialize action here:
http://msdn.microsoft.com/en-us/library/windows/desktop/aa368050%28v=vs.85%29.aspx

WiX: Passing Data to managed custom action does not work

i got a problem regarding wix and managed custom actions: I already managed it to create an managed custom action and got it called from my installer (verified it in the installation log files). My Problem is now that i did't manage it to pass data to the custom action. Afaik if i choose to run the custom action in such a late stage i must mark the action as "deferred". With this limitation it's only possible to access the CustomActionData Property right? Why does this not work in my case? (As far as i can see i adoptet everything correctly from the samples?)
Here is what i already tried:
Custom Action:
public class CustomActions
{
[CustomAction]
public static ActionResult RegisterDflHelp(Session session)
{
session.Log("Begin CustomAction1");
session.Log("Before Access to customactiondata");
//string helpdir = session["HELP_DIR"];
string cad = session["CustomActionData"];
Debugger.Break();
session.Log("Help dir is: " + helpdir);
session.Log("Custom Action Data is: " + cad);
return ActionResult.Success;
}
}
Merge Module which calls custom action:
<CustomAction Id='RegisterDflHelp' BinaryKey='RegisterDflHelpDll' DllEntry='RegisterDflHelp' Execute='deferred' />
<CustomAction Id="HELP_DIR.SetProperty" Return="check" Property="HELP_DIR" Value="Teeest" />
<Property Id='HELP_DIR' Secure='yes'/>
<InstallExecuteSequence>
<Custom Action='HELP_DIR.SetProperty' After='InstallFiles' />
<Custom Action='RegisterDflHelp' After='HELP_DIR.SetProperty' />
</InstallExecuteSequence>
<Directory Id="TARGETDIR" Name="SourceDir">
</Directory>
<ComponentGroupRef Id="HelpGroup"/>
Product which includes Help-Merge Module:
<Product....>
<Package....>
...
<Directory>
<!--Directory which gets the help folder--!>
<Merge Id ="DflHelpInstaller" SourceFile="DflHelpInstaller.msm" Language="1033" DiskId="1" />
Anyone an idea? Thanks in advance!!
Daniel
Btw: What would also be interesting to know: At which stage in the installationprocess must an action marked as deferred? And if i managed it to call my custom action before this stage. Whats the advantage?
You are still setting the HELP_DIR property instead of the custom action data. If you are trying to set the CustomActionData for the RegisterDfHelp action to "Teeest", then I think you should do this:
<CustomAction Id="SetRegisterDflHelpCustomActionData" Return="check"
Property="RegisterDflHelp" Value="Teeest" />
<InstallExecuteSequence>
<Custom Action='SetRegisterDflHelpCustomActionData' After='InstallFiles' />
<Custom Action='RegisterDflHelp' After='SetRegisterDflHelpCustomActionData' />
</InstallExecuteSequence>
somethimes using your head really helps;-)
the reason why the sample above does not work is quite simple:
Instead of:
CustomAction Id="HELP_DIR.SetProperty" Return="check" Property="HELP_DIR" Value="Teeest" />
I head to set the Property Attribute to the Action which should get the custom data!! here:
<CustomAction Id="HELP_DIR.SetProperty" Return="check" Property="RegisterDflHelp" Value="$(var.HelpSourceDir)" />
Hope that helps others to save the time i spend to solve this silly problem;-)