VSTO custom OfficeVersion property - vsto

I'm trying to use a custom property in a VSTO add-in project file to ease pain when switching between VMs with different versions of Office installed. Given a PropertyGroup:
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug2010|AnyCPU' ">
<OfficeVersion>14.0</OfficeVersion>
</PropertyGroup>
This works:
<ProjectProperties HostName="Word" HostPackage="{29A7B9D7-A7F1-4328-8EF0-6B2D1A56B2C1}" OfficeVersion="$(OfficeVersion)" VstxVersion="4.0" ApplicationType="Word" Language="cs" TemplatesPath="VSTOTemplates" DebugInfoExeName="#Software\Microsoft\Office\14.0\Word\InstallRoot\Path#WINWORD.EXE" DebugInfoCommandLine="/x" AddItemTemplatesGuid="{51063C3A-E220-4D12-8922-BDA915ACD783}" />
This does not:
<ProjectProperties HostName="Word" HostPackage="{29A7B9D7-A7F1-4328-8EF0-6B2D1A56B2C1}" OfficeVersion="$(OfficeVersion)" VstxVersion="4.0" ApplicationType="Word" Language="cs" TemplatesPath="VSTOTemplates" DebugInfoExeName="#Software\Microsoft\Office\$(OfficeVersion)\Word\InstallRoot\Path#WINWORD.EXE" DebugInfoCommandLine="/x" AddItemTemplatesGuid="{51063C3A-E220-4D12-8922-BDA915ACD783}" />
The only difference is a literal 14.0 to DebugInfoExeName in the first instance, and a reference to OfficeVersion in the second. Is there a way through this?

Related

Different ItemDefinitionGroup for different ItemGroup

I'm currently working using MSBuild, and in one of the vcxproj files, I'd like to have different ItemDefinitionGroup for different ItemGroup, without overriding each other, so that each ItemGroup has its own specific definition, e.g.
<ItemDefinitionGroup Label="ItemDefGroupA">
<CLCompile>
<AdditionalOptions> /option_for_item_group_A</AdditionalOptions>
</CLCompile>
</ItemDefinitionGroup>
<ItemGroup Label="ItemGroupA">
<CLCompile Include="src\main.cpp" />
</ItemDefinitionGroup>
<ItemDefinitionGroup>
<CLCompile>
<AdditionalOptions> /option_for_item_group_B</AdditionalOptions>
</CLCompile>
</ItemDefinitionGroup>
<ItemGroup Label="ItemGroupB">
<CLCompile Include="src\main2.cpp" />
</ItemDefinitionGroup>
Is this even possible?
Thank you very much in advance, and have a nice day.
The Label attribute is ignored by MSBuild execution engine. The only place where it is used is by IDE code that needs to know a location inside project file to insert new entities, which happens if you modify your project in Visual Studio. The MSDN blog gives some information on how Labels are used by IDE.
So, what you are doing would not work. You only have one CLCompile item group, and your multiple item definition groups override each other. Whatever definition group is evaluated last, it wins. Note that the Item Definitions are evaluated first, after that Items are evaluated on next pass (link).
One possible workaround for your scenario is to create couple of auxiliary groups, like this:
<ItemGroup>
<GroupA Include="src\file1.cpp" />
<GroupA Include="src\file2.cpp" />
</ItemDefinitionGroup>
<ItemGroup>
<GroupB Include="src\file3.cpp" />
<GroupB Include="src\file4.cpp" />
</ItemDefinitionGroup>
Then you initialize your CLCompile group from a combination of the two with different metadata values:
<ItemGroup>
<CLCompile Include="#(GroupA)">
<AdditionalOptions> /option_for_item_group_A</AdditionalOptions>
</CLCompile>
<CLCompile Include="#(GroupB)">
<AdditionalOptions> /option_for_item_group_B</AdditionalOptions>
</CLCompile>
</ItemDefinitionGroup>

MSBuild: How to update default metadata in an item?

I have the following problem in scripting with MSBuild:
I create a default item "itemA" with two metadata "metadata1" and "metadata2", whereby metadata2 refers to metadata1.
When I define itemA later and overwrite metadata1, the metadata2 contains still the default value of metadata1. How can I make the metadata2 to refer to the "new" metadata1?
Illustration in code as below:
<ItemDefinitionGroup>
<itemA>
<Metadata1>default</Metadata1>
<Metadata2>%(itemA.Metadata1)</Metadata2>
</itemA>
</ItemDefinitionGroup>
<ItemGroup>
<itemA Include="first" >
<Metadata1>m_data1</Metadata1>
</itemA>
</ItemGroup>
But see the print
<Message Text="itemA.Metadata1 = %(itemA.Metadata1)" />
<Message Text="itemA.Metadata2 = %(itemA.Metadata2)" />
delivers:
itemA.Metadata1 = m_data1 ***<-- correctly updated***
itemA.Metadata2 = default ***<-- why showing the default value, not* m_data1??**
how can I make itemA.Metadata2 to have the same value as itemA.Metadata1 after it has been updated?
I think this is not possible because order of evaluation Item Definitions - Value Sources - Note:
Item metadata from an ItemGroup is not useful in an ItemDefinitionGroup metadata declaration because ItemDefinitionGroup elements are processed before ItemGroup elements.
You have to override itemA's Metadata2 value in ItemGroup
<ItemDefinitionGroup>
<itemA>
<Metadata1>default</Metadata1>
<Metadata2>%(Metadata1)</Metadata2>
</itemA>
</ItemDefinitionGroup>
<ItemGroup>
<itemA Include="first" >
<Metadata1>m_data1</Metadata1>
<Metadata2>%(Metadata1)</Metadata2>
</itemA>
</ItemGroup>
As palo states, since Metadata2 has already been evaluated, you'll have to explicitly overwrite the value. Your change to Metadata1 won't automatically propagate to other places where it was referenced during initialization.
However, you can "re-evaluate" your items' metadata by starting a new instance of MSBuild and passing the updated metadata in as a property. Running msbuild /t:Wrapper on this project from the command line will result in Metadata1 and Metadata2 printing the same value:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<DefaultMetadata1 Condition="DefaultMetadata1==''">default</DefaultMetadata1>
</PropertyGroup>
<ItemDefinitionGroup>
<itemA>
<Metadata1>$(DefaultMetadata1)</Metadata1>
<Metadata2>%(itemA.Metadata1)</Metadata2>
</itemA>
</ItemDefinitionGroup>
<ItemGroup>
<itemA Include="first" >
<Metadata1>m_data1</Metadata1>
</itemA>
</ItemGroup>
<Target Name="Wrapper">
<MSBuild
Projects="$(MSBuildProjectFile)"
Targets="Worker"
Properties="DefaultMetadata1=%(itemA.Metadata1)"
/>
</Target>
<Target Name="Worker">
<Message Text="itemA.Metadata1 = %(itemA.Metadata1)" />
<Message Text="itemA.Metadata2 = %(itemA.Metadata2)" />
</Target>
</Project>
The usefulness of this approach will depend on what you're trying to accomplish. You can undoubtedly find an alternate solution using properties instead of item metadata.
While the solution above works for the case you describe, it can quickly get out of hand. There's probably a more simple solution that may involve some redundant code.
My recommendation would be to use the simple solution and eliminate as much redundancy as you reasonably can without inventing new ways to get around MSBuild's small feature set. Clever tricks here probably won't save you that many LOC at the end of the day and may result in less readable code, making it more difficult for newcomers to understand what's going on.

MSBuild 4.0 property functions cannot access properties inside of them

Is it a limitation of MSBuild 4.0 property functions that I cannot access a property from inside of one?
Here is an example that works just fine:
<PropertyGroup>
<PartialConnection>$(TargetConnectionString.Substring( 0 + 12))</PartialConnection>
</PropertyGroup>
Here is another example that doe snot work. (I replace the 0 with another property)
<PropertyGroup>
<LocationOfDataSource>$(TargetConnectionString.IndexOf("Data Source="))</LocationOfDataSource>
</PropertyGroup>
<Message Importance="high" Text="Location is = $(LocationOfDataSource)"/>
<PropertyGroup>
<PartialConnection>$(TargetConnectionString.Substring( $(LocationOfDataSource) + 12))</PartialConnection>
</PropertyGroup>
this outputs
Location is = 0
Error MSB4184: The expression ""Data Source=MySQLServer;Integrated Security=True;Pooling=False".Substring(0 + 12)" cannot be evaluated. Input string was not in a correct format.
I took the output and plugged into a console app and it works just fine. I have tried several variations and I they always fail when I put a property inside a property function. (I even tried access the same property twice in a my property function and that failed too.)
Do property functions not support accessing properties?
I think my issue was assuming that math came for free.
I needed to do this kind of thing:
<PropertyGroup>
<LocationOfDataSource>$(TargetConnectionString.IndexOf("Data Source="))</LocationOfDataSource>
<LenthOfDataSourceString>12</LenthOfDataSourceString>
<LocationOfEndOfDataSourceString>$([MSBuild]::Add($(LocationOfDataSource), $(LenthOfDataSourceString)))</LocationOfEndOfDataSourceString>
<PartialConnectionString>$(TargetConnectionString.Substring($(LocationOfEndOfDataSourceString)))</PartialConnectionString>
</PropertyGroup>
Note that I am adding using Add($(Property), $(Property)) in this version. Add is one of the built-in MSBuild Property Functions (since MSBuild 4).
It seems to be working now.

how is MS Build properties hierarchy maintained?

Can anyone tell me how does the MsBuild picks up the value of the property..??
eg.
<TempProperty>Property Value</TempProperty>
now I can use $(TempProperty) anywhere to get the value of it.
now the scenario is I have made custom task that has configuration like this..
<PropertyGroup>
<ItemList>
<ConfigChange>
<PlaceHolder>#MACHINE_NAME#</PlaceHolder>
<Value>$(TempProperty)</Value>
<IsList>False</IsList>
</ConfigChange>
</ItemList>
</PropertyGroup>
now instead of getting "Property Value" in the tag I am getting $(TempProperty)... can anyone tell me how to get actual value in tag ???
thanks in advance.
Hey, Guys I have got the actual Problem and solution too... I think I was not able to describe my problem here.. .the problem was.. In my custom task I was passing the file path of the file containing the above ItemList tag..now when I tried to parse the XMLNode "ItemList" it was getting "$(TempProperty)" as value in the Item.. and I think thats correct because thats what is present in the passed XML.
So to overcome the issue , I did two things..
I created ItemGroup instead of propertyGroup and passed that ItemGroup to my custom task instead of file path.. thus now at my code I am getting the desired values.
thanks for your replies.
There's not enough there to really diagnose your problem. It looks correct at first glance, but where is the PropertyGroup for TempProperty declared?
For an illustrative example, here's a snippet from a C# project file:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
...
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
...
</PropertyGroup>
The Configuration Property is being set to Debug (if it is blank at at that point). In the following group, it keys off of the Confuration and Platform properties.
The only gotcha tidbit is that for a property value to show up properly, it must be declared before it is used.
Please try to provide a little more context, that may help with understanding the issue.
I just tried this:
<Project
xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
ToolsVersion="4.0"
DefaultTargets="Demo" >
<PropertyGroup>
<TempProperty>property value</TempProperty>
<ItemList>
<ConfigChange>
<PlaceHolder>#MACHINE_NAME#</PlaceHolder>
<Value>$(TempProperty)</Value>
<IsList>False</IsList>
</ConfigChange>
</ItemList>
</PropertyGroup>
<Target Name="Demo">
<Message Text="TempProperty: $(TempProperty)"/>
<Message Text="ItemList: $(ItemList)"/>
</Target>
</Project>
And my results where:
Task "Message"
TempProperty: property value
Done executing task "Message".
Task "Message"
ItemList:
<ConfigChange xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PlaceHolder>#MACHINE_NAME#</PlaceHolder>
<Value>property value</Value>
<IsList>False</IsList>
</ConfigChange>
Done executing task "Message".
Are you seeing something else?
on Ritch's suggestion I am adding my solution.
Initially I had
<TempProperty>Property Value</TempProperty>
<PropertyGroup>
<ItemList>
<ConfigChange>
<PlaceHolder>#MACHINE_NAME#</PlaceHolder>
<Value>$(TempProperty)</Value>
<IsList>False</IsList>
</ConfigChange>
</ItemList>
</PropertyGroup>
now my problem was I was providing my properties file part to my custom task
like
<UpdatePegasusConfigXML
Environment="$(Environment)"
Instance="$(Instance)"
BuildSourceRoot="$(BuildSourceRoot)"
></UpdatePegasusConfigXML>
now since I was providing file path itself so it took the value which was put in "Value" tag and was not picking up the property value, thus at code level I was getting "$(TempProperty)" instead of "Property Value"
now what I did was instead of creating property Group I created ItemGroup like this
<ItemGroup>
<PlaceHolders Include="#MACHINE_NAME#">
<Value>$(TempProperty)</Value>
<IsList>True</IsList>
</PlaceHolders>
</ItemGroup>
now I updated my custom task to take IteamGroup as one of the inputs, thus calling changed to
<UpdatePegasusConfigXML
Environment="$(Environment)"
Instance="$(Instance)"
BuildSourceRoot="$(BuildSourceRoot)"
PlaceHolders="#(PlaceHolders)"
></UpdatePegasusConfigXML>
now at code level I am able to get the value in "Value" tag of the ItemGroup.
I hope I had explained my solution that it was understandable.

Find MSBuildProjectDirectory Parent Directory

MSBuild 3.5
I have the following project structure:
trunk/MainSolution.sln
trunk/Build/MyBuild.Proj
trunk/Library/...
trunk/etc...
So far, I've been using the following property to find out the project root folder:
<RootFolder>$(MSBuildProjectDirectory)\..\</RootFolder>
Everything was working great, until I tried using a copy task that relied on this path. It is not resolving correctly. I basically end up getting something like this which is not valid:
C:\Projects\MyProject\Trunk\Build\..\CodeAnalysis\myfile.xml
So basically, I need to get the full path for (MSBuildProjectDirectory)'s Parent.
Item metadata is your friend!
<Target Name="GetMSBuildProjectParentDirectory">
<!-- First you create the MSBuildProject Parent directory Item -->
<CreateItem Include="$(MSBuildProjectDirectory)\..\">
<Output ItemName="MSBuildProjectParentDirectory" TaskParameter="Include"/>
</CreateItem>
<!-- You can now retrieve its fullpath using Fullpath metadata -->
<Message Text="%(MSBuildProjectParentDirectory.Fullpath)"/>
<!-- Create a property based on parent fullpath-->
<CreateProperty Value="%(MSBuildProjectParentDirectory.Fullpath)">
<Output PropertyName="CodeFolder" TaskParameter="Value"/>
</CreateProperty>
</Target>
Nowadays with MSBuild 4.0 and above you don't want to use CreateItem or CreateProperty tasks anymore. What you are asking for can be solved easily with msbuild property functions:
<!-- Prints the parent directory's full path. -->
$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\..'))
If you just want to read the parent directory's folder name you can combine the above statement with the GetFileName property function:
$([System.IO.Path]::GetFileName('$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\..'))'))
A bit verbose but much better than the other answer as this works outside of targets and can be assigned to a property.
In case someone like me is still interested in this, here is how I did it in 2022 ^_^
<PropertyGroup>
<ParentFolderPath>$([System.IO.Directory]::GetParent($(MSBuildProjectDirectory)))</ParentFolderPath>
<ParentFolder>$([System.IO.Path]::GetFileName($(ParentFolderPath)))</ParentFolder>
...
</PropertyGroup>
I'm using this technique to auto-name the assemblies and default namespaces in the complex solutions.
<AssemblyName>$(ParentFolder).$(MSBuildProjectName)</AssemblyName>
<RootNamespace>$(ParentFolder).$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>