Search code examples
c#.netvisual-studio-2010windows-installerversioning

Set AssemblyInfo Version numbers with MSI setup version


I am using a setup project to publish my projects. I want the version of each project to be the same as the setup version.

I want to change my setup version property in Visual Studio and after building, for all project versions to be updated from this property, is this possible?


Solution

  • Projects have Assembly & File version numbers: (not setup versions I edited your question accordingly) enter image description here

    Answer 1:

    If you want to make the Setup projects version number set the Assembly & File version numbers you need to do it with a script/exe that gets triggered by the build.

    enter image description here

    This article on How To Update Assembly Version Number Automatically shows half the solution...

    From the research I did it is not possible to use the SetupVersion in a PreBuildEvent. There isn't a $SetupVersion command for it: http://msdn.microsoft.com/en-us/library/42x5kfw4(v=vs.80).aspx

    Having to change the PreBuildEvent each build as shown in this comment in the Code Project article using the -set: command is not ideal.

    The solution we need is a PreBuildEvent to call the AssemblyInfoUtil.exe and have it read the "ProductVersion" from the vdproj project file. And then update the Assembly version number(s).

    I have modified the code from the article to show you how to read the product version from the Setup.vdproj and this is how it can be called from a PreBuildEvent:

    AssemblyInfoUtil.exe -setup:"C:\Program Files\MyProject1\Setup1\Setup1.vdproj" -ass:"C:\Program Files\MyProject1\AssemblyInfo.cs"
    

    This is the modified code:

    using System;
    using System.IO;
    using System.Text;
    
    namespace AssemblyInfoUtil
    {
        class AssemblyInfoUtil
        {
        private static int incParamNum = 0;    
        private static string fileName = "";  
        private static string setupfileName = "";       
        private static string versionStr = null;    
        private static bool isVB = false;
        [STAThread]
        static void Main(string[] args)
        {
            for (int i = 0; i < args.Length; i++) {
                if (args[i].StartsWith("-setup:")) {
               string s = args[i].Substring("-setup:".Length);
               setupfileName = int.Parse(s);
                }
                else if (args[i].StartsWith("-ass:")) {
               fileName = args[i].Substring("-ass:".Length);
                }
            }
    
            //Jeremy Thompson showing how to detect "ProductVersion" = "8:1.0.0" in vdproj
            string setupproj = System.IO.File.ReadAllText(setupfileName);
            int startPosOfProductVersion = setupproj.IndexOf("\"ProductVersion\" = \"") +20;
            int endPosOfProductVersion = setupproj.IndexOf(Environment.NewLine, startPosOfProductVersion) - startPosOfProductVersion;
            string versionStr = setupproj.Substring(startPosOfProductVersion, endPosOfProductVersion);
            versionStr = versionStr.Replace("\"", string.Empty).Replace("8:",string.Empty);
          
            if (Path.GetExtension(fileName).ToLower() == ".vb")
            isVB = true;
    
            if (fileName == "") {
            System.Console.WriteLine("Usage: AssemblyInfoUtil 
               <path to :Setup.vdproj file> and  <path to AssemblyInfo.cs or AssemblyInfo.vb file> [options]");
            System.Console.WriteLine("Options: ");
            System.Console.WriteLine("  -setup:Setup.vdproj file path");
            System.Console.WriteLine("  -ass:Assembly file path");
            return;
            }
    
            if (!File.Exists(fileName)) {
            System.Console.WriteLine
                ("Error: Can not find file \"" + fileName + "\"");
            return;
            }
    
            System.Console.Write("Processing \"" + fileName + "\"...");
            StreamReader reader = new StreamReader(fileName);
                 StreamWriter writer = new StreamWriter(fileName + ".out");
            String line;
    
            while ((line = reader.ReadLine()) != null) {
            line = ProcessLine(line);
            writer.WriteLine(line);
            }
            reader.Close();
            writer.Close();
    
            File.Delete(fileName);
            File.Move(fileName + ".out", fileName);
            System.Console.WriteLine("Done!");
        }
    
        private static string ProcessLine(string line) {
            if (isVB) {
            line = ProcessLinePart(line, "<Assembly: AssemblyVersion(\"");
            line = ProcessLinePart(line, "<Assembly: AssemblyFileVersion(\"");
            } 
            else {
            line = ProcessLinePart(line, "[assembly: AssemblyVersion(\"");
            line = ProcessLinePart(line, "[assembly: AssemblyFileVersion(\"");
            }
            return line;
        }
    
        private static string ProcessLinePart(string line, string part) {
            int spos = line.IndexOf(part);
            if (spos >= 0) {
            spos += part.Length;
            int epos = line.IndexOf('"', spos);
            string oldVersion = line.Substring(spos, epos - spos);
            string newVersion = "";
            bool performChange = false;
    
            if (incParamNum > 0) {
                string[] nums = oldVersion.Split('.');
                if (nums.Length >= incParamNum && nums[incParamNum - 1] != "*") {
                Int64 val = Int64.Parse(nums[incParamNum - 1]);
                val++;
                nums[incParamNum - 1] = val.ToString();
                newVersion = nums[0]; 
                for (int i = 1; i < nums.Length; i++) {
                    newVersion += "." + nums[i];
                }
                performChange = true;
                }
            }
            
            else if (versionStr != null) {
                newVersion = versionStr;
                performChange = true;
            }
    
            if (performChange) {
                StringBuilder str = new StringBuilder(line);
                str.Remove(spos, epos - spos);
                str.Insert(spos, newVersion);
                line = str.ToString();
            }
            } 
            return line;
        }
         }
    }
    

    Answer 2:

    To my way of thinking a better way is to use a Shared Assembly Info class rather than individual AssemblyInfo class files.

    To implement this, create a file in the solution folder named SharedAssemblyInfo.cs and then add a link in each project to SharedAssemblyInfo.cs. You can also move the linked SharedAssemblyInfo.cs into the Properties folder so that it sits side-by-side with the AssemblyInfo.cs that is specific to each project in the solution, as shown below.

    enter image description here

    Here is a sample SharedAssemblyInfo.cs file:

    using System;
    using System.Reflection;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;
    
    // General Information about an assembly is controlled through the following 
    // set of attributes. Change these attribute values to modify the information
    // associated with an assembly.
    [assembly: AssemblyCompany("Saint Bart Technologies")]
    [assembly: AssemblyProduct("Demo")]
    [assembly: AssemblyCopyright("Copyright ? Saint Bart 2013")]
    [assembly: AssemblyTrademark("")]
    
    // Make it easy to distinguish Debug and Release (i.e. Retail) builds;
    // for example, through the file properties window.
    #if DEBUG
    [assembly: AssemblyConfiguration("Debug")]
    [assembly: AssemblyDescription("Flavor=Debug")] // a.k.a. "Comments"
    #else
    [assembly: AssemblyConfiguration("Retail")]
    [assembly: AssemblyDescription("Flavor=Retail")] // a.k.a. "Comments"
    #endif
    
    [assembly: CLSCompliant(true)]
    
    // Setting ComVisible to false makes the types in this assembly not visible 
    // to COM components.  If you need to access a type in this assembly from 
    // COM, set the ComVisible attribute to true on that type.
    [assembly: ComVisible(false)]
    
    // Note that the assembly version does not get incremented for every build
    // to avoid problems with assembly binding (or requiring a policy or
    // <bindingRedirect> in the config file).
    //
    // The AssemblyFileVersionAttribute is incremented with every build in order
    // to distinguish one build from another. AssemblyFileVersion is specified
    // in AssemblyVersionInfo.cs so that it can be easily incremented by the
    // automated build process.
    [assembly: AssemblyVersion("1.0.0.0")]
    
    // By default, the "Product version" shown in the file properties window is
    // the same as the value specified for AssemblyFileVersionAttribute.
    // Set AssemblyInformationalVersionAttribute to be the same as
    // AssemblyVersionAttribute so that the "Product version" in the file
    // properties window matches the version displayed in the GAC shell extension.
    [assembly: AssemblyInformationalVersion("1.0.0.0")] // a.k.a. "Product version"
    

    Here is a sample AssemblyInfo.cs file:

    // Note: Shared assembly information is specified in SharedAssemblyInfo.cs
    using System.Reflection;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;
    // General Information about an assembly is controlled through the following 
    // set of attributes. Change these attribute values to modify the information
    // associated with an assembly.
    [assembly: AssemblyTitle("WindowsFormsApplication2")]
    // The following GUID is for the ID of the typelib if this project is exposed to COM
    [assembly: Guid("ffded14d-6c95-440b-a45d-e1f502476539")]
    

    So each time you want to change all projects Assembly info you can do it in one spot. I assume you would want to set the MSI Setup Version the same as the Assembly version number, one manual step.


    Answer 3:

    Consider switching to use MSBuild it has all these kinds of benefits but I'm not sure if you have the time to pick it up right now.


    Answer 4:

    Assemblies can auto-increment their build numbers using the following asterisk syntax within AssemblyInfo.cs:

    [assembly: AssemblyVersion("1.0.0.*")]
    

    This is a good method because the point of tracking a build number is to be able to recognize different builds. Having a pre-build changing build numbers defeats this purpose as the build has not yet occurred.


    Answer 5:

    The other CodeProject answer here assumes you want to update the ProductVersion, ProductCode, PackageCode in the Setup MSI Project file. I didn't interpret your question that way and according to this thread there are problems: pre-build event to change setup project's ProductVersion doesn't take effect until after the build


    Answer 6 (new):

    There is a few TFS Build plugins to set "Assembly Info": https://marketplace.visualstudio.com/items?itemName=bleddynrichards.Assembly-Info-Task https://marketplace.visualstudio.com/items?itemName=bool.update-assembly-info https://marketplace.visualstudio.com/items?itemName=ggarbuglia.setassemblyversion-task


    Answer 7 - .Net Core:

    Moving from GlobalAssemblyInfo.cs to Directory.Build.props:

    The old GlobalAssemblyInfo.cs file often contained duplicate code for attributes shared across projects. Switching to Directory.Build.props allows centralisation of these settings without needing to modify C# code directly.

    In the root directory of your solution (where the .sln file is located), create a file named Directory.Build.props with the following content:

    <?xml version="1.0" encoding="utf-8" ?>
    <Project>
      <PropertyGroup>
        <!-- Assembly versioning -->
        <Version>1.0.0.0</Version>
        <FileVersion>1.0.0.0</FileVersion>
        <AssemblyVersion>1.0.0.0</AssemblyVersion>
        <Company>YourCompany</Company>
        <Product>YourProduct</Product>
      </PropertyGroup>
    </Project>