How to read content of text file in Wix Toolset Bootstrapper - installation

I am using the WiX toolset to build an installer, I want to read the version from a text file. The text file is located in mybootstrapper like below:
below is the code where i want to read the content of text file
<Bundle IconSourceFile='product.ico'
Name="Retail Grip"
Version="Version.txt" <!-- i know this is not correct -->
Manufacturer="Company Name"
UpgradeCode="PUT-GUID-HERE">

Oh, it is a WiX bundle - and that's "Wax"? I hear it is a WiX tool of sorts? I am not sure exactly how it works (screenshot down the page in that link). Maybe there are restrictions on the use of compiler variables when using it?
I wrote the below before I saw that Wax file and I thought you had a normal WiX source and not a bundle source. Either way, let me add what I wrote and see if it helps. Similarities.
Also: Neil Sleightholm's WiX Burn Template (towards top). Give that link a spin first please.
In a regular WiX file you could use a pre-processor variable: $(var.CurrentVersion) (compiler variable). Something like this:
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<?define UpgradeCode="PUT-GUID-HERE"?>
<?define CurrentVersion="1.0.0.0"?>
<Product Id="*" Name="Sample" Language="1033" Version="$(var.CurrentVersion)"
Manufacturer="Someone" UpgradeCode="$(var.UpgradeCode)">
<...>
You can put the variables in its own "include file": Variables.wxi.
<Include>
<?define UpgradeCode="PUT-GUID-HERE"?>
<?define CurrentVersion="1.0.0.0"?>
</Include>
Larger sample here for this approach (do have a quick skim of this one).
And then include the file in your main WiX source:
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<?include Variables.wxi ?>
<Product Id="*" Name="Sample" Language="1033" Version="$(var.CurrentVersion)"
Manufacturer="Someone" UpgradeCode="$(var.UpgradeCode)">
<...>
There are also localization variables: WiX (Windows Installer Xml), Create universal variables - link time variable resolution (light.exe), as opposed to the compile time resolution of pre-processor variables (candle.exe). Some context.
Some Relevant Links:
Localization Variables in use: Wix toolset license agreement multi-languages issue
How to make Win64 attribute as a variable in wixlib?
https://helgeklein.com/blog/2014/09/real-world-example-wix-msi-application-installer/

With WiX Include files, you can keep simple values separate from the bulk of the WiX markup. With WiX preprocessing, you can define named substitution values used in attributes or text nodes or conditional compilation, and then refer to them as $(var.name).
-- Version.wxi --
<?xml version="1.0" encoding="utf-8"?>
<Include>
<?define Version="1.2.3" ?>
</Include>
-- Bundle.wxs --
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<?include "Version.wxi" ?>
<Bundle
Version="$(var.Version)"
…
It is sometimes convenient to generate include files in a source project in anticipation of them being used downstream, or generate them as the first steps of a WiX project.
I see you are using Visual Studio for your WiX Bootstrapper project. A Visual Studio project is a specialized MSBuild project (as are most types of Visual Studio projects). That means you can put general MSBuild things into the project file. You can open the .wixproj file as an XML file in an XML editor (such as Visual Studio).
MSBuild allows you define new tasks either from an external DLL or inline, using a .NET language. In this case, a few lines of C# will do fine to define the task. Then you would invoke it before the main build tasks. Like many build systems that use MSBuild, WiX ensures the target BeforeBuild is executed before it gets to work. So, you just have to define BeforeBuild.
The task is named CreateVersionWxi.
<UsingTask
TaskName="CreateVersionWxi"
TaskFactory="CodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v12.0.dll">
<ParameterGroup />
<Task>
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" />
<Using Namespace="System.Xml.Linq" />
<Code Type="Fragment" Language="cs"><![CDATA[
var version = File.ReadAllText("version.txt");
var wxi =
new XDocument(
new XComment("*** GENERATED FILE. DO NOT EDIT ***"),
new XElement("Include",
new XProcessingInstruction("define", "Version='" + version + "'")));
wxi.Save("version.wxi");
]]></Code>
</Task>
</UsingTask>
<Target Name="BeforeBuild">
<CreateVersionWxi />
</Target>
Finally, if you add version.txt to your project with its Build Action, say, as Content, the project will be seen as needing to be rebuilt whenever version.txt changes. That will help if you have the WiX project open in Visual Studio while you are externally changing version.txt.
You don't need to add Version.wxi to your project but doing so increases its visibility to future maintainers.
Tip: Some Visual Studio users are more familiar with the Build Events on the project pages. To clue them in, you could enter this as a pre-build event command line: REM See the BeforeBuild target in the .wixproj file

Related

How do I use a specific version of the F# compiler in MSBuild projects?

I have some F# project files generated using visual studio.
On my computer, which has several F# versions installed, it seems to pick the latest one.
However, I want to use a specific F# compiler - the one installed using version 3.1.2.
How do I do so?
I'm using a similar setup, just that I'm consuming the F# compiler from its nuget package - this works nicer in shared build environments.
You will need a .props file to be included in all of your F# projects, I've called it fsharp_project.props. By changing that props file, you can update the compiler version for all of your F# projects. Its contents should be as follows:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PackageRoot>C:\packages\Fsharp.Compiler.Tools.Nuget</PackageRoot>
<FscToolPath>$(PackageRoot)\tools</FscToolPath>
<FSharpVersion>v3.0</FSharpVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="FSharp.Core">
<HintPath>$(PackageRoot)\tools\fsharp.core.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
You need to adjust the path to your local version of the F# compiler, and also to the core libraries that you wish to use.
Then, modify your .fsproj file to consume that file as follows:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="C:\whereever\fsharp_project.props"/>
...
Further down in your .fsproj file you will see a reference of FSharp.Core.dll. Remove that - in the props file, there is already a reference to the version of the core libraries that come with the compiler.
Re-load your project and re-build, it will print out the full path of the fsc.exe that it is using.
As a side note: The same trick with .props files is also incredibly helpful for referencing the right version of FSharp.Core.dll in C# consumers of your F# code - that's a frequent source of runtime errors. Include a props file that only references FSharp.Core.dll in each .csproj, and you will be able to switch all C# projects to a new version of the core libraries by just updating the `.props' file.

Custom Build Rule fails after converting to VS2013

I need to integrate a legacy VS2008 project into my VS2013 solution. This project uses some custom build rules which initially worked after converting the .vcproj to a .vcxproj. However, when doing a fresh checkout of the project including the .vcxproj, the project file can no longer be opened.
I've tracked it down to this issue:
The project file references a couple of custom build rules like this:
<ImportGroup Label="ExtensionSettings">
<Import Project="..\..\..\tools\build\ms_mc.props" />
(8 similar lines follow)
</ImportGroup>
However, the ms_mc.props file is not present, but there is a ms_mc.rule file. If I convert the VS2008 solution with VS2013 (and assumably also if I opened it in VS2008, which I don't possess), the ms_mc.props file (plus a .targets and a .xml file) is created. However, if I delete that file and open the converted VS2013 project, the file does not get created.
I realized, in the old .vcproj, the corresponding lines are
<ToolFiles>
<ToolFile RelativePath="..\..\..\tools\build\ms_mc.rule" />
(8 similar lines follow)
</ToolFiles>
Why does VS2008 reference the .rule file and VS2013 imports the .props file without specifying the .rule file? And more importantly: How can I make this work again?
The .rule and .props file are added for reference
ms_mc.rule:
<?xml version="1.0" encoding="utf-8"?>
<VisualStudioToolFile
Name="MS MC"
Version="8,00"
>
<Rules>
<CustomBuildRule
Name="MS_MC"
DisplayName="Microsoft Message Catalogue Compiler"
CommandLine="mc [Verbose] [inputs] [RCIncludePath] [CIncludePath]"
Outputs="[$RCIncludePath]\$(InputName).rc;[$RCIncludePath]\$(InputName).h"
FileExtensions="*.mc"
ExecutionDescription="Compiling Message Catalogue $(InputName).mc"
>
<Properties>
<BooleanProperty
Name="Verbose"
DisplayName="Verbose"
Description="Gives verbose output. (-v)"
Switch="-v"
/>
<StringProperty
Name="RCIncludePath"
DisplayName="RC include file path"
Description="Gives the path of where to create the RC include file and the binary message resource files it includes. (-r [pathspec])"
Switch="-r [value]"
DefaultValue=".\"
/>
<StringProperty
Name="CIncludePath"
DisplayName="C include file path"
Description="Gives the path of where to create the include header file. (-h [pathspec])"
Switch="-h [value]"
DefaultValue=".\"
/>
</Properties>
</CustomBuildRule>
</Rules>
</VisualStudioToolFile>
ms_mc.props (after Conversion to VS2013):
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup
Condition="'$(MS_MCBeforeTargets)' == '' and '$(MS_MCAfterTargets)' == '' and '$(ConfigurationType)' != 'Makefile'">
<MS_MCBeforeTargets>Midl</MS_MCBeforeTargets>
<MS_MCAfterTargets>CustomBuild</MS_MCAfterTargets>
</PropertyGroup>
<PropertyGroup>
<MS_MCDependsOn
Condition="'$(ConfigurationType)' != 'Makefile'">_SelectedFiles;$(MS_MCDependsOn)</MS_MCDependsOn>
</PropertyGroup>
<ItemDefinitionGroup>
<MS_MC>
<Verbose>False</Verbose>
<RCIncludePath>.\</RCIncludePath>
<CIncludePath>.\</CIncludePath>
<CommandLineTemplate>mc [Verbose] [inputs] [RCIncludePath] [CIncludePath]</CommandLineTemplate>
<Outputs>%(RCIncludePath)\%(Filename).rc;%(RCIncludePath)\%(Filename).h</Outputs>
<ExecutionDescription>Compiling Message Catalogue %(Filename).mc</ExecutionDescription>
</MS_MC>
</ItemDefinitionGroup>
</Project>
I found this blog post for VS2010 which states the following:
Custom build rule is a build feature introduced in VS2005. It provides the ability for the users to easily Plug-In third party tools to use in their build process. The custom build rule is defined by “.rules” files.
and more importantly
In VS2010, due to the migration to MSBuild, the information in the rules file is represented by three files: .XML, .props and .targets files.
This basically means that the .XML, .props and .targets files are in fact not created by VS2008; instead, they are a replacement of the old .rules file format since VS2010. Using this information, I can now safely check in those new files without breaking the VS2008 solution. I might have to adapt the new files manually in order to make them work as before, as also mentioned in the blog.

Is there a way to add an 'Import' to a project in a VS extension?

I want to add a new <Import> to a project when I detect that a particular type of file has been added to the project. (the <Import> adds a task to the build process that takes the file and performs work during a build).
(Detection of a file having been added to the project is done using IVsSolutionEvents.HandleItemAdded).
I currently have code that uses Microsoft.Build.Evaluation.Project to add an Import element to the project. However this is an edit to a project file on disk. If I use this code to add an Import after detecting the addition of a new item to the project I create a conflict between a change on disk (the new Import) and an in-memory change (the addition of the new file). The user is then presented with a dialog where they must choose which change to throw away.
My question is this:
Is there a way to add a new <Import> to a project via the visual studio extensibility API in such a way that the modification to the project would be "in-memory", avoiding a conflict between the addition of the new project item, and the addition of the Import?
For existing project types, I've found the easiest way is leveraging NuGet. You can define a NuGet package which contains the .targets file in a special build/ folder, and NuGet will automatically add the <Import> when it is added to a project within Visual Studio. It will also update the references if you upgrade the package in the future. A full example is the packaging of the Antlr4BuildTasks package, which currently uses the following .nuspec file to create the package. The key here is the section following the <!-- Build Configuration --> comment.
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata minClientVersion="2.7">
<id>Antlr4</id>
<version>0.0.0</version>
<authors>Sam Harwell, Terence Parr</authors>
<owners>Sam Harwell</owners>
<description>The C# target of the ANTLR 4 parser generator for Visual Studio 2010+ projects. This package supports projects targeting .NET 2.0 or newer, and built using Visual Studio 2010 or newer.</description>
<language>en-us</language>
<projectUrl>https://github.com/sharwell/antlr4cs</projectUrl>
<licenseUrl>https://raw.github.com/sharwell/antlr4cs/master/LICENSE.txt</licenseUrl>
<iconUrl>https://raw.github.com/antlr/website-antlr4/master/images/icons/antlr.png</iconUrl>
<copyright>Copyright © Sam Harwell 2014</copyright>
<releaseNotes>https://github.com/sharwell/antlr4cs/releases/v$version$</releaseNotes>
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<developmentDependency>true</developmentDependency>
<tags>antlr antlr4 parsing</tags>
<title>ANTLR 4</title>
<summary>The C# target of the ANTLR 4 parser generator for Visual Studio 2010+ projects.</summary>
<dependencies>
<dependency id="Antlr4.Runtime" version="$version$" />
</dependencies>
</metadata>
<files>
<!-- Tools -->
<file src="..\tool\target\antlr4-csharp-$CSharpToolVersion$-complete.jar" target="tools"/>
<!-- Build Configuration -->
<file src="..\runtime\CSharp\Antlr4BuildTasks\bin\net40\$Configuration$\Antlr4.net40.props" target="build\Antlr4.props"/>
<file src="..\runtime\CSharp\Antlr4BuildTasks\bin\net40\$Configuration$\Antlr4.net40.targets" target="build\Antlr4.targets"/>
<file src="..\runtime\CSharp\Antlr4BuildTasks\bin\net40\$Configuration$\Antlr4BuildTasks.net40.dll" target="build"/>
</files>
</package>

How to tweak / customize the Visual Studio / MS Build process to execute my new target after a successful build?

I've created a custom task to get me a three-part version number of my assembly that was built in MSBuild.
I've created a custom <Target Name="GetVersion"> for this, and it works nicely - the three-part version number(1.5.2) is stored into a ThreePartBuildNumber property in MSBuild.
But how do I tell MSBuild inside Visual Studio 2010 to call this target once it's compiled my assembly, and before creating my WiX Setup project (where I'd like to set the WiX install script's Product/#Version to this three-part version number automatically)?
How can I "plug" this new target into the usual VS 2010 build process?
Update:
OK, I've managed to get this into the *.wixproj file which is also a MSBuild file, really. In the <Target Name="BeforeBuild">, I can successfully determine the three-part version number, and it's stored inside a MSBuild property called ThreePartVersionNumber.
But how on earth can I now access this properly filled MSBuild property in my WiX setup? I tried setting <Product Version="$(var.ThreePartVersionNumber) ...>, but that doesn't work - it doesn't seem to find the variable.... neither works with the sys. or env. prefixes, either....
So how do I make this MSBuild property that has the information I need "visible" to the WiX installer script/XML ?!?!?!? I can't seem to see the forest for all those angle brackets .....
Use the /verbosity:d switch to get a full view of all the targets that were performed and their rough reason for being called (dependent-on). Identify the exact thing you want to be before or after or dependent upon. Besides using the depends attributes on your Target, there are also various properties that are used to collect dependencies for various purposes. You can identify these by using /preprocess and then looking up the Targets that catch your eye from the previous step.
I've found that specific answers often don't work, as the build situation is different for my language or the exact inclusion order matters or other minor things; so this is how I've found the real triggers in my case.
What I've done in the end:
inside my WiX setup project, in the myproject.wixproj MSBuild file, I've added a new custom task like this:
<UsingTask TaskName="GetThreePartVersion" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<AssemblyPath ParameterType="System.String" Required="true" />
<ThreePartVersion ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Using Namespace="System.Diagnostics" />
<Code Type="Fragment" Language="cs">
<![CDATA[
Log.LogMessage("Getting version details of assembly at: " + this.AssemblyPath, MessageImportance.High);
Version v = Version.Parse(FileVersionInfo.GetVersionInfo(this.AssemblyPath).FileVersion);
this.ThreePartVersion = v.ToString(3);
]]>
</Code>
</Task>
</UsingTask>
and then in the BeforeBuild target, I added these lines to call this task and define a WiX constant with the results:
<Target Name="BeforeBuild">
<GetThreePartVersion AssemblyPath="$(SolutionDir)Plugin\$(OutputPath)Swisscom.Vidia.Plugin.dll">
<Output TaskParameter="ThreePartVersion" PropertyName="ThreePartVersionNumber" />
</GetThreePartVersion>
<PropertyGroup>
<DefineConstants>ThreePartBuildVersion=$(ThreePartVersionNumber)</DefineConstants>
</PropertyGroup>
<Message Text="Three-part version: $(ThreePartVersionNumber)" />
</Target>
and now in my WiX project.wxs file, I can reference that constant that's been defined, and use it for the <Product Version="..." ... /> attribute:
<Product Id="*" Name="MyProject" Language="1033"
Version="$(var.ThreePartBuildVersion)" ......>
It took a bit of twiddling and a lot of trial & mostly error until I finally got it right - but this is the way it works for me now. Hope this might help some other soul some day....

Visual Studio 2010: How to publish an ASP.NET web app to a target folder with MSBUILD?

In Visual Studio 2010, you know how you can change your configuration (debug, release, etc), right-click a project in the solution explorer, click publish, and have all the important web app project files for the selected configuration copied to a target folder along with an xdt-transformed web.config? Well, I am looking for the MSBUILD equivalent of exactly that.
My challenge to you: Provide the ONE LINE that I need to execute at my command prompt in order to accomplish this. No third party programs. No tutorial videos. Just a single, straight-up command that I can literally copy from your response, paste into a command window, modify as necessary to support my directory structure, and then hit enter.
If not, then perhaps someone could provide a link to a complete MSBUILD reference showing every command, switch, and value I can use at the command line.
Put the below to ProjectPublish.MSBuild.xml file (change PropertyGroup as needed):
<?xml version="1.0" encoding="utf-8" ?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Publish">
<PropertyGroup>
<ProjectFile>Path\To\Web.csproj</ProjectFile>
<PublishDir>Path\For\Publish\Output</PublishDir>
<TempDir>Path\To\Temp\Folder</TempDir>
<BuildConfig>Release|Debug</BuildConfig>
</PropertyGroup>
<Target Name="Publish">
<MSBuild Projects="$(ProjectFile)"
Properties="Configuration=$(BuildConfig);WebProjectOutputDir=$(PublishDir);OutDir=$(TempDir)\;BuildingProject=true"
Targets="ResolveReferences;_CopyWebApplication" />
</Target>
</Project>
Calling this from command line (or .bat file) should do the trick:
%windir%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe ProjectPublish.MSBuild.xml
I found the solution I was looking for after all these months here
In case the above link goes bad, here's the skinny of what it says:
Unload then edit your project file. Look for the line where it's importing Microsoft.WebApplication.targets. Will look like:
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />
Beneath that line, paste in this XML:
<Target Name="PublishToFileSystem" DependsOnTargets="PipelinePreDeployCopyAllFilesToOneFolder">
<Error Condition="'$(PublishDestination)'==''" Text="The PublishDestination property must be set to the intended publishing destination." />
<MakeDir Condition="!Exists($(PublishDestination))" Directories="$(PublishDestination)" />
<ItemGroup>
<PublishFiles Include="$(_PackageTempDir)\**\*.*" />
</ItemGroup>
<Copy SourceFiles="#(PublishFiles)" DestinationFiles="#(PublishFiles->'$(PublishDestination)\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="True" />
</Target>
Now, run this in a command prompt within the same folder as your project file:
msbuild TestWebApp.csproj "/p:Platform=AnyCPU;Configuration=Debug;PublishDestination=C:\pub" /t:PublishToFileSystem
Remember to specify the path to MSBUILD in the command or add the path to your global path environmental variable (which is what I did). On my machine, it was here:
C:\Windows\Microsoft.NET\Framework\v4.0.30319
To test this, I put a config transform in my Web.Release.config to add an AppSetting key (if you do this, make sure the AppSettings node is present in your base config file or you will get an error). When I used the above command to build the debug configuration, the key was not present in the published config file as expected. However, when I used the release config, the key was successfully added to the file.
I really wish Microsoft hadn't obfuscated the heck out of this. At any rate, this is the simplest solution I have found anywhere on the internet. I hope it helps the rest of you.

Resources