TeamCity MSBuild, remap folders during deployment - deployment

We have a solution with a website project which is hosted on a load balanced environment. At the moment no CI is being used, and deployments are manual using zip-files >_< however I'm looking on setting it up, and have run into some difficulties.
The solution requires a App_Config folder containing all the configurations for the site in the root, however these configurations differ from each of the hostingserver, where one is the management server and another is the delivery server.
Each individual server configurations is stored in a separate folder at /Configs/servername/ containing a web.config file and the App_Config folder. These have been manually copied from this folder to the root to overwrite those that already existed.
Also deployment of the /Configs/ folder is not wanted.
Preferably no changes should have to be done to the Visual Studio solution.
Is it possible to automate this before deployment in TeamCity?
regards

You may want to set properties with different values based on the computer name.
That's one trick of the trade.
<Choose>
<When Condition=" '$(Computername)'=='MyManagementServer01' ">
<PropertyGroup>
<MyCustomProperty001>Red</MyCustomProperty001>
<MyCustomProperty002>Yellow</MyCustomProperty002>
</PropertyGroup>
</When>
<When Condition=" '$(Computername)'=='MyDeliveryServer01' ">
<PropertyGroup>
<MyCustomProperty001>Black</MyCustomProperty001>
<MyCustomProperty002>White</MyCustomProperty002>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<MyCustomProperty001>NoMatchMyCustomProperty001</MyCustomProperty001>
<MyCustomProperty002>NoMatchMyCustomProperty002</MyCustomProperty002>
</PropertyGroup>
</Otherwise>
</Choose>
You could setup a property called
<ConfigurationSourceFolder>/Configs/MyManagementServer01/</ConfigurationSourceFolder>
Or setup a "DeploymentType"
<DeploymentType>ManagementServerType</DeploymentType>
You can also put Conditions on "Targets" and even Tasks.
<MakeDir Directories="C:\MyCoolDirectory" Condition="('$(MyCustomProperty001)'!='')"/>
//////Preferably no changes should have to be done to the Visual Studio solution.//////
So here is an "in-general" tip.
Instead of putting alot of custom sometimes-hard-to-follow changes in the csproj files....use a basic .msbuild file.
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="AllTargetsWrapped">
<PropertyGroup>
<!-- Always declare some kind of "base directory" and then work off of that in the majority of cases -->
<WorkingCheckout>.</WorkingCheckout>
</PropertyGroup>
<Target Name="AllTargetsWrapped">
<CallTarget Targets="BuildItUp" />
</Target>
<Target Name="BuildItUp" >
<MSBuild Projects="$(WorkingCheckout)\MySolution.sln" Targets="Build" Properties="Configuration=$(Configuration)">
<Output TaskParameter="TargetOutputs" ItemName="TargetOutputsItemName"/>
</MSBuild>
<Message Text="BuildItUp completed" />
</Target>
</Project>

Related

Substitute for $(SolutionDir) in Azure DevOps CI Pipeline

I have the following situation:
A SQL project in which i have a nuget package installed. This package (merely a PS script) is responsible for unpacking the DACPACs that the DB requires, using references to paths relative to the solution folder (to find the packages/ and dacpacs/ folders and to browse through the projects which it extracts from the .sln file). This is called as a pre-build event.
When building the entire solution, the $(SolutionDir) is defined, as expected (locally and ADO).
When building the test project, the $(SolutionDir) is either '' or '*Undefined*'. Again, as expected, because msbuild has no knowledge about the solution when building a single project. I can live with this caveat locally, no problem.
The question is this: is there something "magical" out there that i can use to make this work in Azure DevOps?
I can try various hacks, if anyone is aware of such methods, although i would like a clean solution.
Tried so far:
1) Adding the following PropertyGroup:
<PropertyGroup>
<SolutionDir Condition="'$(SolutionDir)' == '' Or '$(SolutionDir)' == '*Undefined*'">.\</SolutionDir>
</PropertyGroup>
to the test project.
2) Following these sugestions: Prebuild event in Visual Studio replacing $(SolutionDir) with *Undefined*
No effect.
If your folder structure is something like:
1.The Azure Devops Repos contains the solution folder(where exists the xx.sln file).
2.And those projects are under same solution folder.
You can try my script:
<PropertyGroup>
<ProjectFolder>$([System.IO.Directory]::GetParent($(ProjectDir)))</ProjectFolder>
<MySolutionDir>$([System.IO.Directory]::GetParent($(ProjectFolder)))\</MySolutionDir>
</PropertyGroup>
The $(MySolutionDir) represents the path where your sln file and project folders exists. Same like $(SolutionDir), it also has the \. So it's format looks like SomePath\.
And it's recommended to insert my script above the script of PreBuild event. Something like:
<PropertyGroup>
<ProjectFolder>$([System.IO.Directory]::GetParent($(ProjectDir)))</ProjectFolder>
<MySolutionDir>$([System.IO.Directory]::GetParent($(ProjectFolder)))\</MySolutionDir>
It's recommended to add my script and PreBuildEvent in same propertyGroup, and mine should be in the first.
<PreBuildEvent>echo $(MySolutionDir)</PreBuildEvent>
</PropertyGroup>
Edit1:
You can also add condition on that:
<PropertyGroup>
<ProjectFolder>$([System.IO.Directory]::GetParent($(ProjectDir)))</ProjectFolder>
<MySolutionDir>$([System.IO.Directory]::GetParent($(ProjectFolder)))\</MySolutionDir>
<SolutionDir Condition="xxxx">$(MySolutionDir)</SolutionDir>
It's recommended to add my script and PreBuildEvent in same propertyGroup, and mine should be in the first.
<PreBuildEvent>echo $(MySolutionDir)</PreBuildEvent>
</PropertyGroup>
Edit2:
Hmm, I now can reproduce the issue on my side. It's quite a strange behavior and I'm not sure about the root cause of this one. But a quick workaround is to create a new PropertyGroup to insert our custom script instead inserting it into existing PropertyGroup from default template:
It used to be:
....
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">11.0</VisualStudioVersion>
<!-- Default to the v11.0 targets path if the targets file for the current VS version is not found -->
<SSDTExists Condition="Exists('$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets')">True</SSDTExists>
<VisualStudioVersion Condition="'$(SSDTExists)' == ''">11.0</VisualStudioVersion>
<ProjectFolder>$([System.IO.Directory]::GetParent($'(ProjectDir)'))</ProjectFolder>
<ParentFolder>$([System.IO.Directory]::GetParent($'(ProjectFolder)'))\</ParentFolder>
<SolutionDir Condition=" '$(SolutionDir)' == '' Or '$(SolutionDir)' == '*Undefined*' ">$(ParentFolder)</SolutionDir>
<PreBuildEvent>echo $(SolutionDir)</PreBuildEvent>
</PropertyGroup>
<Import Condition="'$(SQLDBExtensionsRefPath)' != ''" Project="$(SQLDBExtensionsRefPath)\Microsoft.Data.Tools.Schema.SqlTasks.targets" />
<Import Condition="'$(SQLDBExtensionsRefPath)' == ''" Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets" />
<ItemGroup>
<Folder Include="Properties" />
</ItemGroup>
<ItemGroup>
<Build Include="test.sql" />
</ItemGroup>
<PropertyGroup>
<PreBuildEvent>echo $(SolutionDir)</PreBuildEvent>
</PropertyGroup>
</Project>
Now change it to be:
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">11.0</VisualStudioVersion>
<!-- Default to the v11.0 targets path if the targets file for the current VS version is not found -->
<SSDTExists Condition="Exists('$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets')">True</SSDTExists>
<VisualStudioVersion Condition="'$(SSDTExists)' == ''">11.0</VisualStudioVersion>
</PropertyGroup>
<Import Condition="'$(SQLDBExtensionsRefPath)' != ''" Project="$(SQLDBExtensionsRefPath)\Microsoft.Data.Tools.Schema.SqlTasks.targets" />
<Import Condition="'$(SQLDBExtensionsRefPath)' == ''" Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets" />
<ItemGroup>
<Folder Include="Properties" />
</ItemGroup>
<ItemGroup>
<Build Include="test.sql" />
</ItemGroup>
<PropertyGroup>
<ProjectFolder>$([System.IO.Directory]::GetParent($(ProjectDir)))</ProjectFolder>
<ParentFolder>$([System.IO.Directory]::GetParent($(ProjectFolder)))\</ParentFolder>
<SolutionDir Condition=" '$(SolutionDir)' == '' Or '$(SolutionDir)' == '*Undefined*' ">$(ParentFolder)</SolutionDir>
<PreBuildEvent>echo $(SolutionDir)</PreBuildEvent>
</PropertyGroup>
Also, remove the extra ' in $(ProjectDir). It should be $(ProjectDir) instead of $'(ProjectDir)' and $'(ProjectFolder)'. I also see you have two PreBuildEvent properties, just keep the one in our custom script. After above steps, you project now works well on my side:

How to add files to build folder in csproj NuGet package without .nuspec

I have installed AsyncUsageAnalyzers for my common project (which is a NuGet package), and I want the rules to be enforced in all of my packages/applications that depends on it.
I have changed the severity of the UseConfigureAwait rule, and a .ruleset has been created.
I have read on https://stackoverflow.com/a/20271758/2663813 that I needed to have a NuGet package with a given structure. That assertion has been confirmed here https://stackoverflow.com/a/46115423/2663813
What I have now:
build/Common.ruleset
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="My common analyzer rules" Description="The rules that are enforced on all of my projects" ToolsVersion="10.0">
<Rules AnalyzerId="AsyncUsageAnalyzers" RuleNamespace="AsyncUsageAnalyzers">
<Rule Id="UseConfigureAwait" Action="Error" />
</Rules>
</RuleSet>
build/My.Package.Id.targets
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)Common.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
</Project>
In my .csproj
<Import Project="build\My.Package.Id.targets" />
My question is : Now that I've done that and that the rules are correctly applied on my project, how do I add the files under build/ inside the NuGet package, inside the build/ folder? I know that I could do that with a .nuspec file as mentionned in the links, but I'd like to do that with the new .csproj syntax.
Sorry for posting too quickly, I just found the solution right under my eyes:
In the page https://learn.microsoft.com/en-us/dotnet/core/tools/csproj#nuget-metadata-properties , in the comments, MarkPflug gives the translation, and quotes
https://learn.microsoft.com/en-us/nuget/schema/msbuild-targets#pack-scenarios for reference.
In my case, I need to use PackagePath as follows (Content would also work, but as Martin Ullrich pointed out, None is a better choice here):
<ItemGroup>
<None Include="build\**" Pack="True" PackagePath="build\" />
</ItemGroup>

Not able see test cases in Text Explorer for the .net core 2 based Test project

I have created a test project based on .Net Core 2 and wrote some NUnit test cases. After installing necessary NuGet packages i.e. NUnit3TestAdapter, I was able to see all test cases in "Test Explorer" and able to execute those. Now, when I looked into the project directory, I found that it's creating "obj" folder and some json files in it. So I tried to change the path of "obj" folder by modifying ".csproj" file. I provided some different path in the parameter "BaseIntermediateOutputPath" and that way, I was able to get rid of "obj" folder. The reason for providing different path was, I wanted to keep json files separate from source code.
However, after modifying that I am not able to see or execute any test cases from Test Explorer.
Is this a Microsoft bug?
Is any packages having dependency on "obj" folder?
P.S.
I am using "NUnit" and "NSubstitute" packages for my test project.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<TargetFramework>netcoreapp2.0</TargetFramework>
<OutputPath>..\..\build\$(Configuration)\UnitTests\</OutputPath>
<BaseIntermediateOutputPath>..\..\work\$(MSBuildProjectName)\</BaseIntermediateOutputPath>
</PropertyGroup>
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
<ItemGroup>
<PackageReference Include="Castle.Core" Version="4.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
<PackageReference Include="NSubstitute" Version="2.0.3" />
<PackageReference Include="NUnit" Version="3.8.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
</ItemGroup>
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
<ItemGroup>
<ProjectReference Include="..\UtilityLibrary\UtilityLibrary.csproj" />
</ItemGroup>
</Project>
When .NET Core projects build, they do not copy all referenced files into the bin folder. When you add Microsoft.NET.Test.Sdk to your test project, one of the things it does is add an AssemblyResolve event handler which loads other dependent assemblies from a list of searchDirectories.
BaseIntermediateOutputPath not working was reported against the VSTest project and is an issue with MSBuild. The workaround is noted in the dotnet sdk repository. From that, you need to use Sdk imports in your csproj instead of the Sdk attribute on the Project element.
<Project>
<PropertyGroup>
<BaseIntermediateOutputPath>obj\XXX\</BaseIntermediateOutputPath>
</PropertyGroup>
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
<!-- Body of project -->
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
</Project>

Is there a way to create more than one deployment with msbuild on a single build?

I am using msbuild command line multiple times to create a deployment zip file for my dev / test / production websites. I have already configured the parameters and configuration xml for each one. I want to know if i can condense my 3 calls to msbuild down to one, and have it build all three at once?
right now i have to run
msbuild.exe myproject.sln /T:Build /p:DeployOnBuild=True /p:PublishProfile="Dev Server"
msbuild.exe myproject.sln /T:Build /p:DeployOnBuild=True /p:PublishProfile="Prod Server"
etc
The continuous deployment solution i'm using (bamboo) is struggling with this multiple call to msbuild for some reason (i have an open ticket and they are perplexed as well). I'm trying to simplify things.
I have a template for building out all skus of the same solution in parallel.
This is the same concept as Stijn's approach that uses an ItemGroup as a project definition rather than a series of options for a particular property + the msbuild task will build both at the same time, saving you time and bubbling up any configuration issues when building in parallel.
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<SolutionToBuild Include="$(MSBuildThisFileDirectory)\MyProject.sln">
<Properties>DeployOnBuild=True;PublishProfile="Dev Server"</Properties>
</SolutionToBuild>
<SolutionToBuild Include="$(MSBuildThisFileDirectory)\MyProject.sln">
<Properties>DeployOnBuild=True;PublishProfile="Prod Server"</Properties>
</SolutionToBuild>
</ItemGroup>
<Target Name="Build">
<MsBuild BuildInParallel="true" ContinueOnError="true" Projects="#(SolutionToBuild)" />
</Target>
<Target Name="Clean">
<MsBuild BuildInParallel="true" ContinueOnError="true" Projects="#(SolutionToBuild)" Targets="Clean" />
</Target>
</Project>
You can only invoke multiple different targets on the commandline, but you can't supply multiple different values for properties. At least not in a way that I'm aware off. The workaround however is simple, easier to extend than a commandline, and the typical msbuild way of doing things: create a master build file like below and call it from Bamboo instead of the solution.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
DefaultTargets=Build>
<Target Name="Build">
<ItemGroup>
<PublishProfiles Include="Dev Server"/>
<PublishProfiles Include="Prod Server"/>
</ItemGroup>
<MsBuild Projects="myproject.sln" Targets="Build"
Properties="DeployOnBuild=True;PublishProfile=%(PublishProfile.Identity)"/>
</Target>
</Project>
(this will invoke MsBuild myproject.sln once for each item in the PublishProfiles list with the properties as shown)

Publishing my asp.net mvc application via script and not Visual Studio

I dont really know much about this to be honest with you...
I have managed to download mscommunity build and I have managed to use the script below to successfully compile and build my application, however I want to get my asp.net mvc application "published" so I want the same files that you when clicking "publish" inside visual studio. My current build file looks like this:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Import the MSBuild Tasks -->
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Release</Configuration>
<ClassLibraryOutputDirectory>c:\publish\</ClassLibraryOutputDirectory>
<ProjectDir>..\PetProject\</ProjectDir >
<ProjectTestDir>$(ProjectDir)PetProject.WebUI\</ProjectTestDir >
<ProjectFile>$(ProjectDir)PetProject.sln</ProjectFile >
<TestProjectFile>$(ProjectTestDir)PetProject.WebUI.csproj</TestProjectFile >
</PropertyGroup>
<!-- Build projects by calling the Project files generated by VS -->
<Target Name="Build">
<MSBuild Projects="$(ProjectFile)" />
<MSBuild Projects="$(TestProjectFile)" />
</Target>
</Project>
I call this in command line using:
C:\Windows\Microsoft.NET\Framework\v3.5>msbuild.exe C:\Projects\PetProject\build
\PetProject.build
Help is greatly appreciated...
NOTE: I want to avoid CI, Nant etc. because I dont really know what they are and I ideally want to get the above working as First Base, then move onto other things like CI or whatever else, I dont want to confuse myself too much...
This should give you the same result as publishing from within Visual Studio:
<Project DefaultTargets="BuildAndPublish" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.targets" />
<PropertyGroup>
<ProjectFile>C:\PetProject\PetProject.csproj</ProjectFile >
<OutDir>C:\PetProject\MyPublish</OutDir>
</PropertyGroup>
<Target Name="BuildAndPublish">
<MSBuild Projects="$(ProjectFile)" Targets="Package" Properties="Configuration=Release;PackageLocation=$(OutDir)\MSDeploy\Package.zip;_PackageTempDir=$(OutDir)\Temp" />
</Target>
</Project>
for your project.
Don't forget to import Microsoft.Web.Publishing.targets which contains the Package target (which I mixed up with Publish in my inital answer).
If you want to build your solution your script should look something like this:
<Project DefaultTargets="BuildAndPublish" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.targets" />
<PropertyGroup>
<OutDir>C:\PetProject\MyPublish\</OutDir>
</PropertyGroup>
<ItemGroup>
<Solution Include="C:\PetProject\PetProject.sln">
<Properties>
OutDir=$(OutDir);
Platform=Any CPU;
Configuration=Release;
DeployOnBuild=True;
DeployTarget=Package;
PackageLocation=$(OutDir)\MSDeploy\Package.zip;
_PackageTempDir=$(OutDir)\Temp
</Properties>
</Solution>
</ItemGroup>
<Target Name="BuildAndPublish">
<MSBuild Projects="#(Solution)" />
</Target>
</Project>
There's a blog post by Code Inside which describes basically the same approach but didn't work when I tried it in my environment.