How to set a Directory path in WIX using CustomAction? - c#

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.

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>

WIX cannot write value to registry if key contains space

I need to enable IE feature for WebBrowser control. To emulate IE11, I need to write a value to registry key
HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION
This manipulation need to be done during installation.
Here is my code in WIX script:
<?define var.IEFeatureEmulationKey = "Software\Microsoft\Internet Explorer\FeatureControl\FEATURE_BROWSER_EMULATION" ?>
....
<Fragment>
<DirectoryRef Id="TARGETDIR">
<Component Id="registryValues" Guid="{some-guid}" >
<RegistryKey Root="HKCU" Key="$(var.IEFeatureEmulationKey)" Action="create">
<RegistryValue Name="MyApp.EXE" Value="11000" Type="integer" Action="write"/>
</RegistryKey>
</Component>
</Fragment>
This code work only if $(var.IEFeatureEmulationKey) contains no spaces. But I need to write a value to this specific key.
Please help, how tell WiX to write value to registry even in registry key contains spaces.
UPD:
Added appropriate issue in WiX repository
This Blogpost uses some special syntax I have not seen before either. It states that variable can be defined this way, even if it contains a white space character like so:
<!–?define var.IEFeatureEmulationKey = "Software\Microsoft\Internet Explorer\FeatureControl\FEATURE_BROWSER_EMULATION" ?>
The !- let aside, it looks quite similar to what you got. However, your value contains more than one whitespaces. I'm not sure if thats the value you truly want or if it was just for pointing the whitespace out clearly...
Still cannot overcome the issue, however I want to show a workaround that helped me.
I used custom action that allowed in WiX. First of all I added custom .NET assembly with following method within it
[CustomAction]
public static ActionResult SetRegistryItems(Session session)
{
session.Log("Begin SetRegistryItems");
try
{
// this private method actually does manipulation with registry
SetRegistry();
}
catch (Exception e)
{
session.Log(e.ToString());
}
return ActionResult.Success;
}
Then this method should be referenced in WiX config file (.wxs)
<Fragment>
...
<CustomAction Id='SetRegistryItems' BinaryKey='<NameOfTheAssemblyWithoutExtension>' DllEntry='SetRegistryItems' Execute='immediate'/>
...
<InstallExecuteSequence>
...
<Custom Action="SetRegistryItems" Before="LaunchConditions"/>
...
</InstallExecuteSequence>
...
</Fragment>

WiX – copy arbitrary files

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");

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.

Getting the SourceDir property from a C# custom action

I have some directories that are bundled with my installer and I need to access them from within a custom action. I have done some research and seen that the SourceDir can be used to obtain the currently executing dir location. However I cannot find any examples of how to obtain this property? Or another way to obtain the current directory?
Can anyone advise or point me to anything other than the unhelpful Microsoft site?
I'm assuming you're using vbscript for the custom action. If so, properties can be accessed via the Session object. See below:
strSourceDir = Session.Property("SourceDir")
Be aware that the SourceDir property is only available at specific times during the installation.
For C#, you'll find that you can do something like this:
[CustomAction]
public static ActionResult MyCustomAction(Session session)
{
string sourceDir = session["SourceDir"];
string path = Path.Combine(sourceDir, "yourfilename.txt");
...
The documentation on MSDN is unfortunately lacking in making this clear.
As w4g3n3r mentions in his answer, SourceDir is only available to you at certain times. In short, you will need to make sure your custom action is called after a call to the ResolveSource action, which can only be called after CostInitialize has run.
Once SourceDir is set, it should be available for use for the remainder of the installation process.
Are you using InstallShield? Here's an example for an InstallScript CA:
MsiGetProperty(hMSI, "CustomActionData", strDirectory, numBuffer);
... where you also used a Set Property "Type 51" custom action to setup CustomActionData for your function to the value SOURCEDIR.

Categories