Encode String to Base64 in MSBuild - msbuild

I want to convert a string to base64 within a msbuild target but msbuild tells me the type convert is not available.
<ItemGroup>
<Headers Include="Authorization">
<Content>$([Convert]::ToBase64String("user:password"))</Content>
</Headers>
</ItemGroup>
Can somone help me?
EDIT:
Found a solution I don't like:
<UsingTask TaskName="ToBase64" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >
<ParameterGroup>
<In ParameterType="System.String" Required="true" />
<Out ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs">
Out = System.Convert.ToBase64String(System.Text.Encoding.Default.GetBytes(In));
</Code>
</Task>
</UsingTask>
<Target Name="WebRequest">
<ToBase64 In="$(auth)">
<Output PropertyName="authBase64" TaskParameter="Out" />
</ToBase64>
</Target>
Is there a better way?

You cannot use MSBuild property functions to do whatever you want. There are some limitations and one is that only a few namespace can be used. You have the whole list here : http://blogs.msdn.com/b/visualstudio/archive/2010/04/02/msbuild-property-functions.aspx
Your problem is that you need the System.Text.Encoding namespace (to get the bytes array of your string) which is not supported.
As a proof :
<ItemGroup>
<Headers Include="Authorization">
<Content>$([System.Convert]::ToBase64String($([System.Text.Encoding]::Default.GetBytes("user:password"))))</Content>
</Headers>
</ItemGroup>
<Target Name="Deploy" >
<Message Text="#(Headers->'%(Content)')" Importance="high" />
</Target>
fails with error MSB4185: The function "Default" on type "System.Text.Encoding" is not available for execution as an MSBuild property function.
but if you set the environment variable MSBUILDENABLEALLPROPERTYFUNCTIONS=1 then it succeeds :
D:\set MSBUILDENABLEALLPROPERTYFUNCTIONS=1
D:\>c:\windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe test.proj
Project "D:\test.proj" on node 1 (default targets).
Deploy:
dXNlcjpwYXNzd29yZA==
Done Building Project "D:\test.proj" (default targets).
For maintenance reasons, I would recommend you stick with your verbose task approach as this environment variable is not supported.

Since April 2022 support to encode/Decode Base64 is directly integrated into msbuild:
https://github.com/dotnet/msbuild/pull/7554
$([MSBuild]::ConvertToBase64("text"))

Related

Compiler Additional Options computed in a custom Target

I have a msbuild custom Target and a Task computing a Value.
The Task will output the Value as Property.
This Property I would like to uses as Additional Option to the Compiler call.
But the Property is empty when used as Additional Option.
My *.targets File looks like this:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask TaskName="GetBranchName_TASK" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >
<ParameterGroup>
<sPath ParameterType="System.String" Required="true" />
<sBranchName ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs">
<![CDATA[
... some Code ...
]]>
</Code>
</Task>
</UsingTask>
<Target Name="GetBranchName_TARGET">
<GetBranchName_TASK sPath="$(MSBuildThisFileDirectory)">
<Output PropertyName="BranchName" TaskParameter="sBranchName" />
</GetBranchName_TASK>
<Message Importance="High" Text="BranchName = $(BranchName)" />
</Target>
<PropertyGroup>
<BuildDependsOn>
GetBranchName_TARGET;
$(BuildDependsOn);
</BuildDependsOn>
</PropertyGroup>
</Project>
My *.props File is like this:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Configuration">
... some Properties here ...
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="IRSGetBranchName.targets" />
<ItemDefinitionGroup>
<ClCompile>
<AdditionalOptions>/DBRANCHNAME=$(BranchName) /DMORE=BAR</AdditionalOptions>
<ClCompile>
<ItemDefinitionGroup>
</Project>
This .props File then is imported into several .vcxproj
The Value printed as Message in my GetBranchName_TARGET is correct as expected (showing the correct TFS-Branch Name).
But when looking at Detailed Build Output, the Value seems empty:
1>ClCompile
1> ..\FOO.cpp
1> AdditionalOptions = /DBRANCHNAME= /DMORE=BAR
I tried for hours but found no solution and I really hope someone help whats wrong here ...
a) Is the Property BranchName not available globally? I tried to print the Property from other custom Targets and it worked well!
b) Or is the ClCompile.AdditionalOptions evaluated/build before my Target is excuted? In this case how can I re-evaluate?
c) ...
I'am very thankful for any Input.
You should be familiar with the msbuild evaluation process, as described here:
When the MSBuild engine begins to process a build file, it is evaluated in a top-down fashion in a multi-pass manner. These passes are described in order in the following list:
Load all environment and global properties, and toolset properties. In Microsoft Visual Studio 2010, for example, C++ defines several properties in the MSBuild 4.0 toolset.
Evaluate properties and process imports as encountered
Evaluate item definitions
Evaluate items
Evaluate using tasks
Start build and reading targets
So, in your case, the ItemDefinitionGroup for ClCompile has been evaluated before the GetBranchName_TARGET has been executed. So, it is empty by design.
In order to achieve the desired behavior, you should Add the following:
<Target Name="GetBranchName_TARGET">
<GetBranchName_TASK sPath="$(MSBuildThisFileDirectory)">
<Output PropertyName="BranchName" TaskParameter="sBranchName" />
</GetBranchName_TASK>
<Message Importance="High" Text="BranchName = $(BranchName)" />
<ItemGroup>
<ClCompile>
<AdditionalOptions>/DBRANCHNAME=$(BranchName) /DMORE=BAR</AdditionalOptions>
</ClCompile>
</ItemGroup>
</Target>
You can use a Condition attribute in the ClCompile in order to include only your sources, for example. Actually, what you are looking for is the feature to modify item metadata after it was declared.

MSB4185: The function "CurrentCulture" on type "System.Globalization.CultureInfo" is not available for execution as an MSBuild property function

I'm trying to use the following in a MSBUILD property in my project:
$([System.DateTime]::Now.ToString("MMMM", $([System.Globalization.CultureInfo]::CurrentCulture)))
and getting the error:
Error MSB4185: The function "CurrentCulture" on type "System.Globalization.CultureInfo" is not available for execution as an MSBuild property function.
I’m trying to get the equivalent of this:
DateTime.Now.ToString("MMMM", CultureInfo.CurrentCulture)
Can anyone suggest how can I fix this in my case.
I'm using it inside a .wixproj that sets ToolsVersion=4.0 in the Project tag. Looking at the log I see that it's using MSBUILD.exe with 12.0.30723.0 version.
I already looked at Error MSB4185: "System.Globalization.CultureInfo" has not been enabled for execution but I need something I can pass in the project instead of setting a commandline property.
Any help here is appreciated.
Regards,
Rajesh
I ended up using MSBUILD Inline Task to workaround this:
<!--Inline Task for getting the Current Month and Year by executing a C# code inside this MSBUILD task-->
<UsingTask TaskName="GetCurrentMonthYear" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >
<ParameterGroup>
<CurrentMonth ParameterType="System.String" Output="true" />
<CurrentYear ParameterType="System.Int32" Output="true" />
</ParameterGroup>
<Task>
<Using Namespace="System.Globalization" />
<Code Type="Fragment" Language="cs">
CurrentMonth = DateTime.Now.ToString("MMMM", CultureInfo.CurrentCulture);
CurrentYear = DateTime.Now.Year;
</Code>
</Task>
</UsingTask>
<!--Calling this target before Compile target to set the appropriate properties-->
<Target Name="SetCurrentMonthYear" BeforeTargets="Compile">
<GetCurrentMonthYear>
<Output PropertyName="CurrentMonth" TaskParameter="CurrentMonth" />
<Output PropertyName="CurrentYear" TaskParameter="CurrentYear" />
</GetCurrentMonthYear>
<PropertyGroup>
<DefineConstants>$(DefineConstants);CurrentMonth=$(CurrentMonth)</DefineConstants>
<DefineConstants>$(DefineConstants);CurrentYear=$(CurrentYear)</DefineConstants>
</PropertyGroup>
</Target>

I want to run a task for al files and exclude those who did not change

I have two buildtargets to check my code quality.
I run the following buildtargets every time i compile. This takes up too much time and i would like them to only check the files that did change.
In other words i want to filter files that did not change from the ItemGroup CppCheckFiles / LinterFiles.
<Target Name="CppCheck">
<ItemGroup>
<CppCheckFiles Include="*main.c" />
<CppCheckFiles Include="Source/*/*.c" />
</ItemGroup>
<Message Text="$(Configuration) starting." Importance="High" />
<Exec Command="C:\Cppcheck\cppcheck.exe %(CppCheckFiles.FullPath) --enable=style --template="{file}({line}): error:{severity}-{id}: {message}"" />
</Target>
<Target Name="SPLint">
<ItemGroup>
<LinterFiles Include="*main.c" />
<LinterFiles Include="Source/*/*.c" />
<LinterFiles Include="Source/*/*.h" />
</ItemGroup>
<Message Text="$(Configuration) starting." Importance="High" />
<Exec Command="splintCaller %(LinterFiles.FullPath)" />
</Target>
I know that the regular build process does this and i wonder if i have to go so fas as to write my own task.
hmm.. this sounds interesting. I can't help you. But it would be nice if the cppcheck wiki or manual had some small example project that did this.
Some people use cppcheck in commit hooks. I've tried it with GIT myself (I added a linux shell script). And there is a TortoiseSVN plugin you can try (http://sourceforge.net/apps/phpbb/cppcheck/viewtopic.php?f=3&t=443).
The solution is incremental Build. Where MSBuild compares Timestamps to exclude complete Buildtargets if nothing changed.
The following target creates a timesstamp for each file and skippes those files that did not change.
cppcheck.exe returns -1 if an error was detected and the timestamp is not written.
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="CppCheck" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<CppCheckFiles Include="*main.c" />
<CppCheckFiles Include="Source/*/*.c" />
</ItemGroup>
<Target Name="CppCheck"
Inputs="#(CppCheckFiles)"
Outputs="CCPCheck\%(CppCheckFiles.Filename)%(CppCheckFiles.Extension).stamp">
<Exec Command="C:\Cppcheck\cppcheck.exe %(CppCheckFiles.FullPath) --enable=style --template="{file}({line}): error:{severity}-{id}: {message}"" />
<MakeDir Directories="CCPCheck"/>
<Touch Files="CCPCheck\%(CppCheckFiles.Filename)%(CppCheckFiles.Extension).stamp" AlwaysCreate = "true" />
</Target>
</Project>

MSBuild: Writing and calling a Custom Task, but MSBuild thinks I need a TaskFactory

I'm getting into MSBuild to handle various targets of projects and I'm finding it to be quite flexible. (It's also helping me understand the possibilities of our CI system, too)
I need to get the current SVN revision of the project, for which I have written a custom task that calls SubWCRev and parses the output.
I reference this using the element:
<UsingTask TaskName="xxx.Elements.Build.MSBuildTasks.SubWCRev" AssemblyFile="D:\dev\xxx_presentation\Build\xxx.Elements.Build.dll">
<ParameterGroup>
<LastCommittedRevision ParameterType="System.Int" Required="False" Output="True" />
<MixedRevisionRangeMinimum ParameterType="System.Int" Required="False" Output="True" />
<MixedRevisionRangeMaximum ParameterType="System.Int" Required="False" Output="True" />
<HasLocalModifications ParameterType="System.Boolean" Required="False" Output="True" />
</ParameterGroup>
</UsingTask>
I then execute the task ...
<Target Name="Version" BeforeTargets="BuildDatabase">
<xxx.Elements.Build.MSBuildTasks.SubWCRev WorkingCopyDir="$(ProjectDir)..">
<Output TaskParameter="LastCommittedRevision" ItemName="LastCommittedRevision" />
<Output TaskParameter="MixedRevisionRangeMinimum" ItemName="MixedRevisionRangeMinimum" />
<Output TaskParameter="MixedRevisionRangeMaximum" ItemName="MixedRevisionRangeMaximum" />
<Output TaskParameter="HasLocalModifications" ItemName="HasLocalModifications" />
</xxx.Elements.Build.MSBuildTasks.SubWCRev>
<Message Text="Revision is #(LastCommittedRevision)" />
</Target>
My problem is that MSBuild insists I use the TaskFactory attribute, which this document says is optional. And I'm also seeing that TaskFactory is particularly for Inline Tasks, which I am not interested in.
The error message is:
The required attribute "TaskFactory" is empty or missing from the element UsingTask.
Where am I going wrong?
(And by the way, I'm finding MSBuild Sidekick 3 to be excellent and reducing the resistance of what can become a pretty complicated script.)
That attribute is optional when you are specifying UsingTask in the traditional manner to just point to a task in an assembly:
<UsingTask
TaskName="MyCustomTask"
AssemblyFile="$(PathToTasks)/MyCustomTasks.dll"
/>
When specifying an inline task, the attribute is no longer optional. You then use:
<UsingTask
TaskName="EnableAllPropertyFunctions"
TaskFactory="CodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>...
<Task>...
</UsingTask>
What you are doing appears to mix both of those. If you are using a task from the built assembly "D:\dev\xxx_presentation\Build\xxx.Elements.Build.dll", then you shouldn't be specifying the ParameterGroup, MSBuild knows how to discover the parameters, and that it is present in your declaration implies that MSBuild should be trying to find the rest of the inline task.

In MSBuild, can I use the String.Replace function on a MetaData item?

In MSBuild v4 one can use functions (like string.replace) on Properties. But how can I use functions on Metadata?
I'd like to use the string.replace function as below:
<Target Name="Build">
<Message Text="#(Files->'%(Filename).Replace(".config","")')" />
</Target>
Unfortunately this outputs as (not quite what I was going for):
log4net.Replace(".config","");ajaxPro.Replace(".config","");appSettings.Replace(".config","");cachingConfiguration20.Replace(".config","");cmsSiteConfiguration.Replace(".config","");dataProductsGraphConfiguration.Replace(".config","");ajaxPro.Replace(".config","");appSettings.Replace(".config","");cachingConfiguration20.Replace(".config","");cmsSiteConfiguration
Any thoughts?
You can do this with a little bit of trickery:
$([System.String]::Copy('%(Filename)').Replace('config',''))
Basically, we call the static method 'Copy' to create a new string (for some reason it doesn't like it if you just try $('%(Filename)'.Replace('.config',''))), then call the replace function on the string.
The full text should look like this:
<Target Name="Build">
<Message Text="#(Files->'$([System.String]::Copy("%(Filename)").Replace(".config",""))')" />
</Target>
Edit: MSBuild 12.0 seems to have broken the above method. As an alternative, we can add a new metadata entry to all existing Files items. We perform the replace while defining the metadata item, then we can access the modified value like any other metadata item.
e.g.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Files Include="Alice.jpg"/>
<Files Include="Bob.not-config.gif"/>
<Files Include="Charlie.config.txt"/>
</ItemGroup>
<Target Name="Build">
<ItemGroup>
<!--
Modify all existing 'Files' items so that they contain an entry where we have done our replace.
Note: This needs to be done WITHIN the '<Target>' (it's a requirment for modifying existing items like this
-->
<Files>
<FilenameWithoutConfig>$([System.String]::Copy('%(Filename)').Replace('.config', ''))</FilenameWithoutConfig>
</Files>
</ItemGroup>
<Message Text="#(Files->'%(FilenameWithoutConfig)')" Importance="high" />
</Target>
</Project>
Result:
D:\temp>"c:\Program Files (x86)\MSBuild\12.0\Bin\MSBuild.exe" /nologo test.xml
Build started 2015/02/11 11:19:10 AM.
Project "D:\temp\test.xml" on node 1 (default targets).
Build:
Alice;Bob.not-config;Charlie
Done Building Project "D:\temp\test.xml" (default targets).
I needed to do something similar, the following worked for me.
<Target Name="Build">
<Message Text="#(Files->'%(Filename)'->Replace('.config', ''))" />
</Target>
Those functions works in properties only (as I know). So create target which will perform operation throw batching:
<Target Name="Build"
DependsOnTargets="ProcessFile" />
<Target Name="ProcessFile"
Outputs="%(Files.Identity)">
<PropertyGroup>
<OriginalFileName>%(Files.Filename)</OriginalFileName>
<ModifiedFileName>$(OriginalFileName.Replace(".config",""))</ModifiedFileName>
</PropertyGroup>
<Message Text="$(ModifiedFileName)" Importance="High"/>
</Target>
Do you really need in your example such kind of task? I mean there exists MSBuild Well-known Item Metadata
EDIT: I should specify that this task processes all items in #(Files).
i dont think you can use functions directly with itemgroups and metadata (that would be easy)
However you can use batching:
Taking the ideas from this post:
array-iteration
I was trying to trim an itemgroup to send to a commandline tool (i needed to lose .server off the filename)
<Target Name="ProcessFile" DependsOnTargets="FullPaths">
<ItemGroup>
<Environments Include="$(TemplateFolder)\$(Branch)\*.server.xml"/>
</ItemGroup>
<MSBuild Projects=".\Configure.msbuild"
Properties="CurrentXmlFile=%(Environments.Filename)"
Targets="Configure"/>
</Target>
<Target Name="Configure" DependsOnTargets="FullPaths">
<PropertyGroup>
<Trimmed>$(CurrentXmlFile.Replace('.server',''))</Trimmed>
</PropertyGroup>
<Message Text="Trimmed: $(Trimmed)"/>
<Exec Command="ConfigCmd $(Trimmed)"/>
</Target>
For MSBuild 12.0, here's an alternative.
<Target Name="Build">
<Message Text="$([System.String]::Copy("%(Files.Filename)").Replace(".config",""))" />
</Target>
Got the same problem (except with MakeRelative), so I passed with another solution : Using good old CreateItem that take a string and transform to Item :)
<ItemGroup>
<_ToUploadFTP Include="$(PublishDir)**\*.*"></_ToUploadFTP>
</ItemGroup>
<CreateItem Include="$([MSBuild]::MakeRelative('c:\$(PublishDir)','c:\%(relativedir)%(filename)%(_ToUploadFTP.extension)'))">
<Output ItemName="_ToUploadFTPRelative" TaskParameter="Include"/>
</CreateItem>
<FtpUpload Username="$(UserName)"
Password="$(UserPassword)"
RemoteUri="$(FtpHost)"
LocalFiles="#(_ToUploadFTP)"
RemoteFiles="#(_ToUploadFTPRelative->'$(FtpSitePath)/%(relativedir)%(filename)%(extension)')"
UsePassive="$(FtpPassiveMode)" ></FtpUpload>