2

I'm trying to put a build stamp on the bottom of my application with details like the build date, framework version and also the Visual Studio version (i.e., the version of Visual Studio used to build the app).

Previously this was hard coded but now that we are migrating, making this programmatic is a good opportunity. I have managed to do the former two attributes like so:

Dim targetFrameworkAttribute As TargetFrameworkAttribute =
Assembly.GetExecutingAssembly().GetCustomAttributes(GetType(TargetFrameworkAttribute), False).SingleOrDefault()

lblBuildDate.Text = $"Build date: {mdBuildDate} - [ VS2017 ] [ {targetFrameworkAttribute.FrameworkDisplayName.Replace(" Framework", "")} ]"

(mdBuildDate is collected from the database as a string)

But I am struggling to find a way of collecting the Visual Studio version from the assembly or elsewhere.

Does anyone know if this is possible?

Jacob JA Shanks
  • 370
  • 1
  • 13

3 Answers3

3

The targeted framework version and the Visual Studio version are available during the build operation as MSBuild properties as either defined or reserved properties. These properties can be made available for use in a T4 text template for code generation.

The following procedure is based on VS2017 Community Edition.

  1. Add a Text Template to your project. Project Menu-> Add New Item ->General -> Text Template. Name the file ProjectInfo.tt.
  2. From the Solution Explorer window, select ProjectInfo.tt and right-click on its and select "Properties". Change the "Build Action" from Content to None.
  3. Replace the contents of ProjectInfo.tt with the following and save the file. You will receive an error of "An expression block evaluated as Null", ignore it.
<#@ template debug="false" hostspecific="true" language="VB" #>
<#@ parameter type="System.String" name="VisualStudioVersion" #>
<#@ parameter type="System.String" name="TargetFrameworkVersion" #>
<#@ assembly name="System.Core" #>
<#@ output extension=".vb" #>
Module ProjectInfo
    Public ReadOnly VisualStudioVersion As String = "<#= VisualStudioVersion #>"
    Public ReadOnly TargetFrameworkVersion As String = "<#= TargetFrameworkVersion #>"
    Public ReadOnly BuildDate As New DateTime(<#= DateTime.Now().Ticks #>L)
End Module
  1. Save the project and select the it in the Solution Explorer window and right-click on it. Select "Unload Project". Right-click it again and select "Edit projname.vbproj".
  2. Scroll down to the end of the file add the following before the "" tag.
  <!-- Define the parameters available T4 Text templates  -->
  <ItemGroup>
    <T4ParameterValues Include="VisualStudioVersion">
      <Value>$(VisualStudioVersion)</Value>
      <Visible>false</Visible>
    </T4ParameterValues>
    <T4ParameterValues Include="TargetFrameworkVersion">
      <Value>$(TargetFrameworkVersion)</Value>
      <Visible>false</Visible>
    </T4ParameterValues>
  </ItemGroup>

  <!-- the following will cause the T4 template to be processed before the build process begins  -->
  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TextTemplating\Microsoft.TextTemplating.targets" />
  <PropertyGroup>
    <TransformOnBuild>true</TransformOnBuild>
    <TransformOutOfDateOnly>false</TransformOutOfDateOnly>
  </PropertyGroup>
  1. Save and close the "vbproj" file. Right-click on the project in the Solution Explorer window and select "Reload project".
  2. Right-click on the project in the Solution Explorer window and select "Rebuild project".
  3. In the Solution Explorer window, enable "Show all files" and expand the "ProjectInfo.tt" node and open the "ProjectInfo.vb" file. you should see the following code (the assigned values may differ based on you project configuration).
Module ProjectInfo
    Public ReadOnly VisualStudioVersion As String = "15.0"
    Public ReadOnly TargetFrameworkVersion As String = "v4.72"
    Public ReadOnly BuildDate As New DateTime(636968364980609475L)
End Module

Once you have successfully built your project, the values defined in "ProjectInfo.vb" will be accessible by other code. The file will be regenerated on each build.

Reference Articles:

  1. Code Generation and T4 Text Templates

  2. Pass build context data into the templates


Edit: As an alternative to editing the projname.vbproj file, you could also place the statements presented under Step 5 in a text file named Directory.Build.targets that would be place in the project folder. The contents need to be enclosed in a <Project> tag.

<Project>
statements from Step 5
</Project> 
TnTinMn
  • 11,522
  • 3
  • 18
  • 39
  • Thanks for the detailed instructions! I’ll give it a try when I’m back in the office – Jacob JA Shanks Jun 23 '19 at 08:13
  • Had no problem setting that up and it works perfectly! The tiny caveat is that it's the version number instead of the year but that's a fair compromise. Many thanks! – Jacob JA Shanks Jun 24 '19 at 08:25
  • 1
    @JacobJAShanks, the `$(something)` command in MSBuild is very versatile in what it can retrieve. It can pull from environment variables, MSBuild properties, or even evaluate static .Net Class methods/properties. With that stated, there appears to be an environment variable named **VisualStudioEdition** that yields "Microsoft Visual Studio Community 2017" for the setup used in my answer. I would recommend that wrap the returned value in single quotes to avoid "Express block evaluated as null" errors if it does not exist. i.e. `'$(VisualStudioEdition)'` – TnTinMn Jun 24 '19 at 15:23
0

You can get it from the standard output of vswhere.exe

Dim proc = New Process() With
{
    .StartInfo = New ProcessStartInfo() With
    {
        .FileName = "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe",
        .UseShellExecute = False,
        .RedirectStandardOutput = True,
        .CreateNoWindow = True
    }
}
Dim lines = New Dictionary(Of String, String)()
proc.Start()
While Not proc.StandardOutput.EndOfStream
    Dim line = proc.StandardOutput.ReadLine()
    If line.Contains(": ") Then
        Dim split = line.Split(": ", StringSplitOptions.None)
        lines.Add(split(0), split(1))
    End If
End While
Dim installationVersion = lines("installationVersion")

by combining this answer and this MSDN post by Daniel Meixner.

djv
  • 15,168
  • 7
  • 48
  • 72
  • That's fine for the developers computers (assuming we are all on the same version) but not on client's computers where Visual Studio is not installed. It really needs to be a record of the Visual Studio version used to build the app, not the version installed on the device. – Jacob JA Shanks Jun 21 '19 at 15:36
  • 2
    Put it in a pre-build app, marking a settings object of your app with the version number. Or possibly a Text Template? – djv Jun 21 '19 at 15:47
  • Appears `vswhere.exe` must be installed manually if not using VS2017+. If you have multiple versions of VS installed, how would this know which version did the build? – topshot Jun 21 '19 at 18:09
  • @topshot For < 2017 there are other methods. See https://learn.microsoft.com/en-us/visualstudio/extensibility/internals/detecting-system-requirements?view=vs-2015 for 2015 for example. Microsoft changed things up. – djv Jun 21 '19 at 18:10
  • What I'm not getting is how to know which version did the actual build like the OP asked when you have multiple versions installed. Maybe I'm not like most devs, but I have 4 versions on my PC. – topshot Jun 21 '19 at 18:15
  • Each version of VS will have its own msbuild.exe so you'll still need to identify the correct one depending on which VS you are running from. – djv Jun 21 '19 at 19:28
-2

I hope this will help

internal static string GetVisualStudioInstalledPath()
{
    var visualStudioInstalledPath = string.Empty;
    var visualStudioRegistryPath = Registry.LocalMachine.OpenSubKey(
                                   @"SOFTWARE\WOW6432Node\Microsoft\VisualStudio\14.0");
    if (visualStudioRegistryPath != null)
    {
        visualStudioInstalledPath = visualStudioRegistryPath.GetValue("InstallDir", string.Empty) 
                                                                      as string;
    }

    if(string.IsNullOrEmpty(visualStudioInstalledPath) || 
                            !Directory.Exists(visualStudioInstalledPath))
    {
        visualStudioRegistryPath = Registry.LocalMachine.OpenSubKey(
                                   @"SOFTWARE\Microsoft\VisualStudio\14.0");
        if (visualStudioRegistryPath != null)
        {
            visualStudioInstalledPath = visualStudioRegistryPath.GetValue("InstallDir", 
                                                                          string.Empty) as string;
        }
    }

    if (string.IsNullOrEmpty(visualStudioInstalledPath) || 
                             !Directory.Exists(visualStudioInstalledPath))
    {
        visualStudioRegistryPath = Registry.LocalMachine.OpenSubKey(
                                   @"SOFTWARE\WOW6432Node\Microsoft\VisualStudio\12.0");
        if (visualStudioRegistryPath != null)
        {
            visualStudioInstalledPath = visualStudioRegistryPath.GetValue("InstallDir", 
                                                                          string.Empty) as string;
        }
    }

    if (string.IsNullOrEmpty(visualStudioInstalledPath) || 
                             !Directory.Exists(visualStudioInstalledPath))
    {
        visualStudioRegistryPath = Registry.LocalMachine.OpenSubKey(
                                   @"SOFTWARE\Microsoft\VisualStudio\12.0");
        if (visualStudioRegistryPath != null)
        {
            visualStudioInstalledPath = visualStudioRegistryPath.GetValue("InstallDir", 
                                                                          string.Empty) as string;
        }
    }

    return visualStudioInstalledPath;
}
  • 1
    Like with the other answer, this issue is fine for developers in the team, but this wont work when the app is run by clients who do not have Visual Studio installed. – Jacob JA Shanks Jun 21 '19 at 16:17
  • @Jacob JA Shanks: I agree but would suggest if Visual Studio Version number can also be added to Database. Just a suggestion to think about. – Srinivasan Jayakumar Jun 21 '19 at 16:48
  • That would be the ultimate solution, but being as my prior research to see if there was an attribute I could take advantage of turned up nothing, thought I could try posting here to see if I missed something :) – Jacob JA Shanks Jun 21 '19 at 17:01