MSBuild: how to control the parsing of a semicolon delimited property - msbuild

When a single property contains semicolons, MSBuild automatically parse the property into a list of properties when used within an itemgroup. Here's a snippet from my project:
<PropertyGroup>
<ConnectionString>workstation id=.;packet size=4096;Integrated Security=SSPI;data source=.;initial catalog=$(SqlDbName)</ConnectionString>
</PropertyGroup>
<ItemGroup>
<InstallShieldProperties Include="
CONNECTIONSTRING=$(ConnectionString);
Another=$(value)"/>
</ItemGroup>
When a task consumes the #(InstallShieldProperties) itemgroup, MSBuild will parse the ConnectionString property into a list of subset properties since it contains semicolons.
foreach (string property in Properties)
{
// Properties array parsed to pieces
}
I know I can change the delimiter of the itemgroup, but that won't make any difference.
I'm trying to avoid manipulating the string[] array within the custom task.

In MSBuild 4.0, you can use $([MSBuild]::Escape($(ConnectionString))).

AFAICS, you can either escape the semicolon in the $(ConnectionString) property like:
<ConnectionString>workstation id=.%3Bpacket size=4096%3B.."</ConnectionString>
Or use some task to replace the ';' in the ConnectionString property to '%3B' and then use that property in InstallShieldProperties item.
The other way could be to change the property type in the custom task from string[] to string, and then split it yourself, the way you want it. You could use enclosing quotes to separate Connection string part from other key/value pairs.
Or if it makes sense for your custom task, then maybe connection string is a special enough property to have as a separate task property.

In MSBuild 4.0, there are now Property Functions. One thing these allow you to do is call .NET String instance methods directly on your properties as if they are strings (which they are).
In your example, instead of using:
$(ConnectionString)
You could use:
$(ConnectionString.Replace(';', '%3B'))
Which will call the String method Replace() to replace semicolons with %3B

Related

how to separate multiple values which has single key in Property files in Mule

Here is an Example which is to be place in a property file and should get separated otherwise it read it as single string.
Trade&Credit= getTrade&Credit;addTrade&Credit;findTrade&Credit;updateTrade&Credit;addTrade&CreditContact;updateTrade&CreditContact;
To access a property from property file in groovy component, you can use the following.
System.getProperty("propertyKey")
Another way round is, assuming you store the value from the property file in a flow variable named property file, splitting in for each. Just place the below expression in for each, and it will split the value as per separator.
#[org.mule.util.StringUtils.split(flowVars.propertyValue, ';')]
You can use StringTokenizer and groovy component to do this. See the below example

How to pass string with symbol ";" from CustomActionData to a CustomAction using WiX?

I have deferred CustomAction in C# and another one to pass some properties to it.
<CustomAction Id="CustomAction1"
Property="CustomAction2"
Value="EncryptedString=[ENCRYPTEDSTRING]"
/>
However, if the property contain symbol ";" then
string encString=session.CustomActionData["EncryptedString"];
outputs only part before ";", because this symbol is considered as a delimiter between properties.
Is there any workaround to pass strings containing ";" ?
for example
ENCRYPTEDSTRING="12;3474dsfgee"
and output
encString="12"
You can't use DTF's CustomActionData; it assumes the custom action items are delimited by semicolons. Instead, grab CustomActionData directly and don't bother with the EncryptedString= prefix.
I will just add as an answer to get proper links. These may be helpful for implementing what Bob Arnson suggests:
Recommended: How to Access Windows Installer Property in Deferred Execution
Basic Differences in "Execute Immediate" and "Execute Deferred" CA
Everything About Properties [Wise Package Studio]

Using MSBuild PropertyGroup outside of Target block

I have a project files collection:
<ItemGroup>
<ApplicationToDeploy
Include="Frontend.WebSite.csproj;11.WebServices.csproj;22.WebServices.csproj"/>
<ApplicationToDeploy
Include="33.WebServices.csproj;44.WebServices.csproj;Workflow55Svc.csproj"/>
</ItemGroup>
I'm trying to get collection of .config-files of these projects:
<Target Name="111">
<PropertyGroup>
<Cfgs>#(ApplicationToDeploy->'%(RootDir)%(Directory)*.config')</Cfgs>
</PropertyGroup>
<ItemGroup>
<InputConfigs Include="$(Cfgs)" />
</ItemGroup>
<Message Text="Cfgs: #(InputConfigs)"/>
</Target>
Inside the Target block all works fine (I see collection of Web.Configs, App.Configs, Log4net.Configs etc.):
Cfgs: C:\Sources\WebServices\11\WebServices\11.WebServices\Web.config;C:\Sources\WebServices\22\WebServices\22.WebServices\web.log4net.config;C:\Sources\WebServices\33\WebServices\33.WebServices\web.environment.config
But I want to initialize this ItemGroup outside of the Target block. Like this:
<PropertyGroup>
<Cfgs>#(ApplicationToDeploy->'%(RootDir)%(Directory)*.config')</Cfgs>
</PropertyGroup>
<ItemGroup>
<InputConfigs Include="$(Cfgs)" />
</ItemGroup>
<Target Name="111">
<Message Text="Cfgs: #(InputConfigs)"/>
</Target>
And when I do this outside of the Target block I get this:
Cfgs: C:\Sources\WebServices\11\WebServices\11.WebServices\*.config;C:\Sources\WebServices\22\WebServices\22.WebServices\*.config;C:\Sources\WebServices\33\WebServices\33.WebServices\*.config
I don't understand what's happens. Is it possible to get the same result outside Target block?
I don't understand what's happens.
This behavior is an effect of the MSBuild evaluation order:
During the evaluation phase of a build:
Properties are defined and modified in the order in which they
appear. Property functions are executed. Property values in the form
$(PropertyName) are expanded within expressions. The property value
is set to the expanded expression.
Item definitions are defined and modified in the order in which they appear. Property functions have already been expanded within expressions. Metadata values are set to the expanded expressions.
Item types are defined and modified in the order in which they appear. Item values in the form #(ItemType) are expanded. Item transformations are also expanded. Property functions and values have already been expanded within expressions. The item list and metadata values are set to the expanded expressions.
During the execution phase of a build:
Properties and items that are defined within targets
are evaluated together in the order in which they appear. Property
functions are executed and property values are expanded within
expressions. Item values and item transformations are also expanded.
The property values, item type values, and metadata values are set to
the expanded expressions."
There's another key point on that link "(...) The string expansion is dependent on the build phase.".
You're using the property 'Cfgs' to recursively map your projects' folders AND to define a wildcard to config files (*.config). When you define 'Cfgs' INSIDE the target, the InputConfigs receives the expanded value of Cfgs (semicolon-separated string list of folders), and just resolve the wildcards. On the other hand, when you define 'Cfgs' OUTSIDE the target, the InputConfigs receives the unexpanded value of Cfgs (#(ApplicationToDeploy->'%(RootDir)%(Directory)*.cs'). When the InputConfigs expands it, it results on the semicolon-separated string list of folders, but it doesn't resolve the wildcards (*.config).
Is it possible to get the same result outside Target block?
I think that InputConfigs should always receive the expanded list of directories. The expansion is made during the execution phase of the build. During this phase, only
properties and items defined within targets are evaluated. So, I would keep all the initialization inside an 'Initialization' Target block. I don't mean it is impossible to do it outside a Target block, but for the reasons mentioned it does not seems logical. =]
Hope this helps,

Specifying multiple MergeSection values when using LINK task on MSBuild

Is there anyway to specify more than one MergeSection value for the MSBuild LINK task? (The MergeSection param is the same as the /merge param for link.exe)
http://msdn.microsoft.com/en-us/library/ee862471.aspx
When calling link.exe you can specify more than one /merge value, but that doesn't seem possible with the MergeSection parameter.
So far the only way I can see to make this work is by using the AddtionalOptions param, but I'm hoping there's a better way to implement this parameter.
Thanks
I think you may have to use AdditionalOptions.
In the Link task the MergeSections property is a string value, not an array, so you can only set one string. Link.exe does not seem to allow you to pass multiple pairs in one command line parameter, you must specify a separate MERGE command line parameter for each pair. The Visual Studio property page only allows a single string for the MergeSections property.

MSBuild tasks can accept primitive arrays, but how do you write one to pass into the task?

I would guess it has to be an ITaskItem since it's a vector instead of scalar, I've got the only 2 MsBuild books here on my desk, and I can't find examples of how to pass in an array to a task. I want to do a string array, but I'd like to know the proper way that would work with any primitive type.
How do you pass in an array of string (or int) to a MsBuild task?
MSBuild tasks can accept ITaskItem, primitives, string or an array of any of those for parameters. You just declare the type in your task and then the values will be converted before passed to the task. If the value cannot convert to the type then an exception will be raised and the build will be stopped.
For example if you have a task which accepts an int[] named Values then you could do.
<Target Name="MyTarget">
<MyTask Values="1;45;657" />
<!-- or you can do -->
<ItemGroup>
<SomeValues Include="7;54;568;432;79" />
</ItemGroup>
<MyTask Values="#(SomeValues) />
</Target>
Both approaches are essentially the same. The other answers stating that all parameters are strings or that you have to use ITaskItem are incorrect.
You said you have two books on MSBuild, then I presume one is my Inside the Microsoft Build Engine book, you should read the chapter on Custom Tasks so that you get a full grasp on these topics. There is a section explaining parameter types specifically.
IIRC, msbuild items are always string arrays - that is the only option. So an array of integers would be stored as an array numeric strings.