How to copy all json file to new folder by MsBuild? - msbuild

Steps to reproduce
On windows it works fine with this command :
<PropertyGroup>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<PreBuildEvent>IF NOT EXIST "$(TargetDir)DataFiles" MKDIR "$(TargetDir)DataFiles"</PreBuildEvent>
<PostBuildEvent>FOR /R "$(SolutionDir)\Api.Tests\Scenarios" %25%25f IN (*.json) DO COPY "%25%25f" "$(TargetDir)DataFiles\" /Y</PostBuildEvent>
</PropertyGroup>
On linux , I have this kind of error
Build FAILED.
[12:44:39][Step 1/1]
[12:44:39][Step 1/1] /usr/share/dotnet/sdk/2.1.302/Microsoft.Common.CurrentVersion.targets(1331,5): error MSB3073: The command "IF NOT EXIST "DataFiles" MKDIR "DataFiles"" exited with code 127. [/opt/jetbrains/buildAgent/work/4fc9032bf5656724/Api.Tests/Api.Tests/Api.Tests.csproj]
[12:44:39][Step 1/1] 0 Warning(s)
[12:44:39][Step 1/1] 1 Error(s)

Your post build event uses CMD.EXE specific code - also known as (Windows) BATCH commands. In other words it is platform specific.
Best thing is to use MSBuild built-in features to copy files:
Remove the PreBuildEvent and PostBuildEvents properties
Add the following inside the "Project" tag of your project file (best at the end).
<PropertyGroup>
<BuildDependsOn>
$(BuildDependsOn);_CopyAuxFiles
</BuildDependsOn>
</PropertyGroup>
<Target Name="_CopyAuxFiles">
<MakeDir Directories="$(TargetDir)DataFiles"/>
<ItemGroup>
<Files Include="$(SolutionDir)\Api.Tests\Scenarios\*.json"/>
</ItemGroup>
<CopyFile SourceFiles="#(Files)" DestinationFolder="$(TargetDir)DataFiles"/>
</Target>
You might need to replace "$(TargetDir)" with "$(OutputPath)".

I have resolved it by
<Target Name="CopyScenarios" AfterTargets="Build">
<ItemGroup>
<Scenarios Include="$(ProjectDir)/Scenarios/**/*.json" />
</ItemGroup>
<Copy SourceFiles="#(Scenarios)" DestinationFiles="#(Scenarios->'$(TargetDir)DataFiles/%(Filename)%(Extension)')" SkipUnchangedFiles="false" />
</Target>

Related

Using Pre and Post build actions in dotnet core csproj files

I am a .NET Core novice and I am trying to set up a pre-build action in my csproj file. According to [1], we can use Target element to specify a pre-build step as follows:
<Target Name="MyPreCompileTarget" BeforeTargets="Build">
<Exec Command="generateCode.cmd"/>
</Target>
However, this element does not seem to be picked up by the MSBuild tool. My complete csproj file is given below:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Folder Include="wwwroot\"/>
</ItemGroup>
<Target Name="MyPreCompileTarget" BeforeTargets="Build">
<Exec Command="echo meow meow"/>
</Target>
<ItemGroup>
<PackageReference Include="FluentValidation.AspNetCore" Version="7.0.0"/>
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2"/>
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3"/>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="1.0.3"/>
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.2"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\my-lib\<my-lib>.csproj"/>
</ItemGroup>
</Project>
References
[1] - https://learn.microsoft.com/en-us/dotnet/articles/core/tools/project-json-to-csproj#the-csproj-format
echo is a builtin of the command shell (cmd.exe) in that case so it won't work.
If you only use e.g. generateCode, msbuild will also look for .bat or .sh files matching that name depending on the platform you run on.
You can run the dotnet build command with /v:diag to get a full diagnostic output.
You can also verify if your target is actually run by adding a task like this inside the target:
<Message Importance="high" Text="Test Message" />
Also, since echo is available on mac, your project file does the expected when I run it on a Mac:
MacBook-Pro:footest martin$ dotnet build
Microsoft (R) Build Engine version 15.3.234.47922 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.
footest -> /Users/martin/tmp/footest/bin/Debug/netcoreapp1.1/footest.dll
meow meow
Build succeeded.
0 Warning(s)
0 Error(s)

How to find if file exist with msbuild command(or before)

I need to find if certain file exist prior to the run of a power-shell script
The file (if exist) will be in a specific folder.
Can i check for it's existence through the proj file or something like that?
Note the second MyCheck looks at the (conditional) value of (the first) MyCheck
<PropertyGroup>
<MyCheck Condition="Exists($(MyFileOrFolderName))">true</MyCheck>
<MyCheck Condition="'$(MyCheck)'==''">false</MyCheck>
</PropertyGroup>
<Message Text="My-File-Or-Folder-Name already exists? : $(MyCheck)" />
OR
<PropertyGroup>
<MyCheck>false</MyCheck>
<MyCheck Condition="Exists($(MyFileOrFolderName))">true</MyCheck>
</PropertyGroup>
<Message Text="MyFileOrFolderNameexists? : $(MyCheck)" />
In order to execute PS script, depending on the existance of a file, you can create a Target element in your *.*proj file with condition depending on the file existance:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="14.0">
<PropertyGroup>
<ScriptLocation>.\Do-Something.ps1</ScriptLocation>
<PowerShellExe Condition=" '$(PowerShellExe)'=='' ">%WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe
</PowerShellExe>
</PropertyGroup>
<Target Name="RunPSScript" Condition="Exists($(ScriptLocation))">
<Exec Command="$(PowerShellExe) -NonInteractive -executionpolicy Unrestricted -command "$(ScriptLocation)""/>
</Target>
</Project>
See more details here about executing PS scripts from msbuild projects. You can use AfterTargets, BeforeTargers, or any other methods to control order of the execution this target.

Issue with ApplicationFile file not found in MSBuild script

my issue is that the msbuild script can't find my MSI installer file.
Here is the script file bootstrapper.msbuild :
<Project ToolsVersion="3.5"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<BootstrapperFile Include="Microsoft.Windows.Installer.4.5" >
<ProductName>Windows Installer 4.5</ProductName>
</BootstrapperFile>
<!--<BootstrapperFile Include="DotNetFX40" >
<ProductName>Microsoft DotNet Framework 4.5 SP1</ProductName>
</BootstrapperFile>-->
</ItemGroup>
<!-- from http://stackoverflow.com/questions/346175/use-32bit-program-files-directory-in-msbuild
-->
<PropertyGroup>
<ProgramFiles32>$(MSBuildProgramFiles32)</ProgramFiles32>
<ProgramFiles32 Condition=" '' == '$(ProgramFiles32)'">$(ProgramFiles%28x86%29)</ProgramFiles32>
<ProgramFiles32 Condition=" '' == '$(ProgramFiles32)'">$(ProgramFiles)</ProgramFiles32>
</PropertyGroup>
<Target Name="SetupExe">
<GenerateBootstrapper
ApplicationFile="..\MySetup\MySetup\bin\Debug\MySetup.msi"
ApplicationName="MyApplication"
Culture="en"
BootstrapperItems="#(BootstrapperFile)"
ComponentsLocation="HomeSite"
Path="$(ProgramFiles32)\Microsoft SDKs\Windows\v7.0A\Bootstrapper\"
OutputPath="output"/>
</Target>
</Project>
The folders structure is as follows :
The file boostrapper.msbuild is inside Mybootstrapper. I've tried the path using command line and it works fine. Why not in ms build ? Am I missing something ?
The question is what is the directory the task GenerateBootstrapper is referring to ?
Could you advice please ?
It looks like your relative path is not correct. Try:
ApplicationFile="..\MySetup\bin\Debug\MySetup.msi"
The issue was I didn't put MSI file in the path where the generated setup.exe is located. I was thinking that this later will contain all files...
Am I wrong ?

msbuild exec task call msbuild

I need to call exec and build a wix setup project.
Currently I have the following in my TFSbuild.proj
<PropertyGroup>
<WebRoot>$(DropLocation)\Latest\x86\Release\_PublishedWebsites\Web</WebRoot>
<DBRoot>$(DropLocation)\Latest\x86\Release\Database</DBRoot>
</PropertyGroup>
<PropertyGroup>
<Msbuildexe>"msbuild"</Msbuildexe>
<Configuration>"/p:Configuration:"Release""</Configuration>
<DefineConstants>" /p:DefineConstants:"WebRoot=$(WebRoot);DBRoot=$(DBRoot)""</DefineConstants>
<WixSolution>"$(MSBuildProjectDirectory)\Setup\Setup.sln"</WixSolution>
</PropertyGroup>
<Message Text="Bulding setup solution" />
<Message Text="$(Msbuildexe) $(Configuration) $(DefineConstants) $(WixSolution)" />
<Exec Command="$(Msbuildexe) $(Configuration) $(DefineConstants) $(WixSolution)" />
I've tried to simply as much as possible so I don't get confused where the " are meant to be. When I run this the debug message (2nd last command) outputs
"msbuild"
"/p:Configuration:"Release"" "
/p:DefineConstants:"WebRoot=\server\drops\app\Installer Build\Latest\x86\Release_PublishedWebsites\Web;DBRoot=\server\drops\app\Installer Build\Latest\x86\Release\Database""
"f:\builds\app\Installer Build\BuildType\Setup\Setup.sln"
And I get the following error in the log
'"msbuild"' is not recognized as an
internal or external command,
operable program or batch file.
f:\builds\app\Installer
Build\BuildType\TFSBuild.proj(538,5):
error MSB3073: The command ""msbuild"
"/p:Configuration:"Release"" "
/p:DefineConstants:"WebRoot=\server\drops\app\Installer Build\Latest\x86\Release_PublishedWebsites\Web;DBRoot=\server\drops\app\Installer Build\Latest\x86\Release\Database""
"f:\builds\app\Installer
Build\BuildType\Setup\Setup.sln""
exited with code 9009.
I'm not sure if this is being caused by not being able to call the msbuild command from the command line or a " issue. If it is because I can't call msbuild from the command line like this how would I go about referencing it, is there a property that points to it?
To start with, you don't need most of the quotes, especially if the paths you are using don't contain spaces, but I'd trim it down to this, allowing for spaces in the paths for $(WebRoot), $(DbRoot) and $(MSBuildProjectDirectory):
<PropertyGroup>
<WebRoot>$(DropLocation)\Latest\x86\Release\_PublishedWebsites\Web</WebRoot>
<DBRoot>$(DropLocation)\Latest\x86\Release\Database</DBRoot>
</PropertyGroup>
<PropertyGroup>
<MsbuildExe>{still-needs-a-path-to}\msbuild</MsbuildExe>
<Configuration>/p:Configuration:Release</Configuration>
<DefineConstants>/p:DefineConstants:"WebRoot=$(WebRoot);DBRoot=$(DBRoot)"</DefineConstants>
<WixSolution>"$(MSBuildProjectDirectory)\Setup\Setup.sln"</WixSolution>
</PropertyGroup>
<Message
Text="Bulding setup solution"
/>
<Message
Text="$(MsbuildExe) $(Configuration) $(DefineConstants) $(WixSolution)"
/>
<Exec
Command="$(MsbuildExe) $(Configuration) $(DefineConstants) $(WixSolution)"
/>
However, you still won't be able to execute MSBuild with this, since the path to MSBuild isn't specified. It is typically found in the $(WINDIR)\Framework\Microsoft.Net\v4.0.30319 folder. There are a few ways to get this, either encode it directly, rely on an environment variable (that has to be set up somehow), use the predefined $(MSBuildBinPath), or extract it from the registry using the MSBuild registry syntax, which would look like this:
$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSBuild\ToolsVersions\4.0\MSBuildToolsPath)
However, it isn't clear why you are running MSBuild using Exec rather than just using the MSBuild task. Change the line with Exec to this:
<MSBuild
Project="$(WixSolution)"
Properties="$(DefineConstants)"
/>
removing your declaration for <Configuration> and changing <DefineConstants> to this:
<DefineConstants>Configuration=$(Configuration);WebRoot=$(WebRoot);DBRoot=$(DBRoot)</DefineConstants>
Following up on my comment I'd suggest you try using the MSBuild Task instead of Exec:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="BuildWiXSolution">
<!-- Include the custom build targets installed with WiX -->
<Import Project="$(MSBuildExtensionsPath)\Wix\Wix.targets"/>
<PropertyGroup>
<WebRoot>$(DropLocation)\Latest\x86\Release\_PublishedWebsites\Web</WebRoot>
<DBRoot>$(DropLocation)\Latest\x86\Release\Database</DBRoot>
</PropertyGroup>
<ItemGroup>
<WiXSolution Include="$(MSBuildProjectDirectory)\Setup\Setup.sln">
<Properties>Configuration=Release</Properties>
<AdditionalProperties>WebRoot=$(WebRoot);DBRoot=$(DBRoot)</AdditionalProperties>
</WiXSolution>
</ItemGroup>
<Target Name="BuildWiXSolution">
<MSBuild Projects="#(WiXSolution)" />
</Target>
</Project>
It allows you to keep configuration properties and additional properties together with your Wix solution.

MSBuild transform not evaluating wildcards

I am having some trouble with a MSBuild file that I am trying to compile some custom libraries.
<PropertyGroup>
<FullVersion>10.8.0.0</FullVersion>
</PropertyGroup>
<ItemGroup>
<LibsToBuild Include=".\Lib1">
<Bin>bin\*.*</Bin>
<Project>Library 1</Project>
<Build>ReleaseNoProtect</Build>
<Version>CurrentVersion</Version>
</LibsToBuild>
<LibsToBuild Include=".\Lib2">
<Bin>bin\*.*</Bin>
<Project>Library 2</Project>
<Build>ReleaseLibrary</Build>
<Version>CurrentVersion</Version>
</LibsToBuild>
</ItemGroup>
<ItemGroup>
<LibsToCopy Include="#(LibsToBuild->'%(FullPath)\%(Version)\%(Bin)')" />
</ItemGroup>
<Target Name="BuildLibs">
<MSBuild
Projects="#(LibsToBuild->'%(FullPath)\%(Version)\Build\Build.proj')"
Targets="%(LibsToBuild.Build)"
Properties="Configuration=Release;APP_VERSION=$(FullVersion);PROJECT_NAME=%(LibsToBuild.Project)"
/>
<Copy
SourceFiles="#(LibsToCopy)"
DestinationFiles="#(LibsToCopy->'.\Libraries\CurrentVersion\%(RecursiveDir)%(Filename)%(Extension)')"
/>
<!--
<Exec Command='xcopy /y #(LibsToCopy) .\Libraries\CurrentVersion' />
-->
</Target>
When I run this through MSBuild, all of the compiles work, but the copy files does not. MSBuild complains with the following errors:
Copying file from "X:\Projects\Lib1\Master\bin\*.*" to ".\Libraries\CurrentVersion\*.*".
X:\Projects\Test Release.build(35,3): error MSB3021: Unable to copy file "X:\Projects\Lib1\Master\bin\*.*" to ".\Libraries\CurrentVersion\*.*". Illegal characters in path.
Copying file from "X:\Projects\Lib2\Master\bin\*.*" to ".\Libraries\CurrentVersion\*.*".
X:\Projects\Test Release.build(35,3): error MSB3021: Unable to copy file "X:\Projects\Lib1\Master\bin\*.*" to ".\Libraries\CurrentVersion\*.*". Illegal characters in path.
I am unable to figure out why the transform in the "LibsToCopy" ItemGroup isn't expanding the filename wildcards.
I have also attempted to use xcopy, but it doesn't like the wildcards either.
Thanks!
Dave
I had a similar problem. Try this, just before the <Copy> task
<CreateItem Include="#(LibsToBuild->'%(FullPath)\%(Version)\%(Bin)')">
<Output TaskParameter="Include" ItemName="LibsToCopy" />
</CreateItem>
Unfortunately the documentation says CreateItem task is deprecated, so I don't know how to solve tis problem in the future.