Environment variables in WIX in BeforeBuild action - wix

On my .wixproj file, I have a before build action to get the assembly version and append it to the output file name. However, how my code builds in my local machine is different than the one on the the TFS Server. So I am trying the following:
<Target Name="BeforeBuild">
<GetAssemblyIdentity AssemblyFiles="$(TargetDir)\myassembly.dll" Condition="$(env.COMPUTERNAME)=='THETFSSERVER'">
<Output TaskParameter="Assemblies" ItemName="AssemblyVersions" />
</GetAssemblyIdentity>
<GetAssemblyIdentity AssemblyFiles="$(SolutionDir)\Source\Project\bin\myassemblydll" Condition="$(env.COMPUTERNAME)!='THETFSSERVER'">
<Output TaskParameter="Assemblies" ItemName="AssemblyVersions" />
</GetAssemblyIdentity>
<CreateProperty Value="$(OutputName)_%(AssemblyVersions.Version)">
<Output TaskParameter="Value" PropertyName="TargetName"/>
</CreateProperty>
</Target>
I've tried &(env.COMPUTERNAME), [&COMPUTERNAME], and several other options that I saw online, and some other that made sense, but I can't manage to find what exactly to use to look at the computer name.... any ideas how to achieve that?

There is. It is $(COMPUTERNAME). There is a very easy way to find out what all the properties are when you first load a build script in MSBuild. All you have to do is generate a log using the diagnostic verbosity. Then look at the top of the log file and it will list out all initial properties and items.
For example:
msbuild.exe /l:FileLogger,Microsoft.Build.Engine;verbosity=diagnostic;logfile=diag.log MyProject.proj
Link from where the answer was taken
Alsol, the tfs is building under msbuild, and you probably building form visual studio. So, the condition can use $(BuildingInsideVisualStudio) property and not the computer name.

Related

Assembly Version Captured Before "BeforeBuild" Target

I am using MSBuild.Community.Tasks to help with two things, namely adjusting the version and zipping up a file. I am not married to this, so an alternate approach is welcome provided it produces what I'm looking for. The goal is to increment the build number before the build, then ZIP up a new DLL (with a couple of other files) after the build. The ZIP file should be named according to the build.
I am almost there, however, my version number in my DLL is always one step behind my version.txt file (auto-gen'd from the Version task). Here is what I have in the BeforeBuild target:
<Target Name="BeforeBuild" BeforeTargets="PrepareForBuild">
<Message Text=" --=== Before Build ===--"></Message>
<ItemGroup>
<PreviousFiles Include="$(MSBuildProjectDirectory)\BuildPackage\$(AssemblyName).*.zip">
<InProject>false</InProject>
</PreviousFiles>
</ItemGroup>
<Delete Files="#(PreviousFiles)"></Delete>
<Delete Files="$(MSBuildProjectDirectory)\BuildPackage\$(AssemblyName).dll"></Delete>
<Version VersionFile="version.txt" RevisionType="Increment">
<Output TaskParameter="Major" PropertyName="Major" />
<Output TaskParameter="Minor" PropertyName="Minor" />
<Output TaskParameter="Build" PropertyName="Build" />
<Output TaskParameter="Revision" PropertyName="Revision" />
</Version>
</Target>
This deletes any files from the previous build, then increments the version.txt file correctly.
Next, in my AfterBuild target I have put this together:
<Target Name="AfterBuild" AfterTargets="Build">
<Message Text=" --=== After Build ==--"></Message>
<AssemblyInfo CodeLanguage="CS" OutputFile="AssemblyVersion.cs" AssemblyVersion="$(Major).$(Minor).$(Build).$(Revision)" AssemblyFileVersion="$(Major).$(Minor).$(Build).$(Revision)" />
<ItemGroup>
<ProjectOutputFiles Include="bin\$(AssemblyName).dll">
<InProject>false</InProject>
</ProjectOutputFiles>
<ZipFiles Include="$(MSBuildProjectDirectory)\BuildPackage\*.*" Exclude="$(MSBuildProjectDirectory)\BuildPackage\*.zip">
<InProject>false</InProject>
</ZipFiles>
</ItemGroup>
<Copy SourceFiles="#(ProjectOutputFiles)" DestinationFolder="$(MSBuildProjectDirectory)\StorePackage" />
<Zip Files="#(ZipFiles)" WorkingDirectory="$(MSBuildProjectDirectory)\BuildPackage" ZipFileName="$(MSBuildProjectDirectory)\BuildPackage\$(AssemblyName).$(Major)-$(Minor)-$(Build)-$(Revision).zip" ZipLevel="9" />
</Target>
Basically, I'm updating AssemblyInfo.cs and specifying some file groups. I then copy the project output over and finally ZIP up the required files.
This all works great, except my DLL version is always 1 revision number behind my actual revision number - i.e., what is stored in version.txt and what the name of the .ZIP file is saved out as.
Am I missing something obvious here? It's like the version is captured before the build process even starts or something.
Thanks in advance.
GOSH, yes, I am missing something obvious. As soon as I re-read the question, right before posting, it became clear what I had done incorrectly. I even spelled it out.
The fix was to move the AssemblyInfo task up to the BeforeBuild target. This is the task that outputs the .cs file containing the attributes required to inject the correct versioning information into the DLL. Leaving the update of this file until after the build has completed meant that my DLL was going to be losing the race in perpetuity.
I'm going to go ahead and post this anyways, in case someone else runs into something similar.
Cheers.
Edit For what it's worth, the final version of these targets required moving the ZIP operation out into a separate target (I called it ZipProjectOutput) because of a race condition that I couldn't resolve: the DLL was never showing up in the ZIP file, and I think that the timing of the OS releasing a lock on the file or something might have been to blame.
I used the AfterTargets="AfterBuild" to have the ZipProjectOutput target execute when the other was complete. I'm not entirely happy about this as I'm not certain I'm just "winning" the race here, rather than solving the problem, but this is working for me now rather slickly. #WFM

Using XmlUpdate to set Version information from an Assembly

Using MSBuild and MSBuild Community Tasks I am trying to do something very simple:
Get version information from an assembly.
Update a .nuspec file with that version information.
My MSBuild target looks like this:
<Target Name="Package">
<GetAssemblyIdentity AssemblyFiles="%(PackageDir.FullPath)\MyAssembly.dll">
<Output TaskParameter="Assemblies" ItemName="AssemblyIdentity" />
</GetAssemblyIdentity>
<XmlUpdate
Prefix="nu"
Namespace="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"
XmlFileName="%(PackageDir.FullPath)\MyAssembly.nuspec"
XPath="/nu:package/nu:metadata/nu:version"
Value="%(AssemblyIdentity.Version)" />
</Target>
The problem I'm having is that the NuGetPack task runs TWICE: The first time, the Assembly version is missing but the paths are correct, the second time the Assembly version is correct but the paths are missing!
Here is the output:
Updating Xml Document "D:\MyProject\package\MyAssembly.nuspec".
1 node(s) selected for update.
XmlUpdate Wrote: "".
Updating Xml Document "\MyAssembly.nuspec".
D:\MyProject\MyProject.build(64,9): error : Could not find file
'D:\MyAssembly.nuspec'.
Done Building Project "D:\MyProject\MyProject.build" (Package target(s)
) -- FAILED.
I also tried using the NuGetPack task, but got similar results. Help is greatly appreciated!
I seem to have solved it, though I'm still not sure why the code in my original question does not work.
Instead of specifying paths via concatenation (e.g. AssemblyFiles="%(PackageDir.FullPath)\MyAssembly.dll") I put each path into its own item:
<ItemGroup>
...
<PackageVersionAssembly Include=".\build-artifacts\package\MyAssembly.dll"/>
<NuSpecFile Include=".\build-artifacts\package\MyAssembly.nuspec"/>
...
</ItemGroup>
I made the same change in the task and made the same change to references to the .nuspec file.
The new Package target looks like this:
<Target Name="Package">
<GetAssemblyIdentity AssemblyFiles="#(PackageVersionAssembly)">
<Output TaskParameter="Assemblies" ItemName="AssemblyIdentity" />
</GetAssemblyIdentity>
<XmlUpdate
Prefix="nu"
Namespace="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"
XmlFileName="#(NuSpecFile)"
XPath="/nu:package/nu:metadata/nu:version"
Value="%(AssemblyIdentity.Version)" />
</Target>
I hope this helps others!

MSBuild - Create a property with wildcards?

I'm currently moving my project to Visual Studio 2012 and start using nuget.
So I'll use the "NUnit Runners" nuget package instead of a nunit library.
The problem being that nuget creates folders with the package version. For example, NUnit Runners is inside the folder:
src\packages\NUnit.Runners.2.6.1\
Until now, Nunit was inside my lib\NUnit folder.
So, inside my MSBuild file, I was executing the tests by specifying the path:
<PropertyGroup>
<NUnitFolder>$(MSBuildProjectDirectory)\lib\NUnit</NUnitFolder>
</PropertyGroup>
<NUnit Assemblies="..." ToolPath="$(NUnitFolder)" />
But I don't want to have to specify a version number inside my msbuild task, that I would have to update everytime NUnit.Runners is updated.
I tried to play around with CreateProperty, but it doesn't seem to accept wildcards.
I also tried ItemGroup, but it works for a list of files, not a folder.
In the end, instead of trying to create a property with a wildcard, in my case I retrieved the version of NUnit.Runners from the packages.config file.
I now have a Target like this:
<Target Name="GetNUnitFolder">
<!-- Retrieves the version of NUnit.Runners from the solution's packages.config file -->
<XmlRead Namespace=""
XPath="packages/package[#id='NUnit.Runners']/#version"
XmlFileName="$(MSBuildProjectDirectory)\src\.nuget\packages.config">
<Output TaskParameter="Value" PropertyName="NUnitVersion" />
</XmlRead>
<CreateProperty Value="$(MSBuildProjectDirectory)\src\packages\NUnit.Runners.$(NUnitVersion)\tools">
<Output TaskParameter="Value" PropertyName="NUnitFolder" />
</CreateProperty>
</Target>
Note: to be able to use XmlRead, you need the MSBuildCommunityTasks.
And once I have the version, I rebuild my NUnitFolder property.

How to read property value from external file?

I have AssemblyInfo.cs file automatically generated during build. Here's part of .csproj file:
<PropertyGroup>
<Major>2</Major>
<Minor>3</Minor>
<Build>0</Build>
<Revision>0</Revision>
</PropertyGroup>
<Target Name="BeforeBuild">
<SvnVersion LocalPath="$(MSBuildProjectDirectory)" ToolPath="C:\Program Files\VisualSVN Server\bin">
<Output TaskParameter="Revision" PropertyName="Revision" />
</SvnVersion>
<AssemblyInfo CodeLanguage="CS"
OutputFile="$(MSBuildProjectDirectory)\Properties\VersionInfo.cs"
AssemblyVersion="$(Major).$(Minor).$(Build).$(Revision)"
AssemblyFileVersion="$(Major).$(Minor).$(Build).$(Revision)"/>
</Target>
But I don't know how to specify Major and Minor properties outside .csproj file so I don't have to unload project every time I want to change version. I need either to load them from special text file inside project or to somehow set them in project properties dialog. Any suggestions?
Used ReadLinesFromFile to make version in separate file:
<ReadLinesFromFile File="Properties\Version.txt">
<Output TaskParameter="Lines" ItemName="Ver" />
</ReadLinesFromFile>
<SvnVersion LocalPath="$(MSBuildProjectDirectory)" ToolPath="C:\Program Files (x86)\VisualSVN Server\bin">
<Output TaskParameter="Revision" PropertyName="Revision" />
</SvnVersion>
<Message Text="Version: #(Ver).$(Revision)" />
<AssemblyInfo
CodeLanguage="CS"
OutputFile="$(MSBuildProjectDirectory)\Properties\VersionInfo.cs"
AssemblyVersion="#(Ver).$(Revision)"
AssemblyFileVersion="#(Ver).$(Revision)"/>
It is possible to do this using the ability of PropertyFunctions to call certain .NET functions directly. Essentially, you can call File.ReadAllText() when setting a property value.
<PropertyGroup>
<Version>$([System.IO.File]::ReadAllText("Version.txt"))</Version>
</PropertyGroup>
<SvnVersion LocalPath="$(MSBuildProjectDirectory)" ToolPath="C:\Program Files (x86)\VisualSVN Server\bin">
<Output TaskParameter="Revision" PropertyName="Revision" />
</SvnVersion>
<Message Text="Version: $(Version).$(Revision)" />
<AssemblyInfo
CodeLanguage="CS"
OutputFile="$(MSBuildProjectDirectory)\Properties\VersionInfo.cs"
AssemblyVersion="$(Version).$(Revision)"
AssemblyFileVersion="$(Version).$(Revision)"/>
The Version.txt file would just contain the first three version numbers, i.e. "1.2.3" or whatever.
I'm not sure exactly when this was added to MSBuild, but it was probably after this question was asked and answered.
Use property pages, so you could set these properties on the project property sheets dialogs.
You will need to create a properties file and edit your project file (only once) to add an import directive to your properties file. Here is an example.
You can use your external tools
<Exec Command="newversion incMinor AssemblyInfo.cs > newversion.log" />
If it's locking of the csproj files by VS that's your concern, my answer to this question - How to turn off caching of build definitions in Visual studio might help you.
You could move the content of your BeforeBuild task (including the version propertygroup) into a separate proj file and invoke it with an MSBuild task (using a random filename generated by the example in the linked answer above). This would allow you to manually edit your version number properties without having to unload/load your csproj file.

Output all files from a solution in an MSBuild task

In MSBuild, I would like to call a task that extracts all the files in all the project in a specific solution and hold these files in a property that can be passed around to other tasks (for processing etc.)
I was thinking something along the lines of:
<ParseSolutionFile SolutionFile="$(TheSolutionFile)">
<Output TaskParameter="FilesFound" ItemName="AllFilesInSolution"/>
</ParseSolutionFile>
<Message Text="Found $(AllFilesInSolution)" />
which would output the list of all files in the projects in the solution and I could use the AllFilesInSolution property as input to other analysis tasks. Is this an already existing task or do I need to build it myself? If I need to build it myself, should the task output an array of strings or of ITaskItems or something else?
I don't know about tasks, but there are already properties that hold all items. Just look in your typical project file and you'll see which collection they're being added to.
Note the properties Content, Compile, Folder... any time you add a file to a project, it gets put in one of the main collections like this:
<ItemGroup>
<Content Include="Default.aspx" />
<Content Include="Web.config" />
</ItemGroup>
<ItemGroup>
<Compile Include="Default.aspx.cs">
<SubType>ASPXCodeBehind</SubType>
<DependentUpon>Default.aspx</DependentUpon>
</Compile>
<Compile Include="Default.aspx.designer.cs">
<DependentUpon>Default.aspx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<Folder Include="App_Data\" />
</ItemGroup>
Then you can do stuff like this to put the values from existing properties into your properties (the Condition attribute acts as a filter):
<CreateItem Include="#(Content)" Condition="'%(Extension)' == '.aspx'">
<Output TaskParameter="Include" ItemName="ViewsContent" />
</CreateItem>
Or you can do it manually (the Include attribute uses the existing property OutputPath, but it indicates a path that inclues all files):
<CreateItem Include="$(OutputPath)\**\*">
<Output TaskParameter="Include" ItemName="OutputFiles" />
</CreateItem>
There are more details in the MSDN MSBuild documentation that I read when I was mucking with custom build tasks and stuff that was very helpful. Go read up on the CreateItem task and you'll be able to make more sense out of what I posted here. It's really easy to pick up on.
I use the following for solutions with SSRS projects (which dont build under TFS w/o vs installed on the build box). Basically we require that the RDLs be bundled into a build output so we can mark a build for release.
<Target Name="CopyArtifactstoDropLocation">
<CreateItem Include="$(SolutionRoot)\**\*.*">
<Output TaskParameter="Include" ItemName="YourFilesToCopy" />
</CreateItem>
<Copy
SourceFiles="#(YourFilesToCopy)"
DestinationFiles="#(YourFilesToCopy->'$(DropLocation)\$(BuildNumber)\Release\%(RecursiveDir)%(Filename)%(Extension)')" />
</Target>
Just replace the usage of the Copy Task with whatever you need to do with your bundle. Granted this is going to get everything in your solution root, but if your using TFS then you should only have buildable artifacts.