How to create custom project file that works with fast-up-to-date (and avoids other problems)? - msbuild

I am trying to create a project file that performs few custom steps (specifically, it "wraps" existing Angular CLI project).
Here is my best attempt (myproject.csproj):
<Project ToolsVersion="Current" DefaultTargets="Build">
<PropertyGroup>
<ProjectGuid>{...some-guid...}</ProjectGuid>
<!-- do not include files by default -->
<EnableDefaultItems>false</EnableDefaultItems>
<!-- this removes 'Publish...' menu in VS -->
<OutputType>Library</OutputType>
<!-- output directory name -->
<AngularProject>MyWebFiles</AngularProject>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<PlatformTarget>x64</PlatformTarget>
<OutputPath>bin\Debug\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<PlatformTarget>x64</PlatformTarget>
<OutputPath>bin\Release\</OutputPath>
</PropertyGroup>
<ItemGroup>
<AngularFile Include="**" Exclude="node_modules\**" />
</ItemGroup>
<Target Name="Build" Inputs="#(AngularFile)" Outputs="$(OutputPath)$(AngularProject)\index.html">
<Exec Command="ng build --no-progress --output-path $(OutputPath)$(AngularProject)\" Condition="'$(Configuration)'=='Debug'" />
<Exec Command="ng build --no-progress --output-path $(OutputPath)$(AngularProject)\ --prod" Condition="'$(Configuration)'=='Release'" />
</Target>
<Target Name="Clean">
<RemoveDir Directories="$(OutputPath)$(AngularProject)\" />
</Target>
<Target Name="Rebuild" DependsOnTargets="Clean;Build" />
</Project>
Everything works fine, I can add this project to VS2019 solution, compile, etc. But it has problems:
Fast up-to-date check doesn't work. Related logging produces this:
Build started...
1>Project 'myproject' is not up to date. Error (0x8000FFFF).
I've tried specifying fast up-to-date files manually (via UpToDateCheckInput, etc), but it didn't work (presumably because it relies on additional definitions pulled in when you specify Sdk attribute of Project tag).
VS configuration manager has empty 'Platform' combo box. I'd like to be able to have x64 in it:
it is rather obvious that PlatformTarget is getting ignored by VS.
Opening project in VS results in creation of obj\x64\Debug\TempPE\ directory (if current Configuration is Debug). Nothing ever gets generated in it -- would be nice to avoid it being created.
Is it possible to fix these 3 problems? I suspect relates subsystems expect certain values/properties to be generated, I've tried digging in .props/.targets that come with VS in attempt to locate them, but quickly got lost.

Here is how to do it:
<Project Sdk="Microsoft.Build.NoTargets/3.2.14">
<ItemGroup>
<PackageReference Include="Microsoft.Build.NoTargets" Version="3.2.14" />
</ItemGroup>
<PropertyGroup>
<!-- Any target framework you want as long as its compatible with your referenced NuGet packages -->
<TargetFramework>net462</TargetFramework>
<Platforms>x64</Platforms>
<!-- Do not add TargetFramework to OutputPath -->
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<!-- Do not expect pdb files to be generated (this is for fast up-to-date check) -->
<DebugType>None</DebugType>
<!-- Do not include files by default -->
<EnableDefaultItems>false</EnableDefaultItems>
<!-- Output subdir name -->
<AngularProject>MyWebFiles</AngularProject>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<OutputPath>..\..\Bin\Debug\</OutputPath>
<BuildCommand>ng build --no-progress --output-path $(OutputPath)$(AngularProject)\</BuildCommand>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<OutputPath>..\..\Bin\Release\</OutputPath>
<BuildCommand>ng build --no-progress --output-path $(OutputPath)$(AngularProject)\ --prod</BuildCommand>
</PropertyGroup>
<ItemGroup>
<None Include="**" Exclude="node_modules\**;$(BaseIntermediateOutputPath)\**;$(MSBuildProjectFile)" />
<!-- This deals with fast up-to-date checks -->
<UpToDateCheckBuilt Original="package-lock.json" Include="node_modules/.build" />
<UpToDateCheckInput Include="#(None);$(MSBuildProjectFile)" Set="AngularFiles" />
<UpToDateCheckOutput Include="$(OutputPath)$(AngularProject)\index.html" Set="AngularFiles" />
</ItemGroup>
<Target Name="InitModules" Inputs="package-lock.json" Outputs="node_modules/.build">
<Exec Command="npm ci --no-progress --no-color" YieldDuringToolExecution="true" />
<Exec Command="cd . > node_modules/.build" />
</Target>
<Target Name="BuildAngular" BeforeTargets="AfterBuild" Inputs="#(None);$(MSBuildProjectFile)" Outputs="$(OutputPath)$(AngularProject)\index.html" DependsOnTargets="InitModules">
<Exec Command="$(BuildCommand)" YieldDuringToolExecution="true" />
</Target>
<Target Name="CleanAngular" BeforeTargets="AfterClean">
<RemoveDir Directories="$(OutputPath)$(AngularProject)\" />
</Target>
</Project>
Notes:
it will still generate additional local directory (obj), but it can be moved away by overriding IntermediateOutputPath

Related

How can I include different packages in different Configuration/Platform combinations?

I have a library that is designed to work with Sap Business One's SDK. The SDK for V10 is different to the one for V9.3, I also have x86/x64 and SQL/HANA builds, this gives me 8 permutations and therefore 8 packages.
The projects that consume these packages will also have 8 builds. I would like to set up the project file and targets so that a specific package is selected for a specific Configuration & Platform. I am trying to work this out, but it makes absolutely no sense.
Currently I have the following in my project file:
<ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'B1v93SQL|x64' ">
<PackageReference Include="OchALCommon.v93SQLx64" Version="1.0.*" />
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'B1v93SQL|x86' ">
<PackageReference Include="OchALCommon.v93SQLx86" Version="1.0.*" />
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'B1v93HANA|x64' ">
<PackageReference Include="OchALCommon.v93HANAx64" Version="1.0.*" />
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'B1v93HANA|x86' ">
<PackageReference Include="OchALCommon.v93HANAx86" Version="1.0.*" />
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'B1v10SQL|x64' ">
<PackageReference Include="OchALCommon.v10SQLx64" Version="1.0.*" />
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'B1v10SQL|x86' ">
<PackageReference Include="OchALCommon.v10SQLx86" Version="1.0.*" />
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'B1v10HANA|x64' ">
<PackageReference Include="OchALCommon.v10HANAx64" Version="1.0.*" />
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'B1v10HANA|x86' ">
<PackageReference Include="OchALCommon.v10HANAx86" Version="1.0.*" />
</ItemGroup>
Visual studio indicates all 8 packages and a build process the dependencies of each one - this seems wrong. I also tried this code within a "Directory.Build.targets" file, which initially seemed to work, but then Visual Studio stopped reponding to changes in the targets file (even after a reboot).
We have always used this kind of referencing in the past with Assembly references,and it seems to work, I have no idea how to make PackageReference function. Does anybody know how best to package this library in my scenario?
In an ideal world, I'd want to somehow store my 64 bit and 32 bit build plus some appropriate targets in a single nuget package so that the consuming project gets the right bitness and the right sub project references. Currently I can't work out how to do this, nor get any other workable scenario going.
Again, does anybody know how to do anything like this?
Thanks.
Thus far I have been able to solve the issue by using separate files (.targets) for each ProjectReference, and then ensuring that only those files containing the reference information are included in the project by referencing specific .targets files.
I have then put .targets files into an umbrella nupkg to select which of the actual payloads I want to use. The entire arrangement looks as follows:
I published 8 payload files as follows:
MyLibrary.v93HANAx64
MyLibrary.v93HANAx86
MyLibrary.v93SQLx64
MyLibrary.v93SQLx86
MyLibrary.v10HANAx64
MyLibrary.v10HANAx86
MyLibrary.v10SQLx64
MyLibrary.v10SQLx86
Each payload file simply contains a standard lib/net40 folder with libraries as specified in the https://learn.microsoft.com/en-us/nuget/create-packages/creating-a-package page.
My consuming project has 4 Configurations:
B1v93HANA
B1v93SQL
B1v10HANA
B1v10SQL
I then have an umbrella project "MyLibrary.Targets" containing content as follows:
##build/net40/MyLibrary.Targets.targets
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
<Import Project="MyLibrary/$(Configuration).targets" />
</Project>
##build/net40/MyLibrary/B1v10HANA.targets
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
<ItemGroup>
<PackageReference Include="MyLibrary.v10HANA$(Platform)" Version="1.0.*" />
</ItemGroup>
</Project>
##build/net40/MyLibrary/B1v10SQL.targets
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
<ItemGroup>
<PackageReference Include="MyLibrary.v10SQL$(Platform)" Version="1.0.*" />
</ItemGroup>
</Project>
##build/net40/MyLibrary/B1v93HANA.targets
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
<ItemGroup>
<PackageReference Include="MyLibrary.v93HANA$(Platform)" Version="1.0.*" />
</ItemGroup>
</Project>
##build/net40/MyLibrary/B1v93SQL.targets
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
<ItemGroup>
<PackageReference Include="MyLibrary.v93SQL$(Platform)" Version="1.0.*" />
</ItemGroup>
</Project>
I also have targets files in this folder, "Debug.targets" and "Release.targets" which use my preferred default library in those cases.
.nupec files for all of the packages are pretty standard as per the above linked Microsoft guide. A default nuspec file can be created with the 'nuget spec' command and can then be edited.
My functional package nuspec files have content in 'package/metadata/dependencies' which identify required packages:
<dependencies>
<group targetFramework=".NETFramework4.0">
<dependency id="CryptLib" version="*" />
<dependency id="SAPBusinessOneSDK.HANA" version="10.0.*" />
</group>
</dependencies>
My Selector package "MyLibrary.targets" does not have dependencies, but does have a files section 'package/files':
<files>
<file src="readme.txt" target="" />
<file src="build\**" target="build" />
</files>
Hopefully this saves somebody some time.

ASP .Net Core 3.1 build to customer folder with custom name not to netcoreapp3.1

I have developed a class library for existing project solutions. And It builds successfully.
It's a nop commerce plugin and I need to build it into a specific folder with my project name then plugin manager searching my plugin by name and load it into a page to install it..
When I build it then it builds to a folder named 'netcoreapp3.1'. But I need to build it into a custom folder.
This is my .proj file
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<Copyright>Copyright © Company, Ltd</Copyright>
<Company>Company, Ltd</Company>
<Authors>Isanka Thalagala</Authors>
<PackageLicenseUrl></PackageLicenseUrl>
<PackageProjectUrl>http://www.nopcommerce.com/</PackageProjectUrl>
<RepositoryUrl>https://github.com/nopSolutions/nopCommerce</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<OutputPath>..\..\Presentation\Nop.Web\Plugins\Image.Upload.Azure</OutputPath>
<OutDir>$(OutputPath)</OutDir>
<!--Set this parameter to true to get the dlls copied from the NuGet cache to the output of your project.
You need to set this parameter to true if your plugin has a nuget package
to ensure that the dlls copied from the NuGet cache to the output of your project-->
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
</PropertyGroup>
<!-- This target execute after "Build" target -->
<Target Name="NopTarget" AfterTargets="Build">
<!-- Delete unnecessary libraries from plugins path -->
<MSBuild Projects="#(ClearPluginAssemblies)" Properties="PluginPath=$(MSBuildProjectDirectory)\$(OutDir)" Targets="NopClear" />
</Target>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<OutputPath>D:\LabFriend\JohnMorrisCore\API\Presentation\Nop.Web\Plugins\Image.Upload.Azure</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<OutputPath>D:\LabFriend\JohnMorrisCore\API\Presentation\Nop.Web\Plugins\Image.Upload.Azure</OutputPath>
</PropertyGroup>
<ItemGroup>
<None Remove="plugin.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ImageResizer" Version="4.2.5" />
<PackageReference Include="Microsoft.Azure.Storage.Blob" Version="11.1.3" />
<PackageReference Include="WindowsAzure.Storage" Version="9.3.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Presentation\Nop.Web.Framework\Nop.Web.Framework.csproj" />
<ProjectReference Include="..\JohnMorris.Plugin.Core\JohnMorris.Plugin.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="plugin.json" />
<Content Update="plugin.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
This is the setting screen
You can set the following in your .csproj to disable this behavior.
<PropertyGroup>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
I downgrade it to .net core 2.2. and now it's allowed to build with custom name

Current build number not being considered during tfsbuild

Something is not right. i am trying to build and deploy thru the below code snippet, but it so happens that the current build doesnt get deployed, whereas if i give a build number older than a current build, that gets deployed. I am puzzled what is wrong ... Can you please help me ...
I am not sure why the current buildnumber is not being considered ...
<PropertyGroup>
<deployappsvr>\\vdev\$(HostedFolder);\\vdev2\$(HostedFolder)</deployappsvr>
<prjbin>Release\_PublishedWebsites\RE.Service</prjbin>
</PropertyGroup>
<Target Name ="AfterEndToEndIteration" Condition=" '$(IsDesktopBuild)'!='true' ">
<!-- Starting deployment to servers -->
<Message Text="Starting deployment to servers" />
<CallTarget Targets="DeployBatching" />
<Message Text="finished deploying to servers" />
<!-- Unmap TFS mapping -->
<Exec Command="tf workfold /unmap $(tfsmap) /workspace:$(WorkspaceName) /collection:http://tfsapp:8080/tfs"/>
</Target>
<ItemGroup>
<SrcToCopy Include="$(DropLocation)\$(BuildNumber)\$(prjbin)\**\*"/>
<DestToCopy Include="$(deployappsvr)"/>
</ItemGroup>
<Target Name="DeployBatching" Outputs="%(DestToCopy.FullPath)">
<PropertyGroup>
<DestToCopy>%(DestToCopy.FullPath)</DestToCopy>
</PropertyGroup>
<RemoveDir Directories="#(DestToCopy)"/>
<MakeDir Directories="#(DestToCopy)"/>
<Message Text="111 #(SrcToCopy) 222 $(prjbin) 333 "/>
<Message Text="444 Copying source files #(SrcToCopy->'$(DestToCopy)\%(RecursiveDir)\%(Filename)%(Extension)') "/>
<Copy
SourceFiles="#(SrcToCopy)"
DestinationFiles="#(SrcToCopy->'$(DestToCopy)\%(RecursiveDir)\%(Filename)%(Extension)')"/>
<Message Text="Finished Copying source files"/>
<Exec Command="powershell Invoke-Command -computername vdev -scriptblock {md c:\buildtestfolder} > c:\power\pwrcmd.log 2>&1"/>
</Target>
I tired deploying from the build server i.e deploying build artifacts from the server copy instead of dropzone, that seem to be working but deploying from dropzone doesn't seem to be working still.

using dirPath Provider with WebDeploy

I have a wcf application hosted in iis that i am trying to package using webdeploy. Everything works great with the visual studio tools, but i need to also create a logs folder and set permissions on it. For this i created a ProjectName.wpp.target file in my web project.
The file looks like this
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="CreateLogsDirectory" AfterTargets="AddIisSettingAndFileContentsToSourceManifest">
<!-- This must be declared inside of a target because the property
$(_MSDeployDirPath_FullPath) will not be defined at that time. -->
<ItemGroup>
<MsDeploySourceManifest Include="dirPath">
<Path>$(_MSDeployDirPath_FullPath)\logs</Path>
<enableRule>DoNotDeleteRule</enableRule>
</MsDeploySourceManifest>
</ItemGroup>
</Target>
<Target Name="DeclareCustomParameters" AfterTargets="AddIisAndContentDeclareParametersItems">
<!-- This must be declared inside of a target because the property
$(_EscapeRegEx_MSDeployDirPath) will not be defined at that time. -->
<ItemGroup>
<MsDeployDeclareParameters Include="LogsDirectoryPath">
<Kind>ProviderPath</Kind>
<Scope>dirPath</Scope>
<Match>^$(_EscapeRegEx_MSDeployDirPath)\\logs$</Match>
<Value>$(_DestinationContentPath)/log</Value>
<ExcludeFromSetParameter>True</ExcludeFromSetParameter>
</MsDeployDeclareParameters>
</ItemGroup>
</Target>
</Project>
i can see that dirPath provider is added to the sourcemanifest file, but when i deploy the package it tries to create the source file path. Essentially the LogsDirectoryPAth item is not replacing the path. can someone point out what i need to do ? thanks !
Considering your additional directory is inside your web application, it's not really necessary to include another dirPath provider and doing so would only lead to more headaches (additional parameter declarations, etc).
Here are some helpers I use to help with this kind of thing. Your application specific values can be declared in your wpp.targets file:
<!-- Items specific to your application (these should be in your wpp.targets) -->
<ItemGroup>
<SkipDeleteFiles Include="logs" />
<EmptyDirectoriesToDeploy Include="logs" />
<AdditionalAcls Include="logs">
<AclAccess>Write</AclAccess>
</AdditionalAcls>
</ItemGroup>
And the following convention-based definitions can be either put in you wpp.targets or in a common targets file that can be imported into your wpp.targets:
<!--
Empty directories
-->
<PropertyGroup>
<BeforeAddContentPathToSourceManifest>
$(BeforeAddContentPathToSourceManifest);
CreateEmptyDirectories;
</BeforeAddContentPathToSourceManifest>
</PropertyGroup>
<Target Name="CreateEmptyDirectories">
<MakeDir Directories="$(_MSDeployDirPath_FullPath)\%(EmptyDirectoriesToDeploy.Identity)"
Condition="'#(EmptyDirectoriesToDeploy)' != ''" />
</Target>
<!--
Additional ACLs
-->
<ItemDefinitionGroup>
<AdditionalAcls>
<AclAccess>Write</AclAccess>
<ResourceType>Directory</ResourceType>
</AdditionalAcls>
</ItemDefinitionGroup>
<PropertyGroup>
<AfterAddIisSettingAndFileContentsToSourceManifest>
$(AfterAddIisSettingAndFileContentsToSourceManifest);
AddAdditionalAclsToSourceManifest;
</AfterAddIisSettingAndFileContentsToSourceManifest>
<AfterAddIisAndContentDeclareParametersItems>
$(AfterAddIisAndContentDeclareParametersItems);
AddAdditionalAclsDeclareParameterItems
</AfterAddIisAndContentDeclareParametersItems>
</PropertyGroup>
<Target Name="AddAdditionalAclsToSourceManifest">
<ItemGroup Condition="'#(AdditionalAcls)' != ''">
<MsDeploySourceManifest Include="setAcl">
<Path>$(_MSDeployDirPath_FullPath)\%(AdditionalAcls.Identity)</Path>
<setAclResourceType Condition="'%(AdditionalAcls.ResourceType)' != ''">%(AdditionalAcls.ResourceType)</setAclResourceType>
<setAclAccess>%(AdditionalAcls.AclAccess)</setAclAccess>
<AdditionalProviderSettings>setAclResourceType;setAclAccess</AdditionalProviderSettings>
</MsDeploySourceManifest>
</ItemGroup>
</Target>
<Target Name="AddAdditionalAclsDeclareParameterItems">
<ItemGroup Condition="'#(AdditionalAcls)' != ''">
<MsDeployDeclareParameters Include="Add %(AdditionalAcls.AclAccess) permission to %(AdditionalAcls.Identity) Folder">
<Kind>ProviderPath</Kind>
<Scope>setAcl</Scope>
<Match>^$(_EscapeRegEx_MSDeployDirPath)\\#(AdditionalAcls)$</Match>
<Description>Add %(AdditionalAcls.AclAccess) permission to %(AdditionalAcls.Identity) Folder</Description>
<DefaultValue>{$(_MsDeployParameterNameForContentPath)}/#(AdditionalAcls)</DefaultValue>
<DestinationContentPath>$(_DestinationContentPath)/#(AdditionalAcls)</DestinationContentPath>
<Tags>Hidden</Tags>
<ExcludeFromSetParameter>True</ExcludeFromSetParameter>
<Priority>$(VsSetAclPriority)</Priority>
</MsDeployDeclareParameters>
</ItemGroup>
</Target>
<!--
Skip delete files and directories
-->
<PropertyGroup>
<ImportPublishingParameterValuesDependsOn>
$(ImportPublishingParameterValuesDependsOn);
AddSkipDirectives;
</ImportPublishingParameterValuesDependsOn>
</PropertyGroup>
<ItemGroup>
<SkipDeleteItems Include="#(SkipDeleteFiles)"
Condition="'#(SkipDeleteFiles)' != ''">
<Provider>filePath</Provider>
</SkipDeleteItems>
<SkipDeleteItems Include="#(SkipDeleteDirectories)"
Condition="'#(SkipDeleteDirectories)' != ''">
<Provider>dirPath</Provider>
</SkipDeleteItems>
</ItemGroup>
<!-- Uses MSBuild trickery to add an escaped version of the skip path to as
"EscapedPath" metadata -->
<Target Name="AddRegexEscapedPathMetadata" Outputs="%(SkipDeleteItems.EscapedPath)">
<EscapeTextForRegularExpressions Text="%(SkipDeleteItems.Identity)">
<Output TaskParameter="Result"
PropertyName="_Temp_EscapeRegEx_SkipDeleteItemPath" />
</EscapeTextForRegularExpressions>
<ItemGroup>
<SkipDeleteItems Condition="'%(SkipDeleteItems.Identity)' == '%(Identity)'" >
<EscapedPath>$(_Temp_EscapeRegEx_SkipDeleteItemPath)</EscapedPath>
</SkipDeleteItems>
</ItemGroup>
<PropertyGroup>
<!-- Clear value -->
<_Temp_EscapeRegEx_SkipDeleteItemPath></_Temp_EscapeRegEx_SkipDeleteItemPath>
</PropertyGroup>
</Target>
<Target Name="AddSkipDirectives" DependsOnTargets="AddRegexEscapedPathMetadata">
<ItemGroup>
<MsDeploySkipRules Include="%(SkipDeleteItems.Identity)">
<SkipAction>Delete</SkipAction>
<ObjectName>%(SkipDeleteItems.Provider)</ObjectName>
<AbsolutePath>%(SkipDeleteItems.EscapedPath)</AbsolutePath>
</MsDeploySkipRules>
</ItemGroup>
</Target>
NB If you go to the extra effort to separate your packaging process from your deployment process, then technically your SkipDeleteFiles should be in your pubxml rather than your wpp.targets.

MSBuild and creating ZIP files

Here is my build script:
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
<PropertyGroup>
<!-- Path where the solution file is located (.sln) -->
<ProjectPath>W:\Demo</ProjectPath>
<!-- Location of compiled files -->
<DebugPath>W:\Demo\bin\Debug</DebugPath>
<ReleasePath>W:\Demo\bin\Release</ReleasePath>
<!-- Name of the solution to be compiled without the .sln extension --> <ProjectSolutionName>DemoTool</ProjectSolutionName>
<!-- Path where the nightly zip file will be copyd -->
<NightlyBuildPath>W:\Nightly_Builds\Demo</NightlyBuildPath>
<!-- Name of the nighly zip file (YYYYMMDD_NightlyZipName.zip, date added automatically) -->
<NightlyZipName>Demo</NightlyZipName>
</PropertyGroup>
<ItemGroup>
<!-- All files and folders from ./bin/Debug or ./bin/Release what will be added to the nightly zip -->
<DebugApplicationFiles Include="$(DebugPath)\**\*.*" Exclude="$(DebugPath)\*vshost.exe*" />
<ReleaseApplicationFiles Include="$(ReleasePath)\**\*.*" Exclude="$(ReleasePath)\*vshost.exe*" />
</ItemGroup>
<Target Name="DebugBuild">
<Message Text="Building $(ProjectSolutionName) Debug Build" />
<MSBuild Projects="$(ProjectPath)\$(ProjectSolutionName).sln" Targets="Clean" Properties="Configuration=Debug"/>
<MSBuild Projects="$(ProjectPath)\$(ProjectSolutionName).sln" Targets="Build" Properties="Configuration=Debug"/>
<Message Text="$(ProjectSolutionName) Debug Build Complete!" />
<CallTarget Targets="CreateNightlyZip" />
</Target>
<Target Name="CreateNightlyZip">
<PropertyGroup>
<StringDate>$([System.DateTime]::Now.ToString('yyyyMMdd'))</StringDate>
</PropertyGroup>
<MakeDir Directories="$(NightlyBuildPath)"/>
<Zip Files="#(DebugApplicationFiles)"
WorkingDirectory="$(DebugPath)"
ZipFileName="$(NightlyBuildPath)\$(StringDate)_$(NightlyZipName).zip"
ZipLevel="9" />
</Target>
</Project>
My script works perfectly, only there is one strange problem. When i build a project first time and there is no \bin\Debug folder and its created during the build, but the ZIP file still comes empty. Running the build script second time when the \bin\Debug folder is now in place with builded files then the file are added to the ZIP.
What could be the problem that running script first time the ZIP file is empty?
The problem is in the DebugApplicationFiles item collection. It is created before the build is invoked. Move the DebugApplicationFiles into CreateNightlyZip target. Update your script this way:
<Target Name="CreateNightlyZip">
<PropertyGroup>
<StringDate>$([System.DateTime]::Now.ToString('yyyyMMdd'))</StringDate>
</PropertyGroup>
<ItemGroup>
<DebugApplicationFiles Include="$(DebugPath)\**\*.*" Exclude="$(DebugPath)\*vshost.exe*" />
</ItemGroup>
<MakeDir Directories="$(NightlyBuildPath)"/>
<Zip Files="#(DebugApplicationFiles)"
WorkingDirectory="$(DebugPath)"
ZipFileName="$(NightlyBuildPath)\$(StringDate)_$(NightlyZipName).zip"
ZipLevel="9" />
</Target>
If powershell 5.0 or greater is available, you could use powershell command directly.
<Target Name="Zip" BeforeTargets="AfterBuild">
<ItemGroup>
<ZipFiles Include="$(OutDir)release\file1.exe" />
<ZipFiles Include="$(OutDir)release\file2.exe" />
</ItemGroup>
<Exec Command="PowerShell -command Compress-Archive #(ZipFiles, ',') $(OutDir)release\zippedfiles.zip" />
</Target>
Should you wish to zip a whole folder for 'xcopy deploy', since MSBuild 15.8 there is a simple way - the ZipDirectory build task.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="ZipOutputPath" AfterTargets="Build">
<ZipDirectory
SourceDirectory="$(OutputPath)"
DestinationFile="$(OutputPath)\..\$(AssemblyName).zip"
Overwrite=="true" />
</Target>
</Project>
[1] https://learn.microsoft.com/en-us/visualstudio/msbuild/zipdirectory-task?view=vs-2019