Build with msbuild and dynamically set project references - msbuild

I have a couple of projects which reference SQL Server assemblies. With SQL Server 2005 and SQL Server 2008 I am currently maintaining 2 project files which point to the same source files and the only difference is the references to the SQL Server assemblies.
Is there some way that I can only maintain one project and dynamically specify the references in my build script?

Searching for a solution to the same problem you had I came to the proposed solution of having a Condition on the ItemGroup. But this had a side effect because in Visual Studio references I could see both references, which also impacted ReSharper.
I finally use a Choose When Otherwise and I don't have any issue anymore with ReSharper and Visual Studio showing two References.
<Choose>
<When Condition=" '$(Configuration)' == 'client1DeployClickOnce' ">
<ItemGroup>
<ProjectReferenceInclude="..\client1\app.Controls\app.Controls.csproj">
<Project>{A7714633-66D7-4099-A255-5A911DB7BED8}</Project>
<Name>app.Controls %28Sources\client1\app.Controls%29</Name>
</ProjectReference>
</ItemGroup>
</When>
<Otherwise>
<ItemGroup>
<ProjectReference Include="..\app.Controls\app.Controls.csproj">
<Project>{2E6D4065-E042-44B9-A569-FA1C36F1BDCE}</Project>
<Name>app.Controls %28Sources\app.Controls%29</Name>
</ProjectReference>
</ItemGroup>
</Otherwise>
</Choose>
You might read more about it on my blog post: [ProjectReference with Condition in your MSBuild project file][https://laurentkempe.com/2009/12/03/ProjectReference-with-Condition-in-your-MSBuild-project-files/]

Every MSBuild element (ok almost every) can have a Condition associated with it. What I would suggest is that you edit the project file (which is an MSBuild file itself) and place all the SQL server references in an ItemGroup which has a condition on it for instance:
<ItemGroup Condition="'$(SqlServerTargetEdition)'=='2005'">
<!-- SQL Server 2005 References here -->
<Reference Include="..."/>
</ItemGroup>
And another ItemGroup for Sql server 2008:
<ItemGroup Condition="'$(SqlServerTargetEdition)'=='2008'">
<!-- SQL Server 2008 References here -->
<Reference Include="..."/>
</ItemGroup>
You should provide a default value for the property SqlServerTargetEdition before those items are declared. Then at the command line you can override that value using the /p switch when invoking msbuild.exe.
Sayed Ibrahim Hashimi
My Book: Inside the Microsoft Build Engine : Using MSBuild and Team Foundation Build

Related

Change NuGet Package Referenced Based on Profile

We have a NuGet package we created locally. We have a version that includes non-production environments and one that includes the production environment. Let's use Connection and ConnectionProd for reference.
Is there a way that I can set Debug or my non Prod publish profile that I have set up to use the Connection package, but have the Production profile use ConnectionProd? I know the PackageReference has conditions, but I wasn't sure if there was a way to tie that to the selected publish profile?
For security reasons, most of our developers don't have access to the ConnectionProd NuGet source location and I'd like to not have to create another TFS branch solely to handle the production NuGet reference.
You specify different package name and version depending on the envirounment:
<ItemGroup Condition="'$(Configuration)'=='Debug'">
<PackageReference Include="Connection" Version="1.0.0-dev" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'=='Release'">
<PackageReference Include="ConnectionProd" Version="1.0.0" />
</ItemGroup>
UPDATE
Theorically '$(ASPNETCORE_ENVIRONMENT)'=='Development' or using any environment variable for the condition should do it, but somehow I couldn't make it work on my test project.
However, I found another way to make it run with any custom variable, just define your variable inside a <PropertyGroup> then use it to define the condition:
<PropertyGroup>
<MyVariable Condition="'$(MyVariable)'==''">MyValue</MyVariable>
</PropertyGroup>
<ItemGroup Condition="'$(MyVariable)'=='MyValue'">
...
</ItemGroup>
Reference: https://learn.microsoft.com/en-us/visualstudio/msbuild/how-to-build-the-same-source-files-with-different-options?view=vs-2019#example

Condition on "PropertyGroup" in Directory.build.props not working

I've created a Directory.build.props file so I can set the C# language version in there.
But I also have Visual Basic Projects, so i wanted to limit the setting to C# projects.
<Project>
<PropertyGroup Condition="'$(ProjectExt)'=='.csproj'">
<LangVersion>7.2</LangVersion>
</PropertyGroup>
</Project>
But my project is not loading it / the UI is not displaying the language version 7.2.
I've tried to apply the same condition inside the csproj file, also not working.
<PropertyGroup>
<LangVersion Condition="'$(ProjectExt)'=='.csproj'">7.2</LangVersion>
</PropertyGroup>
However, this will work:
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Message Text="Condition working" Importance="high" Condition="'$(ProjectExt)'=='.csproj'"/>
</Target>
The build will output my message
Why is the condition not working on my LanguageVersion? Any Clues?
You will need to use a property to condition on that is available very early in the build. In your case, you should condition on MSBuildProjectExtension:
<PropertyGroup>
<LangVersion Condition="'$(MSBuildProjectExtension)'=='.csproj'">7.2</LangVersion>
</PropertyGroup>
See MSBuild reserved and well-known properties for the complete set of available properties.
ProjectExt is only defined late in the build definition and is therefore not available in Directory.Build.props, which is imported very early into the project.

How to Apply Transforms to SSDT publish profiles

Using Visual Studio 2013
I have a set of 8 SSDT projects that can all be deployed to several distinct environments. The advanced publish settings for each project, however, are meant to be identical. At present I have created a distinct publish profile for each environment, meaning I have ~20 publish profiles all using the exact same settings but different connection strings.
Tweaking the publish settings (which happens with some regularity as I am still a bit new to SSDT) for my databases is most annoying, but I have yet to find a way around this as I am unable to apply transforms to publish profiles like I can to web.config files in an ASP.NET project. I even tried installing the Visual Studio SlowCheetah plugin, but it doesn't appear to work with SSDT projects as the option to apply transform does not appear when right-clicking on a publish profile.
I don't want my team to have to think about entering connection details manually when deploying a DB to dev or QA environments. Is there any way to set a master publish profile or otherwise specify a group of shared settings so that I don't have to manage 20 nearly identical publish profiles??
EDIT:
Using SAS' answer I was able to cobble together the following XML for my .sqlproj file:
<PropertyGroup>
<PublishProfileDir>$(ProjectDir)Publish Profiles\</PublishProfileDir>
<TemplatePublishProfile>$(PublishProfileDir)Baseline\publish.xml</TemplatePublishProfile>
</PropertyGroup>
<Target Name="CopyXml" AfterTargets="Build">
<Copy SourceFiles="$(TemplatePublishProfile)" DestinationFolder="$(PublishProfileDir)Dev"/>
<Copy SourceFiles="$(TemplatePublishProfile)" DestinationFolder="$(PublishProfileDir)Qa"/>
</Target>
<ItemGroup>
<DevPublishUpdates Include="ConfigUpdates">
<XPath>/msb:Project/msb:PropertyGroup/msb:TargetDatabaseName</XPath>
<NewValue>CountyRecordsDev</NewValue>
</DevPublishUpdates>
<DevPublishUpdates Include="ConfigUpdates">
<XPath>/msb:Project/msb:PropertyGroup/msb:DeployScriptFileName</XPath>
<NewValue>CountyRecords.Dev.Sql</NewValue>
</DevPublishUpdates>
</ItemGroup>
<Target Name="UpdateXml" AfterTargets="CopyXml">
<Message Text="Editing Derived Xml Publish Profiles" Importance="high" />
<XmlPoke Namespaces="<NamespacePrefix='msb'Uri='http://schemas.microsoft.com/developer/msbuild/2003'/>"
XmlInputath="$(PublishProfileDir)Dev\publish.xml"
Query="%(DevPublishUpdates.XPath)"
Value="%(DevPublishUpdates.NewValue)" />
</Target>
The only downside is that I seem to need a separate folder for all my publish profiles in order to prevent one transform from overwriting another, I could not seem to find a way to simply overwrite an existing file. For XmlPoke, the namespaces attribute is critical to operation. I learned more about this process from this blog post by Sayed Ibrahim Hashimi.
We are using a template xml file that is copied automagically as a pre-step in the publish, for all our targets, so any changes need only be mande in the template. The target server name is replaced dynamically as the publish xml files are created. We also had to modify the xaml for this. We use Copy and XMLPoke tags in common proj-file thar is included in our proj-files. It takes some work, but works fine.
Edit: I have pasted in some code below to try to explain, it is only part of the original but I hope it is enough to get everyone started:
This part of what is in our common file (SQLCommonInclude.proj):
<Target Name="CreatePublishXMLFile">
<PropertyGroup>
<VersionNumber Condition="'$(VersionNumber)'==''">Local Build</VersionNumber>
<CurrentDate>$([System.DateTime]::Now.ToString(yyyy-MM-dd HH:mm:ss))</CurrentDate>
<SqlPublishProfilePath Condition="'$(SqlPublishProfilePath)'==''">Publish\$(TargetServerParam).publish.xml</SqlPublishProfilePath>
<TargetXMLFile>$(ProjectDir)Publish\$(TargetServerParam).publish.xml</TargetXMLFile>
<ChangeSets Condition="'$(ChangeSets)'==''">Unknown</ChangeSets>
</PropertyGroup>
<XmlPoke XmlInputPath="$(TargetXMLFile)" Query="/*[local-name()='Project']/*[local-name()='PropertyGroup']/*[local-name()='TargetConnectionString']" Value="Data Source=$(TargetServerParam)%3BIntegrated Security=True%3BPooling=False" />
<XmlPoke XmlInputPath="$(TargetXMLFile)" Query="/*[local-name()='Project']/*[local-name()='PropertyGroup']/*[local-name()='TargetDatabaseName']" Value="$(ProjectName)" />
<XmlPoke XmlInputPath="$(TargetXMLFile)" Query="/*[local-name()='Project']/*[local-name()='ItemGroup']/*[local-name()='SqlCmdVariable'][#Include='ChangeSets']/*[local-name()='Value']" Value="$(ChangeSets)" />
</Target>
Then call this repeatedly, for each target server:
<Target Name="CreateAllPublishXMLFiles">
<MSBuild Projects="$(MSBuildProjectFile)" Targets="CreatePublishXMLFile" Properties="TargetServerParam=OURSERVER1" />
<MSBuild Projects="$(MSBuildProjectFile)" Targets="CreatePublishXMLFile" Properties="TargetServerParam=OURSERVER2" />
</Target>
In each Project file we include and call the common code:
<Import Project="$(SolutionDir)SQLCommonInclude.proj" />
<Target Name="BeforeBuild" DependsOnTargets="CreateAllPublishXMLFiles">
Then, In a Post-deployment Script we set the Extended Properties like this:
IF NOT EXISTS (SELECT NULL FROM SYS.EXTENDED_PROPERTIES WHERE class_desc = 'DATABASE' AND name = 'SSDT ChangeSets')
EXEC sp_addextendedproperty #name = N'SSDT ChangeSets', #value = '';
EXEC sp_updateextendedproperty #name = N'SSDT ChangeSets', #value = '$(ChangeSets)';

MSBuild and _PublishedWebsites

After MSbuild has built my solution (with an asp.net website), and the webdeployment project has built and put the website in the directory _PublishedWebsites:
c:\mybuilds\buildName\Daily_20090519.3\Release_PublishedWebsites\MyWebsite.
How do I copy this to the fixed directory where IIS points to for the test website?
I have found loads of code snippets, but I cannot seem to find one that will take into account the fact that this directory name changes.
This is pretty easy. You can edit the project and insert something similar to the following.
<PropertyGroup>
<OutputDest>$(MSBuildProjectDirectory)\..\OutputCopy\</OutputDest>
</PropertyGroup>
<Target Name="AfterBuild">
<!-- Create an item with all the output files -->
<ItemGroup>
<_OutputFiles Include="$(OutputPath)**\*" Exclude="$(OutputPath)obj\**\*" />
</ItemGroup>
<!-- You probably don't want to include the files in the obj folder so exclude them. -->
<Message Text="OutputDest : $(OutputDest)" />
<Copy SourceFiles="#(_OutputFiles)"
DestinationFiles="#(_OutputFiles->'$(OutputDest)%(RecursiveDir)%(Filename)%(Extension)')"/>
</Target>
Is this what you are looking for?
My Book: Inside the Microsoft Build Engine : Using MSBuild and Team Foundation Build
I'm using different technique.
<PropertyGroup>
<BinariesRoot>c:\BinariesForIis\</BinariesRoot>
</PropertyGroup>
The c:\BinariesForIis\ will be used for direct output compiled binaries (before copy to ...\Daily_20090519.3\Release_ ...).

MSBuild task configuration property

I have three Visual Studio solutions. The first is configured to build as Release, and the other two are set to build as Debug.
When running a simple MSBuild script explicitly stating the configuration to build (Debug), the first project is still built as Release.
Sample script:
<Target Name="Build">
<ItemGroup>
<ProjectToBuild Include="$(SolutionsPath)\Solution1.sln"/>
<ProjectToBuild Include="$(SolutionsPath)\Core\Solution2.sln"/>
<ProjectToBuild Include="$(SolutionsPath)\UI\Solution3.sln"/>
</ItemGroup>
<MSBuild Projects="#(ProjectToBuild)"
Targets="Rebuild"
Properties="Configuration=Debug;Platform=Any CPU"/>
</Target>
I have tried variations of the above such as the following, but I always end up with the same result.
<Target Name="Build">
<ItemGroup>
<ProjectToBuild Include="$(SolutionsPath)\Solution1.sln">
<Properties>Configuration=Debug</Properties>
</ProjectToBuild>
<ProjectToBuild Include="$(SolutionsPath)\Core\Solution2.sln">
<Properties>Configuration=Debug</Properties>
</ProjectToBuild>
<ProjectToBuild Include="$(SolutionsPath)\UI\Solution3.sln">
<Properties>Configuration=Debug</Properties>
</ProjectToBuild>
</ItemGroup>
<MSBuild Projects="#(ProjectToBuild)"
Targets="Rebuild"
Properties="Platform=Any CPU"/>
</Target>
I note there is a similar question, MSBuild task - Build fails because one solution being built in release instead of debug, but that is specific to TFS and Teambuild. I am talking pure MSBuild with a simple project file created from scratch.
How can I fix this problem?
Regarding the question of spelling of platform any cpu, it turns out there is an issue, already reported elsewhere here on StackOverflow and Microsoft. It affects MSBuild in general and the entire issue of Platform documentation is omitted in my dotnet v3.5 MSBuild /help. So perhaps this will help someone!
Links
"AnyCPU" vs "Any CPU" in TFS 2010
MSBuild inconsistent platform for "Any CPU" between solution and project
Closed as Won't Fix
Type: Bug
ID: 503935
Opened: 10/26/2009 1:29:12 PM
Access Restriction: Public
0 Workaround(s)
5 User(s) can reproduce this bug
The MSBuild Platform property has a different value for Any CPU depending upon whether you are building a solution or building a project.
- for Solution - use Platform="Any CPU" - with space
- for Project - use Platform="AnyCPU" - without space
OK I have found the issue. Nothing related to MSBuild, but instead the solution being built. Posting to save someone else the heartache.
For whatever reason the Debug configuration was configured within the solution like so:
alt text http://www.freeimagehosting.net/uploads/cad0bdf1c0.jpg
So MSBuild was only doing what it was told too...
I was getting this same error. The solution was to explicitly specify the target platform with:
msbuild.exe /p:Platform="Any CPU"
This only started happening since I upgraded to windows 7, so I guess it is something to do with that.
Have you tried running with /v:diag?
Also, aside: I think you want "AnyCPU" (no space).