Getting the file version of a native exe in MSBuild - visual-studio

I have a number of Visual C++ projects in a Visual Studio 2010 solution. Also in this solution is a WiX project that builds the installer for the executable that is the product of one of the C++ projects.
The executable has a resource file in its project which writes the version of the program to the executable.
Now I'd like to version the WiX-built installer with the same number as the one written to the executable by the resource file. I've searched the WiX related posts on StackOverflow and found this post:
Referencing a WixVariable defined in a WiX Library Project from a WiX Setup Project
The accepted answer to which, seems to indicate that a possible solution is to use MSBuild and the GetAssemblyIdentity task in the BeforeBuild Target to acquire the version number from another file (in the case of the SO question a DLL, in my case the executable) and expose it to WiX before WiX builds the installer.
I tried adding this to the MSBuild portion of my .wixproj file but when I try to build the installer I get an error returned saying:
error MSB3441: Cannot get assembly name for "<ExePath>". Could not load file or assembly '<ExeName>.exe' or one of its dependencies. The module was expected to contain an assembly manifest.
I can't seem to find any information on MSDN about this error as it relates to MSBuild. I've checked the built executable and it definitely has a version number on it (as well as the rest of the information from the .rc file) and the WiX project depends on the project that outputs the executable; so I assume its BeforeBuild task is running after the project it depends on has been built completely.
Should I be using a different task instead of GetAssemblyIdentity to retrieve the version number from the .exe in MSBuild, are there other requirements to satisfy before GetAssemblyIdentity will work, or is it just not possible to get this type of information about .exe files in MSBuild?
EDIT :
I accepted Rob's answer since I was misunderstanding the difference between ProductVersion and FileVersion, and the WiX technique that he suggested was working as intended and is a step towards the solution I needed.
FileVersion is an attribute of executables only. Msi files are essentially databases and ProductVersion is an entry in that database; they have no FileVersion attribute to set. The method he suggests correctly sets ProductVersion in the .msi database.
The title of this question is now not really related to what the problem I actually had was, since I was asking for a solution I believed I needed at the time. I've now solved the root problem which was simply getting access to the ProductVersion of the installer. I found a cscript script posted online here: http://kentie.net/article/wixnameversion/index.htm that shows how to access the ProductVersion of the .msi. Using that has allowed me to extract the ProductVersion and use it in other tools.

An easier solution, if you don't need the version in MSBuild, is to just reference the version of the file directly in your .wxs file. Here's a snippet that shows what to do:
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Version="!(bind.fileVersion.ExeWithVersion)" ...>
...
<Component ...>
<File Id="ExeWithVersion" Source="path\to\your\versioned\file.exe" />
</Component>
...
</Product>
</Wix>
The magic is that the !(bind.fileVersion.Xxx) says to look up the File element with Id='Xxx' and get its version. This is far away the easiest way to get the version of the file into your MSI package.

I needed the file version one time, and I ended up writing a custom task to get the FileVersion because I couldn't find anything.
namespace GranadaCoder.Framework.CrossDomain.MSBuild.Tasks.IO//.FileVersionTask
{
using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Globalization;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Security;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
public class FileVersionTask : FileBasedTaskBase
{
private static readonly string ROOT_DIRECTORY = "myrootdir";
private static readonly string FULL_PATH = "myfullpath";
private static readonly string FILE_NAME = "myfilename";
private static readonly string DIRECTORY = "mydirectory";
private static readonly string EXTENSION = "myextension";
private static readonly string VERSION = "myfileversion";
/// <summary>
/// Gets or sets the source files.
/// </summary>
/// <value>The source files.</value>
[Required]
public string SourceFiles { get; set; }
/// <summary>
/// Gets the file versions as a Task Output property.
/// </summary>
/// <value>The file versions.</value>
[Output]
public ITaskItem[] FileVersions
{ get; private set; }
/// <summary>
/// Task Entry Point.
/// </summary>
/// <returns></returns>
protected override bool AbstractExecute()
{
InternalExecute();
return !Log.HasLoggedErrors;
}
/// <summary>
/// Internal Execute Wrapper.
/// </summary>
private void InternalExecute()
{
IList<string> files = null;
if (String.IsNullOrEmpty(this.SourceFiles))
{
Log.LogWarning("No SourceFiles specified");
return;
}
if (!String.IsNullOrEmpty(this.SourceFiles))
{
Console.WriteLine(this.SourceFiles);
files = base.ConvertSourceFileStringToList(this.SourceFiles);
}
//List<string> fileVersions = new List<string>();
ArrayList itemsAsStringArray = new ArrayList();
foreach (string f in files)
{
FileInfoWrapper fiw = null;
fiw = this.DetermineFileVersion(f);
IDictionary currentMetaData = new System.Collections.Hashtable();
currentMetaData.Add(ROOT_DIRECTORY, fiw.RootDirectory);
currentMetaData.Add(FULL_PATH, fiw.FullPath);
currentMetaData.Add(FILE_NAME, fiw.FileName);
currentMetaData.Add(DIRECTORY, fiw.Directory);
currentMetaData.Add(EXTENSION, fiw.Extension);
currentMetaData.Add(VERSION, fiw.Version);
itemsAsStringArray.Add(new TaskItem(fiw.Version, currentMetaData));
}
this.FileVersions = (ITaskItem[])itemsAsStringArray.ToArray(typeof(ITaskItem));
}
/// <summary>
/// Determines the file version.
/// </summary>
/// <param name="fileName">Name of the file.</param>
/// <returns>File version or 0.0.0.0 if value cannot be determined</returns>
private FileInfoWrapper DetermineFileVersion(string fileName)
{
FileInfoWrapper fiw = new FileInfoWrapper();
fiw.Directory = string.Empty;
fiw.Extension = string.Empty;
fiw.FileName = string.Empty;
fiw.FullPath = string.Empty;
fiw.RootDirectory = string.Empty;
fiw.Version = "0.0.0.0";
try
{
if (System.IO.File.Exists(fileName))
{
fiw.Extension = System.IO.Path.GetExtension(fileName);
fiw.FileName = System.IO.Path.GetFileNameWithoutExtension(fileName);
fiw.FullPath = fileName;// System.IO.Path.GetFileName(fileName);
fiw.RootDirectory = System.IO.Path.GetPathRoot(fileName);
//Take the full path and remove the root directory to mimic the DotNet default behavior of '%filename'
fiw.Directory = System.IO.Path.GetDirectoryName(fileName).Remove(0, fiw.RootDirectory.Length);
FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(fileName);
if (null != fvi)
{
if (null != fvi.FileVersion)
{
fiw.Version = fvi.FileVersion;
}
}
}
}
catch (Exception ex)
{
if (ex is IOException
|| ex is UnauthorizedAccessException
|| ex is PathTooLongException
|| ex is DirectoryNotFoundException
|| ex is SecurityException)
{
Log.LogWarning("Error trying to determine file version " + fileName + ". " + ex.Message);
}
else
{
Log.LogErrorFromException(ex);
throw;
}
}
return fiw;
}
/// <summary>
/// Internal wrapper class to hold file properties of interest.
/// </summary>
internal sealed class FileInfoWrapper
{
public string Directory { get; set; }
public string Extension { get; set; }
public string FileName { get; set; }
public string FullPath { get; set; }
public string RootDirectory { get; set; }
public string Version { get; set; }
}
}
}
.msbuild example
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="AllTargetsWrapper" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask AssemblyFile="GranadaCoder.Framework.CrossDomain.MSBuild.dll" TaskName="FileVersionTask"/>
<Target Name="AllTargetsWrapper">
<CallTarget Targets="FileVersionTask1" />
<CallTarget Targets="FileVersionTask2" />
</Target>
<PropertyGroup>
<WorkingCheckout>c:\Program Files\MSBuild</WorkingCheckout>
</PropertyGroup>
<ItemGroup>
<MyTask1ExcludeFiles Include="$(WorkingCheckout)\**\*.rtf" />
<MyTask1ExcludeFiles Include="$(WorkingCheckout)\**\*.doc" />
</ItemGroup>
<ItemGroup>
<MyTask1IncludeFiles Include="$(WorkingCheckout)\**\*.exe" Exclude="#(MyTask1ExcludeFiles)" />
</ItemGroup>
<Target Name="FileVersionTask1">
<FileVersionTask SourceFiles="#(MyTask1IncludeFiles)" >
<Output TaskParameter="FileVersions" ItemName="MyFileVersionItemNames"/>
</FileVersionTask>
<Message Text=" MyFileVersionItemNames MetaData "/>
<Message Text=" ------------------------------- "/>
<Message Text=" "/>
<Message Text="directory: "/>
<Message Text="#(MyFileVersionItemNames->'%(mydirectory)')"/>
<Message Text=" "/>
<Message Text=" "/>
<Message Text="extension: "/>
<Message Text="#(MyFileVersionItemNames->'%(myextension)')"/>
<Message Text=" "/>
<Message Text=" "/>
<Message Text="filename: "/>
<Message Text="#(MyFileVersionItemNames->'%(myfilename)')"/>
<Message Text=" "/>
<Message Text=" "/>
<Message Text="fullpath: "/>
<Message Text="#(MyFileVersionItemNames->'%(myfullpath)')"/>
<Message Text=" "/>
<Message Text=" "/>
<Message Text="rootdir: "/>
<Message Text="#(MyFileVersionItemNames->'%(myrootdir)')"/>
<Message Text=" "/>
<Message Text=" "/>
<Message Text="fileversion: "/>
<Message Text="#(MyFileVersionItemNames->'%(myfileversion)')"/>
<Message Text=" "/>
<Message Text=" "/>
<Message Text=" "/>
<Message Text=" "/>
<Message Text="rootdir + directory + filename + extension: "/>
<Message Text="#(MyFileVersionItemNames->'%(myrootdir)%(mydirectory)%(myfilename)%(myextension)')"/>
<Message Text=" "/>
<Message Text=" "/>
<Message Text="List of files using special characters (carriage return)"/>
<Message Text="#(MyFileVersionItemNames->'"%(myfullpath)"' , '%0D%0A')"/>
<Message Text=" "/>
<Message Text=" "/>
</Target>
<ItemGroup>
<MyTask2IncludeFiles Include="c:\windows\notepad.exe" />
</ItemGroup>
<Target Name="FileVersionTask2">
<FileVersionTask SourceFiles="#(MyTask2IncludeFiles)" >
<Output TaskParameter="FileVersions" PropertyName="SingleFileFileVersion"/>
</FileVersionTask>
<Message Text="SingleFileFileVersion = $(SingleFileFileVersion) "/>
</Target>
</Project>

Related

How to Publish to Folder while including Assembly Version in Path in Visual Studio 2022?

I'm building a .Net 6 WPF application in Visual Studio 2022 and I'm publishing to a folder.
I'd like the assembly version included in the folder path e.g. C:\Code\Publish\MyApplication_1.2.0.28\
My Google Fu is failing me and I can't find a solution.
I'm using a Text Template to generate the Assembly Version from a manually set <major>.<minor>.<patch>. and an autoincrementing <revision>. See below. This works exceptionally well. Thanks to https://makolyte.com/auto-increment-build-numbers-in-visual-studio/ including comments.
Following this StackOverflow I tried creating and calling a static method getting the assembly version from the publishprofile. I added the method to the .tt just to keep it together. The static method works if I just call it from MainWindow, but when editing the FolderProfile.pubxml as text and adding the call to the PublishDir: <PublishDir>C:\Code\Publish\MyApplication\$([VersionGetter]::GetVersion())</PublishDir>
and hitting the Publish-button, it opens the wizard to create a new publishprofile, so that is not correct syntax. From the linked documentation, it looks like I can call certain types from here. It works creating a GUID string with the following, so I guess that's just not the way to go: <PublishDir>C:\Code\Publish\MyApplication\$([System.Guid]::NewGuid())</PublishDir>
I actually stumpled upon this StackOverflow answer, but I'm unsure of how to implement it. It creates a directory, but how does the publish profile know about this directory? See code from that answer at the buttom.
So, StackOverflow, please help me :)
FolderProfile.pubxml
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>C:\Code\Publish\MyApplication\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>net6.0-windows</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<PublishReadyToRun>false</PublishReadyToRun>
</PropertyGroup>
</Project>
Added this to .csproj file
<PropertyGroup>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TextTemplating\Microsoft.TextTemplating.targets" />
<!-- Automatic versioning end -->
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>embedded</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>embedded</DebugType>
<TransformOnBuild>true</TransformOnBuild>
<OverwriteReadOnlyOutputFiles>true</OverwriteReadOnlyOutputFiles>
<TransformOutOfDateOnly>false</TransformOutOfDateOnly>
</PropertyGroup>
Text Template: VersionAutoIncrement.tt
<## template debug="false" hostspecific="true" language="C#" #>
<## output extension=".cs" #>
<## assembly name="System.Core" #>
<## import namespace="System.IO" #>
<## import namespace="System.Linq" #>
<#
try
{
string currentDirectory = Path.GetDirectoryName(this.Host.TemplateFile);
string fullPath = Path.Combine(currentDirectory, "VersionAutoIncrement.cs");
string currentRevisionNumber = File.ReadLines(fullPath).First().Replace("//", "");
Revision = Convert.ToUInt16(currentRevisionNumber);
Revision++;
}
catch( Exception )
{
//Throws the first time since the output file doesn’t exist yet
}
#>
//<#= this.Revision #>
using System.Reflection;
[assembly: AssemblyVersion("1.2.0.<#= this.Revision #>")]
namespace FlexPOS_Importer
{
public static class VersionGetter
{
public static string GetVersion()
{
return Assembly.GetExecutingAssembly().GetName().Version.ToString();
}
}
}
<#+
UInt16 Revision = 1;
#>
The generated VersionAutoIncrement.cs file
//29
using System.Reflection;
[assembly: AssemblyVersion("1.2.0.29")]
namespace FlexPOS_Importer
{
public static class VersionGetter
{
public static string GetVersion()
{
return Assembly.GetExecutingAssembly().GetName().Version.ToString();
}
}
}
Section copied from this StackOverflow answer
As follows[Altered version of the code as described in the given
links]:
<PropertyGroup>
<AssemblyList>myfolder\myLibrary.dll</AssemblyList>
</PropertyGroup>
<Target Name="AssemblyInformations">
<GetAssemblyIdentity AssemblyFiles="$(AssemblyList)">
<Output TaskParameter="Assemblies" ItemName="AssemblyInfos"/>
</GetAssemblyIdentity>
</Target>
<Message Text="Files: %(AssemblyInfos.Version)"/>
And then I would use something like the following to create a directory in the place you
want to publish:
<CreateProperty Value="$(Public_Shared_Folder)$(ProjectName)\">
<Output TaskParameter="Value" PropertyName="PublicFolderToDropZip" />
</CreateProperty>
<MakeDir Directories="$(PublicFolderToDropZip)"
Condition="$(Configuration)=='Release' AND Exists('$(PublicFolderToDropZip)')" />
The entire operation can be completely automated.

Unable To Set Application Property Using MSbuild.exe

If I go to Project->MyApp->Properties->Settings and enter Name like WhichApp of type string and enter the Scope as Application and Value of PCB. When I show the value in
my main form using MessageBox.Show(Properties.Settings.Default.WhichApp); I see a message box showing "PCB".
But if I try to set the property using MSBuild.exe -property:WhichApp=SUB when I view the property value I still see "PCB".
How can I call the MSBuild.exe compiler tool to set the property at build time?
I tried used External Tools in Visual Studio..
Title: Set Property
Command: C:\Program Files(x86)\Microsoft Visual Studio\2017\WDExpress\MSBuild\15.0\Bin\MSBuild.exe
Arguments: -property:WhichApp=SUB
Initial Directory: $(ProjectDir)
---- WHAT I'M TRY TO ACCOMPLISH IN LOAD() METHOD OF MAIN FORM ----
if (Properties.Settings.Default.PCAppConfig == "PCB")
{
// create new PCB form
// PCBForm.ShowDialog();
}
else if (Properties.Settings.Default.PCAppConfig == "SUB")
{
// create new SUB form
// SUBForm.ShowDialog();
}
--- In solution by jtijn, I tried this to add another property called VersionNum but fails... ---
<Target Name="BeforeBuild">
<PropertyGroup>
<!--Default value.-->
<PcAppConfig Condition="'$(PcAppConfig)' == ''">PCB</PcAppConfig>
<VersionNum Condition="'$(VersionNum)' == ''">1.0.0.66</VersionNum>
<!--The source code.-->
<TheSourceCode>internal static class PcAppConfig{ public static readonly string value = "$(PcAppConfig)"%3B%0A }internal static class VersionNum{ public static readonly string value = "$(VersionNum)"%3B</TheSourceCode>
</PropertyGroup>
<!--Get current source so we can only create it again when needed, to avoid being recompiled.-->
<ReadLinesFromFile File="PcAppConfig.cs" ContinueOnError="True">
<Output TaskParameter="Lines" ItemName="CurrentSourceCode" />
</ReadLinesFromFile>
<!--Write source, if needed.-->
<WriteLinesToFile File="PcAppConfig.cs" Overwrite="True" Lines="$(TheSourceCode)" Condition="'#(CurrentSourceCode)' != '$(TheSourceCode)'" />
</Target>
--- Doesn't compile when values are changed, Fails to create setup files ...
My arguments to MSBuild.exe
SelectApp.csproj.user /p:PcAppConfig=SUB /p:VersionNum=1.0.0.67
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Compile Update="Form1.cs">
<SubType>Form</SubType>
</Compile>
</ItemGroup>
<Target Name="BeforeBuild">
<PropertyGroup>
<!--Default value.-->
<PcAppConfig Condition="'$(PcAppConfig)' == ''">PCB</PcAppConfig>
<VersionNum Condition="'$(VersionNum)' == ''">1.0.0.66</VersionNum>
<!--The source code.-->
<TheSourceCode>internal static class PCAppConfig{ public static readonly string value = "$(PcAppConfig)"%3B }%0Ainternal static class VersionNum{ public static readonly string value = "$(VersionNum)"%3B }</TheSourceCode>
</PropertyGroup>
<!--Get current source so we can only create it again when needed, to avoid being recompiled.-->
<ReadLinesFromFile File="PcAppConfig.cs" ContinueOnError="True">
<Output TaskParameter="Lines" ItemName="CurrentSourceCode" />
</ReadLinesFromFile>
<!--Write source, if needed.-->
<WriteLinesToFile File="PcAppConfig.cs" Overwrite="True" Lines="$(TheSourceCode)" Condition="'#(CurrentSourceCode)' != '$(TheSourceCode)'" />
<ItemGroup>
<Compile Update="PcAppConfig.cs">
<SubType>Component</SubType>
</Compile>
</ItemGroup>
</Target>
<Target Name="AfterCompile">
<Exec Command=""$(ProgramFiles)\Microsoft Visual Studio\2019\Professional\Common7\IDE\devenv.com" C:\DummyApps\SelectApp\PCBSetup\PCBSetup.vdproj /build "Debug|AnyCPU""/>
</Target>
</Project>
You can have the project generate C# source code based on an msbuild property. This target goes at the end in the project file:
<Target Name="BeforeBuild">
<PropertyGroup>
<!--Default value.-->
<PcAppConfig Condition="'$(PcAppConfig)' == ''">PCB</PcAppConfig>
<!--The source code.-->
<TheSourceCode>internal static class PcAppConfig{ public static readonly string value = "$(PcAppConfig)"%3B }</TheSourceCode>
</PropertyGroup>
<!--Get current source so we can only create it again when needed, to avoid being recompiled.-->
<ReadLinesFromFile File="PcAppConfig.cs" ContinueOnError="True">
<Output TaskParameter="Lines" ItemName="CurrentSourceCode" />
</ReadLinesFromFile>
<!--Write source, if needed.-->
<WriteLinesToFile File="PcAppConfig.cs" Overwrite="True" Lines="$(TheSourceCode)" Condition="'#(CurrentSourceCode)' != '$(TheSourceCode)'" />
</Target>
The resulting source file needs to be added to your project, can do this in VS after building once or else add this line along with the other Compile items:
<Compile Include="PcAppConfig.cs" />
Now in your code you can just use PcAppConfig.value.
To change the value build like
msbuild my.csproj /p:PcAppConfig=SUB

automatic set of assembly version in csproj via task

I want to set Assembly Version of C# project in csproj file with a self generated task.
I want to set Assembly Version to:
Major Version -> Project version: for example 3
Minor Version -> 0
Build Number -> Year + Calendarweek
Revision -> Current day of week
My idea was the following code.
But currently it is not working.
Any ideas what I should change?
<UsingTask TaskName="DetectAssemblyVersion" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<Result ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs">
<![CDATA[
string year = System.DateTime.Now.ToString("yy");
int dayOfWeek = (int) System.DateTime.Now.DayOfWeek;
System.Globalization.DateTimeFormatInfo dfi = System.Globalization.DateTimeFormatInfo.CurrentInfo;
System.Globalization.Calendar cal = dfi.Calendar;
string calendarWeek = cal.GetWeekOfYear(System.DateTime.Now, dfi.CalendarWeekRule,
dfi.FirstDayOfWeek).ToString();
Result = "3.0." + year + calendarWeek + "."+ dayOfWeek.ToString();
Log.LogMessage(MessageImportance.High, "detect of current version: " + Result);
]]>
</Code>
</Task>
</UsingTask>
<Target Name="TestBuild" AfterTargets="Build">
<DetectAssemblyVersion>
<Output PropertyName="AssemblyVersion" TaskParameter="Result" />
</DetectAssemblyVersion>
<Message Importance="high" Text="new version: $(AssemblyVersion)" />
</Target>

What is syntax of nested property of MSBuild?

I am using MSBuild.
I am getting the value of the Person_1 through the $(Person_1). How can I get the value of the Name subelement of Person_2? I need the syntax.
<PropertyGroup>
<Person_1>Bob</Person_1>
<Person_2>
<Name>Bob</Name>
</Person_2>
</PropertyGroup>
RE: https://msdn.microsoft.com/en-us/library/ms171458.aspx
A property that contains XML is simply that. You cannot access parts of the content just because it is XML. To understand this do the following;
<PropertyGroup>
<MyProperty>
<PropertyContentXML>
<InnerXML1>Blablabla</InnerXML1>
<InnerXML2>More blablabla</InnerXML2>
</PropertyContentXML>
</MyProperty>
</PropertyGroup>
<Target Name="Build">
<Message Text="$(MyProperty)" />
</Target>
The output of this will be:
<PropertyContentXML xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<InnerXML1>Blablabla</InnerXML1>
<InnerXML2>More blablabla</InnerXML2>
</PropertyContentXML>
You are mixing Properties and ItemGroups.
Properties are simple named values, ItemGroups are items with an identity and with properties. You can not use both in the same way.
Properties are defined as :
<PropertyGroup>
<name>value</name>
</Propertygroup>
and are accessed by using the $(name) syntax.
Item groups are defined as:
<ItemGroup>
<Item Include="item1">
<ItemPropery>value</ItemProperty>
</Item>
</ItemGroup>
and are accessed by using this syntax: %(Item.ItemProperty).
See also this reference for the 'intuitive' syntax
You'll need something advanced, like an inline task:
<UsingTask TaskName="TransformXmlToItem"
TaskFactory="CodeTaskFactory"
AssemblyName="Microsoft.Build.Tasks.Core">
<ParameterGroup>
<Xml Required="true"/>
<Elements ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true"/>
</ParameterGroup>
<Task>
<Reference Include="System.Xml" />
<Using Namespace="System.Collections.Generic" />
<Using Namespace="System.Xml" />
<Code Type="Fragment" Language="cs">
<![CDATA[
using (var xr = new XmlTextReader(Xml, XmlNodeType.Element,
new XmlParserContext(null, null, null, XmlSpace.Default))) {
xr.Namespaces = false;
xr.MoveToContent();
var items = new List<ITaskItem>();
while (!xr.EOF) {
if (xr.NodeType == XmlNodeType.Element) {
var item = new TaskItem(xr.Name);
var text = xr.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(text)) {
item.SetMetadata("text", text);
}
}
xr.Read();
}
Elements = items.ToArray();
}
]]>
</Code>
</Task>
The task reads the XML elements and creates items from it. The text is transformed into metadata.
You can then write a task like this:
<Target Name="DeconstructPropertyXml">
<TransformXmlToItem Xml="$(Person_2)">
<Output TaskParameter="Elements" ItemName="Person_2I"/>
</TransformXmlToItem>
<Message Text="%(Person_2I.Identity) = %(Person_2I.text)" Importance="high"/>
</Target>
Which should just output Name = Bob.
The same way you could add additional metadata from attributes, etc.

How to access backstage checkbox value in an Office addin?

I have a boolean property Settings.Default.MarkAsRead in the Setting.settings file, which I can access in my Ribbon class. What I'd like to do is set the value of a check box in my backstage section depending on the value of this property. Also if the user modifies it, I'll need to save the new value.
Any way I can do this?
This is my (simplified) xml:
<?xml version="1.0" encoding="UTF-8"?>
<customUI onLoad="Ribbon_Load"
xmlns="http://schemas.microsoft.com/office/2009/07/customui">
<backstage>
<tab id="MyBackstageSection" label="MyBackstageSection"
columnWidthPercent="30" insertAfterMso="TabInfo" visible="true" >
<firstColumn>
<group id="grpOne" label="Configuration">
<bottomItems>
<checkBox id="markAsRead" label="Mark as read"
getPressed="markAsRead_GetPressed" />
<button id="save" label="Save Preferences" onAction="save_Click"/>
</bottomItems>
</group>
</firstColumn>
</tab>
</backstage>
</customUI>
I didn't find a way to access the xml elements from the Ribbon_Load method, so I've created a boolean property in the ribbon class that I update using the GetPressed and OnAction callbacks:
xml:
<checkBox id="markAsRead" label="Mark as read"
onAction="markAsRead_OnAction" getPressed="markAsRead_GetPressed"/>
c#:
private bool MarkAsRead { get; set; }
public bool markAsRead_GetPressed(Office.IRibbonControl control)
{
this.MarkAsRead = Settings.Default.MarkAsRead;
return this.MarkAsRead;
}
public void markAsRead_OnAction(Office.IRibbonControl control, bool isPressed)
{
this.MarkAsRead = isPressed;
}

Resources