How to check string contains a string in msbuild? - msbuild

I have a problem writing a targets file for my vcxproj project.
In my targets file the first thing I want to set some build properties based on the configuration so I have this:
<Choose>
<When Condition="$(Configuration.Contains('Debug', StringComparison.OrdinalIgnoreCase))">
<PropertyGroup>
</PropertyGroup>
</When>
</Choose>
But when I try to open the project with imported target file I get an error
error : The expression ""Debug".Contains(Debug, StringComparison.OrdinalIgnoreCase)" cannot be evaluated. Method 'System.String.Contains' not found.
I am using Visual Studio 2022 Preview.
Acording to MSBuild docs the string is just a .Net5 string and should have the method.
Could someone help me please?

That overload of Contains is only available in recent .Net versions, not in e.g. 4.8 so that is why it is not found. Workaround: convert to lower then compare
<Choose>
<When Condition="$(Configuration.ToLowerInvariant().Contains('debug'))">
</When>
</Choose>

Related

MSBuild: How Get AppData Local Directory?

I need help getting the AppData Local directory for MSBuild.
File Explorer:
%LOCALAPPDATA%
C#:
System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData)
PowerShell:
$([System.Environment]::GetFolderPath("LocalApplicationData"))
I've tried the following four variations to get it for MSBuild:
<PropertyGroup>
<AppDataLocalDir1>$([System.Environment]::GetFolderPath([System.Environment.SpecialFolder]::LocalApplicationData))</AppDataLocalDir1>
<AppDataLocalDir2>$([System.Environment]::GetFolderPath([System.Environment.SpecialFolder.LocalApplicationData]))</AppDataLocalDir2>
<AppDataLocalDir3>([System.Environment]::GetFolderPath("LocalApplicationData"))</AppDataLocalDir3>
<AppDataLocalDir4>([System.Environment]::GetFolderPath('LocalApplicationData'))</AppDataLocalDir4>
</PropertyGroup>
I feel that my syntax must be close, as this works to get the current date:
<PropertyGroup>
<Today>$([System.DateTime]::Now.ToString('yyyy.MM.dd'))</Today>
</PropertyGroup>
I have found sources that state that MSBuild can call [System.Environment]::GetFolderPath, but I cannot find any that show the syntax for passing in an argument.
Thanks a lot in advance.
From "Property Functions: GetFolderPath" the syntax for the enum for GetFolderPath is:
<PropertyGroup>
<AppDataLocalDir>$([System.Environment]::GetFolderPath(SpecialFolder.LocalApplicationData))</AppDataLocalDir>
</PropertyGroup>

Azure DevOps - conditional package restoration

A solution that is built via Azure DevOps pipeline has some projects that use conditional package references such as:
<Choose>
<When Condition="'$(Configuration)'=='Debug'">
<ItemGroup>
<PackageReference Include="Mock.MyPackage" Version="1.0.0" />
</ItemGroup>
</When>
<Otherwise>
<ItemGroup>
<PackageReference Include="MyPackage" Version="1.2.0" />
</ItemGroup>
</Otherwise>
</Choose>
The package source is a private NuGet feed.
The dotnet restore task does not respect the conditional package selection. (it restores Mock.MyPackage)
Question:
how can I conditionally restore packages (based on a $Configuration) ?
Remarks:
I have also tried restoring during Visual Studio Build task by specifying an MsBuild argument: /t:restore.
In that case it fails with a message: Failed to retrieve information about XX from remote source. If this command can restore packages how can I specify authorization args for the private feed ?
There is an issue on Github: https://github.com/NuGet/Home/issues/5895 where such issue is mentioned at the end.
Azure DevOps - conditional package restoration
This issue should be related your specify requirement and the limitation for current Visual Studio Build task/dotnet restore.
Just as you test, if we use the restore task, we could not specify the configuration parameter with this task. Since there is no such option to receive the configuration parameter for the restore task. That is the reason why it always restores the default package Mock.MyPackage.
If you use the Visual Studio build task, we could not to specify authorization args for the private feed.
To resolve this issue, I use the Command line V1 task to invoke MSBuild to restore and build the project with following MSBuild argument:
-t:restore;build "CoreConditionRestore/CoreConditionRestore/CoreConditionRestore.csproj" -p:RestoreSources="<MyFeed>/v3/index.json" -p:RestoreConfigFile="<MyNugetConfigPath>\nuget.config" /p:Configuration=Debug
Note: we could save the authorization args for the private feed in the nuget.config file.
As test, it works fine on my side with Devops.
Hope this helps.
A much, much simpler answer is to add in NUGET_RESTORE_MSBUILD_ARGS with a value of /p:Configuration=$(BuildConfiguration) to your pipeline variables. This will actually just pass your build configuration to the NuGet restore task.
This is incredibly poorly documented and it took scouring github bug requests to find it
Environment variable documentation:
https://learn.microsoft.com/en-us/nuget/reference/cli-reference/cli-ref-environment-variables
Github issue I found this on:
https://github.com/NuGet/Home/issues/7575
Not sure why this isn't enabled by default, either.
As a note, you still need to do the (imo strange)
<Choose>
<When Condition="'$(Configuration)'=='Debug'">
<ItemGroup>
<PackageReference Include="Mock.MyPackage" Version="1.0.0" />
</ItemGroup>
</When>
</Choose>
in your project file rather than the more intuitive:
<PackageReference Include="Mock.MyPackage" Version="1.0.0" Condition="'$(Configuration)' == 'Debug'" />
...which does work with normal Reference directives eg:
<Reference Include="MOCK_DLL" Condition="'$(Configuration)' == 'Debug'">
<HintPath>..\References\MOCK_DLL.dll</HintPath>
</Reference>
I wanted minimal change in deployment configuration, so the following was acceptable.
An empty project is created and all packages which are restored conditionally are referenced there (without any conditions).
Additional argument to msbuild task is added: '/t:restore'
During build the following will happen:
'dotnet restore' will restore all the packages thanks to 1).
Since it does not know a selected configuration it may pick a wrong package (Mock.MyPackage instead of MyPackage).
That is where 2) comes, where msbuild task will restore packages from a local cache made by 'dotnet restore'.
Remarks:
As #(Leo Liu-MSFT) wrote, dotnet restore can authenticate but it does not know configuration, and msbuild knows configuration but it can not authenticate, so package restoration in AzDevOps from private feeds is tricky.

Changing BaseIntermediateOutputPath causes build error CS0103

I have a simple .net standard project that included Xamarin.Forms in Visual studio 2017. The build was successful until I change the default BaseIntermediateOutputPath in project file.
Snippet of the .csproj file:
<PropertyGroup>
<TargetFramework>netstandard1.4</TargetFramework>
<PackageTargetFallback>portable-net45+win8+wpa81+wp8</PackageTargetFallback>
<BaseIntermediateOutputPath>hello/</BaseIntermediateOutputPath> <!-- Comment this out and build passes -->
</PropertyGroup>
The build error was:
Page1.xaml.cs(17,4): error CS0103: The name 'InitializeComponent' does not exist in the current context.
After inspecting the msbuild output. It looks like the XamlG target never included/executed.
Any insight?
Thanks.
C.

MSBuild: ProjectReference Output Path in Wix Projects

I'm trying to get the Output folder of referenced project in my Wix Visual Studio project, using MsBuild task. But it is failing with following error:
C:\Program Files
(x86)\MSBuild\14.0\bin\Microsoft.Common.CurrentVersion.targets(724,5):
error : The OutputPath property is not set for project
'ConsoleApplicatio n1.csproj'. Please check to make sure that you
have specified a valid combination of Configuration and Platform for
this project. Configuration='Release' Platfo rm='x86'. You may be
seeing this message because you are trying to build a project without
a solution file, and have specified a non-default Configuration or
Plat form that doesn't exist for this project.
[C:\Users\fwaheed\Documents\Visual Studio
2015\Projects\ConsoleApplication1\ConsoleApplication1\ConsoleApplication1.cspro
j]
Following is the Target calling MsBuild task.
<Target Name="AfterBuild">
<MSBuild
Projects="#(ProjectReference)"
Targets="GetTargetPath"
BuildInParallel="false"
Condition="'%(Name)'=='ConsoleApplication1'" >
<Output TaskParameter="TargetOutputs" ItemName="DependentAssemblies" />
</MSBuild>
</Target>
Please note that same target worked perfectly if it is CSharp project, but failing in Wix project.
Can someone guide how to get ReferencedProjects output dirs in Wix Projects?
Thanks
You can try to see how Wix does it for passing the reference values to candle on build. They're in the wix2010.targets or wix200x.targets file. Unfortunately I don't have the time to really dig into this stuff but the properties these tasks set should still exist to be used in your AfterBuild target.
Just search for "ResolveReferences" in one of those targets files.
You can also just try setting
<OutputPath>somepathhere</OutputPath>
in your csproj file since msbuild is complaining that the property isn't set.

How to always execute a target in MSBuild

I have an MSBuild file that manipulates the AssemblyInfo file before the application is compiled. At the end of the build, it restores the AssemblyInfo file. It does this by backing up the file, manipulating it, and then after build time, restoring the file.
This works fairly well except when an error occurs during the build. It then does not restore the original file. Is there a way I can tell MSBuild to execute a target at the end of a build no matter if it succeeded or failed?
Based on your last comment to the original question I would take another approach, and forget the approach you are currently taking. You should know that your version info doesn't have to be in the AssemblyInfo.cs file. It can be in any code file, just as long as you only have attributes AssemblyVersion and AssemblyFileVersion defined once each. With that being said what I would do is follow these steps:
Remove AssemblyVersion & AssemblyFileVersion from AssemblyInfo.cs
Create a new file, name it whatever you want want in my case I put it at Properties\VersionInfo.cs. Do not add this file to the project.
Edit the project file to include that file into the list of file to be compiled only when you want it
Let's expand a bit on #3. When you build a .NET project, the project itself is an MSBuild file. Inside that file you will find an item declared Compile. This is the list of files that will be sent to the compiler to be compiled. You can dynamically include/exclude files from that list. In you case you want to include the VersionInfo.cs file only if you are building on the build server (or whatever other condition you define). For this example I defined that condition to be if the project was building in Release mode. So for Release mode VersionInfo.cs would be sent to the compiler, and for other builds not. Here are the contents of VersionInfo.cs
VersionInfo.cs
[assembly: System.Reflection.AssemblyVersion("1.2.3.4")]
[assembly: System.Reflection.AssemblyFileVersion("1.2.3.4")]
In order to hook this into the build process you have to edit the project file. In that file you will find an element (maybe more than 1 depending on project type). You should add a target similar to the following there.
<Target Name="BeforeCompile">
<ItemGroup Condition=" '$(Configuration)'=='Release' ">
<Compile Include="Properties\VersionInfo.cs" />
</ItemGroup>
</Target>
Here what I've done here is to define a target, BeforeCompile, which is a well-known target that you can override. See this MSDN article about other similar targets. Basically this is a target which will always be called before the compiler is invoked. In this target I add the VersionInfo.cs to the Compile item only if the Configuration property is set to release. You could define that property to be whatever you wanted. For instance if you have TFS as your build server then it could be,
<Target Name="BeforeCompile">
<ItemGroup Condition=" '$(TeamFoundationServerUrl)'!='' ">
<Compile Include="Properties\VersionInfo.cs" />
</ItemGroup>
</Target>
Because we know that TeamFoundationServerUrl is only defined when building through TFS.
If you are building form the command line then something like this
<Target Name="BeforeCompile">
<ItemGroup Condition=" '$(IncludeVersionInfo)'=='true' ">
<Compile Include="Properties\VersionInfo.cs" />
</ItemGroup>
</Target>
And when you build the project just do msbuild.exe YourProject.proj /p:IncludeVersion=true. Note: this will not work when building a solution.
What about changing the problem:
Add a "template" AssemblyInfo.cs.template to version control that represents your "ideal" AssemblyInfo.cs with regex hooks in there
Before build, copy the template to the real and apply your regexes
Add some kind of subversion ignore for AssemblyInfo.cs (I'm no svn expert, but I'm pretty sure there is a way you can tell it to ignore certain files)
In the event that your devs need to add some kind of customization that would normally appear in an AssemblyInfo.cs (eg InternalsVisibleTo), then get them to add it to a different .cs file that IS checked in.
As a further refinement, combine Sayed's solution with mine and remove version info stuff from the actual AssemblyInfo.cs and have a VersionInfo.cs.template that is checked in, that creates a VersionInfo.cs in BeforeBuild.
I never used it, but from the documentation it seems that the OnError Element is useful to what you're trying to achieve.
Causes one or more targets to execute,
if the ContinueOnError attribute is
false for a failed task.