I am building a string with the PowershellTaskFactory that returns a list of files separated with a semicolon.
When I try to pass this to my in Wix it is interpreted as a string. I've been fighting it for quite a while now and decided to reach out. What I really want is a list of wxs to be passed to wix's candle application and I thought MsBuild would recognize that the string contained the delimiter and split it up...but it gets passed to candle as a string argument.
Update- Solved
I ended up using the new MSBUILD 4.0 feature of Property Functions, similar to this
<Compile Include="$(builtString.Split(';'))"/>
Take the string, which is a property, and convert it to an item array,
<PropertyGroup>
<ListOfFilesFromPowerShell>a.wxs;b.wxs;c.wxs</ListOfFilesFromPowerShell>
</PropertyGroup>
<ItemGroup>
<ListOfFilesFromPowerShell Include="$(ListOfFilesFromPowerShell)" />
</ItemGroup>
Now, use #(ListOfFilesFromPowerShell) instead of $(ListOfFilesFromPowerShell). When you say "passed to wix's candle application" I'm assuming you mean passed on the command line to candle.exe using the Exec task, or something similar, which would look like this,
<Exec
CommandLine="candle.exe #(ListOfFilesFromPowerShell, ' ') ..."
...
/>
This will give the following command line,
candle.exe a.wxs b.wxs c.wxs ...
The special syntax [, ' '] on the item array is used to supply an alternate separator character.
Related
I have a program, let's call it q.exe, that accepts a list of quoted paths on the command line. I have an MSBuild item containing files to pass on that command line. How should I pass such a list of files?
You can turn any MSBuild item into a quoted, space separated string with an item spec similar to the following:
#(AnyMSBuildItem->'"%(FullPath)"', ' ')
Leading to something like:
<ItemGroup>
<ProcessMe Include="..." />
<ProcessMe Include="..." />
<ProcessMe Include="..." />
</ItemGroup>
<Target Name="Build">
<Exec Command="q.exe #(ProcessMe->'"%(FullPath)"', ' ')" />
</Target>
This works by using the -> syntax to add the quotes, and the , syntax to change the separator from the default ; to a space.
Please read till I state my question clearly. I'm trying to implement a custom msbuild task that will accept a variable number of input parameters from a msbuild script. I am aware of arrays of input parameters in customs tasks
public ITaskItem[] ArrayofItems { get; set; }
These can be declared as follows using either propertygroup/itemgroup
<PropertyGroup>
<Item1>1</Item>
<Item2>2</Item>
<Item3>3</Item>
<Item4>4</Item>
<Item5>5</Item></PropertyGroup>
<ItemGroup>
<File Include="1"></File>
<File Include="2"></File>
<File Include="3"></File>
<File Include="4"></File>
<File Include="5"></File> </ItemGroup>
Then from VS 2010 command line I can set/override the property as following
msbuild somefile.csproj /t:MyTarget /p:Item1=Name1;Item2=Name2...etc
My question is :- Is it possible to declare variable number of propertygroup/itemgroup in the build file so that I can pass in 'n' variable parameters from msbuild command line something like this using propertygroup/itemgroup?
msbuild somefile.csproj /t:MyTarget /p:Item1=Name1;Item2=Name2;ItemN=NameN ('N' Only for illustration purposes)
Is this even possible?
Thanks in advance, Any help will be greatly appreciated.
If you call your build project like this...
> msbuild My.proj /p:ItemProperty="1;2;3;4;5"
And the project does this...
<ItemGroup>
<FromProperty Include="$(ItemProperty)" />
</ItemGroup>
<Message Text="%(FromProperty.Identity)" />
...you have essentially converted a property into an array of items. If you were to convert to dymanically created properties, there would be no easy way to reference them in the rest of your script, since you wouldn't know their names ahead of time when the script is authored.
Excerpted from MSBuild Trickery, trick #30 which has many pages of additional detail on this manipulation
Below is a portion of a MSBuild file that I'm working on:
<ItemGroup>
<Tests Include="$(SolutionDir)\**\bin\$(TestPlatform)\$(Configuration)\*.Tests.dll" />
</ItemGroup>
<PropertyGroup>
<TestProperties>/testcontainer:%(Tests.FullPath)</TestProperties>
</PropertyGroup>
I want to have a property that holds a command line switch. However, when I try to use $(TestProperties) in an Exec Command string, %(Tests.FullPath) is never resolved to the absolute path of the Tests item. Instead, it's always processed literally, as "%(Tests.FullPath)".
Am I doing something wrong or is this a standard MSBuild behavior? If the latter, is there a way for me to workaround this?
Thanks
P.S. - I realize I probably don't need to access the FullPath property since my Include value is an absolute path. However, I'd still like to understand the issue, along with how to handle it.
You have a syntax error. Item lists are referenced via the # character and item meta data is referenced via %. Reference the MSBuild Special Character Reference for details. To access the well known item metadata, you need to apply a transform inside the Property itself.
<ItemGroup>
<Tests Include="MyFile.txt" />
</ItemGroup>
<PropertyGroup>
<TestProperties>/testcontainer:#(Tests->'%(FullPath)')</TestProperties>
</PropertyGroup>
You can find more help here
I am trying to setup some properties that I use multiple times in my MSBuild script. I have the following property section:
<PropertyGroup>
<BuildDependsOn>$(BuildDependsOn); MyAfterBuild </BuildDependsOn>
<SubstitutionsFilePath>$(ProjectDir)app.config.substitutions.xml </SubstitutionsFilePath>
<AppConfig>$(TargetPath).config</AppConfig>
<HostConfig>$(TargetDir)$(TargetName).vshost.exe.config</HostConfig>
</PropertyGroup>
When I run this I get the following error:
The expression "#(TargetPath).config" cannot be used in this context. Item lists cannot be concatenated with other strings where an item list is expected. Use a semicolon to separate multiple item lists.
I don't understand this error, as the use of the $(BuildDependsOn) and $(ProjectDir) work fine. And I know the $(TargetXXX) values generate properly as when I put them directly into the Tasks section below, they work fine.
The reason for this problem is that TargetDir is defined as an item list, not a property; presumably to cater to the scenario where your outputs are distributed amongst several output directories?
I came up against this same problem and managed to work around it by using the $(OutDir) property instead of $(TargetDir).
(The OutDir property is defined in Microsoft.Common.Targets (lines 100-102) as a normalised version of the OutputPath defined in your project file.)
First try running your build with the /v:diag option, which will output a lot more information and give you a clue as to what part of the build is failing.
A clue might be in the Microsoft.Common.targets file (located in %SystemRoot%\Microsoft.NET\Framework\v2.0.50727) in the PrepareForBuild target:
<!--
These CreateProperty calls are required because TargetDir and TargetPath are defined
to contain an item list. We want that item list to be expanded so that it can be used
as a regular property value and not as an item-list-with-transform.
-->
<CreateProperty Value="$(TargetDir)">
<Output TaskParameter="Value" PropertyName="TargetDir" />
</CreateProperty>
<CreateProperty Value="$(TargetPath)">
<Output TaskParameter="Value" PropertyName="TargetPath" />
</CreateProperty>
To me this looks like a bug, you can report it at https://connect.microsoft.com/feedback/Search.aspx?SiteID=210.
I would like to run a task if any file in an item list is missing. How do I do that?
My current script has a list of "source" files #(MyComFiles) that I translate another list of "destination" files #(MyInteropLibs), using the following task:
<CombinePath BasePath="$(MyPath)\interop"
Paths="#(MyComFiles->'%(filename).%(extension)')">
<Output TaskParameter="CombinedPaths"
ItemName="MyInteropLibs" />
</CombinePath>
I want to check if any of the files in #(MyInteropLibs) is missing and run a task that will create them.
If you only need to create the missing files, and not get a list of the files that were missing you can you the touch task, which will create if the files don't exist.
<Touch Files="#(MyInteropLibs)" AlwaysCreate="True" />
If you only want to create the missing files, and avoid changing timestamps of the existing files, then batching can help
<Touch Files="%(MyInteropLibs.FullPath)" AlwaysCreate="True"
Condition=" ! Exists(%(MyInteropLibs.FullPath)) "/>
If you want a list of the files created then
<Touch Files="%(MyInteropLibs.FullPath)" AlwaysCreate="True"
Condition=" ! Exists(%(MyInteropLibs.FullPath)) ">
<Output TaskParameter="TouchedFiles" ItemName="CreatedFiles"/>
</Touch>
<Message Text="Created files = #(CreatedFiles)"/>
I am not very experienced with MSBuild so there may be better solutions than this but you could write a FilesExist task that takes the file list and passes each file to File.Exists returning true if they do exist and false otherwise and thenn react based on the result
Sorry I can't provide code to help out, my knowlege of MSBuild sytax is not strong
You can find out pretty easily using Exec.
To test if ALL of a set of files exists: The DOS FOR /D command accepts a semicolon-separated list of files - i.e. a flattened item array.
<!-- All exist -->
<Exec
Command="for /D %%i in (#(MyFiles)) do if not exist %%i exit 1"
IgnoreExitCode="true">
<Output TaskParameter="ExitCode" PropertyName="ExistExitCode"/>
</Exec>
To test if ANY of a set of files exists: The DOS DIR command accepts a semicolon-separated list of files. It sets the %ERRORLEVEL% to 0 if it finds any files in the list, nonzero if it finds none. (This is the simpler case, but it does not address the original question...)
<!-- Any exists -->
<Exec Command="dir /B #(MyFiles)" IgnoreExitCode="true">
<Output TaskParameter="ExitCode" PropertyName="DirExitCode"/>
</Exec>
Then most likely you will want to define a boolean property based on the output.
EDIT: BTW this is a code smell. Usually when you find yourself wanting to do this, it's an indication that you should set the Outputs property of the target so it will loop over the items.