开发者

How to include version number in VS Setup Project output filename

Is there a way to include the version number as part of the output.msi filename in a VS2008 Setup Project?

I'd like for example an output file called: "myinstaller-1.0.13.msi" w开发者_运维百科here the version part is automatically set based on the version number I have put in the deployment project properties.


I didn't want to use the .exe method above and had a little time spare so I started diggind around. I'm using VS 2008 on Windows 7 64 bit. When I have a Setup project, lets call it MySetup all the details of the project can be found in the file $(ProjectDir)MySetup.vdproj.

The product version will be found on a single line in that file in the form

ProductVersion="8:1.0.0"

Now, there IS a post-build event on a setup project. If you select a setup project and hit F4 you get a completely different set of properties to when you right-click and select properties. After hitting F4 you'll see that one of the is PostBuildEvent. Again assuming that the setup project is called MySetup the following will set the name of the .msi to include the date and the version

set datevar=%DATE:~6,4%%DATE:~3,2%%DATE:~0,2%
findstr /v PostBuildEvent $(ProjectDir)MySetup.vdproj | findstr ProductVersion >$(ProjectDir)version.txt
set /p var=<$(ProjectDir)version.txt
set var=%var:"=%
set var=%var: =%
set var=%var:.=_%
for /f "tokens=1,2 delims=:" %%i in ("%var%") do @echo %%j >$(ProjectDir)version.txt
set /p realvar=<$(ProjectDir)version.txt
rename "$(ProjectDir)$(Configuration)\MySetup.msi" "MySetup-%datevar%-%realvar%.msi"

I'll take you through the above.

datevar is the current date in the form YYYYMMDD.

The findstr line goes through MySetup.vdproj, removes any line with PostBuildEvent in, then returns the single line left with productVersion in, and outputs it to a file. We then remove the quotes, spaces, turn dots into underscores.

The for line splits the remaining string on colon, and takes the second part, and outputs it to a file again.

We then set realvar to the value left in the file, and rename MySetup.msi to include the date and version.

So, given the ProductVersion above, if it was 27th March 2012 the file would be renamed to

MySetup-20120327-1_0_0.msi

Clearly using this method you could grab ANY of the variables in the vdproj file and include them in your output file name and we don't have to build any extra .exe programs to do it.

HTH


Same concept as Jim Grimmett's answer, but with less dependencies:

FOR /F "tokens=2 delims== " %%V IN ('FINDSTR /B /R /C:" *\"ProductVersion\"" "$(ProjectDir)MySetupProjectName.vdproj"') DO FOR %%I IN ("$(BuiltOuputPath)") DO REN "$(BuiltOuputPath)" "%%~nI-%%~nxV%%~xI"

Some points of note:

MySetupProjectName.vdproj should be changed to the name of your project file. Forgetting to change this results in a build error: 'PostBuildEvent' failed with error code '1' and the Output window shows which file FINDSTR could not open.

Step by step description:

FINDSTR /B /R /C:" *\"ProductVersion\"" $(ProjectDir)MySetupProjectName.vdproj

  • This finds the "ProductVersion" = "8:x.y.z.etc" line from the project file.

FOR /F "tokens=2 delims== " %%V IN (...) DO ... %%~nxV ...

  • This is used to parse out the x.y.z.etc part from the above result.

$(BuiltOuputPath)

  • This is the original output path, as per what it says in Post-build Event Command Line's "Macros".

FOR %%I IN (...) DO ... %%~nI-%%~nxV%%~xI

  • This is used to convert the string foo.msi to foo-x.y.z.etc.msi.

REN "$(BuiltOuputPath)" ...

  • This just renames the output path to the new name.

FOR ... DO FOR .. DO REN ...

  • It's written on one line like this so that an error along way cleanly breaks the build.


Not sure whether you still require this or not but wanted answer this as we did similar kind of operation in the postbuild event. As far as the research I did this is not possible to set the file name as you want internally through setup process.

You can do this in other way by naming the output file through an external application in post build event.

Here is what you can do:

In the post build event ->

[MsiRenamerAppPath]\MsiRenamer.exe "$(BuildOutputPath)"

Create an application which will rename the msi file with the version number from the deployment project. Following is the code used for the application. This should fulfill your requirement I guess.

Getting msi properties code is used from alteridem article

class MsiRenamer
  {
    static void Main(string[] args)
    {
      string inputFile;
      string productName = "[ProductName]";

      if (args.Length == 0)
      {
        Console.WriteLine("Enter MSI file:");
        inputFile = Console.ReadLine();
      }
      else
      {
        inputFile = args[0];
      }

      try
      {
        string version;

        if (inputFile.EndsWith(".msi", StringComparison.OrdinalIgnoreCase))
        {
          // Read the MSI property
          version = GetMsiProperty(inputFile, "ProductVersion");
          productName = GetMsiProperty(inputFile, "ProductName");
        }
        else
        {
          return;
        }
        // Edit: MarkLakata: .msi extension is added back to filename
        File.Copy(inputFile, string.Format("{0} {1}.msi", productName, version));
        File.Delete(inputFile);
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message);
      }
    }

    static string GetMsiProperty(string msiFile, string property)
    {
      string retVal = string.Empty;

      // Create an Installer instance  
      Type classType = Type.GetTypeFromProgID("WindowsInstaller.Installer");
      Object installerObj = Activator.CreateInstance(classType);
      Installer installer = installerObj as Installer;

      // Open the msi file for reading  
      // 0 - Read, 1 - Read/Write  
      Database database = installer.OpenDatabase(msiFile, 0);

      // Fetch the requested property  
      string sql = String.Format(
          "SELECT Value FROM Property WHERE Property='{0}'", property);
      View view = database.OpenView(sql);
      view.Execute(null);

      // Read in the fetched record  
      Record record = view.Fetch();
      if (record != null)
      {
        retVal = record.get_StringData(1);
        System.Runtime.InteropServices.Marshal.FinalReleaseComObject(record);
      }
      view.Close();
      System.Runtime.InteropServices.Marshal.FinalReleaseComObject(view);
      System.Runtime.InteropServices.Marshal.FinalReleaseComObject(database);

      return retVal;
    }
  }


If you use a WIX project (as opposed to a VS Setup & Deployment project) then this article explains exactly how to achieve what you are after.


I did it with 2 lines in powershell.

$versionText=(Get-Item MyProgram.exe).VersionInfo.FileVersion
(Get-Content MySetup.vdproj.template).replace('${VERSION}', $($versionText)) | Set-Content MySetup.vdproj

Rename your existing .vdproj to be MySetup.vdproj.template and insert "${VERSION}" wherever you want to insert the version of your primary exe file.

VS will then detect the change in the vdproj file and ask you if you want to reload it.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using WindowsInstaller;


// cscript //nologo "$(ProjectDir)WiRunSql.vbs" "$(BuiltOuputPath)" "UPDATE `Property` SET `Property`.`Value`='4.0.0.1' WHERE `Property`='ProductVersion'"
// "SELECT `Property`.`ProductVersion` FROM `Property` WHERE `Property`.`Property` = 'ProductVersion'"

/* 
 * That's a .NET wrapper generated by tlbimp.exe, wrapping the ActiveX component c:\windows\system32\msi.dll.  
 * You can let the IDE make one for you with Project + Add Reference, COM tab, 
 * select "Microsoft Windows Installer Object Library". 
 */
namespace PostBuildEventModifyMSI
{
    /* Post build event fro Rename MSI file.
     * $(SolutionDir)PostBuildEventModifyMSI\bin\Debug\PostBuildEventModifyMSI.exe "$(SolutionDir)TestWebApplicationSetup\Debug\TestWebApplicationSetup.msi"
     */

    [System.Runtime.InteropServices.ComImport(), System.Runtime.InteropServices.Guid("000C1090-0000-0000-C000-000000000046")]
    class Installer { }
    class Program
    {
        static void Main(string[] args)
        {
            #region New code.

            string msiFilePath = string.Empty;
            if (args.Length == 0)
            {
                Console.WriteLine("Enter MSI file complete path:");
                msiFilePath = Console.ReadLine();
            }
            else
            {
                Console.WriteLine("Argument Received args[0]: " + args[0]);
                msiFilePath = args[0];
            }

            StringBuilder sb = new StringBuilder();
            string[] words = msiFilePath.Split('\\');
            foreach (string word in words)
            {
                sb.Append(word + '\\');

                if (word.Contains("Debug"))
                {
                    break;
                }
                else
                {

                }
            }

            // Open a view on the Property table for the Label property 
            //UPDATE Property set Value = '2.06.36' where Property = 'ProductVersion'
            Program p = new Program();
            string version = p.GetMsiVersionProperty(msiFilePath, "ProductVersion");
            string productName = p.GetMsiVersionProperty(msiFilePath, "ProductName");

            string newMSIpath = sb.ToString() + string.Format("{0}_{1}.msi", productName, version);
            Console.WriteLine("Original MSI File Path: " + msiFilePath);
            Console.WriteLine("New MSI File Path: " + newMSIpath);


            System.IO.File.Move(msiFilePath, newMSIpath);

            #endregion




            //Console.Read();
        }

        private string GetMsiVersionProperty(string msiFilePath, string property)
        {
            string retVal = string.Empty;

            // Create an Installer instance  
            WindowsInstaller.Installer installer = (WindowsInstaller.Installer) new Installer();

            // Open the msi file for reading  
            // 0 - Read, 1 - Read/Write  
            Database db = installer.OpenDatabase(msiFilePath, WindowsInstaller.MsiOpenDatabaseMode.msiOpenDatabaseModeReadOnly); //// Open the MSI database in the input file 

            // Fetch the requested property  
            string sql = String.Format(
                "SELECT Value FROM Property WHERE Property='{0}'", property);
            View view = db.OpenView(sql);
            //View vw = db.OpenView(@"SELECT `Value` FROM `Property` WHERE `Property` = 'ProductVersion'");
            view.Execute(null);

            // Read in the fetched record  
            Record record = view.Fetch();
            if (record != null)
            {
                retVal = record.get_StringData(1);
                System.Runtime.InteropServices.Marshal.FinalReleaseComObject(record);
            }
            view.Close();

            System.Runtime.InteropServices.Marshal.FinalReleaseComObject(view);
            System.Runtime.InteropServices.Marshal.FinalReleaseComObject(db);

            return retVal;
        }

    }
}


I made some modifications to the @JPreddy submission to have a input and output folder for the rename and also some logging.

Create and new console project for/with .net framework 4.8 Download it here

Here is how to install and use. Go under you installer project and click on "PostBuildEvents". In the parameter you will need to call the .exe and then add the variables/macros as follows:

EXE INPUTFOLDER OUTPUTFOLDER

C:\temp\MsiRenamer.exe $(BuiltOuputPath) $(ProjectDir)$(Configuration)\

Here is the app.config code for the application

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
    </configSections>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
    </startup>
    <log4net>
        <appender name="MyConsole" type="log4net.Appender.ConsoleAppender">
            <file value="${ProgramData}\MsiRenamer\Log\MsiRenamer_" />
            <datepattern value="yyyyMMdd'_${USERNAME}.log'" />
            <threshold value="ALL" />
            <appendToFile value="true" />
            <RollingStyle value="size,date" />
            <maximumFileSize value="1MB" />
            <maxSizeRollBackups value="10" />
            <layout type="log4net.Layout.PatternLayout">
                <!-- Pattern to output the caller's file name and line number -->
                <conversionPattern value="%date [%thread] %-5level %logger [%identity] | %message%newline" />
            </layout>
        </appender>
        <appender name="MyColoredConsoleAppender" type="log4net.Appender.ColoredConsoleAppender">
            <mapping>
                <level value="ERROR" />
                <foreColor value="Red, highintensity" />
            </mapping>
            <mapping>
                <level value="WARN" />
                <foreColor value="Yellow, highintensity" />
            </mapping>
            <mapping>
                <level value="ALL" />
                <foreColor value="Green, highintensity" />
            </mapping>
            <layout type="log4net.Layout.PatternLayout">
                <conversionPattern value="*%-10level %-30date %message [%logger] [%thread] %newline" />
            </layout>
        </appender>
        <appender name="MyFileAppender" type="log4net.Appender.rollingFileAppender">
            <file value="${ProgramData}\MsiRenamer\Log\MsiRenamer_" />
            <datepattern value="yyyyMMdd'_${USERNAME}.log'" />
            <threshold value="ALL" />
            <appendToFile value="true" />
            <maximumFileSize value="1MB" />
            <MaxSizeRollBackups value="10" />
            <RollingStyle value="size,date" />
            <staticLogFileName value="false" />
            <preserveLogFileNameExtension value="true" />
            <layout type="log4net.Layout.PatternLayout">
                <conversionPattern value="%date [%thread] %-5level %logger [%identity] | %message%newline" />
            </layout>
        </appender>
        <appender name="MyMemoryAppender" type="log4net.Appender.MemoryAppender">
        </appender>
        <appender name="RichTextBoxAppender" type="Sample.RichTextBoxAppender, MyFFLBookAPIImport">
            <formName value="FrmSmple" />
            <textBoxName value="RtbOutput" />
            <layout type="log4net.Layout.PatternLayout">
                <conversionPattern value="%date %-5level - %message%newline" />
            </layout>
        </appender>
        <root>
            <level value="ALL" />
            <appender-ref ref="MyFileAppender" />
            <appender-ref ref="MyConsole" />
            <appender-ref ref="MyColoredConsoleAppender" />
            <appender-ref ref="MyMemoryAppender" />
            <appender-ref ref="RichTextBoxAppender" />
        </root>
    </log4net>
</configuration>

Here is the C# code for the application:

using log4net;
using System;
using System.IO;
using WindowsInstaller;

namespace MsiRenamer
{

    internal class MsiRenamer
    {
        private static readonly ILog _log = LogManager.GetLogger(typeof(MsiRenamer));

        static void Main(string[] args)

        {
            log4net.Config.XmlConfigurator.Configure();
            string inputFile;
            string outputFolder;
            string outputFileName;
            string productName = "[ProductName]";

            if (args.Length == 0)
            {
                Console.WriteLine("Enter MSI Input PathFileName:");
                inputFile = Console.ReadLine();
                _log.Info("InputFile: " + inputFile);
                Console.WriteLine("Enter MSI Output Folder:");
                outputFolder = Console.ReadLine();
                _log.Info("Output Folder: " + outputFolder);

            }
            else
            {
                inputFile = args[0];
                _log.Info("InputFile: " + inputFile);
                outputFolder = args[1];
                _log.Info("Output Folder: " + outputFolder);
            }

            try
            {
                string version;

                if (inputFile.EndsWith(".msi", StringComparison.OrdinalIgnoreCase))
                {
                    // Read the MSI property
                    version = GetMsiProperty(inputFile, "ProductVersion");
                    _log.Info("Version: " + version);
                    productName = GetMsiProperty(inputFile, "ProductName");
                    _log.Info("ProductName: " + productName);
                }
                else
                {
                    return;
                }
      
                outputFileName = outputFolder + string.Format("{0}{1}.msi", productName, version);
                if (File.Exists(outputFileName))
                {
                    File.Delete(outputFileName);
                    _log.Info("Delete existing file :" + outputFileName);
                }

                _log.Info("OutputFileName: " + outputFileName);
                File.Copy(inputFile, outputFileName);
                _log.Info("Copied file from : " + inputFile + " to " + outputFileName);
                //File.Copy(inputFile, string.Format("{0} {1}.msi", productName, version));
                File.Delete(inputFile);
                _log.Info("Deleting original file :" + inputFile);
            }
            catch (Exception ex)
            {
                _log.Error(ex.Message);
                Console.WriteLine(ex.Message);
            }
        }

        static string GetMsiProperty(string msiFile, string property)
        {
            string retVal = string.Empty;

            // Create an Installer instance  
            Type classType = Type.GetTypeFromProgID("WindowsInstaller.Installer");
            Object installerObj = Activator.CreateInstance(classType);
            Installer installer = installerObj as Installer;

            // Open the msi file for reading  
            // 0 - Read, 1 - Read/Write  
            Database database = installer.OpenDatabase(msiFile, 0);

            // Fetch the requested property  
            string sql = String.Format(
                "SELECT Value FROM Property WHERE Property='{0}'", property);
            View view = database.OpenView(sql);
            view.Execute(null);

            // Read in the fetched record  
            Record record = view.Fetch();
            if (record != null)
            {
                retVal = record.get_StringData(1);
                System.Runtime.InteropServices.Marshal.FinalReleaseComObject(record);
            }
            view.Close();
            System.Runtime.InteropServices.Marshal.FinalReleaseComObject(view);
            System.Runtime.InteropServices.Marshal.FinalReleaseComObject(database);

            return retVal;
        }
    }
}

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜