WIX Win64="no" no effect - wix

I have following component in installer
<Component Id="my.dll" Guid="*" Win64="no">
<File Id="my.dll" Name="my.dll" KeyPath="yes" ReadOnly="yes" DiskId="1" Source="$(var.TargetDir)/my.dll" />
</Component>
In visual studio configuration manager for solution platform x64 the installer project is set to x64.
When building x64, the build is still failing on this part, looking for my.dll although Win64 is set to "no".
In the wixproj I have manually added by copying the x86 section:
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
<OutputPath>bin\$(Configuration)\</OutputPath>
<IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath>
<DefineConstants>Debug</DefineConstants>
</PropertyGroup>
Did same for 'Release|x64'. and also tried removing following:
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
and also changing the x86 to x64 but this did not help.
Why is x64 build still regarding this line although Win64="no"?

Reference: Using Project References and Variables.
TargetPath: Maybe try to use $(var.ProjectName.TargetPath) instead of $(var.TargetDir)/my.dll. I think that is what is wrong. The TargetPath will be the whole path with file name for what you build. See the above "Reference" link for all these parameters (preprocessor variables).
Extra Notes: A couple of further things:
Notice the ProjectName section: if your Visual Studio project is called MyLittleProject, then you should add: $(var.MyLittleProject.TargetPath).
The Win64="no" attribute for the Component element in WiX files affects where the file is installed on the target system where end-user installation happens, not where the Visual Studio build files are located on disk after successful build:
Program Files (x86) (32-bit) or Program Files (64-bit)
Windows (64-bit) or SysWOW64 (32-bit, yes amazingly)
Quick Sample: This is from memory and probably not very good, but here is a sample of Preprocessor variables in action (got this long answer on such constructs):
<?if $(var.MyProjectName.Platform) = "x64" ?>
<Component>
<File Source="C:\Test\Test1.exe" />
</Component>
<?endif?>
<?if $(var.MyProjectName.Platform) = "Win32" ?>
<Component>
<File Source="C:\Test\Test2.exe" />
</Component>
<?endif?>
<Component>
<File Source="$(var.MyProjectName.TargetPath)" />
</Component>
Make sure the configuration is set to rebuild all projects including your WiX project. There could be unexpected exclusions. Notice how platform might say Win32 versus x64 for platforms (instead of the perhaps expected x86) - common errors. Inspect the compiled MSI. Also be sure to set a proper project build order (manipulate project dependencies).
sys.BUILDARCH: There is also the sys.BUILDARCH built-in variable, which has me a little confused to be honest. I have not used it. Search for it here: Preprocessor. And github.

Related

Adventures in Installing a C#/WPF Application (WiX)

I've got a C#/WPF application that is one step up from simple - it's got the main application, a couple of libraries, and then some NuGet packages. If I look in the project's bin/release/netcoreapp3.1 directory, there's a single EXE, a couple of JSONs, and a bunch of DLLs (some of which have PDBs). Nothing too crazy. The client wants an installer which doesn't sound like an unreasonable request.
I first try the installer that comes with Visual Studio 2019 and am astounded at how useless it is. After deleting it, I poke around on the interwebs and discover that WiX seems to be relatively popular. Ok, says I, I will try it.
After installing the WiX Toolset Build Tools (which sounds a bit redundant) and the WiX Toolset Visual Studio Extension, and restarting Visual Studio, it's looking good. I right-click on my solution, select "Add" and "New Project" and see "Setup Project for WiX v3" which looks good. I click Next, name it Installer, and I get this:
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Name="Installer" Language="1033" Version="1.0.0.0" Manufacturer="" UpgradeCode="2995de5a-87ef-443d-a25a-450d39720349">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<MediaTemplate />
<Feature Id="ProductFeature" Title="Installer" Level="1">
<ComponentGroupRef Id="ProductComponents" />
</Feature>
</Product>
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLFOLDER" Name="Installer" />
</Directory>
</Directory>
</Fragment>
<Fragment>
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<!-- TODO: Remove the comments around this Component element and the ComponentRef below in order to add resources to this installer. -->
<!-- <Component Id="ProductComponent"> -->
<!-- TODO: Insert files, registry keys, and other resources here. -->
<!-- </Component> -->
</ComponentGroup>
</Fragment>
</Wix>
Which looks promising. If I try to build it gives me the error "The Product/#Manufacturer attribute's value cannot be an empty string" which is a textbook example of what an error message should look like. Precise, tells me exactly what is happening, and points me in the direction of what I need to do. I'm liking this! I put some text in the Manufacturer attribute, rebuild, and it says "Build: 1 succeeded" and I have an installer. It does give me the warning "The media table has no entries" which makes perfect sense as I haven't told it to install any files.
I start reading the official WiX tutorial and it's painfully verbose. It's not actually a tutorial as much as a treatise on why WiX is the way it is. I don't actually care at this point, I just want the darn thing to work. I want a tutorial, not the entire history of the company. Eventually I find a link for "Getting Started" and I feel a little better. But again, instead of getting me started, I get a lengthy explanation of the internal workings of the system. At this point I DON'T CARE. I want to get a basic installer working, then you can hit me with some details. After several more pages of the author thinking he or she is a university professor in a lecture, I finally get some XML - and it's for a media tag that I may or may not need, it's not at all clear. And, since there isn't a Media tag in the WXS file that was generated for me, I don't know where it goes.
Giving up on the tutorial, I do a Google search for "wix "visual studio" project" and find this page which has a link for "Creating a simple setup" which is perfect. It actually takes me through what I need, step-by-step. I follow the instructions, hit Build, and it says "Build: 1 succeeded". I find the MSI file, run it, there's no UI which seems odd but I can live with that, and it creates a folder in my "Program Files (x86)" folder that has the primary DLL for my project. Progress!
So now I need to include the DLLs constructed from my library projects, and all the DLLs that were included from NuGet packages. It would be cool if I could tell WiX "please include all the files required by my various NuGet packets" but poking around the web, it appears WiX doesn't know how to do that, but that's OK, really all I need is for the installer to include all of the DLLs that end up in my bin/release/netcoreapp3.1 directory.
Trying the obvious wildcard approach:
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<Component Id="ProductComponent">
<File Source="$(var.MyApplication.TargetPath)" />
<File Source="$(var.MyApplication.TargetDir)/*" />
</Component>
</ComponentGroup>
Just gives an error that is not as easy to read as the first error, but does seem to indicate the asterisk is a no-go. If I do a Google search for "wix add all files in directory" returns a whole bunch of different suggestions, all of which are complicated, which seems odd for such a basic thing. But following the instructions in this page, I end up with an installer that almost works. I get the error "Undefined preprocessor variable $(var.HarvestPath)". This page happened to have the answer to that one, and I have a working installer again. But now it's including absolutely everything in that folder, and I only want the DLLs. Doing a Google search for "wix heatdirectory exclude files" gives no answers, it appears this feature is relatively brain-dead.
So here is my current WIXPROJ:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" InitialTargets="EnsureWixToolsetInstalled" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<DefineConstants>HarvestPath=...\Deploy</DefineConstants>
<HeatDefinitions>MySourcePath=..\src\Your.App\bin\Release</HeatDefinitions>
</PropertyGroup>
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProductVersion>3.10</ProductVersion>
<ProjectGuid>4ece284d-9247-4f7a-84a7-34820606a003</ProjectGuid>
<SchemaVersion>2.0</SchemaVersion>
<OutputName>Installer</OutputName>
<OutputType>Package</OutputType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<OutputPath>bin\$(Configuration)\</OutputPath>
<IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath>
<DefineConstants>Debug</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<OutputPath>bin\$(Configuration)\</OutputPath>
<IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath>
</PropertyGroup>
<ItemGroup>
<Compile Include="Product.wxs" />
<!-- This will be your default one -->
<Compile Include="HeatGeneratedFileList.wxs" />
<!-- This is the Heat created one -->
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MyApplication\MyApplication.csproj">
<Name>MyApplication</Name>
<Project>{6064dca8-d649-4592-bed5-34039bb3d30d}</Project>
<Private>True</Private>
<DoNotHarvest>True</DoNotHarvest>
<RefProjectOutputGroups>Binaries;Content;Satellites</RefProjectOutputGroups>
<RefTargetDir>INSTALLFOLDER</RefTargetDir>
</ProjectReference>
</ItemGroup>
<Import Project="$(WixTargetsPath)" Condition=" '$(WixTargetsPath)' != '' " />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets" Condition=" '$(WixTargetsPath)' == '' AND Exists('$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets') " />
<Target Name="EnsureWixToolsetInstalled" Condition=" '$(WixTargetsImported)' != 'true' ">
<Error Text="The WiX Toolset v3.11 (or newer) build tools must be installed to build this project. To download the WiX Toolset, see http://wixtoolset.org/releases/" />
</Target>
<Target Name="BeforeBuild">
<HeatDirectory Directory="..\MyApplication\bin\$(Configuration)\netcoreapp3.1"
PreprocessorVariable="var.MyApplication.ProjectDir"
OutputFile="HeatGeneratedFileList.wxs"
ComponentGroupName="HeatGenerated"
DirectoryRefId="INSTALLFOLDER"
AutogenerateGuids="true"
ToolPath="$(WixToolPath)"
SuppressFragments="true"
SuppressRegistry="true"
SuppressRootDirectory="true" />
</Target>
<!--
To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Wix.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
All I want is a simple installer that copies over the EXE and associated DLLs. Why is this so difficult? Can WiX do what I need? Or do I need a different installer toolkit?

Installer conditionally picking up files

I'm building a Wix installer and I need two separate versions of said installer. One that picks up the latest development build of the project and one that picks up the latest release build. Currently my fragment looks like this:
<Property Id="Program.ReleaseBuild" Value="0" />
<?define ReleaseBuild = [Program.ReleaseBuild]?>
<Fragment>
<ComponentGroup Id="ProductComponents" Directory="InstallFolder">
<Component Id="TheExe" Guid="GUID_GOES_HERE">
<?if $(var.ReleaseBuild) = 1?>
<File Id="ProjectExe" Source="(Rel Project Path)/program.exe" />
<?else?>
<File Id="ProjectExe" Source="(Dev Project Path)/program.exe" />
<?endif?>
</Component>
</ComponentGroup>
</Fragment>
And I have a transform on the msi that transforms the file after build. But the problem is that the file is picked up on compile time not install time, so both version of the installer end up having the same file contained in them. Any idea how I can conditionally grab a dev file or a rel file in the same wix project?
If you want to create installation packages based on build quality (debug versus release), you can use two product configuration and select the source based on it. This way, you can run msbuild twice, one for each configuration. I don't understand the purpose of the transform you mentioned.
So here are steps you could take to accomplish this:
Create an empty solution.
Add your wixproj to it.
Add your csproj to it.
Add a reference of the csproj to the wixproj.
Modify your File[Source] to use the project reference, this way:
<File Source="$(var.MyProject.TargetPath)" Id="ProjectExe" />
The $(var.MyProject.TargetPath) will automatically get the exe from the correct path.
Create a batch file to run the msbuild twice, one for each configuration, with the following commands:
C:\> msbuild mySolution.sln /p:Configuration=Debug
C:\> msbuild mySolution.sln /p:Configuration=Release
The result will be two installation packages, one for each configuration.

How to install on System 32 folder using an x86 compilation with Wix

I'm building an x86 compilation of a Wix project.
For some technical reasons, I need to copy some .dlls in system 32 folder. To do so, I wrote the following lines:
// In an external .wxs file
<?define System32Dir= "C:\Windows\System32" />
<Component Id="cmp32bits" Directory="System32Dir" Guid="*">
<Condition>NOT VersionNT64</Condition>
<File Id="file32bits" KeyPath="yes" Source="mypathtothefile" />
</Component>
<Component Id="cmp64bits" Directory="System32Dir" Guid="*">
<Condition>VersionNT64</Condition>
<File Id="file32bits" KeyPath="yes" Source="mypathtothefile" />
</Component>
But then it fails as System32Dir contains slashes, points...
Following this guide, if I use the SystemFolder for x86 systems and the System32Folder for x64 systems, when I install the product, for 64 bits machines the .dlls are installed in SysWOW64. I understand that, if I compile the Wix project in x86, the System32Folder will be translated the same for 64 bits systems.
That's the reason why I came to hand write the "C:\Windows\System32", but it's not working yet.
The question and important point is, how to copy anything in C:\Windows\System32 directory if I'm installing an x86 project into a 64 bits machine?
Thanks a lot.
EDIT: Added a CustomAction but could not make it work.
<CustomAction Id="CopyToSystem32" ExeCommand="[INSTALLFOLDER]copy.bat" Directory="INSTALLFOLDER" Execute="deferred" Return="asyncWait" />
<InstallExecuteSequence>
<Custom Action="CopyToSystem32" After="InstallFiles" >NOT Installed</Custom>
</InstallExecuteSequence>
Where the .bat file looks like this:
copy 64bits.txt C:\Windows\System32
But actually, I can't make the CustomAction work...
You can't officially install 64-bit components from a 32-bit package:
http://msdn.microsoft.com/en-us/library/aa367451(v=vs.85).aspx
and Windows will typically keep redirecting you to 32-bit folders.
I'd examine the reasons why you are being asked to install Dlls to the 64-bit system folder from a 32-bit install. If they are 64-bit Dlls then you potentially need a separate 64-bit install:
http://blogs.msdn.com/b/heaths/archive/2008/01/15/different-packages-are-required-for-different-processor-architectures.aspx
and if they are 32-bit Dlls then an app that requires them in the 64-bit system folder needs some re-architecting to get up to date with 64-bit systems.
I did the below things in my installer for this requirement.
Ship files to somewhere in installation location and later copy those files to “C:\Windows\System32” using DTF deferred custom action.

Writing in the x64 part of the registry with an otherwise x86 mai pack created in Wix

I'm writing an installer pack for a product using Wix, the whole thing is in x86, but now i need to add a key to the x64 part of the registry. I looked around and found this stack answer which I thought would solve my problem. But I'm getting a ICE80 error (not a warning) which tells me that I basically need to change my Package Platform attribute to x64.
I would however rather avoid that because as I mentioned it's only one registry key that needs to be in x64.
So my question is: Is there another way to resolve the ICE80 error or do I need to build two msi packages, one for x86 and one for x64.
Here is some of my code to further illustrate what I'm trying to do:
<Component Id="Foo" Guid="{GUID}" Win64="yes">
<RegistryKey Root="HKLM" Key="Software\Microsoft\Windows NT\CurrentVersion\Terminal Server\Compatibility\IniFiles">
<RegistryValue Type="integer" Name="Hello" Value="1"/>
</RegistryKey>
<Condition><![CDATA[VersionNT64]]></Condition>
</Component>
<Component Id="Bar" Guid="{GUID}">
<RegistryKey Root="HKLM" Key="Software\Microsoft\Windows NT\CurrentVersion\Terminal Server\Compatibility\IniFiles">
<RegistryValue Type="integer" Name="Hello" Value="1"/>
</RegistryKey>
</Component>
Any help is appreciated!
Windows Installer doesn't support a 32-bit package writing to the 64-bit registry (or file system). A 64-bit package can write to both 32-bit and 64-bit portions.
Perhaps it didn't work then. I am using Wix v10 and in my x86 WIX project, and adding Win64="yes"initially generated ICE80 error. Once I suppressed that warning (in Visual Studio, "Tool Settings" -> "Suppress specific validation:" column), I no longer get that error. When I ran the x86 installer on Windows 2012 R2, those x64 reg keys were created.
Add Win64="yes" to the registry entry you want to put in the 64-bit in the registry..:) I have not included the condition in my own and it works perfectly with just the Win64 attribute.

WIX: Howto set the name of the msi output file dynamically

I want to include some dynamic part in the filename of the msi file my wix projects produce. This dynamic part should be controlled by variables which are part of my wix project and are declared like this:
<?define ProductVersion="7.1.0.1" ?>
Does anybody know about a way of sending that value of that wix variable to the linker to use it as a part of the output filename?
By the way: I'm using Wix3
You could update the OutputName of your .wixproj and use an MSBuild variable to pass through the version number or any other variable you like.
My build script looks like this:
set PRODUCTVERSION=7.1.0.1
MSBuild.exe /p:Configuration=Debug /p:ProductVersion=%PRODUCTVERSION% Installer.wixproj
And my WiX project looks like this:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProductVersion>1.0.0.0</ProductVersion>
<ProjectGuid>{b7415c44-8d59-4ac2-b698-03e399a305e3}</ProjectGuid>
<SchemaVersion>2.0</SchemaVersion>
<OutputName>Installer.$(ProductVersion)</OutputName>
...
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<OutputPath>bin\$(Configuration)\</OutputPath>
<IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath>
<DefineConstants>Debug</DefineConstants>
<WixVariables>ProductVersion=$(ProductVersion)</WixVariables>
</PropertyGroup>
...
</Project>
The output would be:
Installer.7.1.0.1.msi
The msi file name is not determined by your wix files, but by the light.exe -out switch. You can use the same value for -out and inside your wix files if you do the following in your build script, assuming it is a batch script:
set an environment variable with set
productversion=1.2.3
Pass -out foo%productversion%.msi to the
light.exe linker
use the same environment variable in
your wix files as
$(env.productversion)
Open *.wixproj (example: Setup.wixproj)
Go to the end of the file.
$(Configuration) = Debug| Release …
Set path of your application on AssemblyFiles.
<Target Name="BeforeBuild">
<GetAssemblyIdentity AssemblyFiles="..\App\bin\$(Configuration)\App.exe">
<Output TaskParameter="Assemblies" ItemName="AsmInfo" />
</GetAssemblyIdentity>
<CreateProperty Value="$(SolutionName)_%(AsmInfo.Version)_$(Configuration)">
<Output TaskParameter="Value" PropertyName="TargetName" />
</CreateProperty>
</Target>
Output = App_1.0.0.0_Debug.msi
I found a couple of great reference posts to do just this operation:
http://blog.tentaclesoftware.com/archive/2009/05/03/38.aspx
and a follow-on with a better method of creating the output file using a pre-build event:
http://blog.tentaclesoftware.com/archive/2010/08/05/99.aspx
I know the links are old, but the method is sound.
Here are the basic steps:
Put the version you wish to use into a file you can access from your project. I use the main executable of my installation because I also bind to that version in my wxs. In my case, since I am building with C# and use SVN, I have a template version of my assembly.cs, assembly.cs.txt, that I run subwcrev on as a pre-build event to create the assembly.cs that gets compiled into my executable (I actually do it in a separate project in my solution). Subwcrev inserts some date and revision information that I use to create a version in the form "major.minor.version.0" where I use "year.month.revision.0" for my version.
Now, I usually just set AssemblyFileVersion with this method, but to be able to use my version number in the wixproj build event referenced in the post above, I also need to set AssemblyVersion since that is what can be accessed with GetAssemblyIdentity. This method would be questionable if I were really using an assembly someone else links to, but for me it is OK since it is in my final application executable.
Follow the steps outlined in the second post (the first post discusses using the binding method for version in wxs and how to unload and edit the wixproj - valuable context for the second post).
Works like a charm!
Update some years later - it looks like the links I referenced are dead. Here is a snapshot of what I changed to make it work.
In my main product wxs file, I changed (some names have been changed or omitted to protect the innocent and the guilty):
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<!-- TODO: Remove the comments around this Component element and the ComponentRef below in order to add resources to this installer. -->
<Component Id="ProductComponent" Guid="DF1AB3A6-17D0-4176-B3AC-C073AC47AA80">
<RemoveFile Id="PurgeAppFolder" Name="*.*" On="uninstall" />
<File Source="$(var.MyApp.TargetPath)" KeyPath="yes"/>
<File Source="$(var.MyApp.ProjectDir)\bin\Release\MyData.dll"/>
<File Source="$(var.MyApp.ProjectDir)\bin\$(var.MyApp.Configuration)\ICSharpCode.SharpZipLib.dll"/>
</Component>
</ComponentGroup>
to
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<!-- TODO: Remove the comments around this Component element and the ComponentRef below in order to add resources to this installer. -->
<Component Id="ProductComponent" Guid="DF1AB3A6-17D0-4176-B3AC-C073AC47AA80">
<RemoveFile Id="PurgeAppFolder" Name="*.*" On="uninstall" />
<File Id="MAINEXECUTABLE" Source="$(var.MyApp.TargetPath)" KeyPath="yes">
<Shortcut Directory="ApplicationProgramsFolder" Id="MYAPPEXEAPF" Name="My App Name" Icon="MyAppIcon.exe" IconIndex="0" Advertise="yes" />
<Shortcut Directory="ApplicationDesktopFolder" Id="MYAPPEXEADF" Name="My App Name" Icon="MyAppIcon.exe" IconIndex="0" Advertise="yes" />
</File>
<File Source="$(var.MyApp.ProjectDir)\bin\Release\MyData.dll"/>
<File Source="$(var.MyApp.ProjectDir)\bin\$(var.MyApp.Configuration)\ICSharpCode.SharpZipLib.dll"/>
</Component>
</ComponentGroup>
and up at the top I added:
<Product Id="C86505BF-303F-4D6B-8F5F-43A57635F85D" Name="My Application Name" Language="1033" Version="!(bind.FileVersion.MAINEXECUTABLE)" Manufacturer="My Software Shop" UpgradeCode="D1C628E5-5478-4DA5-A31A-F9191D5B1544">
Note that the Version is bound to the file version of MAINEXECUTABLE, which is defined in the main product component.
I hope this helps!
If you have several configurations then inside .wixprj file you can do the following
<OutputName Condition="'$(Configuration)' == 'User'">User.Setup</OutputName>
<OutputName Condition="'$(Configuration)' == 'Designer'">Designerr.Setup</OutputName>
Since it's just a file name, why not have a post-build action that renames the file in your build script (assuming MSBuild)?
Do the variables have to be defined in WiX? I'm building my setup binaries from MSBuild, and I've simply set the output file name to MyProject_$(Platform) -- I expect that any MSBuild variable substitution will work equally well.
Product Id="GUID" Name="whatevername $(var.ProductVersion)"