passing variable with an custom action WIX installer - vb.net

I have a problem with passing the required variable. the problem is this:
I am creating and installer that needs to work on both x86 and x64. changing the installation destination I manage with the following code:
<?if $(sys.BUILDARCH)=x64?>
<?define PlatformProgramFilesFolder = "ProgramFilesX86"?>
<?define FOLDER_NAME = Program Files (x86) ?>
<?else?>
<?define PlatformProgramFilesFolder = "ProgramFilesFolder"?>
<?define FOLDER_NAME = Program Files ?>
<?endif?>
<Directory Id="$(var.PlatformProgramFilesFolder)" Name="$(var.FOLDER_NAME)">
this works fine, however I also have an CA.dll that needs to get the variable FOLDER_NAME since the script makes some changes in files and the path to those files need to be changed as well in order to point towards the right location.
I do have a custom action that transfers propertys to the same script
<Property Id ="MACHINE_ID_NUMBER" Secure ="yes">
<![CDATA[]]>
</Property>
<Property Id ="MACHINE_TYPE" Secure ="yes">
<![CDATA[]]>
</Property>
<CustomAction
Id="SetProperties"
Property="ValueAdaptionScript"
HideTarget="no"
Value="MachineID=[MACHINE_ID_NUMBER];MachineType=[MACHINE_TYPE]"
/>
<CustomAction
Id="ValueAdaptionScript"
BinaryKey="StringTransfer"
DllEntry="CustomAction1"
Execute="deferred"
Impersonate="no"
Return="check"
/>
<InstallExecuteSequence>
<Custom Action="SetProperties" Before="ValueAdaptionScript" />
<Custom Action="ValueAdaptionScript" Before="InstallFinalize">NOT REMOVE="ALL"</Custom>
</InstallExecuteSequence>
The problem is that I am not able to get those two pieces of code combined and working. the problem is that I can't get the value of the variable in an property as is recuired to use it in the custom action.
what am I missing to get it working or am I doing it completely the wrong way?
thanks in advance,

I figured it out myself, it turned out to be pretty easy I was just looking in the wrong direction.
this is what I did:
<Property Id ="PLATFORM_PROGRAM_FILES" Secure ="yes">
<![CDATA[$(var.FOLDER_NAME)]]>
</Property>
all I had to do was to give the property the variable.

Related

Registering SharpShell extension using SRM via WIX installer

Firstly I should clarify that I am a novice and have been struggling to understand the WIX formatting, but by cobbling together examples found on-line, I now have the files installing fine so I next need to register my DLL.
I used the example here as a starting point: How to deploy a SharpShell-based shell extension via WiX? but it seems that the SharpShell tool srm.exe may not be getting called at installation.
If I manually call srm.exe as follows, it works as hoped i.e. the DLL is registered and my shell extension works.
srm install MyExtension.dll -codebase
I can also see that the registration has been successful via the Server Manager application that comes with SharpShell.
I can also manually uninstall with the following - not that this is particularly relevant to my problem but it at least confirms that the manual methods work:
srm uninstall MyExtension.dll
Here is a fragment of my WXS file. When I run the resultant MSI, the files are installed but the DLL is not being registered; confirmed via SharpShell's Server Manager. Where am I going wrong?
</Component>
<Component Id="SRMexe" Guid="C17BB61F-6471-46F9-AA87-2D14D2456632">
<File Id='srm' Name='srm.exe' DiskId='1' Source='..\MyExtension\packages\SharpShellTools.2.2.0.0\lib\srm.exe' KeyPath='yes'>
</File>
</Component>
<!-- TODO: Insert files, registry keys, and other resources here. -->
<!-- </Component> -->
</ComponentGroup>
</Fragment>
<Fragment>
<CustomAction Id="InstallShell" FileKey="srm"
ExeCommand='install "[INSTALLFOLDER]\MyExtension.dll" -codebase'
Execute="deferred" Return="check" Impersonate="no" />
<CustomAction Id="UninstallShell" FileKey="srm"
ExeCommand='uninstall "[INSTALLFOLDER]\MyExtension.dll"'
Execute="deferred" Return="check" Impersonate="no" />
<InstallExecuteSequence>
<Custom Action="InstallShell"
After="InstallFiles">
NOT Installed
</Custom>
<Custom Action="UninstallShell"
Before="RemoveFiles">
(NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")
</Custom>
</InstallExecuteSequence>
</Fragment>
It doesn't look like you have any references to the Fragment with the CustomAction definitions so they are not linked into your final output MSI.
Add a CustomActionRef from your Product element to create the reference.

Copying files to different directory during WIX Upgrade

I have been working on a WIX installer for an application and am stuck on a small part of the upgrade: there are two XML configuration files in the install directory that I would like to copy to the new ProgramData directory (since they will not be in ...\Program Files... going forward).
I have tried several solutions, including different brackets/apostrophes/", to no avail. When I compile the WIX installer, I receive several warnings from CANDLE about the Property containing [CommonAppDataProduct] and [PRODUCTNAMEFOLDER], but I am unsure if there needs to be some reference / PropertyRef from those directories defined in the Product.wxs to each custom action.
Snippets of Product.wxs:
<Product Id="*" Name="$(var.ProductName)" Language="0" Version="$(var.Version)" Manufacturer="$(var.Manufacturer)" UpgradeCode="$(var.UpgradeCode)">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<InstallExecuteSequence>
<SelfUnregModules/>
<SelfRegModules/>
<Custom Action="CopyConfigFilesToTemp" After="InstallValidate" />
<Custom Action="LaunchDPInstActionx86" Before="InstallFinalize">NOT Installed OR MaintenanceMode="Modify"</Custom>
<Custom Action="CopyConfigFilesFromTemp" After="LaunchDPInstActionx86" />
</InstallExecuteSequence>
</Product>
...
<Fragment>
<Directory Id="$(var.PlatformProgramFilesFolder)">
<Directory Id="PRODUCTNAMEFOLDER" Name="$(var.ProductName)"/>
</Directory>
</Fragment>
Custom action CopyConfigFilesToTemp
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<Property Id="QuietExec2" Value='"xcopy.exe [PRODUCTNAMEFOLDER]*.xml" %TEMP% /I /Y'/>
<CustomAction Id="CopyConfigFilesToTemp" BinaryKey="WixCA" DllEntry="WixQuietExec" Execute="immediate" Return="ignore"/>
</Fragment>
</Wix>
Custom action CopyConfigFilesFromTemp
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<Property Id="QuietExec3" Value='"xcopy.exe %TEMP%\*.xml [CommonAppDataProduct]" /I /Y /R'/>
<CustomAction Id="CopyConfigFilesFromTemp" BinaryKey="WixCA" DllEntry="WixQuietExec" Execute="immediate" Return="ignore"/>
</Fragment>
</Wix>
These custom actions must be deferred custom actions because they are trying to modify something in the program files path which requires elevated privileges. The only portion of the install that has elevated privileges is the server server portion of the install when it is copying over files to the install directory.
Custom actions that are deferred have a special requirement on them if you intend to use one of the msi properties within the action.
As per microsoft's website on Deferred Actions
Because the installation script can be executed outside of the installation session in which it was written, the session may no longer exist during execution of the installation script. This means that the original session handle and property data set during the installation sequence is not available to a deferred execution custom action.
This essentially means that you need to put the values of your properties in a special location that is guaranteed to exist while the elevated portion of the install is happening and must be formatted in such a way that it knows exactly where to look to get that value.
So to run these actions they must be scheduled between InstallInitialize and InstallFinalize and must also be able to grab the property values from a special location.
To use a deferred custom action you just need to change the execution to deferred however, we must add this special property with a formatted value so that you can get the values of QuietExec and QuietExec2 from within your custom actions.
You need to declare a custom action as follows for each of the deferred actions:
<CustomAction Id="CustomActionNameHere" Property="CopyConfigFilesToTemp" Value="QuietExec2="xcopy.exe [PRODUCTNAMEFOLDER]*.xml" %TEMP% /I /Y" />
<CustomAction Id="CustomActionNameHere" Property="CopyConfigFilesFromTemp" Value="QuietExec3="xcopy.exe %TEMP%\*.xml [CommonAppDataProduct]" /I /Y /R" />
Generally I call these the same name as the custom action they're setting a property for with "Set" prefixed to the name. IE: SetCopyConfigFilesFromTemp and SetCopyConfigFilesToTemp so that they are easy to locate.
You also must schedule these custom actions and you can't go wrong scheduling them before the action they set properties for and match the conditions.
<Custom Action="SetCopyConfigFilesToTemp" Before="CopyConfigFilesToTemp">
<Custom Action="SetCopyConfigFilesFromTemp" Before="CopyConfigFilesFromTemp">
In the custom action code, you need to use session.CustomActionData["PropertyName"] instead of just session["PropertyName"]
I would also consider the situations when you want to run these copy commands since I don't think you want to do them when uninstalling the product or if its a fresh install and not an upgrade.

Wix ProgramFiles64Folder is still

I am having a problem very similar to the one described here: ProgramFiles64Folder is installing to \Program Files (x86)\ in WIX Installer
However, the solution there does not work for me. Wix still generates a .msi that installs to C:\Program Files (x86)
I placed the following code in my Product.wxs file:
<?if $(var.Platform) = x64 ?>
<?define Win64 = "yes" ?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?define ConfigFolder = "Release" ?>
<?else ?>
<?define Win64 = "no" ?>
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
<?define ConfigFolder = "Release_x86" ?>
<?endif?>
Then later I try to specify installation folder as follows:
<Directory Name="SourceDir" Id="TARGETDIR">
<Directory Name="$(var.PlatformProgramFilesFolder)" Id="$(var.PlatformProgramFilesFolder)">
...
I verified that var.Platform is set properly because it copies source files from the correct ConfigFolder. However, it seems that both ProgramFiles64Folder and ProgramFilesFolder are set to C:\Program Files (x86)
I verified that candle.exe is invoked with -dPlatform=x64 option.
I even tried to specify platform in my Package tag
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" Platform="x64" />
Nothing helps, it still tries to install to C:\Program Files (x86)
Any idea what else I can try?
Thank you.
I am using WiX version 3.9
It turned out that a different .wxs file was messing with WixPerUserFolder and other related variables.
The following code fix the problem:
<CustomAction Id="Overwrite_WixSetDefaultPerMachineFolder" Property="WixPerMachineFolder"
Value="[ProgramFiles64Folder][ApplicationFolderName]" Execute="immediate" />
<InstallUISequence>
<Custom Action="Overwrite_WixSetDefaultPerMachineFolder" After="WixSetDefaultPerMachineFolder" />
</InstallUISequence>
<InstallExecuteSequence>
<Custom Action="Overwrite_WixSetDefaultPerMachineFolder" After="WixSetDefaultPerMachineFolder" />
</InstallExecuteSequence>
BTW, I created a project to simplify the configuration of wix. Hope it can help: https://github.com/xinnj/WiXCover
The only thing I can think of is that the components you are installing to that location are 32-bit components, so they get redirected to the x86 location. A 64-bit package can have 32 and 64-bit components, so you may need to mark them explicitly as Win64='yes'
Follow the sample in this blog and release a new 64 bit package and you will have your installation in Program Files folder.
http://msdn.microsoft.com/en-us/library/gg513929.aspx
Using WixUI_Advanced? This doesn't set the correct default folder on x64.
Workaround is to add this to the Product item:
<!-- Workaround Wix Bug: https://github.com/wixtoolset/issues/issues/2165 -->
<CustomAction Id="Overwrite_WixSetDefaultPerMachineFolder" Property="WixPerMachineFolder"
Value="[$(var.PlatformProgramFilesFolder)][ApplicationFolderName]" Execute="immediate" />
<InstallUISequence>
<Custom Action="Overwrite_WixSetDefaultPerMachineFolder" After="WixSetDefaultPerMachineFolder" />
</InstallUISequence>
<InstallExecuteSequence>
<Custom Action="Overwrite_WixSetDefaultPerMachineFolder" After="WixSetDefaultPerMachineFolder" />
</InstallExecuteSequence>
<SetProperty Id="ARPINSTALLLOCATION" Value="[APPLICATIONFOLDER]" After="CostFinalize" />
For the bug description and the workaround source look here:
https://github.com/wixtoolset/issues/issues/2165

WiX Remove files on uninstall but not update

I have an app that can log when given the correct flags at install time (/logLevel=debug on install gets passed to the app when the service starts). Our update process is a automated uninstall then install with a new MSI package. I know there is built in patch functionality with WiX, but this is our process.
Similarly with the logLevel parameter, I'd like to pass something to the effect of UPDATE="true" on the command line during uninstall. When this parameter is passed to the uninstaller it should not delete the log files. Currently we delete the files every time, but would like to retain the log files during an update. This is what I am trying to extend as of right now:
<?if $(var.BUILD_CONFIG) = "Debug" ?>
<?else?>
<CustomAction Id="Cleanup_logfile" Directory="TempTest"
ExeCommand="cmd /C "del %systemroot%\temp\hexis_hawkeye_g.log.*""
Execute="deferred" Return="ignore" HideTarget="no" Impersonate="no" />
<InstallExecuteSequence>
<Custom Action="Cleanup_logfile" Before="RemoveFiles" >
REMOVE="ALL"
</Custom>
</InstallExecuteSequence>
<?endif?>
And I've been playing with code similar to something like the following but it doesn't seem to work:
<?if $(var.BUILD_CONFIG) = "Debug" ?>
<?else?>
<?if '[UPDATE]' = "true" ?>
<?else?>
<CustomAction Id="Cleanup_logfile" Directory="TempTest"
ExeCommand="cmd /C "del %systemroot%\temp\hexis_hawkeye_g.log.*""
Execute="deferred" Return="ignore" HideTarget="no" Impersonate="no" />
<InstallExecuteSequence>
<Custom Action="Cleanup_logfile" Before="RemoveFiles" >
REMOVE="ALL"
</Custom>
</InstallExecuteSequence>
<?endif?>
<?endif?>
I'm not sure if I'm not initializing the UPDATE variable correctly, or if this really is some pre-processing that cannot be implemented in this fashion. I would think it would not work because these constructs are described on the preprocessor doc page, however, the /logLevel and various other parameters seem to work fine at run-time installation. I'm totally WiX illiterate and have been trying to read their documentation to no avail, any helpful links appreciated.
The problem as I see it: during a major upgrade when the application is uninstalled (and later on installing the new version) REMOVE=ALL is also true during uninstalling the application, so the files will be deleted.
You need to additionally check if the UPGRADINGPRODUCTCODE is also set or not, which would only be true during an update.
Check this answer where the correct condition is given (and bookmark the question as I did, it is very very useful for all the possible states and conditions ;-)).
The correct condition should be the following in your case:
<InstallExecuteSequence>
<Custom Action="Cleanup_logfile" Before="RemoveFiles" >
(NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")
</Custom>
</InstallExecuteSequence>
This is probably a bit hackish, but I was able to pass what I wanted by insinuating from the LOGLEVEL what action to take instead of passing an arbitrary variable:
msiexec.exe /x {blah-blah-guid-blah} INSTALLLEVEL=2
And for the configuration of my custom action:
<?if $(var.BUILD_CONFIG) = "Debug" ?>
<?else?>
<CustomAction Id="Cleanup_logfile" Directory="TempTest"
ExeCommand="cmd /C "if [INSTALLLEVEL] GEQ 2 del %systemroot%\temp\hexis_hawkeye_g.log.*""
Execute="deferred" Return="ignore" HideTarget="no" Impersonate="no" />
<InstallExecuteSequence>
<Custom Action="Cleanup_logfile" Before="RemoveFiles" >
REMOVE="ALL"
</Custom>
</InstallExecuteSequence>
<?endif?>

Why isn't my Wix Property being evaluated?

I'm trying to simulate the InstallURL property of a VS.net install MSI... I've got to the ponit where the WIX MSI will open a browser to the download page that I want it to go to. I thought things were going great because on my test machine, the web page opened when I didn't have the MSXML6 component installed. However things went downhill when I discovered that the web page opened even when I DID have the component installed.
I'm searching for the MSXML6 component using a Property w/ a RegistrySearch. However, as best as I can tell, the registry value isn't even being evaluated, and thus it "always" looks like it isn't installed.
Here's the relevant portion of my WXS:
<Property Id="MSXML6">
<RegistrySearch Id="MSXML6Search" Root="HKCR" Key="Msxml2.DOMDocument.6.0" Type="raw" />
</Property>
<Property Id="TEST">
<RegistrySearch Id="TESTSearch" Root="HKLM" Type="raw" Name="Version" Key="SOFTWARE\Microsoft\DirectX" />
</Property>
<Property Id="cmd.exe" Value="cmd.exe" />
<CustomAction Id="OpenMSXML6Download" Property="cmd.exe" ExeCommand="/c start http://www.microsoft.com/downloads/details.aspx?FamilyID=993c0bcf-3bcf-4009-be21-27e85e1857b1" Execute="immediate" Return="check" />
<CustomAction Id="OpenMSXML6DownloadError" Error="This component requires MSXML6. =[MSXML6]=[cmd.exe]=[TEST]= A web browser has been opened to the download page. Please install MSXML6 and then re-install the connector." />
<!-- installation execution sequence -->
<InstallExecuteSequence>
<!-- wires the error dialog to the downgrade event -->
<Custom Action="PreventDowngrading" After="FindRelatedProducts">NEWPRODUCTFOUND</Custom>
<!-- execution to delete old install info after upgrade-->
<RemoveExistingProducts After="InstallValidate" />
<!-- Forces MSXML6 to be pre-installed -->
<!-- <Custom Action="OpenMSXML6Download" Before="FindRelatedProducts">NOT MSXML6</Custom> -->
<Custom Action="OpenMSXML6Download" Before="FindRelatedProducts">NOT MSXML6</Custom>
<Custom Action="OpenMSXML6DownloadError" After="OpenMSXML6Download">NOT MSXML6</Custom>
</InstallExecuteSequence>
<!-- ui information for the custom actions above. -->
<InstallUISequence>
<Custom Action="PreventDowngrading" After="FindRelatedProducts">NEWPRODUCTFOUND</Custom>
<Custom Action="OpenMSXML6Download" Before="FindRelatedProducts">NOT MSXML6</Custom>
<Custom Action="OpenMSXML6DownloadError" After="OpenMSXML6Download">NOT MSXML6</Custom>
</InstallUISequence>
What this does is if MSXML6 isn't defined then it opens the web page and then prints the custom error message. Note that I'm trying to print the value of the property in the error message (I'm not sure if this is valid or not, but it seems to be.) The text that I see says "This component requires MSXML6. ==[cmd.exe]==..." so it is printing the value of the 'cmd.exe' property but not the other two... maybe that's because I define the property explicitly, I'm not sure... Anyway, I also ran the MSI with debugging on, and in the log file, I see absolutely no reference at all to the MSXML6 or the TEST properties ever being set. I've confirmed that the registry values are indeed set, although I'm not 100% sure how to handle the Msxml2 registry key, since it doesn't have any real values, only a default value. (I'm assuming that leaving off the 'Name' parameter is the right way to handle this.)
Help??
I managed to figure this one out... it was quite a simple answer. Basic issue was that the Custom Actions were executing before AppSearch, which is where the RegistrySearch properties get evaluated. See my blog post at CTICoder for details.