Define custom variable in Msbuild executed in Azure Devops build pipeline - msbuild

I have an MsBuild (csproj) file in which I do some custom conditional items and targets.
Everything runs fine for one configuration but I get an error for others that are cloned from the original. The error comes from the fact a custom variable out of a regex does not get set.
Here are the main excerpts from the MsBuild script:
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'N1|AnyCPU' ">
<DefineConstants>TRACE;DEBUG;SITE_N1</DefineConstants>
</PropertyGroup>
<PropertyGroup Label="Site Extractor">
<MySite>$([System.Text.RegularExpressions.Regex]::Match($(DefineConstants), '(?<=(?:^|;)SITE_)([^;$]+)(?=;|$)'))</MySite>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'N2|AnyCPU' ">
<DefineConstants>TRACE;DEBUG;SITE_N2</DefineConstants>
</PropertyGroup>
<PropertyGroup Label="N1 Specifics" Condition="$(MySite) == 'N1'">
<SharedResPath>..\..\Resources\N1.1</PathPilotageKX>
</PropertyGroup>
<PropertyGroup Label="N2 Specifics" Condition="$(MySite) == 'N2'">
<SharedResPath>..\..\Resources\N2.0</PathPilotageKX>
</PropertyGroup>
<Target Name="My Validations" BeforeTargets="BeforeBuild">
<Message Text="DefinedConstants: [$(DefineConstants)]" />
<Message Text="Site: [$(MySite)]" />
<Message Text="Shared Res Path: [$(SharedResPath)]" />
<Message Text="Configuration: $(Configuration)" />
<Error Text="Site code (SITE_$(MySite)) is not supported by csproj (MSBuild)." Condition="$(SharedResPath) == ''" />
</Target>
Validation task result is:
My Validations:
DefinedConstants: [TRACE;SITE_N1]
Site: []
Shared Res Path: []
Configuration: N1
##[error]MyProject.csproj(352,5): Error : Site code (SITE_) is not supported by csproj (MSBuild).
Edit 1:
After some more fiddling, it looks like it is not related to Azure DevOps after all since I was able to reproduce the problem on the dev machine; so I removed every mentions of it. Also, it does not seem to neither be related to the value of DefineConstants since copying the value from one that is working to one that is not and vice-versa doesn't change anything.
Edit 2
Since the order of the elements has importance after all, I edited the excerpts to represent what was really in my project. Sorry for misleading, I didn't realize it had importance since my focus was elsewhere.

I hope the original question was not too misleading though I'm not sure the culprit could have been identified with it the way it was originally presented. (I modified the question to represent the real situation).
So the thing is, the order in which the PropertyGroup blocks appear is important. The csproj I modified was created using Visual Studio and the new configurations (in that case N2) were added to the end of the file but I had the custom property (MySite) set at the top close to the original property group (here N1).
Concretely, if I have
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'N1|AnyCPU' ">
<DefineConstants>TRACE;DEBUG;SITE_N1</DefineConstants>
</PropertyGroup>
<PropertyGroup Label="Site Extractor">
<MySite>$([System.Text.RegularExpressions.Regex]::Match($(DefineConstants), '(?<=(?:^|;)SITE_)([^;$]+)(?=;|$)'))</MySite>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'N2|AnyCPU' ">
<DefineConstants>TRACE;DEBUG;SITE_N2</DefineConstants>
</PropertyGroup>
DefineConstants is not set yet when the regex attemps to extract the value. If instead I define the project with
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'N1|AnyCPU' ">
<DefineConstants>TRACE;DEBUG;SITE_N1</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'N2|AnyCPU' ">
<DefineConstants>TRACE;DEBUG;SITE_N2</DefineConstants>
</PropertyGroup>
<PropertyGroup Label="Site Extractor">
<MySite>$([System.Text.RegularExpressions.Regex]::Match($(DefineConstants), '(?<=(?:^|;)SITE_)([^;$]+)(?=;|$)'))</MySite>
</PropertyGroup>
Everything now works fine.

Related

Visual Studio 2017 Always Deploying in Development Mode

I have an ASP.NET Core application that I'm attempting to perform web deploy to a server and no matter what build configuration I select in the profile wizard, it always deploys multiple appsettings files as well as the PDB files for the DLLs. Anyone know what could be causing this?
If you want to exclude .pdb files for release publishing, then you may disable their generation during release build by adding next property to .csproj file (found this here)
<PropertyGroup>
<DebugType Condition=" '$(Configuration)' == 'Release' ">None</DebugType>
</PropertyGroup>
Regarding any configuration (or not) file, it will be published based on CopyToPublishDirectory attribute value. So again, you may use Condition attribute and have something like this (just an idea):
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<Content Update="appsettings.debug.json" CopyToPublishDirectory="Always"/>
<Content Update="appsettings.release.json" CopyToPublishDirectory="Never"/>
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)' == 'Release' ">
<Content Update="appsettings.debug.json" CopyToPublishDirectory="Never"/>
<Content Update="appsettings.release.json" CopyToPublishDirectory="Always"/>
</ItemGroup>
But in general, it's better to depend on environment setting (Dev, Prod) when we are talking about configs.

MSBuild Update property in csproj

I have been trying to update the ApplicationVersion property in my csproj file.witch works fine; i have added a Target that runs an custom task to extract the AssemblyFileVersion from my assemblyinfo.cs; this works there is no doubt about that.
But then when i want to use my updated ApplicationVersion to determan where to put my newly build files, i get the default value set in the property.
<PropertyGroup>
...
<ApplicationVersion>1.0.0.0</ApplicationVersion>
...
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\media-converter-BUILD\debug\$(ApplicationVersion)\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>..\media-converter-BUILD\debug\$(ApplicationVersion)\MediaConverter.XML</DocumentationFile>
</PropertyGroup>
My Targets
<UsingTask AssemblyFile="GetAssemblyFileVersion.dll" TaskName="GetAssemblyFileVersion.GetAssemblyFileVersion" />
<Target Name="MainAfterCompile">
<CallTarget Targets="AfterCompile" />
<CallTarget Targets="VerifyParam" />
</Target>
<Target Name="AfterCompile">
<GetAssemblyFileVersion strFilePathAssemblyInfo="Properties\AssemblyInfo.cs">
<Output TaskParameter="strAssemblyFileVersion" PropertyName="ApplicationVersionModded" />
</GetAssemblyFileVersion>
<PropertyGroup>
<ApplicationVersion>$(ApplicationVersionModded)</ApplicationVersion>
</PropertyGroup>
</Target>
<Target Name="VerifyParam">
<Message Text="New $(ApplicationVersionModded)" Importance="high"/>
<Message Text="Old Updated $(ApplicationVersion)" Importance="high"/>
</Target>
the GetAssemblyFileVersion.dll i more or less stole from some post i found on the internet, just can't find it again, so i can't add a link, sorry.
My theory on why it does not work is that the transforms and parameters in PropertyGroups are rendered before both InitailTagets and DefaultTargets is run. And there for will my plan never work
but if anyone knows of a way to make it work, i will be grateful to here it
My theory on why it does not work is that the transforms and parameters in PropertyGroups are rendered before both InitailTagets and DefaultTargets is run indeed, that's how the evaluation order works: msbuild evaluates global properties in the first pass of the file, you define OutputPath, that is used by the Microsoft.Common.CurrentVersion.targets file to derive OutDir/BaseIntermediateOutputPath/.... Then in another pass your targets run and update the version number, but there isn't another pass which evaluates the global OutputPath property again.
You can however override the value of OutputPath and derived paths in a Target, and it will take effect, you just have to take care of running it early in the build so that other targets use the updated version. This does the trick:
<Target Name="GetApplicationVersion">
<GetAssemblyFileVersion strFilePathAssemblyInfo="Properties\AssemblyInfo.cs">
<Output TaskParameter="strAssemblyFileVersion" PropertyName="ApplicationVersion" />
</GetAssemblyFileVersion>
</Target>
<Target Name="SetOutputPaths" DependsOnTargets="GetApplicationVersion"
BeforeTargets="PrepareForBuild">
<PropertyGroup>
<OutputPath>bin\$(Configuration)\$(ApplicationVersion)\</OutputPath>
<OutDir>$(OutputPath)</OutDir>
</PropertyGroup>
<Message Text="Set OutDir to $(OutDir)" Importance="high" />
</Target>
Another way to deal with this is doing things the other way around: define the application version as a global msbuild property, then use it to define OutputPath and to update the number in AssemblyVersion.cs before it is compiled.

How to reference WixDifxAppExtension in a .wixproj file?

I am developing an installer for Windows using the WiX toolset. Instead of invoking the WiX tools directly, I created a .wixproj file and am using MsBuild to build it. My installer needs to install a driver, so I decided to try the Difxapp Extension.
The problem is that I don't know how to properly add the Difxapp extension in my .wixproj file. Inside the ItemGroup tag, I added <WixExtension Include="WixDifxAppExtension" /> and that causes MSBuild to add the argument -ext "C:\Program Files (x86)\WiX Toolset v3.8\bin\WixDifxAppExtension.dll" when it invokes Light.exe. However, I think I need to do something else, because I am getting the following error message:
error LGHT0094: Unresolved reference to symbol 'CustomAction:MsiProcessDrivers'
According to this thread from 2009, I need to add another argument to light.exe command, and that argument should be:
"$(WIX)bin\difxapp_x86.wixlib"
Does anyone have an example .wixproj they could share that shows how WixDifxAppExtension needs to be referenced? Alternatively, does anyone know how to just add an arbitrary linker argument? There must be a way.
Here is my current .wixproj file:
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProductVersion>1.0.4</ProductVersion>
<DefineConstants>ProductVersion=$(ProductVersion)</DefineConstants>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProjectGuid>{FACDEEA0-F843-4ca7-8FCC-09AFFFCAAE85}</ProjectGuid>
<SchemaVersion>2.0</SchemaVersion>
<OutputName>installer-$(ProductVersion)</OutputName>
<OutputType>Package</OutputType>
<DefineSolutionProperties>false</DefineSolutionProperties>
<WixTargetsPath Condition=" '$(WixTargetsPath)' == '' ">$(MSBuildExtensionsPath)\Microsoft\WiX\v3.x\Wix.targets</WixTargetsPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<OutputPath>bin\$(Configuration)\</OutputPath>
<IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath>
<DefineConstants>Debug;ProductVersion=$(ProductVersion)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<OutputPath>bin\$(Configuration)\</OutputPath>
<IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath>
</PropertyGroup>
<ItemGroup>
<Compile Include="installer.wxs" />
<WixExtension Include="WixUIExtension" />
<WixExtension Include="WixDifxAppExtension" />
<!-- TODO: add "$(WIX)bin\difxapp_x86.wixlib" -->
</ItemGroup>
<Import Project="$(WixTargetsPath)" />
</Project>
Add a WixLibrary item:
<WixLibrary Include="difxapp_x86.wixlib" />
I just added the 'difxapp_x86.wixlib' to 'References'.
That removed the error "Unresolved reference to symbol 'CustomAction:MsiProcessDrivers'".

MSBuild dynamic AssemblyName

How can I change AssemblyName dynamically, based on content of some file?
For instance, I have file named "AssemblyBaseName.txt" which contains "Abcd" string I want my assembly DLL/EXE name to be Abcd.Common.dll, where "Common" is constant.
I've tried to use ReadLinesFromFile like this
<Target Name="Build">
<ItemGroup>
<AssemblyBaseNameFile Include="Test.txt"/>
</ItemGroup>
<ReadLinesFromFile File="#(AssemblyBaseNameFile)">
<Output TaskParameter="Lines" ItemName="AssemblyBaseName" />
</ReadLinesFromFile>
<Message Text="AssemblyBaseName: $(AssemblyBaseName)" />
</Target>
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{FEE469DB-44BD-4CD9-BA08-91F1DFDE9679}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Common</RootNamespace>
<AssemblyName Condition=" '$(AssemblyName)' == '' ">$(AssemblyBaseName).Common</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
but did not succeed.
Since static properties are processed before any targets are executed, your target changed the value of $(AssemblyBaseName), but only after the value of the statically declared $(AssemblyName) was set – using the original value of $(AssemblyBaseName).
You need to redefine $(AssemblyName) dynamically (inside your target) as well.
Also, you've overridden the "Build" target. I'm not sure how this assembly will build, you need to use a differently named target, and be sure that it executes prior to any use of $(AssemblyName) by the normal build pipeline.

Is there any MSBuild property that shows we are in publish?

I want to conditionally undefine DEBUG if it's a publish build.
is there a property I can check to see if we're currently publishing?
You can wire in your own target to set a property that you can then key behavior off of, or do whatever you want. The project modification below shows how to wire into the existing Publish target dependencies with your own before and after target. The before target sets a property. Then, in the existing part of your project where DEBUG is defined within the $(DefineConstants) property, you conditionally decide on whether or not to add DEBUG into the constant list, based on the property you set when the build is being performed because of a Publish.
<PropertyGroup>
<PublishDependsOn>MyBeforePublish;$(PublishDependsOn);MyAfterPublish</PublishDependsOn>
</PropertyGroup>
<Target Name="MyBeforePublish">
<PropertyGroup>
<DetectPublishBuild>true</DetectPublishBuild>
</PropertyGroup>
</Target>
<Target Name="MyAfterPublish">
<PropertyGroup>
<DetectPublishBuild>false</DetectPublishBuild>
</PropertyGroup>
</Target>
...
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<PlatformTarget>x86</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<DefineConstants
Condition="'$(DetectPublishBuild)' != 'true'"
>DEBUG;$(DefineConstants)</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
Tested in VS2019 16.10.1.
<Target Name="XXX" Condition="'$(PublishProtocol)'!=''">
<Copy SourceFiles="Web.Base.config" DestinationFiles="Web.config" OverwriteReadOnlyFiles="True" Condition="!('$(PublishProfileName)' == '' And '$(WebPublishProfileFile)' == '')" />
This will perform the "Copy" only when the build is using the PublishProfile flag.
http://sedodream.com/2013/01/06/commandlinewebprojectpublishing.aspx
<Choose>
<When Condition="'$(BuildType)' == 'publish'">
<PropertyGroup>
<DefineConstants>Release</DefineConstants>
</PropertyGroup>
</When>
</Choose>
You may need other values in there besides release. But, this should work.
What we do at our place though is to actually have a publish, debug, and release. We created publish by having it copy from release so it has all the settings in it.