Get NuGet package folder in MSBuild - msbuild

I want to call executable tools like NUnit which I manage via NuGet in MSBuild:
<Target Name="Test">
<CreateItem Include="$(BuildCompileDirectory)\*.Tests.*dll">
<Output TaskParameter="Include" ItemName="TestAssemblies" />
</CreateItem>
<NUnit
Assemblies="#(TestAssemblies)"
ToolPath="$(PackagesDirectory)\NUnit.2.5.10.11092\tools"
WorkingDirectory="$(BuildCompileDirectory)"
OutputXmlFile="$(BuildDirectory)\$(SolutionName).Tests.xml" />
</Target>
The problem is that the folder of a NuGet packages is containing the version number of the package. For instance nunit-console.exe is in the folder packages\NUnit.2.5.10.11092\tools. If I update the NUnit package this path will change and I have to update my MSBuild script. That isn't acceptable.
MSBuild doesn't allow Wildcards in directories, so this isn't working:
ToolPath="$(PackagesDirectory)\NUnit.*\tools"
How can I call tools in MSBuild without having to update my build script whenever I update a NuGet package?

You can use MSBuild Transforms to get the relative directory of a specific tool:
<ItemGroup>
<NunitPackage Include="$(PackagesDirectory)\NUnit.*\tools\nunit-console.exe"/>
</ItemGroup>
<Target Name="Test">
<CreateItem Include="$(BuildCompileDirectory)\*.Tests.*dll">
<Output TaskParameter="Include" ItemName="TestAssemblies" />
</CreateItem>
<NUnit
Assemblies="#(TestAssemblies)"
ToolPath="#(NunitPackage->'%(relativedir)')"
WorkingDirectory="$(BuildCompileDirectory)"
OutputXmlFile="$(BuildDirectory)\$(SolutionName).Tests.xml" />
</Target>

The comment of Mike Rosoft links to Patrik Svensson his blog post and it helped me as follows:
Add GeneratePathProperty="true" to PackageReference of the NuGet package you want to have the location of.
Use it as $(PkgPackage_Name) whereby the dots are replaced by an underscore. Note the Pkg prefix.
This example forces Nswag to use the 32 bit dotnet.exe by overwriting the existing NSwagExe_Net60 property. This was necessary on an x86 project.
<PropertyGroup>
<NSwagExe_Net60>"$(MSBuildProgramFiles32)\dotnet\dotnet.exe" "$(PkgNSwag_MSBuild)\tools\Net60\dotnet-nswag.dll"</NSwagExe_Net60>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NSwag.MSBuild" Version="13.16.1" GeneratePathProperty="true">
</ItemGroup>

Related

Wix bootstrapper - Set version number in Bundle

I've got a Wix installer that uses a bootstrapper to launch my msi file. I've done this by calling a batch file as a post build event in my wix project. This then calls candle and light manually and passes various variables into the Bundle.wxs file. This all works and generates the exe which calls my msi file..
However, I now want to pass the msi BuildVersion into the bundle file. In the wxs file that creates the msi I am using the BuildVersion that I have setup in the BeforeBuild section, using the BuildVersion=%(AssemblyVersion.Version).
I cannot access this variable no matter what I try, in order to pass it to my build_bootstrapper.bat file. I can however pass in hardcoded values. I am currently setting up my own AssemblyVersionNumber enviornment variable as you can see below in the AfterBuild section:
<AssemblyVersionNumber Condition="'$(AssemblyVersionNumber)' == ''">$(BuildVersion)</AssemblyVersionNumber>
but it is empty by the time it gets to my script file (even though it's populated if hardcoded). I've tried everything.
Does anybody have any ideas of how I can get the %(AssemblyVersion.Version); to my command file from the post build step?
Thanks in advance
<Target Name="BeforeBuild">
<GetAssemblyIdentity AssemblyFiles="..\..\App\AppThing\bin\Release\AppThing.exe">
<Output TaskParameter="Assemblies" ItemName="AssemblyVersion" />
</GetAssemblyIdentity>
<PropertyGroup>
<DefineConstants>BuildVersion=%(AssemblyVersion.Version);</DefineConstants>
</PropertyGroup>
</Target>
<Target Name="AfterBuild">
<PropertyGroup>
<DefineConstants>BuildVersion=%(AssemblyVersion.Version);</DefineConstants>
<AssemblyVersionNumber Condition="'$(AssemblyVersionNumber)' == ''">$(BuildVersion)</AssemblyVersionNumber>
</PropertyGroup>
</Target>
<PropertyGroup>
<PreBuildEvent>$(ProjectDir)scripts\copy_services.bat $(SolutionDir) $(ProjectDir)</PreBuildEvent>
</PropertyGroup>
<Target Name="AfterClean">
<Message Text="Cleaning wix files, TargetDir is: $(TargetDir)" Importance="High" ContinueOnError="true" />
<CreateItem Include="$(TargetDir)\**\*.*">
<Output TaskParameter="Include" ItemName="BinFilesDir" />
</CreateItem>
<Delete Files="#(BinFilesDir)" />
</Target>
<PropertyGroup>
<PostBuildEvent>$(ProjectDir)scripts\build_bootstrapper.bat $(ProjectDir) $(ConfigurationName) $(AssemblyVersionNumber)</PostBuildEvent>
</PropertyGroup>
$(BuildVersion) isn't set to anything.
You're setting define constants to "BuildVersion=%(AssemblyVersion.Version)" but never actually defining a MSBuild property called "BuildVersion" so the value of $(BuildVersion) is "".
Use %(AssemblyVersion.Version).
<AssemblyVersionNumber Condition="'$(AssemblyVersionNumber)' == ''">%(AssemblyVersion.Version)</AssemblyVersionNumber>

NuGet pack and msbuild - how can pack reference assemblies in another location?

I am using NuGet pack for the first time, using the AfterBuild target in the .csproj file.
<Target Name="AfterBuild">
<!-- package with NuGet -->
<Exec WorkingDirectory="$(BaseDir)" Command="$(NuGetExePath) pack -Verbose -OutputDirectory $(OutDir) -Symbols -Prop Configuration=$(Configuration)" />
</Target>
This works fine when building the project itself with msbuild ("msbuild MyProj.csproj"). NuGet is able to find the compiled assemblies in projectdir/bin/Release or projectdir/bin/Debug.
But, this project is one of many in a solution and there is a build file dedicated to building the entire solution. The directory tree is like this:
- project root
- build
- src
- MyProj
- MyProj.csproj
- MyProj.nuspec
- AnotherProj
- AnotherProj.csproj
- AnotherProj.nuspec
- project.proj (msbuild file)
This msbuild file overrides the output path of the Visual Studio build.
<PropertyGroup>
<CodeFolder>$(MSBuildProjectDirectory)\src</CodeFolder>
<CodeOutputFolder>$(MSBuildProjectDirectory)\build\$(Configuration)</CodeOutputFolder>
</PropertyGroup>
<Target Name="Build" DependsOnTargets="CleanSolution">
<Message Text="============= Building Solution =============" />
<Message Text="$(OutputPath)" />
<Message Text="$(BuildTargets)" />
<MsBuild Projects="$(CodeFolder)\$(SolutionName)"
Targets="$(BuildTargets)"
Properties="Configuration=$(Configuration);RunCodeAnalysis=$(RunCodeAnalysis);OutDir=$(OutputPath);" />
</Target>
Now that the build redirects the assemblies to the build directory, when I run pack, NuGet can't find them. How do I get NuGet to find the assemblies in the build directory?
Giving NuGet a TargetPath property for where to find the assembly works for this.
<Target Name="AfterBuild" Condition="'$(Configuration)' == 'Release' ">
<!-- package with NuGet -->
<Exec WorkingDirectory="$(BaseDir)" Command="$(NuGetExePath) pack -OutputDirectory $(OutDir) -Symbols -Prop Configuration=$(Configuration);TargetPath=$(OutDir)$(AssemblyName)$(TargetExt)" />
</Target>
Try to define files section in your *.nuspec files, set copy Copy to Output Directory property to Copy always or Copy if newer for them in VS properties. After that all you compiled files and nuspecs will be in $(OutputPath). And then change AfterBuild to:
<Target Name="AfterBuild">
<PropertyGroup>
<NuspecPath>$([System.IO.Path]::Combine($(OutputPath), "$(ProjectName).nuspec"))</NuspecPath>
</PropertyGroup>
<!-- package with NuGet -->
<Exec WorkingDirectory="$(BaseDir)" Command="$(NuGetExePath) pack $(NuspecPath) -Verbose -OutputDirectory $(OutDir) -Symbols -Prop Configuration=$(Configuration)" />
</Target>

MSBUILD Unable to force PackageLocation for Package target

I'm trying to build a deployment package for my web service from msbuild just like you can do in Visual Studio by right-clicking on the project file.
The package gets created fine and is put in the /obj/Release/Packages folder under my source directory for the project file.
You should be able to specify where that package is created by setting the PackageLocation property in a PropertyGroup inside the project file. However, that is not working for me. It still puts the package in /obj/Release/Packages under the source directory.
Here is the snippet from my project file:
<Import Project="$(SrcTreeRoot)\Build\TaskInit.Tasks" />
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />
<Import Project="$(SrcTreeRoot)\Build\TaskOverrides.Tasks" />
<PropertyGroup>
<Platform>Any Cpu</Platform>
<Configuration>Dev</Configuration>
<PackageLocation>$(PackageOutputDir)\MatrixOnCdsService\MatrixOnCdsService.zip</PackageLocation>
<PackageAsSingleFile>True</PackageAsSingleFile>
<EnablePackageProcessLoggingAndAssert>True</EnablePackageProcessLoggingAndAssert>
<!--OutDir>$(PackageOutputDir)\MatrixOnCdsService\</OutDir-->
</PropertyGroup>
We are also using a MasterBuild.proj that has the following sections:
<PackageProject Include="..\Source\AnalysisSuite\MatrixOnCdsService\MatrixOnCdsService.csproj"/>
...
<Target Name="Package">
<MSBuild Projects="#(PackageProject)" Targets="Package" Properties="Platform=$(Platform);
Configuration=$(Configuration);
DeployOnBuild=true;
DeployTarget=Package;
PackageLocation=$(PackageLocation);"/>
</Target>
TaskInit.tasks is our own custom import file that contains the PackageOutputDir property that we use to tell the project where to put the package.
My question is why is the package still being placed in the /obj/Release/Packages folder even after specifying the PackageLocation?
Stick the property group you have in a target:
<Target Name="SetValues">
<PropertyGroup>
<Platform>Any Cpu</Platform>
<Configuration>Dev</Configuration>
<PackageLocation>$(PackageOutputDir)\MatrixOnCdsService\MatrixOnCdsService.zip</PackageLocation>
<PackageAsSingleFile>True</PackageAsSingleFile>
<EnablePackageProcessLoggingAndAssert>True</EnablePackageProcessLoggingAndAssert>
<!--OutDir>$(PackageOutputDir)\MatrixOnCdsService\</OutDir-->
</PropertyGroup>
</Target>
then add this as a DependsOnTarget for your Package Target and i think you will have your values passed.
e.g. <Target Name="Package" DependsOnTargets="SetValues">
You could do the following in your MasterBuild.proj.
<Target Name="Package">
<ConvertToAbsolutePath Paths="$(PackageOutputDir)">
<Output TaskParameter="AbsolutePaths" PropertyName="Source_Dir_Abs"/>
</ConvertToAbsolutePath>
<MSBuild Projects="#(PackageProject)" Targets="Package"
properties="Platform=$(Platform);
Configuration=$(Configuration);
DeployOnBuild=false;
DeployTarget=Package;
PackageLocation=$(Source_Dir_Abs)\$(PackageProjectName).zip;
PackageAsSingleFile=true;
ExcludeFilesFromDeployment=Web.config;
_PackageTempDir=$(PackageOutputDir)\temp;">
</MSBuild>
</Target>
Where you are calling msbuild you will need to add a property that will be used in $(PackageProjectName) by doing the following:
msbuild.exe /property:PackageProjectName=$project

MSBuild several metadata requests in the same expression

I am writing my first MSBuild script and ran into a problem.
I have several projects, defined in an itemgroup
<ItemGroup>
<Projects Include="Project1Dir\Project1.csproj"/>
<Projects Include="Project2Dir\Project2.csproj"/>
</ItemGroup>
Then, on deployment step, I am trying to do this:
The following should collect all the files for deployment into separate itemgroups for each project ("Project1deploymentFiles" and "Project2deploymentFiles")
<CreateItem Include="$(WebPublishDir)\%(Projects.Filename)\**\*.*">
<Output ItemName="%(Projects.Filename)deploymentFiles" TaskParameter="Include"/>
</CreateItem>
Thes line, should copy each project's files into separate folder
<Copy SourceFiles="#(%(Projects.Filename)deploymentFiles)" DestinationFolder="$(DeploymentDir)\%(Projects.Filename)\%(RecursiveDir)\" />
But it seems that MSBuild resolves %(RecursiveDir) metadata to empty string, as all the files are copied to the same root folder (different for each project).
Any suggestions what am I doing wrong here?
I've found a solution myself:
<CreateItem Include="$(WebPublishDir)\%(Projects.Filename)\**\*.*" AdditionalMetadata="ProjectDir=%(Projects.Filename)\">
<Output ItemName="deploymentFiles" TaskParameter="Include"/>
</CreateItem>
<Copy SourceFiles="#(deploymentFiles)" DestinationFolder="$(DeploymentDir)\%(ProjectDir)\%(RecursiveDir)\" />
Main idea here is to use one item for all projects and just add AdditionalMetadata to values, containing projectname

How can I change AssemblyProduct, AssemblyTitle using MSBuild?

I have an MSBuild script which compiles my existing solution but I'd like to change some properties of one of the projects within the solution at compile-time, including but not limited to AssemblyProduct and AssemblyTitle.
Here's a snippet of my build script:
<Target Name="Compile" >
<MSBuild Projects="..\MySolution.sln"
Properties="Configuration=MyReleaseConfig;Platform=x86" />
</Target>
I've got one main executable and several DLLs that are compiled. I am aware of the MSBuild Extension Pack and I suspect it might help me to get to where I need to be, although I'm not sure how to proceed.
Can I selectively change AssemblyInfo properties at build time?
You're on the right track with the MSBuild Extension Pack.
I find the easiest way to conditionally generate the assembly details at build time is to add an "AssemblyVersion" target directly to my .csproj file(s) that require an updated AssemblyInfo file. You can add the target directly to each csproj file that requires an updated AssemblyInfo file, or as I prefer to do it, create a custom targets file with the AssemblyVersion target and have each csproj file include your custom targets file.
Either way you likely want to use the MSBuild Extension Pack or the MSBuild Community Tasks to use their respective AssemblyInfo task.
Here's some code from our build scripts:
<!-- Import the AssemblyInfo task -->
<Import Project="$(MSBuildCommunityTasksPath)\MSBuild.Community.Tasks.Targets"/>
<!-- Overriding the Microsoft.CSharp.targets target dependency chain -->
<!-- Call our custom AssemblyVersion target before build, even from VS -->
<PropertyGroup>
<BuildDependsOn>
AssemblyVersion;
$(BuildDependsOn)
</BuildDependsOn>
</PropertyGroup>
<ItemGroup>
<AssemblyVersionFiles Include="$(MSBuildProjectDirectory)\Properties\AssemblyInfo.cs"/>
</ItemGroup>
<Target Name="AssemblyVersion"
Inputs="#(AssemblyVersionFiles)"
Outputs="UpdatedAssemblyVersionFiles">
<Attrib Files="%(AssemblyVersionFiles.FullPath)"
Normal="true"/>
<AssemblyInfo
CodeLanguage="CS"
OutputFile="%(AssemblyVersionFiles.FullPath)"
AssemblyCompany="$(CompanyName)"
AssemblyCopyright="Copyright $(CompanyName), All rights reserved."
AssemblyVersion="$(Version)"
AssemblyFileVersion="$(Version)">
<Output TaskParameter="OutputFile"
ItemName="UpdatedAssemblyVersionFiles"/>
</AssemblyInfo>
</Target>
Sneal's answer was very helpful, but I'd like to show what I actually ended up doing. Instead of editing csproj files (there are several) I instead added tasks to my build script. Here's a snippet:
<PropertyGroup>
<ProductName>MyApp</ProductName>
<CompanyName>MyCompany</CompanyName>
<Major>1</Major>
<Minor>0</Minor>
<Build>0</Build>
<Revision>0</Revision>
</PropertyGroup>
<ItemGroup>
<AssemblyVersionFiles Include="..\MyMainProject\Properties\AssemblyInfo.cs"/>
</ItemGroup>
<Target Name="AssemblyVersionMAIN" Inputs="#(AssemblyVersionFiles)" Outputs="UpdatedAssemblyVersionFiles">
<Attrib Files="%(AssemblyVersionFiles.FullPath)" Normal="true"/>
<AssemblyInfo
CodeLanguage="CS"
OutputFile="%(AssemblyVersionFiles.FullPath)"
AssemblyProduct="$(ProductName)"
AssemblyTitle="$(ProductName)"
AssemblyCompany="$(CompanyName)"
AssemblyCopyright="© $(CompanyName) 2010"
AssemblyVersion="$(Major).$(Minor).$(Build).$(Revision)"
AssemblyFileVersion="$(Major).$(Minor).$(Build).$(Revision)"
AssemblyInformationalVersion="$(Major).$(Minor).$(Build).$(Revision)">
<Output TaskParameter="OutputFile" ItemName="UpdatedAssemblyVersionFiles"/>
</AssemblyInfo>
</Target>
<Target Name="Compile" DependsOnTargets="AssemblyVersionMAIN">
<MSBuild Projects="..\MySolution.sln"
Properties="Configuration=Release;Platform=x86;Optimize=true" />
</Target>
Then, I can override my variables from the command line, or a batch script, like so:
set MAJ=1
set MIN=2
set BLD=3
set REV=4
msbuild buildScript.xml /t:Compile /p:Major=%MAJ% /p:Minor=%MIN% /p:Build=%BLD% /p:Revision=%REV%
<Target Name="SetVersion">
<ItemGroup>
<AssemblyInfoFiles Include="$(TargetDir)\**\AssemblyInfo.cs"/>
</ItemGroup>
<Message Text="change the Version number for:"/>
<Message Text="%(AssemblyInfoFiles.FullPath)"/>
<MSbuild.ExtensionPack.Framework.AssemblyInfo
AssemblyInfoFiles="#(AssemblyInfoFiles)"
AssemblyTitle="newTitle"
AssemblyMajorVersion="2"
AssemblyMinorVersion="0"/>
</Target>