2

I followed Kent Boogaart's excellent article on how to add dynamic content to the splash screen. Things work in the sense that I can see version number on the splash screen now. The problem however is that the article uses $(Version) property, which in my case is 1.0.0.0.

I am using shared AssemblyInfo.vb and need to get next major/minor/build/revision numbers from this file to draw them on the splash screen. The shared AssemblyInfo file contains a version number like 2.5.*. I need the actual number that will be generated by MSBuild (2.5.abc.xyz).

I thought about using Reflection inside the UsingTask to get version number from the assembly, but since this task is run before the build process, the assembly doesn't yet have the version number that will be generated in the following build. Moreover the assembly simply may not exist (e.g. a Clean command before build) at that point.

I installed MSBuild Community Tasks but cannot see anything except SvnVersion task.

Can someone help me with sending the to-be-generated version number to my UsingTask?

dotNET
  • 33,414
  • 24
  • 162
  • 251
  • Add the default `SplashScreen` of VB project. It contains the revision number of your Application. – kiLLua Nov 02 '17 at 14:45
  • @kiLLua: This is not a WinForms project. WPF SplashScreen template is simply a PNG file with no text on it. – dotNET Nov 02 '17 at 15:10
  • How is the executing assembly supposed to know the version of the *next* build assembly? Why don't you display the version of the currently running application? – mm8 Nov 02 '17 at 15:31
  • @mm8: Please see the article I have linked above. It isn't that straight-forward in WPF to show dynamic content on splash screen when using the built-in `SplashScreen` build action, since WPF only accepts a **bitmap file** for this purpose. To workaround this limitation, we dynamically write version number on the bitmap at build time, using MSBuild tasks. – dotNET Nov 02 '17 at 15:53
  • If you write the version at *build* time, what's the issue? Just display the written version number? – mm8 Nov 02 '17 at 15:55
  • @mm8: The problem is that I need to get assembly version from the shared `assemblyinfo.vb`. Reading a line from a file in a build step is one problem and then translating it to actual version number (see question) is another. – dotNET Nov 02 '17 at 16:02
  • You said that you "dynamically write version number on the bitmap at build time". I don't understand why you need to get the version number from AssemblyInfo if MSBuild generates if for you anyway. You are not supposed to check in version numbers. It is the responsibility of the build process to generate the build numbers/versions. – mm8 Nov 02 '17 at 16:05
  • @mm8: Perhaps I haven't been able to elaborate it correctly. This is my first time dealing with deep MSBuild. I'll post the solution once I have it. – dotNET Nov 02 '17 at 17:00

1 Answers1

2

Finally! For anyone else trying to do the same, here are the steps, assuming that you're using shared AssemblyInfo that resides in solution directory (it can work for default AssemblyInfo too, but you'll need to adjust paths and filenames accordingly):

  1. Download and install MSBuild Community Tasks on your machine.
  2. Add a targets file (a simple XML file with .targets extension) to your project.
  3. Add UsingTask presented in Kent Boogaart's article I linked in the question to this file. This will perform actual version writing on the splash image.
  4. Use <Version>, <FileUpdate> and <AddTextToImage> tasks (first two are available in MSBuild, third one is from the UsingTask we added in step 3) to write new version number to your shared AssemblyInfo file and splash image.

The final .targets file looks like this:

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask TaskName="AddTextToImage" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
    <ParameterGroup>
      <InputPath ParameterType="System.String" Required="true" />
      <OutputPath ParameterType="System.String" Required="true" />
      <TopMiddlePoint ParameterType="System.String" Required="true" />
      <Text1 ParameterType="System.String" Required="true" />
      <Text2 ParameterType="System.String" Required="false" />
      <Text3 ParameterType="System.String" Required="false" />
    </ParameterGroup>
    <Task>
      <Reference Include="WindowsBase" />
      <Reference Include="PresentationCore" />
      <Reference Include="PresentationFramework" />
      <Reference Include="System.Xaml" />
      <Using Namespace="System" />
      <Using Namespace="System.Globalization" />
      <Using Namespace="System.IO" />
      <Using Namespace="System.Windows" />
      <Using Namespace="System.Windows.Media" />
      <Using Namespace="System.Windows.Media.Imaging" />
      <Code Type="Fragment" Language="cs">
        <![CDATA[         
              var originalImageSource = BitmapFrame.Create(new Uri(InputPath));

              var visual = new DrawingVisual();

              using (var drawingContext = visual.RenderOpen())
              {
                drawingContext.DrawImage(originalImageSource, new Rect(0, 0, originalImageSource.PixelWidth, originalImageSource.PixelHeight));

                var typeFace = new Typeface(new FontFamily("Arial"), FontStyles.Normal, FontWeights.Bold, FontStretches.Normal);
                var formattedText = new FormattedText(Text1, System.Globalization.CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeFace, 18, Brushes.Red);
                var topMiddlePoint = Point.Parse(TopMiddlePoint);
                var point = new Point(topMiddlePoint.X - (formattedText.Width / 2), topMiddlePoint.Y);
                drawingContext.DrawText(formattedText, point);

                if(!string.IsNullOrEmpty(Text2))
                {
                  formattedText = new FormattedText(Text2, System.Globalization.CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeFace, 18, Brushes.Red);
                  topMiddlePoint.Y += formattedText.Height + 5;
                  point = new Point(topMiddlePoint.X - (formattedText.Width / 2), topMiddlePoint.Y);
                  drawingContext.DrawText(formattedText, point);
                }

                if(!string.IsNullOrEmpty(Text3))
                {
                  formattedText = new FormattedText(Text3, System.Globalization.CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeFace, 18, Brushes.Red);
                  topMiddlePoint.Y += formattedText.Height + 5;
                  point = new Point(topMiddlePoint.X - (formattedText.Width / 2), topMiddlePoint.Y);
                  drawingContext.DrawText(formattedText, point);
                }

                drawingContext.Close();
              }

              var renderTargetBitmap = new RenderTargetBitmap(originalImageSource.PixelWidth, originalImageSource.PixelHeight, originalImageSource.DpiX, originalImageSource.DpiY, PixelFormats.Pbgra32);

              renderTargetBitmap.Render(visual);

              var bitmapFrame = BitmapFrame.Create(renderTargetBitmap);

              BitmapEncoder encoder = new PngBitmapEncoder();

              encoder.Frames.Add(bitmapFrame);

              using (var stream = File.OpenWrite(OutputPath))
              {
                encoder.Save(stream);
                stream.Close();
              }
            ]]>
      </Code>
    </Task>
  </UsingTask>

  <PropertyGroup>
    <MajorVersion>2</MajorVersion>
    <MinorVersion>5</MinorVersion>
  </PropertyGroup>

  <Target Name="BeforeBuild">
    <Version BuildType="Automatic" RevisionType="Automatic" Major="$(MajorVersion)" Minor="$(MinorVersion)">
      <Output TaskParameter="Major" PropertyName="Major" />
      <Output TaskParameter="Minor" PropertyName="Minor" />
      <Output TaskParameter="Build" PropertyName="Build" />
      <Output TaskParameter="Revision" PropertyName="Revision" />
    </Version>

    <FileUpdate Files="$(SolutionDir)SharedAssemblyInfo.vb"
            Regex="Assembly: AssemblyVersion\(&quot;\d+\.\d+((\.\*)|(\.\d+\.\d+))?&quot;\)"
            ReplacementText="Assembly: AssemblyVersion(&quot;$(Major).$(Minor).$(Build).$(Revision)&quot;)" />

    <FileUpdate Files="$(SolutionDir)SharedAssemblyInfo.vb"
            Regex="Assembly: AssemblyFileVersion\(&quot;\d+\.\d+((\.\*)|(\.\d+\.\d+))?&quot;\)"
            ReplacementText="Assembly: AssemblyFileVersion(&quot;$(Major).$(Minor).$(Build).$(Revision)&quot;)" />

    <SvnVersion LocalPath=".">
      <Output TaskParameter="Revision" PropertyName="SvnRevision" />
    </SvnVersion>

    <AddTextToImage InputPath="$(ProjectDir)Resources\Splash.png"
                    OutputPath="$(ProjectDir)Resources\SplashWithVersion.png"
                    TopMiddlePoint="250,150"
                    Text1="$(Major).$(Minor).$(Build).$(Revision)"
                    Text2="SVN Version: $(SvnRevision)" />

    <Message Text="Updated version number on splash screen to: $(Major).$(Minor).$(Build).$(Revision)" Importance="high"/>
  </Target>
</Project>

This will update your AssemblyInfo and the output image. Note that you must mark your output image as SplashScreen (as Build Action) in Visual Studio. Also note that I'm writing both assembly version as well as SVN Revision number on the splash screen. You may want to adjust yours according to your needs.

dotNET
  • 33,414
  • 24
  • 162
  • 251