Updating HelpFileVersion Dynamically Using MSBuild Targets (From VS2010)

Topics: User Forum
Sep 26, 2013 at 10:29 PM
Edited Sep 27, 2013 at 4:11 AM
Hi all,
Sorry, I am probably making this more complex than it needs to be. But I am having a tough time dynamically changing the value of HelpFileVersion.

What I am trying to do is set this property by pulling out the version components from an overall SolutionInfo.cs that provides the version for all of my other projects being built. I first tried placing the following code under the root <PropertyGroup> tag in my .shfbproj:
<HelpFileVersion>{@Major}.{@Minor}.{@Build}.{@Revision}</HelpFileVersion>
<In>$([System.IO.File]::ReadAllText('$(MSBuildProjectDirectory)\..\SolutionInfo.cs'))</In>
<Pattern>^\s*\[assembly: AssemblyVersion\(\D*(\d+)\.(\d+)\.(\d+).(\d+)</Pattern>
<Major>$([System.Text.RegularExpressions.Regex]::Match($(In), $(Pattern), System.Text.RegularExpressions.RegexOptions.Multiline).Groups[1].Value)</Major>
<Minor>$([System.Text.RegularExpressions.Regex]::Match($(In), $(Pattern), System.Text.RegularExpressions.RegexOptions.Multiline).Groups[2].Value)</Minor>
<Build>$([System.Text.RegularExpressions.Regex]::Match($(In), $(Pattern), System.Text.RegularExpressions.RegexOptions.Multiline).Groups[3].Value)</Build>
<Revision>$([System.Text.RegularExpressions.Regex]::Match($(In), $(Pattern), System.Text.RegularExpressions.RegexOptions.Multiline).Groups[4].Value)</Revision>
I thought this solution alone worked great, until I realized that the shfb project had to be reloaded before my SolutionInfo.cs would be read in again. Next, I realized I could just perform this functionality as a build event, thus guaranteeing the current version would be read in every time the documentation is built.

So then I moved the code from above to an MSBuild target in my .shfbproj:
<Target Name="BeforeBuildHelp">
  <PropertyGroup>
    <HelpFileVersion>{@Major}.{@Minor}.{@Build}.{@Revision}</HelpFileVersion>
    <In>$([System.IO.File]::ReadAllText('$(MSBuildProjectDirectory)\..\SolutionInfo.cs'))</In>
    <Pattern>^\s*\[assembly: AssemblyVersion\(\D*(\d+)\.(\d+)\.(\d+).(\d+)</Pattern>
    <Major>$([System.Text.RegularExpressions.Regex]::Match($(In), $(Pattern), System.Text.RegularExpressions.RegexOptions.Multiline).Groups[1].Value)</Major>
    <Minor>$([System.Text.RegularExpressions.Regex]::Match($(In), $(Pattern), System.Text.RegularExpressions.RegexOptions.Multiline).Groups[2].Value)</Minor>
    <Build>$([System.Text.RegularExpressions.Regex]::Match($(In), $(Pattern), System.Text.RegularExpressions.RegexOptions.Multiline).Groups[3].Value)</Build>
    <Revision>$([System.Text.RegularExpressions.Regex]::Match($(In), $(Pattern), System.Text.RegularExpressions.RegexOptions.Multiline).Groups[4].Value)</Revision>
  </PropertyGroup>
  <Message Importance="High" Text="HelpFileVersion: $(Major).$(Minor).$(Build).$(Revision)" />
</Target>
I know my Major, Minor, Build, and Revision properties are updating correctly (seen by the message task). But I can't get the HelpFileVersion property to be updated within the target (value remains the default 1.0.0.0). I have seen this issue online in other places (such as here or here), but that bug seems to be related only to targets calling other targets.

If anyone has a suggestion of how I can update the HelpFileVersion property within an MSBuild task, I would be greatly appreciative!

Thanks,
-Mike
Coordinator
Sep 27, 2013 at 1:02 AM
If you're building from within the standalone GUI, it won't execute the other targets. It might if building from the command line or from within Visual Studio.

Eric
Sep 27, 2013 at 4:07 AM
Thanks for your response Eric!

Sorry I was not very clear. My documentation project is part of a Visual Studio 2010 solution (thanks to your excellent SHFB extension). I build my solution from Visual Studio (as opposed to invoking MSBuild directly).

I was tired of manually syncing the HelpFileVersion property in my .shfbproj every time I build. So I decided to automate it using custom project variables and I came across a closed issue on this very property (see issue 19164).

Do you think my method of parsing out the version from a common file and stuffing the components into custom project variables is the best way to do this? I know I am properly obtaining the version components, I just can't set HelpFileVersion within the MSBuild target. I have seen mention of problems with the scope of a property within a target, but can you think of any other hints I could try?

Thanks again for the amazing tools,
-Mike
Coordinator
Sep 27, 2013 at 3:16 PM
I just noticed that you put HelpFileVersion property in the BeforeBuildHelpGroup. It's probably not picking that one up as it's not expecting it there. Delete that one and put the value in the standard project property value and see if it works then.

Eric
Sep 30, 2013 at 2:35 PM
Hi Eric,

Thanks for your suggestion. If I put the HelpFileVersion property in the standard project property group, I have to define the version component properties there as well (e.g. Major, Minor, Build, Revision). Unfortunatly, updates to the version component properties in the BeforeBuildHelpGroup are still not reflected in the HelpFileVersion property.

I think you have properly identified what is going on--the HelpFileVersion property is not being picked up in the BeforeBuildHelpGroup. Can you think of another way I can accomplish dynamically updating HelpFileVersion?

Thanks again and have a great day!
-Mike
Coordinator
Sep 30, 2013 at 7:52 PM
I did get it to work as follows:
<!-- Insert this property group at the start of the SHFB project just inside the opening Project element.
     Note that if built from within Visual Studio or the standalone GUI, you'll need to unload and reload the
     project to get it to re-evaluate the values if you change the version in the referenced file since Visual
     Studio and the standalone GUI will be unaware that the value has changed. -->
<PropertyGroup>
  <In>$([System.IO.File]::ReadAllText('$(MSBuildProjectDirectory)\ClassLibrary1\Properties\AssemblyInfo.cs'))</In>
  <Pattern>^\s*\[assembly: AssemblyVersion\(\D*(\d+)\.(\d+)\.(\d+).(\d+)</Pattern>
  <Major>$([System.Text.RegularExpressions.Regex]::Match($(In), $(Pattern), System.Text.RegularExpressions.RegexOptions.Multiline).Groups[1].Value)</Major>
  <Minor>$([System.Text.RegularExpressions.Regex]::Match($(In), $(Pattern), System.Text.RegularExpressions.RegexOptions.Multiline).Groups[2].Value)</Minor>
  <Build>$([System.Text.RegularExpressions.Regex]::Match($(In), $(Pattern), System.Text.RegularExpressions.RegexOptions.Multiline).Groups[3].Value)</Build>
  <Revision>$([System.Text.RegularExpressions.Regex]::Match($(In), $(Pattern), System.Text.RegularExpressions.RegexOptions.Multiline).Groups[4].Value)</Revision>
</PropertyGroup>
Set the Help File Version property to "{@Major}.{@Minor}.{@Build}.{@Revision}". When you build the file, it will pick up the version number as expected. However, as noted in the comments, if built from within the standalone GUI or Visual Studio, you will need to unload and reload the project after changing the version number in the referenced source code file since the project will be unaware of the change until you do. When built from the command line or on a build server, it will always pick up the current value.

Eric
Sep 30, 2013 at 8:49 PM
Edited Sep 30, 2013 at 8:54 PM
Eric,

Thanks for the time you put into helping me and countless others--Sandcastle and SHFB really are amazing tools.

Since I am building from within Visual Studio, I was hoping to avoid having to unload and reload the project to re-evaluate the values in the referenced file. That was my original motivation to update HelpFileVersion from within the BeforeBuildHelpGroup. At least now I know I was not doing something wrong--I just have to remember to unload/reload the project after updating the version in the referenced file.

Thanks again for your input,
-Mike

PS-Is it by design that HelpFileVersion cannot be picked up in an MSBuild target override? I know you mentioned that this property is not expected there, but that would be an elegant solution to my use case if it was supported.
Coordinator
Oct 1, 2013 at 7:38 PM
The reason it's not picking up the value is a side-effect of the way the build task gets the project instance. If it's in the global collection which is publicly accessible, it gets it from there. If not, it uses reflection to get the currently executing project instance (a bit of a hack so not a preferred approach). It appears that only the currently executing project instance obtained through reflection contains updates from other build tasks which is why it works when ran from the command line with MSBuild since it doesn't appear to use the global collection. In Visual Studio, it's typically in the global collection so it uses that copy of the project.

As far as I know Visual Studio only edits project properties found at the project's root level and won't see any within a separate build task such as BeforeBuildHelp. That's why you don't see the custom properties such as Major and Minor in the User Defined tab of the SHFB project properties window.

Eric
Marked as answer by miesch1 on 10/4/2013 at 9:05 AM
Oct 4, 2013 at 4:29 PM
Thanks a ton Eric. Sorry for the delayed response.

I prefer to keep building from Visual Studio, as opposed to a build script. That may change in the future. Until then, I will just have to remember to reload my documentation project before building. If you know of any way to force Visual Studio to re-evaluate project properties at build time, or to force the build tasks to get the currently executing project instance through reflection, please let me know.

Thanks,
-Mike