Adding environment variable in WiX bootstrapper - windows

I have Wix bootstrapper that installs few MSI packages.
Is there any way I can add to path install folder one of these packages INSIDE this bootstrapper?
Something like this:
<Environment Id="PATH" Name="PATH" Value="[INSTALLDIR]" Permanent="yes" Part="last" Action="set" System="no" />
Or I have to add it inside one of these packages as component?

Bootstrapper projects are not supposed to change the state of the system (outside of the individual packages).
You would need to add it to one of the MSI projects.
This code block works for me. Note CreateFolder is necessary to ensure the block is executed.
<Component Id="pathComponent" Guid="*" KeyPath="yes">
<CreateFolder />
<Environment Id="PATH" Name="PATH" Value="[INSTALLDIR]" Permanent="no" Part="last" Action="set" System="yes" />
</Component>

Related

How do you add multiple entries to the `PATH` Envirnoment Variable using WiX?

I'm creating a MSI installer and need to add more than one entry to the PATH environment variable. Per the MSDN documentation:
Each row can contain only one value. For example, the entry Value;Value;[~] is more than one value and should not be used because it causes unpredictable results. The entry Value;[~] is just one value.
My installer source code looks like this currently (note, this is a per-machine installation), which is a violation of the above documentation:
<!-- NOTE: These two features are mutually exclusive -->
<Feature Id="Feature1" Level="1000" Absent="allow" AllowAdvertise="no" InstallDefault="local" TypicalDefault="install">
<Component Directory="INSTALLFOLDER">
<RegistryValue Action="write" Type="int" Root="HKLM" Key="SOFTWARE\MyProduct" Name="MyPathEntry1" Value="1" KeyPath="yes" />
<Environment Id="AddPathEntry1" Name="PATH" Value="[INSTALLFOLDER]SubDir1" Action="set" Permanent="yes" Part="last" System="yes" />
</Component>
</Feature>
<Feature Id="Feature2" Level="1000" Absent="allow" AllowAdvertise="no" InstallDefault="local" TypicalDefault="install">
<Component Directory="INSTALLFOLDER">
<RegistryValue Action="write" Type="int" Root="HKLM" Key="SOFTWARE\MyProduct" Name="MyPathEntry2" Value="1" KeyPath="yes" />
<Environment Id="AddPathEntry2" Name="PATH" Value="[INSTALLFOLDER]SubDir1;[INSTALLFOLDER]SubDir2;[INSTALLFOLDER]SubDir3" Action="set" Permanent="yes" Part="last" System="yes" />
</Component>
</Feature>
Now even though the above is "technically" a violation according to the MSDN documentation, it seems to work. I've tested fresh installations, modifying an installation, and upgrading. All seem to work with no hitch. But one thing I've learned with MSI is whenever possible, it's best to follow the rules to avoid messing up people's machines.
The natural solution of adding independent (i.e. not mutually exclusive) features containing only individual path components won't work because with MSI, you cannot guarantee the order in which features and/or components are installed. However, in this case, the order in which path components are added to the PATH environment variable is important due to how the PATH variable is used when finding unqualified executables.
The other question that may come to mind is, why am I using features? I want to give installers of the product the option to change their installation choice at a later date in time via the standard Add/Remove Programs or Program and Features Control Panel applet.
So, how can I add more than one path entry to the PATH environment variable in a deterministic order while following the recommended guidance from the MSDN? Or is the guidance from MSDN outdated and what I'm currently doing is perfectly fine?
I figured a way to do this which does not violate the guidance at MSDN for my particular situation.
The exact situation is I have a main program executable that installers may or may not want to be able to run from a Windows Command Shell. Additionally, there are other utilities that also installers may or may not wish to run from a Windows Command Shell.
The way this was originally modeled was as a set of 3 mutually exclusive features using radio buttons to decide how to update the system PATH environment variable. The 3 features boiled down to:
Don't modify the system's PATH environment variable (i.e. the program and optional tools can't be run from a Windows Command Shell by simply typing the name of the executable at any directory location).
Update the system's PATH environment variable so that only the main executable can be run from a Windows Command Shell at any given path.
Update the system's PATH environment variable to also add additional utility tool paths.
What I came to realize is that there are really only 2 features here, and they're not even mutually exclusive, but one is dependent on the other. The first radio button listed above is not really a feature--it's a no-op; it doesn't install anything! The second radio button represents the "main" feature in that the primary executable's directory is added to the system's PATH. The 3rd radio button depends upon the second: if the main executable is added to the PATH, then you can additionally add other utilities to the PATH as well.
This results in a much simpler UI. Instead of radio buttons, use checkboxes, and the second checkbox is disabled unless the first checkbox is checked. This also results in a much simpler implementaion for ensuring that the combination of features selected (say, for a command-line installation) is valid.
In a nutshell, here's what the WiX XML will boil down to in my particular case. I'm sure that this can be generalized (or modified) to suit other similar scenarios.
<Feature Id="AddExeToPath" Level="1" Absent="allow" AllowAdvertise="no" InstallDefault="local" TypicalDefault="install">
<Component Id="ExePath" Guid="{YOURGUID-HERE-0000-0000-00000000}" Directory="INSTALLFOLDER">
<CreateFolder />
<Environment Id="ExePath" Name="MY_PATHS" Value="[INSTALLFOLDER]SubDir1" Action="set" Part="first" System="yes" />
</Component>
<Component Directory="INSTALLFOLDER">
<RegistryValue Action="write" Type="integer" Root="HKLM" Key="SOFTWARE\MyProduct" Name="AddExeToPath" Value="1" KeyPath="yes" />
<Environment Id="AddToPath" Name="PATH" Value="%MY_PATHS%" Action="set" Part="last" System="yes" />
</Component>
<Component Id="RemoveExeFromPathOnUninstall" Guid="{YOURGUID-HERE-0000-0000-00000000}" Directory="INSTALLFOLDER">
<Condition>REMOVE><AddExeToPath OR REMOVE="ALL"</Condition>
<Environment Id="RemoveFromPath" Name="PATH" Value="%MY_PATHS%" Action="remove" />
</Component>
</Feature>
<Feature Id="AddToolsToPath" Level="1000" Absent="allow" AllowAdvertise="no" InstallDefaut="local" TypicalDefault="install">
<Component Id="SubDir2" Guid="{YOURGUID-HERE-0000-0000-00000000}" Directory="INSTALLFOLDER">
<CreateFolder />
<Environment Id="SubDir2" Name="MY_TOOLS" Value="[INSTALLFOLDER]SubDir2" Action="set" Part="first" System="yes" />
</Component>
<Component Id="SubDir3" Guid="{YOURGUID-HERE-0000-0000-00000000}" Directory="INSTALLFOLDER">
<CreateFolder />
<Environment Id="SubDir3" Name="MY_TOOLS" Value="[INSTALLFOLDER]SubDir3" Action="set" Part="last" System="yes" />
</Component>
<Component Directory="INSTALLFOLDER">
<RegistryValue Action="write" Type="integer" Root="HKLM" Key="SOFTWARE\MyProduct" Name="AddToolsToPath" Value="1" KeyPath="yes" />
<Environment Id="AddToolsToPath" Name="MY_PATHS" Value="%MY_TOOLS%" Action="set" Part="last" System="yes" />
</Component>
</Feature>
<CustomAction Id="InvalidPathFeatureSelection" Error="25000" Execute="firstSequence" />
<InstallExecuteSequence>
<Custom Action="InvalidPathFeatureSelection" Before="InstallValidate">
<![CDATA[NOT (REMOVE="ALL" OR (&AddToolsToPath >= 3 IMP &AddExeToPath >= 3))]]>
</Custom>
</InstallExecuteSequence>
This set of features and components results in the following:
If the feature AddExeToPath is selected for installation, the following components of the feature result in:
An environment variable named MY_PATHS is created and contains the path to the main executable.
The environment variable PATH is updated, placing %MY_PATHS% at the end its current value.
If the feature AddToolsToPath is selected, 3 components are installed:
The environment variable MY_TOOLS is created/set, and [INSTALLFOLDER]SubDir2 is put at the front of the variable's existing value (if any).
The environment variable MY_TOOLS is created/set and [INSTALLFOLDER]SubDir3 is put at the end of the variable's existing value (if any).
Note
These two components defined the way that they are ensure that the paths added to MY_TOOLS are added in the appropriate order.
The environment variable MY_PATHS is updated, placing %MY_TOOLS% at the end of the exisiting variable; again, this preserves the correct ordering of paths.
And so what you end up with is:
MY_TOOLS = [INSTALLFOLDER]SubDir2;[INSTALLFOLDER]SubDir3
MY_PATHS = [INSTALLFOLDER]SubDir1;%MY_TOOLS%
PATH = <existing_PATH_value>;%MY_PATHS%

Wix installed app (and shortcut) shows admin symbol

After successfully creating and testing an application I've also manually created the installer for this app using Wix instead the ClickOnce provided by VS.
Anyway, the installation is successful, places all the registry keys in correct locations, same for files where they need to be, and the shortcuts (and all is cleaned up afterwards).
The issue is not critical, I'm just really picky :D
On the main exe file that the Wix setup is installing, and on the shortcuts that points to this, they have the little blue and yellow admin shield on the bottom right of the icons. The application does not require admin permissions to work properly, nor does the application actually bring up the UAC or run as admin anyway (unless explicitly done through right-click > Run as admin).
The question is how do I prevent the shield from being applied to the application and shortcut icons?
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:netfx="http://schemas.microsoft.com/wix/NetFxExtension">
<?include "Macros.wxi" ?>
<!-- Installation Settings -->
<Product Id="*"
Name="$(var.NameApp)"
Language="1033"
Version="1.0.0.0"
Manufacturer="$(var.NameCompany)"
UpgradeCode="$(var.GUID_Upgrade)">
<Package InstallerVersion="200"
Compressed="yes"
InstallScope="perMachine"
Comments="Windows Installer Package"/>
<Media Id="1"
Cabinet="product.cab"
EmbedCab="yes"/>
<MajorUpgrade DowngradeErrorMessage="A newer version of this software is already installed" />
<!-- .NET Framework Check -->
<PropertyRef Id="NETFRAMEWORK40CLIENT" />
<Condition Message="This application requires .NET Framework 4.0. Please install the .NET Framework then try again">
<![CDATA[Installed OR NETFRAMEWORK40CLIENT]]>
</Condition>
<!-- Installation files, folders, reg-keys, shortcuts, etc -->
<Directory Id="TARGETDIR" Name="SourceDir">
<!-- Program Files Folder -->
<Directory Id="ProgramFilesFolder">
<!-- Company Application Folder -->
<Directory Id="INSTALLDIR" Name="$(var.NameCompany)">
<!-- Main Application Files -->
<Component Id="CmpAppMain" Guid="$(var.GUID_CmpAppMain)">
<File Id="FileAppMainEXE" Source="$(var.PathExe)" Vital="yes" />
<RegistryKey Root="HKLM"
Key="SOFTWARE\$(var.NameCompany)\$(var.NameApp)">
<RegistryValue Name="installed"
Type="integer"
Value="1"
KeyPath="yes" />
</RegistryKey>
</Component>
<!-- Common DLLs for multiple apps -->
<Component Id="CmpAppLibs" Guid="$(var.GUID_CmpAppLibs)">
<File Id="FileDeviceDLL" Source="$(var.PathLibDevice)" Vital="yes" />
<File Id="FileUtilDLL" Source="$(var.PathLibUtil)" Vital="yes"/>
<RemoveFile Id="FileClrDevice" Directory="INSTALLDIR" Name="Comms.log" On="uninstall"/>
<RegistryKey Root="HKLM"
Key="SOFTWARE\$(var.NameCompany)">
<RegistryValue Name="Lib Path"
Type="string"
Value="[INSTALLDIR]" />
<RegistryValue Name="Lib Ver"
Type="string"
Value="1.0.0"
KeyPath="yes" />
</RegistryKey>
</Component>
<!-- Common Resource Files -->
<Directory Id="FolderResource" Name="rsc">
<Component Id="CmpAppRsc" Guid="$(var.GUID_CmpAppRscs)">
<File Id="RscOilDb" Source="$(var.PathRscOil)" Vital="no" KeyPath="yes"/>
</Component>
</Directory>
</Directory>
<!-- END - Company Application Folder -->
</Directory>
<!-- END - Program Files Folder -->
<!-- Start Menu Folder -->
<Directory Id="ProgramMenuFolder">
<!-- Start Menu Company Folder -->
<Directory Id="ProgramMenuCompany" Name="$(var.NameCompany)">
<Component Id="CmpLnks" Guid="$(var.GUID_CmpLnks)">
<Shortcut Id="LnkStartMenu"
Name="$(var.NameApp)"
Description="$(var.NameApp)"
Target="[INSTALLDIR]$(var.NameExe)"
WorkingDirectory="INSTALLDIR">
<Icon Id="IconApp" SourceFile="$(var.PathRscIco)" />
</Shortcut>
<RegistryKey Root="HKCU"
Key="SOFTWARE\$(var.NameCompany)">
<RegistryValue Name="Lnk"
Type="integer"
Value="1"
KeyPath="yes" />
</RegistryKey>
<RemoveFolder Id="RemoveStartLnk" Directory="ProgramMenuCompany" On="uninstall" />
</Component>
</Directory>
<!-- END - Start Menu Company Folder -->
</Directory>
<!-- END - Start Menu Programs Folder -->
</Directory>
<!-- END - TARTGETDIR -->
<Feature Id="FeatCore" Title="Core Application Files" Level="1">
<ComponentRef Id="CmpAppMain" />
<ComponentRef Id="CmpAppLibs" />
<ComponentRef Id="CmpAppRsc" />
</Feature>
<Feature Id="FeatLnks" Title="Start Menu Shortcut" Level="1">
<ComponentRef Id="CmpLnks" />
</Feature>
</Product>
</Wix>
Does the app have a manifest at all? I'm wondering if it's got a
highestavailable or asInvoker setting that means that it might
sometimes elevate, and I'm assuming from what you said that it doesn't
have a requiresAdministrator setting there.
A manifest is nearly always embedded in the exe itself, that's what
needs verifying. I'm guessing that the exe is being built with an
embedded manifest. No need to include it in the install.
Thanks for the info. The issue was with the manifest which wasn't being generated in the first place due to ClickOnce settings, and then once I've generated or made my own manifest for the project it's also not being embedded into the executable.
ClickOnce publishing places it within the installation directory with the installer it generates for you. Because I didn't want to use click once (and I assumed after reading about the manifest it would be embedded in exe) my app didn't have a manifest...
The only thing I'm curious about now is why the default behavior is to ask for admin rights (I thought that would be the worst thing to do by default).
Anyway... Thanks for the help

How to conditionally set a file extension using Wix?

I'd like to conditionally set a file extension during install. As I understand it, in order to do anything conditionally in Wix, it should be a self-contained component. So for each file type association I would like to allow the user to set, I've got a component similar to the following:
<Component Id="FileAssocComponent_PS" Guid="DAFE9461-2DF0-934A-F204-6B28CEA23C01">
<Condition>FILE_ASSOC_PS</Condition>
<RegistryValue Root="HKLM" Key="SOFTWARE\PrinterApp\Capabilities\FileAssociations" Name=".prn" Value="PrinterApp.ps" Type="string" />
<RegistryValue Root="HKLM" Key="SOFTWARE\PrinterApp\Capabilities\MIMEAssociations" Name="application/postscript" Value="PrinterApp.ps" Type="string" />
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\PrinterApp.ps" Name="FriendlyTypeName" Value="PostScript File" Type="string" />
<ProgId Id="PrinterApp.ps" Description="PostScript File" Icon="PrinterApp.ico" Advertise="yes">
<Extension Id="ps">
<Verb Id="open" Command="Open" Argument=""%1""/>
</Extension>
</ProgId>
</Component>
But this gives me the following error:
error LGHT0204: ICE19: Extension: 'ps' advertises component: 'FileAssocComponent_PS'. This component cannot be advertised because the KeyPath type disallows it.
I've tried setting KeyPath="yes" on one of the registry entries, but that doesn't work - and from what I've been able to find it's expecting a file KeyPath. But this is a component that doesn't contain any files!
How do I get around this error, or am I going about this the wrong way?
Advertised components require a keyfile so here are some ways around the error.
1)
Give the component a fake file ( printermimeinstalled.txt ) that won't harm the system.
2)
Author PrinterAppMime.ps as the keyfile of this component. Use the CopyFile element to get the file copied to PrinterApp.ps
Author PrinterAppNoMime.ps (same contents) as the keyfile of another component. Also use the CopyFile element to get the file copied to PrinterApp.ps. Give this component a mutually exclusive component condition so that only 1 component ever gets instaleld.
3)
Change the design of your app a little bit. Have PrinterApp.ps always installed and PrinterAppMimeServer.ps conditionally installed.
4)
Eliminate this custom action and use a custom action to author MSI temp table rows at installtime to defind the MIME stuff if the checkbox is selected.
Each of these 4 approaches have pro's and con's and I personally would choose #3.
If you set Advertise="no" you should be able to use the code you wrote. Here's an example I posted a couple of years ago here using a separate component for optional file associations.
<Component ....>
<ProgId Id="AcmeFoobar.Document" hDescription="ACME XYZ Document">
<Extension Id="pdf" ContentType="application/xyz">
<Verb Id="open" Command="Open" TargetFile="[APPLICATIONFOLDER]AcmeFoobar.exe" Argument="%1" />
</Extension>
</ProgId>
<Condition><![CDATA[DEFAULTVIEWER=1]]></Condition>
</Component>
I found a solution that worked for me. The issue I had was that I had a condition on the association of the extension to the exe. If the extension was unchecked to not associate, I needed the exe component to get installed but without the progid. Problem was that if a put a condition on the component, the progid wouldn't be created but the exe didn't get installed as well. The solution I found was two create two components. One with the condition and one with mutually exclusive condition. This is basically option 2 from Christopher Painters post.
See below:
<Component Id="My.exe" Guid="{D9CF6FDD-1234-4E90-85A1-3BF1F912C1E3}">
<Condition>NOT FILES_ASSOCIATIONS_ABC</Condition>
<File Id="My.exe.without_assoc" Name="My.exe" KeyPath="yes" Vital="yes" Compressed="yes" DiskId="1" Source=".\SourceDir\My.exe" />
</Component>
<Component Id="My.exe_assoc" Guid="{07F96643-5D74-1234-9DAE-CDEB5AC2D11E}">
<File Id="My.exe.with_assoc" Name="My.exe" KeyPath="yes" Vital="yes" Compressed="yes" DiskId="1" Source=".\SourceDir\My.exe" />
<Condition>FILES_ASSOCIATIONS_ABC</Condition>
<ProgId Id="My.Document" Description="My exe" Icon="MyIcon" Advertise="yes">
<Extension Id="abc">
<Verb Id="open" Command="My Exe" Argument=""%1"" />
<MIME Advertise="yes" ContentType="application/abc" Default="yes" />
</Extension>
</ProgId>
</Component>

Unregister COM component in WIX during rollback, is it necessary?

I am creating a WIX installer project. Part of the work in the installation is to register some COM component. I have (hopefully) succeeded with that by using heat.exe to generate the COM component declaration, although I needed to modify a little after the wxs files were generated. To make it clear, I am pasting my COM component declaration here:
<ComponentGroup Id="COMComponent" Directory="INSTALLDIR">
<Component Id="COMDll" Guid="MY_GUID1">
<Class Id="{MY_CLASSID1}" Context="InprocServer32" Description="DESCRIPTION1" ThreadingModel="both" ForeignServer="mscoree.dll">
<ProgId Id="MYID1" Description="DESCRIPTION1" />
</Class>
...
<File Id="FILE_ID_1" KeyPath="yes" Source="MY_COM.dll" />
<File Id="FILD_ID2" KeyPath="no" Source="MY_COM.tlb">
<TypeLib Id="{xxx}" Description="xxx" HelpDirectory="INSTALLDIR" Language="0" MajorVersion="525" MinorVersion="0">
<Interface Id="{xxx}" Name="xxx" ProxyStubClassId32="{00020424-0000-0000-C000-000000000046}" />
...
</TypeLib>
</File>
<ProgId Id="Record" />
<RegistryValue Root="HKCR" Key="CLSID\{xxx}\Implemented Categories\{xxx}" Value="" Type="string" Action="write" />
<RegistryValue Root="HKCR" Key="CLSID\{xxx}\InprocServer32\525.0.4573.25681" Name="Class" Value=" MYID1" Type="string" Action="write" />
...
</Component>
</ComponentGroup>
I replaced some IDs with xxx, so they are not a problem. If there is anything else wrong, please inform me.
Now my question is: do I need to explicitly specify to uninstall the COM component in the rollback phase? Because I have experienced some problem, that after some failure installation, the further tries of installations wouldn't go through and some "ActiveX object can't be created" error popped up. I suspected it is because COM component was not unregistered successfully in the previous fail installation. But I am not sure. In fact I have limited knowledge in COM interop, so I am not sure what's necessary for install/uninstall them. Any help would be appreciated. Thanks!
When you use Heat to harvest the COM components, that will be sufficient. It should handle unregistering your COM components if the installation fails and is rolled back.

Unable to update "PATH" environment variable using WIX

I have used the following wix fragment to update "PATH" environment variable.
<DirectoryRef Id="MyDir">
<Component Id ="setEnviroment"
Guid=" xxxxx">
<CreateFolder />
<Environment Id="SET_ENV"
Action="set"
Name="PATH"
Part="last"
Permanent="no"
System="yes"
Value="[INSTALLLOCATION]" />
</Component>
</DirectoryRef>
<Feature Id="Feature3" Title="3Feature"
Level="1"
Absent="disallow"
AllowAdvertise="no">
<ComponentRef Id="setEnviroment"/>
</Feature>
<InstallExecuteSequence>
<WriteEnvironmentStrings/>
<InstallExecuteSequence/>
This was working initially but now it doesn't update the environment variable.
The Verbose log shows the execution of this action and return value 1.
Checked after restarting machine.
In the log for action FeaturePublish For Feature3 there is garbage value but Installation is successful.
Request your help in this......
Thanks a lot....
I think you are using INSTALLLOCATION where you mean to use INSTALLDIR. Here is a working example which updates the PATH environment var with the installation directory of the new app.
<Environment
Id="PATH"
Name="PATH"
Value="[INSTALLDIR]"
Permanent="yes"
Part="last"
Action="set"
System="yes" />
If do intend to use INSTALLLOCATION, and have it defined elsewhere, then please post the rest of your code and we will go further down the rabbit hole.
I needed to use INSTALLFOLDER instead, to get it working.
<Environment
Id="PATH"
Name="PATH"
Value="[INSTALLFOLDER]"
Permanent="yes"
Part="last"
Action="set"
System="yes" />

Resources