How does association between Directory and Property/Variable work in WiX? - wix

In Wix (3.11) there seems to be an implicit link between a with the Id 'ProgramFilesFolder' and the 'ProgramFilesFolder' variable. That is, a directory with that id will be named according to the property value.
When I try replicating this on my own with a "Test" directory:
<Property Id="Test" Value="Test"/>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="Test"/>
...
I get a warning from WiX, and the installer actually hangs. I've also tried declaring "Test" as a WixVariable, but in that case it appeared the element was simply ignored - iow, no "Test" folder was created.
My question is how does this implicit link work for WiX variables, but not for my own?

ProgramFilesFolder isn't really a WiX variable or Id. It's a Windows Installer standard property that you can't change - it refers to the 32-bit Program Files path on the system that you're installing on. Your Test directory will be beneath the Program Files folder.
There isn't enough of your WiX source to see what's going on, but just declaring those locations and properties won't create the directories if nothing is installed there. A complete working example of the issue would be useful.

You don't need to define a separate property for your directory, the id of the directory doubles as a property because directories are properties. If the property (directory id) is all uppercase, it becomes a public property, and can be set from the command line.
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="TEST" Name="Test" />
...
Setting the install path of the TEST directory from the command line:
msiexec /i A:\Example.msi TEST=c:\Example\Test /qn
The directory id "ProgramFilesFolder" is a bit different because it's one of the System Folder Properties, pre-defined by Windows Installer.
You should probably specify a name for your TEST directory. See Directory Element:
Do not specify this attribute (or the LongName attribute) if this directory represents the same directory as the parent (see the Windows Installer SDK's Directory table topic for more information about the "." operator).

Related

Why can't Wix find the SystemFolder?

In my Product, I've defined a custom action that looks like this:
<CustomAction Id="InstallScreensaver"
Directory="SystemFolder"
Return="asyncNoWait"
ExeCommand="rundll32.exe desk.cpl,InstallScreenSaver [#screensaver]"/>
following this blog post: https://ithoughthecamewithyou.com/post/wix-tricks-for-screen-savers
But when I link it (light) I get this error:
error LGHT0094 : Unresolved reference to symbol 'Directory:SystemFolder' in section 'Product:*'.
Why is that?
On Wix's documentation of predefined variables, I can SystemFolder:
SystemFolder - gets the well-known folder for CSIDL_SYSTEMX86 on 64-bit Windows and CSIDL_SYSTEM on 32-bit Windows.
and I also create a shortcut that uses that variable:
<Shortcut Id="Shrt_Install_Screensaver"
Name="Install Screensaver"
WorkingDirectory="SystemFolder" Icon="icon.ico"
Target="[SystemFolder]rundll32.exe"
Arguments="desk.cpl,InstallScreenSaver [#screensaver]"/>
Maybe try to add SystemFolder directly underneath TARGETDIR as a first test (compiles and runs for me with a rushed mock-up):
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="SystemFolder" />
<...>
</Directory>
I think this is enough for the msiexec.exe engine to "fill in the rest", even if you don't specify a real folder name since this is one of the System Folder Properties.
I am not sure whether to call your link problem a WiX bug or not. It should probably be handled auto-magically since the folder in question is a system folder. I would go with calling it a bug or a missing piece of auto-magic.
Is your screen saver 64-bit or 32-bit? For the record System32 contains 64-bit files and SysWOW64 contains 32-bit files, but I guess you already knew that. Only in Windows...
Some links for safekeeping:
How to reference SystemFolder in WiX Icon.SourceFile property?
"SystemFolder" in WIX and C#
Why does the TARGETDIR directory need a name?
In WiX files, what does Name="SourceDir" refer to?

MSI installing file inside a folder which contains in its name the substring %Path%?

I've come across a project that builds an MSI package. One of the folders from that package has this name:
%P_%F_%Path%alfa
At install time, during InstallFiles standard action, Windows Installer will resolve the substring %Path% as an environment variable, thus the character ':' appears inside the folder name and the installation errors out (invalid char in folder name).
EDIT: The same error occurs for DuplicateFiles standard action too.
If I create an MSI that creates this folder empty (i.e. during CreateFolders standard action) Windows Installer does not try to resolve the substring %Path% to an environment variable and the installation succeeds, creating the folder with the name presented above.
I never met this situation before. Anybody else did? If yes, can you give more details about what is going on exactly and if there is a workaround available?
Note! I added all the tags of different MSI authoring tools because I suspect this to be a tool independent situation.
I can confirm, using WiX and IsWiX. IsWiX authors a folder with a file like this:
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="MergeRedirectFolder">
<Directory Id="owd6248671CA393CCC018715A2FB53AD2D6" Name="%P_%F_%Path%alfa">
<Component Id="owcA59F51CBEAEE88B00B715AF4FEE6BF72" Guid="1619af96-1b2b-64ea-91f5-1a297c3c636a">
<File Id="owfA59F51CBEAEE88B00B715AF4FEE6BF72" Source="$(var.SourceDir)\test.txt" KeyPath="yes" />
</Component>
</Directory>
</Directory>
IsWiX authors an empty folder like this:
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="MergeRedirectFolder">
<Directory Id="owd6248671CA393CCC018715A2FB53AD2D6" Name="%P_%F_%Path%alfa">
<Component Id="owc6248671CA393CCC018715A2FB53AD2D6" Guid="071c27cb-0566-40b1-9a50-5672b3fbd5e1">
<CreateFolder />
</Component>
</Directory>
</Directory>
</Directory>
Both create MSI's that compile and pass validation but the folder with a file gives the error you describe while the folder with the CreateFolder element works.
Forbidden Folder Names: Interesting, all sorts of pecularities. This is not as much an answer, as a couple of further pointers for MSI folder name peculiarities. Did you know that you can't create folder names with any of the following names in Windows Explorer? con, prn, nul, aux? The list goes on with lpt0 to lpt9 and com0 to com9.
Device References: This is all for legacy reasons. You can't make folders that have "System Action" or "Device" references. These names are old devices and legacy concepts. Device names were recognized before path names. CON was the console device, AUX was the auxiliary device, PRN was the printer, along with LPR<digit>. COM<digit> refers to a com port. There are others. Note that these names ignored the file extension such that CON.EXE or con.txt still meant the console.
Tool Handling: I believe you can still create such folders with Win32 API calls or even with a command prompt, but they are not valid Windows names allowed in Windows Explorer. Incidentally it seems Advanced Installer and Installshield allow them as MSI folder names, but you get runtime errors whilst installing - or you get a warning that a folder name is invalid on MSI launch. I understand the desire to not add code to disallow such folders in the tools - there are always new bugs possible when you start to protect against things like these - rather people should know about these folders I guess. Actually a compile time warning would be great - just a list of illegal folder names to check, and illegal character sequences. It sure is an odd problem to discover when you don't know what is causing it.
Some Links:
What characters are forbidden in Windows and Linux directory names?
Why can't we make CON, PRN,Null folder in windows?
Naming Files, Paths, and Namespaces
Sources (I copied so much from some of these, that they should be listed for reference):
Why can't we make CON, PRN, Null folder in windows? (SuperUser)
Why can't I save a folder named "con" in Windows? (Quora)
Q: why can't i create a folder with "con" name? (Microsoft Community)
Unable to rename a folder or a file as 'con' (SuperUser)

SetDirectory with Directory Properties

I'm trying to build a multi-instance installer that creates a directory with an appropriate name underneath the INSTALLDIR directory:
<Directory Id="INSTALLDIR" Name="My Product">
<Directory Id="SERVERDIR" Name="Server" />
</Directory>
<SetDirectory Id="SERVERDIR" Value="[INSTALLDIR]Server ([INSTANCEID])">NOT (INSTANCEID="DEFAULT")</SetDirectory>
I'd hoped at least the INSTALLDIR property would have been passed in at the start of the InstallExecute sequence even if most other directories hadn't been resolved from it yet by CostFinalize. Is there any way to find the user specified installation directory before CostFinalize so I can correctly set the SERVERDIR path?
Update 1: I'm guessing in most cases INSTALLDIR itself gets resolved by CostFinalize unless it's set on the command line (hence why it's blank to me). I could have my own property that defaults to where I expect INSTALLDIR to be unless it's been set by the user. Need to figure out how the UI passes it in - hoping it just passes INSTALLDIR normally.
Update 2: The UI passes across INSTALLDIR. But it also passes across all other resolved directories including the directories beneath SERVERDIR. I need to run SetDirectory action in both sequences and come up with a default INSTALLDIR property myself. I should also buy myself a rubber duck.
SetDirectory can use properties in formatted strings, but you need to be careful as to when such properties are set. In a normal UI install:
InstallUISequence runs
CostFinalize resolves and sets the directory properties
Dialogs are shown (INSTALLDIR has already been set by CostFinalize)
InstallExecute runs
Install directory properties are pushed into the sequence
The SetDirectory element runs before CostFinalize, and paths it uses must be full paths. To base the SERVERDIR property off the INSTALLDIR property as above make sure a default INSTALLDIR has been set that represents how the resolve would normally occur:
<SetDirectory Action="SetInstallDir" Id="INSTALLDIR" Value="[$(var.Variables_ProgramFilesFolderId)]$(var.Variables_ManufacturerDir)\$(var.Variables_ProductNameShort)\">INSTALLDIR=""</SetDirectory>
<SetDirectory Action="SetServerInstallDir" Id="SERVERINSTALLDIR" Value="[INSTALLDIR]Server ([INSTANCEID])\">NOT (INSTANCEID="DEFAULT")</SetDirectory>

How to use ProgramFilesFolder value in a variable in a wxi file

In my wxi file, I want to set a variable that has the Program Files directory. I want it to pick the localized value of Program Files.
<?define MyDirectory="!(wix.LocalizedProgramFilesFolder)\MyFiles"?>
I have defined LocalizedProgramFilesFolder as:
<WixVariable Id="LocalizedProgramFilesFolder" Value="[ProgramFilesFolder]"/>
However during installation the MyDirectoryis picked as:
"[ProgramFilesFolder]\MyFiles".
It does not expand ProgramFilesFolder.
How do I use ProgramFilesFolder value in a variable in my wxi file?
Not sure why you are using a MyDirectory variable. As the WixVariable docs say:
WiX variables do not persist into the msi/msm/pcp file, so they cannot
be used when an MSI file is being installed; it's a WiX-only concept.
Its value is written as text into wherever you use it. So, if you want the value to have properties substituted at install-time, you must use it only in such a context.
Typical usage of ProgramFilesFolder is as a Directory/#Id, which can have a descendant Directory, such as MyFiles. Note: a Directory/#Id is also a property so it can be used as such.
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLFOLDER" Name="MyFiles" />
</Directory>
</Directory>
INSTALLFOLDER is used instead of MyDirectory. You can use whatever you want but that the default from the project template. It is all caps, which makes it a public property. A public property value can be passed into the installer sequence from the UI or using msiexec or other programs, such a bootstrappers.

Pass command line variables into WiXx based Windows Installer MSI

I am building an MSI installer with WiX and I am using the WixUI_Advanced. The definition of my ApplicationFolder looks like this, following the advice in another SO answer (WiX tricks and tips).
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="$(var.PlatformProgramFilesFolder)">
<Directory Id="APPLICATIONFOLDER" Name="$(var.InstallName)">
I now want to give the user the option to do a silent install and pass the ApplicationFolder name on the path, either relative to the appropriate program files folder or absolute.
I know that I can pass public property values on the command-line of msiexec, but how do I use that as value for ApplicationFolder and how do I set this up for absolute vs relative paths.
You just define the property on the command line when running msiexec:
msiexec /i product.msi APPLICATIONFOLDER="C:\Program Files\Company\Product\"
The files will be installed into "C:\Program Files\Company\Product" directory.
I'd advice using absolute path here. A relative path may lead to unexpected results.