WiX – copy arbitrary files - c#

The folder where my setup.exe is located contains a subfolder CALhaving files named something like xyz1234.cal – their names vary from customer to customer. These files have to be copied into a folder CAL in the target directory.
So I created a CustomAction and a C# dll which uses the File.Copy() function. My C# function receives the strings srcDir and destDir as parameters, e.g. D:\installation\CAL and C:\MyApp\CAL.
However, when I check the existence of the folders with Directory.Exists(srcDir), an Exception is thrown, although the directory D:\installation\CAL exists:
ERROR in custom action myFunction System.IO.DirectoryNotFoundException: Could not find a part of the path 'C:\Windows\Installer\MSID839.tmp-\D:\installation\CAL'.
This happens no matter whether the CustomAcion is executed immediate or deferred. C:\Windows\Installer\MSID839.tmp-\ seems to be the path of the executed assembly, but I certainly don’t want to have it as a part of the FullPath. How can I get rid of it?
CustomAction and properties are defined like so:
<CustomAction Id='myCA' BinaryKey='myCABin' DllEntry='myFunction' Execute="deferred" HideTarget="no" Impersonate="no"/>
<Property Id="myCA" Value="Arg1=[CURRENTDIRECTORY];Arg2=[INSTALLDIR]" />
The parameters are used like so:
CustomActionData data = session.CustomActionData;
string srcDir = data["Arg1"]+ "\\CAL";
string destDir = data["Arg2"]+ "\\CAL";
if (Directory.Exists(srcDir))
// copy files

I recreated your app and it works fine. Here is my wix code (it's inside my product node):
<CustomAction Id='Test' BinaryKey='RegistryHelperCA' DllEntry='Test' Execute="deferred" HideTarget="no" Impersonate="no"/>
<Property Id="Test" Value="Arg1=[CURRENTDIRECTORY];Arg2=[INSTALLDIR]" />
<InstallExecuteSequence>
<Custom Action="Test" After="InstallFiles"></Custom>
</InstallExecuteSequence>
My custom action:
[CustomAction("Test")]
public static ActionResult Test(Session session)
{
string dir = session.CustomActionData["Arg1"];
session.Log("DIRECTORY equals " + dir);
if (Directory.Exists(dir))
session.Log("Success");
return ActionResult.Success;
}
It spits out dir as C:\Users\user\Desktop. Verify you aren't assigning to your CURRENTDIRECTORY property anywhere and, if you don't find anything, try setting your custom action to Execute="immediate" and accessing the data like this
string srcDir = session["CURRENTDIRECTORY"]+ "\\CAL";
If that doesn't work, surely that property is being overwritten somewhere. Good luck!

After some trial-and-error-sessions I found that Directory.Exists(srcDir) and Directory.Exists(destDir) didn't work, because the not the values but the property names are passed as parameter to the Exist() function - in contrast to session.Log(srcDir) which properly yields the value.
Finally I ended up with setting execute="immediate" and retrieving the values like so:
srcDir = session["CURRENTDIRECTORY"];
destDir = session.GetTargetPath("INSTALLDIR");

Related

Default install path to ProgramFilesFolder without specifying ProgramFilesFolder in wxs

I currently have a legacy Visual Studio Install Projects project that creates an MSI. With this I can specify "TARGETDIR="somepath"" on the command line and have it install to "somepath". Now with WIX, if I don't specify ProgramFilesFolder in my wxs, "TARGETDIR" still works, however in my UI of the installer the default path is "C:\Manufacturer\Product" whereas I still want it to default to ProgramFilesFolder. Having support of "TARGETDIR" is necessary to also support upgrading to the legacy MSI from within the app itself.
I have found some ways to change the UI default directory to ProgramFilesFolder, however TARGETDIR doesn't change to this directory (or a directory the user specifies) so it still installs to C:\Manufacturer\Product.
Does anyone have any ideas here? I'm guessing some kind of custom action will do it but I feel like I've tried most of the suggestions such as:
https://stackoverflow.com/a/13058798/9993088 but this would override any selected directory in the or at the command line
I've also tried creating a WiX property "TARGETDIR" to expose it on the command line but as this is already an internal one it didn't make a difference.
How to set default value of WiX Msi install path in the ui to Program Files? as mentioned this would then prevent me from using "TARGETDIR" on the command line
https://wixtoolset.org//documentation/manual/v3/wixui/dialog_reference/wixui_advanced.html and then using a custom action to set "TARGETDIR" to "APPLICATIONFOLDER". With this I can set the default location to ProgramFilesFolder but then I haven't found a way to set "TARGETDIR" at the right time to use the command line value or the user selected one. I almost need a way to do "if 'APPLICATIONFOLDER' is not default; use its value OR if 'TARGETDIR' is not default; use its value ELSE use the default value". This also seems to not allow variable expansion so I can't use "[ProgramFilesFolder]", I have to explicitly write out "C:\Program Files..." which isn't ideal.
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLDIR" Name="blah">
As mentioned, I must be able to use "TARGETDIR" and not "INSTALLDIR" (although that works).
If I really have to use "INSTALLDIR" then I can make it work but it makes maintaining the legacy MSI and the WiX one tricky due to the nature of how they're used.
Edit
Solution:
<Custom Action="SetINSTALLDIR" Before="AppSearch">Not Installed</Custom> in both InstallExecuteSequence and InstallUISequence.
This points to:
<CustomAction Id="SetINSTALLDIR" BinaryKey="CustomActionsBinary" DllEntry="SetInstallDir" />
SetInstallDir is the following:
[CustomAction]
public static ActionResult SetInstallDir(Session session)
{
TraceLogger.yRTraceInfo(nameof(SetInstallDir));
string installDir = session["APPLICATIONFOLDER"];
string targetDir = string.Empty;
try
{
targetDir = session["TARGETDIR"];
}
catch (Exception e)
{
Console.log(e.Message);
}
if (string.IsNullOrEmpty(installDir) && !string.IsNullOrEmpty(targetDir))
{
session["APPLICATIONFOLDER"] = targetDir;
console.log($"Setting APPLICATIONFOLDER to {targetDir}");
}
return ActionResult.Success;
}
I suppose you could have a SetProperty custom action to assign INSTALLDIR a value if it is empty and TARGETDIR has a value and Not Installed. Schedule it early on in both the Install UI and Install Execute sequences ahead of AppSearch.
FYI in WiX INSTALLLOCATION is more commonly used. INSTALLDIR is more of an InstallShield thing and TARGETDIR a Visual Studio thing.
<SetProperty Id="INSTALLDIR" Value="[TARGETDIR]" Before="AppSearch">Not INSTALLDIR and TARGETDIRDIR and Not Installed</SetProperty>

System.IO.FileInfo adds unexpected string to path when used in WIX Custom Action

I've got an WIX(V3.11.1) installer in which I'm creating an FileInfo based on value which is passed to Custom Action.
Value which was passed to Custom Action is correct, session.CustomActionData["INSTALLFOLDER"] returns proper path, which is C:\Program Files(x86)\MyApplication.
Unfortunately, when I create FileInfo targetDir = new FileInfo(session.CustomActionData["INSTALLFOLDER"]), the result of targetDir.FullName is C:\Windows\Installer\MSIE335.tmp-\C:\Program Files(x86)\MyApplication\.
I've tried to find any information about how constructor of FileInfo works, but without any result.
Do you have any ideas why C:\Windows\Installer\MSIE335.tmp-\ appears in FileInfo and how to create it with real path?
Code which is used by me to check all values:
string path = session.CustomActionData["INSTALLFOLDER"];
session.Log(path); //result is C:\Program Files(x86)\MyApplication
FileInfo targetDir = new FileInfo(path);
session.Log(targetDir.FullName); // result is C:\Windows\Installer\MSIE335.tmp-\C:\Program Files(x86)\MyApplication\
My setup-sense is guessing that the value of INSTALLFOLDER in your CustomActionData is actually the value [INSTALLFOLDER]. When logging, that syntax will get resolved to its proper value. That is why it looks good. However, what FileInfo is actually getting is a value like:
FileInfo targetDir = new FileInfo("[INSTALLFOLDER]");
Which of course is "The file named "[INSTALLFOLDER]" in the current directory". That matches your second log line.
The fix would be to ensure you're passing the value of INSTALLFOLDER in your CustomActionData. A few different ways to do that depending how you are scheduling your deferred custom action and setting the named property. For example, using SetProperty should be an easy way to fix it.
Update: Hawex provided a snippet that defined the custom action. It looked like:
<Property Id="CustomActionOnInstall" Value="INSTALLFOLDER=[INSTALLFOLDER]" />
<CustomAction Id="CustomActionOnInstall" BinaryKey="CustomActions" Execute="deferred"
Impersonate="no" DllEntry="OnInstall" Return="check" />
<InstallExecuteSequence>
<Custom Action="CustomActionOnInstall" Before="InstallFinalize">NOT Installed</Custom>
</InstallExecuteSequence>
to fix, just change the static (unevaluated) Property to a SetProperty like so:
<SetProperty Id="CustomActionOnInstall" Value="INSTALLFOLDER=[INSTALLFOLDER]"
Before="CustomActionOnInstall" Sequence="execute" />

How to set a Directory path in WIX using CustomAction?

I have a directory structure like this in WIX
<Directory Id="TARGETDIR" Name="SourceDir" >
<Directory Id="XXXFOLDER" Name="XXX">
<Directory Id="YYYFOLDER" Name="YYY">
<Directory Id="MAINFOLDER" Name="MAIN">
Now this MAINFOLDER Directory Resolves to D:\XXX\YYY\MAIN\
I get a path for the MAINFOLDER from a service which resolves to E:\XXX\YYY\MAIN
I have also assigned a customAction in a cs file
Below is the code
[CustomAction]
public static ActionResult GetNewDataPath(Session session)
{
sNewDataDir = xxxservice.GetPath();
if (!String.IsNullOrEmpty(sNewDataDir.ToString()))
{
sNewDataDir+= "\\MAIN\\";
}
session["MAINFOLDER"] = sNewDataDir;
return ActionResult.Success;
}
My Custom Actions are as below :
<CustomAction Id="GETDATAPATH" BinaryKey="InstallerCA"
DllEntry="GetNewDataPath" Execute="immediate"/>
This is the Install Sequence:
<Custom Action="GETDATAPATH" Before="CostFinalize" />
The sNewDataDir contains this value = "E:\XXX\YYY\MAIN" and I assign to session["MAINFOLDER"]. It gets assigned. But It does not get reflected on WIX side because my files are still being copied to D:\XXX\YYY\Main inspite of assigning it to E:\XXX\YYY\Main . How do we change the direcory path of session["MAINFOLDER"] using CustomAction?
It's usually a matter of sequence. The values of the properties are assigned to the Directory paths during the CostFinalize action per MSDN. You're custom action above must be sequenced sometime before CostFinalize runs in the execute sequence.
It can also be a matter of privilege: MAINFOLDER may be a restricted public property and isn't making it to the execute sequence (doesn't apply if your custom action ran in the execute sequence). Read about Restricted Public Properties to see if that might be your issue.
And it can also be your computer's anti-virus or some other issue with the script engines.
To have a good idea (or at least find someone else who can figure out what the issue really is) you will need to generate a good log of your failed attempt. Most of the time voicewarmup (or /l*v) is the best value to use (tends to give you most but not all of what you want, along with way too much of what you don't) and is the value most installation development experts use when generating the logs they use and share. It does slow down your installs a fair bit, however.

Is it possible to define a variable in the config?

Hey all quick question from someone new to C#/programming. I have a console app where one of my string variables in the job is a path to a directory. However, I am always changing it between 2 different paths (1 prod and 1 dev). So I constantly need to recompile/rebuild the solution when I want to change the hard-coded path.
Is there a way to define it somehow in the config and then a variable in the job can point to it somehow? I'd like to just get away from having the path hard-coded in the solution.
Any advice would be appreciated. Thank you!
That is what settings are for. Easiest way to add them is via the project properties:
After you added them you will get a grid to enter a default value.
This value can be accessed in code via:
var path = MyApp.Properties.Settings.Default.PATH;
The default value is stored in the app.config file, which is right next to the MyApp.exe as MyApp.exe.config. If you choose the setting to be "user-changeable" a changed value (after calling MyApp.Properties.Settings.Default.Save();) is stored in %AppData%.
See THIS question on how to transform the app.config depending on build-type.
Add an app.config file to your project by right-clicking it in Solution Explorer. app.config is XML file.
In app.config, under section, add your path like this:
Access path in code like this:
string path = ConfigurationManager.AppSettings["path"];
Then just change the value inside app.config when you want to point to a different path, without recompiling your code.
The alternative to chrfin's answer is to define the variable as so
#if Debug
string myPath = "something";
#else
string myPath = "something else";
then when you choose your build type it will change the line of code that is compiled.

WiX: Conditional Inner Text Not Evaluating Properly

WiX first timer here.
I am building an installer for my product using WiX, and I am attempting to validate that MSMQ is installed before continuing the installation, following this SO answer. I am using a Condition element, defined like this:
<Condition Message="MSMQ must be installed in order to proceed.">
<![CDATA[MSMQ_INSTALLED<>"false"]]>
</Condition>
My Property and RegistrySearch look like this:
<Property Id="MSMQ_INSTALLED" Value="false" Secure="yes">
<RegistrySearch Id="Msmq.RS"
Root="HKLM"
Key="SOFTWARE\Microsoft\MSMQ"
Name="Values"
Type="raw"/>
</Property>
But it never evaluates properly. The installation stops with the message, regardless that the Registry Key does exist. So my questions are:
Am I using the Condition element correctly?
What have I defined incorrectly in the evaluation?
On further testing, I have found that the MSMQ_INSTALLED property contains the value "1: 0 2:", regardless of the Registry Key that I search for, either existing or fake.
EDIT: The Condition element exists inside the Product element; that is an important distinction as the Condition element is overloaded.
EDIT: Modified Condition to use CDATA directive, and invert Inner Condition logic, to more accurately reflect issue.
Well, the answer was on SO the whole time. Apparently, searching for a Registry Key is not supported out of the box with WiX, so I created a Custom Actions project and used the Binary tag to import it into my MSI, then run the Custom Action at the appropriate spot during the install. In my case, it was before LaunchConditions.
For reference, the code is:
public class CustomActions
{
[CustomAction]
public static ActionResult CustomAction1(Session session)
{
session.Log("Begin CustomAction1");
var key = Registry.LocalMachine.OpenSubKey(#"SOFTWARE\Microsoft\MSMQ");
session["MSMQ_INSTALLED"] = key == null ? "-1" : "1";
return ActionResult.Success;
}
}
(The only class in the Custom Actions project)
<Binary Id="WixCustomAction" SourceFile="C:\work\RelayMed\src\dev\WixCustomAction\bin\Debug\WixCustomAction.CA.dll"/>
<CustomAction Id="CheckMsmq" BinaryKey="WixCustomAction" DllEntry="CustomAction1" Execute="immediate" Return="check"/>
(The import of the Binary into WiX, under the Product node.)
<InstallUISequence>
<Custom Action="CheckMsmq"
Before="LaunchConditions"/>
</InstallUISequence>
(The running of the Custom Action before LaunchConditions)
The Condition and the Property remained the same from the original post. The RegistrySearch was removed completely.
EDIT: Noted removal of the RegistrySearch tag.
Your authoring says "if HKLM\SOFTWARE\Microsoft\MSMQ#Values has the literal value 'false', then the install can continue."
Just use "MSMQ_INSTALLED" to check for any string found in the registry value.

Categories