Able to zip files in MSBuild...How to zip folders? - msbuild

I have configured MSBuild to zip all of the files in my bin directory upon compilation using MSBuild.Community.Tasks. However, it is not zipping the folders in my bin directory.
How do I configure MSBuild to zip my folders as well?
Here is the configuration:
<Target Name="Zip">
<CreateItem Include="bin\*">
<Output ItemName="ZipFiles" TaskParameter="Include" />
</CreateItem>
<Zip ZipFileName="zip\WebBinder.zip" WorkingDirectory="bin" Files="#(ZipFiles)" />
</Target>

Set the Include of your CreateItem to have "bin\**\*" to recurse into the directories.
<CreateItem Include="bin\**\*">

Related

Copying entire directory using MSBuild

I have the following on an ASP.NET Core 3.1 application:
<Target Name="OnBuild" BeforeTargets="Build">
<Exec WorkingDirectory="approot" Command="npm run build --prod" />
<Copy SourceFiles="approot\dist" DestinationFolder="wwwroot" />
</Target>
When I build I get an error:
The source file "approot/dist" is actually a directory.
The "Copy" task does not support copying directories.
How can I copy the directory approot\dist to wwwroot using MSBuild?
Try this:
<Target Name="OnBuild" BeforeTargets="Build">
<ItemGroup>
<Folder Include="**\approot\**\*.*" />
</ItemGroup>
<Copy SourceFiles="#(Folder)" DestinationFolder="wwwroot\%(RecursiveDir)"></Copy>
</Target>

Remove Files and Folders Copied From AfterBuild Target

I would like to avoid hard coding the dll and folder names in the AfterClean target, is there a dynamic way to do this? Ideally it would only delete the files and folders created by the Copy in the AfterBuild target.
I tried to simplify this by changing the DestinationFolder to include a subdirectory in the OutputPath. The AfterClean target would only have to remove that subdirectory at this point. However, some of the library's DLLImport paths don't take that subdirectory into consideration which results in a crash.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="AfterBuild">
<ItemGroup>
<NativeLibs Include="$(MSBuildThisFileDirectory)..\lib\native\**\*.*" />
</ItemGroup>
<Copy SourceFiles="#(NativeLibs)" DestinationFolder="$(OutputPath)\%(RecursiveDir)" />
</Target>
<Target Name="AfterClean">
<Delete Files="$(OutputPath)\LumiAPI.dll" />
<Delete Files="$(OutputPath)\LumiCore.dll" />
<Delete Files="$(OutputPath)\LumiInOpAPI.dll" />
<RemoveDir Directories="$(OutputPath)\SPM" />
<RemoveDir Directories="$(OutputPath)\plugin" />
</Target>
</Project>
Project Structure:
src
ConsumingProject
ConsumingProject.csproj
ConsumingProject.sln
packages
my-project.5.7.0.12
build
lib
native
plugin
VenusDvc.dll
SPM
sSPM_1.bin
LumiAPI.dll
LumiCore.dll
LumiInOpAPI.dll
net45
my-project.5.7.0.12.nupkg
Essentially I want to delete all the files and folders that were copied from the native folder to the output of the project (ie LumiAPI.dll, LumiCore.dll, SPM (folder), eSPM_1.bin, etc). However I want it to be generic enough so that if I add another folder to the native directory, it will delete those folders/files as well.
Use a seperate target which lists input and output files, then use that list in both other targets. Note this uses the DestinationFiles attribute from the Copy task instead of DestinationFolders. And it might print some messages about non-existing directories being passed to RemoveDir because the top directory gets removed already before child directories.
update since you don't want to remove the root output directory as it still has files, figured applying the 'only remove output directory if it's empty' principle for any destination directory is probably the safest way to go. Credits go to the answer here.
<Target Name="GetMyOutputFiles">
<ItemGroup>
<NativeLibs Include="$(MSBuildThisFileDirectory)..\lib\native\**\*.*" />
<!--Now add some metadata: output dir and output file-->
<NativeLibs>
<DestinationDir>$(OutputPath)\%(RecursiveDir)</DestinationDir>
<Destination>$(OutputPath)\%(RecursiveDir)%(FileName)%(Extension)</Destination>
</NativeLibs>
</ItemGroup>
</Target>
<Target Name="AfterBuild" DependsOnTargets="GetMyOutputFiles">
<!--Copy one-to-one-->
<Copy SourceFiles="#(NativeLibs)" DestinationFiles="#(NativeLibs->'%(Destination)')" />
</Target>
<Target Name="AfterClean" DependsOnTargets="GetMyOutputFiles">
<Delete Files="#(NativeLibs->'%(Destination)')" />
<!--Find number of files left in each destination directory-->
<ItemGroup>
<NativeLibs>
<NumFiles>0</NumFiles>
<!--Condition is to avoid errors when e.g. running this target multiple times-->
<NumFiles Condition="Exists(%(DestinationDir))">$([System.IO.Directory]::GetFiles("%(DestinationDir)", "*", System.IO.SearchOption.AllDirectories).get_Length())</NumFiles>
</NativeLibs>
</ItemGroup>
<!--Only remove empty directories, use 'Distinct' to skip duplicate directories-->
<RemoveDir Directories="#(NativeLibs->'%(DestinationDir)'->Distinct())" Condition="%(NumFiles)=='0'" />
</Target>

MSBuild fails to find directory with huge sub-directory

I want to create an MSBuild item that includes all files in a sub-directory, recursively, except the "node_modules" directory and a few others.
<MyItem Include="MyDir\**\*.*" Exclude="MyDir\node_modules\**\*.*;MyDir\tmp\**\*.*" />
In the build log I see this:
Input file "MyDir\**\*.*" does not exist.
When I delete the "node_modules" directory it works. I suspect MSBuild fails to process it because it contains over 30,000 files and a deep sub-directory structure. Is there a way I can work around this without listing all the sub-directories I want to include, which may change from time to time?
OK, I figured out a way using another target with CreateItems:
<ItemGroup>
<InputDirs Include="MyDir" />
<InputExcludedSubDirs Include="MyDir\tmp;MyDir\node_modules" />
</ItemGroup>
<Target Name="PrepareForMyTarget">
<ItemGroup>
<InputSubDirs Include="$([System.IO.Directory]::GetDirectories("%(InputDirs.Identity)"))" Exclude="#(InputExcludedSubDirs)" />
</ItemGroup>
<CreateItem Include="%(InputSubDirs.Identity)\**\*.*">
<Output TaskParameter="Include" ItemName="Inputs" />
</CreateItem>
</Target>
<Target Name="MyRealTarget" DependsOnTargets="PrepareForMyTarget">
... use Inputs item here as normal ...
</Target>

Move AfterBuild target to a .targets file

I have a AfterBuild target that I would like to use for multiple projects in a solution. Is there a way that I can put that target into a .targets file and reference the file in each project.
Below is what I tried which does not seem to work.
Project File:
<Import Project="..\debug.targets"/>
.Targets File:
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="AfterBuild">
<PropertyGroup>
<WebsiteDirectory>C:\Inetpub\wwwroot</WebsiteDirectory>
</PropertyGroup>
<ItemGroup>
<output Include=".\**\*.dll" Exclude=".\**\obj\**" />
<output Include=".\**\*.pdb" Exclude=".\**\obj\**" />
<output Include=".\**\*.svc" />
<output Include=".\**\*.xap" />
<output Include=".\**\*.aspx" />
<output Include=".\**\*.js" />
<output Include=".\**\*.config" />
</ItemGroup>
<PropertyGroup>
<VirtualDirectoryPath>$(WebsiteDirectory)\$(RootNamespace)</VirtualDirectoryPath>
</PropertyGroup>
<copy SourceFiles="#(output)" DestinationFiles="#(output->'$(VirtualDirectoryPath)\%(RecursiveDir)%(Filename)%(Extension)')" />
</Target>
Use this
<Import Project="$(MSBuildThisFileDirectory)\debug.targets"/>
$(MSBuildThisFile) = The current project file.
$(MSBuildThisFileDirectory) = The directory that contains current project file.
Relative paths in project files are difficult to use depending on what is invoking the project file. Using msbuild directly and the relative path will resolve to the project file. Use VS and the relative path will use the solution file as the base path.
Using $(MSBuildThisFileDirectory) will force the relative path to use a pre-determined beginning path. All you need to do is fill in the rest of the relative path.
What you are doing is fundamentally correct, but ensure that your Import statement is the last Import in the project file.
To verify that the target is being invoked correctly, run msbuild in diag mode from the command line and note the output regarding your target.
msbuild myproj.proj /v:diag

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