1. 程式人生 > 實用技巧 >Advanced Installer15.2 安裝包的製作案例--------打包Winform安裝程式以及建立桌面快捷方式和選單程式以及設定解除安裝和開機啟動以及安裝必備元件

Advanced Installer15.2 安裝包的製作案例--------打包Winform安裝程式以及建立桌面快捷方式和選單程式以及設定解除安裝和開機啟動以及安裝必備元件

1、新建解決方案,命名QingLong

2、新增Windows窗體應用(.NET Framework),命名TestWinForm.該窗體僅用於測試使打包使用,實際專案中可根據自己的需要新建對應的專案

3、新增類庫(.NET Framework),命名TestCustomInstallerLib,該專案用於打包時的自定義操作使用的類庫,該專案中包括一個自定義安裝程式類,用於安裝使用

4、TestCustomInstallerLib專案新增CustomInstaller

CustomInstaller中主要用到了安裝程式類System.Configuration.Install.Installer

官方文件,這是 .NET Framework 中所有自定義安裝程式的基類,繼承該類就可以在計算機上安裝應用程式的元件

通過Installers屬性,安裝程式包含作為子級的其他安裝程式的集合。執行安裝程式時,它會迴圈除錯其子級,並呼叫Install、Commit、Rollback或Uninstall。有關Installers集合中物件的示例,請參閱EventLogInstaller。

Context屬性包含有關安裝的資訊。例如,有關安裝的日誌檔案的位置的資訊,儲存Uninstall方法所需的資訊的檔案的位置,以及執行安裝可執行檔案時輸入的命令列。有關安裝可執行檔案的示例,請參閱installutil.exe (安裝程式工具)。

Install、Commit、Rollback和Uninstall方法並不總是在Installer的同一例項上呼叫。例如,你可以使用Installer來安裝和提交應用程式,然後釋放對該Installer的引用。

稍後,解除安裝應用程式會建立對Installer的新引用,這意味著Uninstall方法在Installer的其他例項上呼叫。出於此原因,請不要在安裝程式中儲存計算機的狀態。

相反,請使用跨呼叫保留並傳入Install、Commit、Rollback和Uninstall方法的IDictionary。

步驟如下:

TestCustomInstallerLib專案刪除Class1.cs檔案,然後新增新建一個安裝程式類,命名CustomInstaller

CustomInstaller.cs內容如下:

using Microsoft.Win32;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration.Install;
using System.Diagnostics;
using System.IO;
using System.Reflection;

namespace TestCustomInstallerLib
{
    [RunInstaller(true)]
    public partial class CustomInstaller : System.Configuration.Install.Installer
    {
        public CustomInstaller()
        {
            InitializeComponent();
        }

        protected override void OnAfterInstall(IDictionary savedState)
        {
            //獲取自定義安裝使用者介面上的埠值
            string portId = this.Context.Parameters["PortId"];
            string path = this.Context.Parameters["targetdir"];
            Logger($"OnAfterInstall新增 targetdir savedState:{path}");
            //開機啟動 1、硬編碼,2設定Setup Projects的登錄檔編輯器
            //1、安裝完成以後可以把硬編碼把該軟體寫到登錄檔中,這樣可以設定開機啟動,
            //2、當然還有另外一種開機啟動的方式是可以使用Setup Projects的登錄檔編輯器的來進行註冊
            savedState.Add("savedState", path);
            Assembly asm = Assembly.GetExecutingAssembly();
            string asmpath = asm.Location.Remove(asm.Location.LastIndexOf("\\")) + "\\";
            Logger($"OnAfterInstall asmpath:{asmpath}");
           
            SetAutoStart(true, "TestWinForm", asmpath + "TestWinForm.exe");
            //Process.Start(asmpath + "\\ServiceXStart.exe");//要執行的程式
            Process.Start(asmpath + "TestWinForm.exe");//要執行的程式
            //Process.Start(path + "MyTestWinFrm.exe");//要執行的程式
            base.OnAfterInstall(savedState);
        }

        protected override void OnBeforeUninstall(IDictionary savedState)
        {
            Trace.Listeners.Clear();
            Trace.AutoFlush = true;
            Trace.Listeners.Add(new TextWriterTraceListener(@"F:\Test\OnBeforeUninstall.txt"));

            Trace.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} OnBeforeUninstall");
            //Process[] processes = Process.GetProcessesByName("ServiceXStart");
            Process[] processes = Process.GetProcessesByName("TestWinForm");
            foreach (Process item in processes)
            {
                Trace.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} OnBeforeUninstall 程序:" + item);
                item.Kill();
                item.WaitForExit();
                item.Close();
            }
            base.OnBeforeUninstall(savedState);
        }

        protected override void OnAfterUninstall(IDictionary savedState)
        {
            Trace.Listeners.Clear();
            Trace.AutoFlush = true;
            Trace.Listeners.Add(new TextWriterTraceListener(@"F:\Test\123.txt"));

            Trace.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} OnBeforeUninstall");
            try
            {
                Assembly asm = Assembly.GetExecutingAssembly();
                string asmpath = asm.Location.Remove(asm.Location.LastIndexOf("\\")) + "\\";
                Logger($"AfterUninstall123456 asmpath:{asmpath}");
                if (Directory.Exists(asmpath))
                {
                    RemoveSubDirectory(new DirectoryInfo(asmpath));
                    Trace.WriteLine($"刪除目錄:{asmpath} 成功");
                }
                Trace.WriteLine("AfterUninstall  完成了。。。。");
            }
            catch (Exception ex)
            {
                Trace.WriteLine($"AfterUninstall刪除異常:原因{ex}");
            }
            base.OnAfterUninstall(savedState);
        }

        /// <summary>
        /// 解除安裝完成後刪除多餘的檔案
        /// </summary>
        /// <param name="uper"></param>
        private static void RemoveSubDirectory(DirectoryInfo directory)
        {
            Logger($"目錄資訊 directory:{directory}");
            foreach (FileInfo subFile in directory.GetFiles())
            {
                if (subFile.Exists)
                {
                    Trace.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} Exists 刪除檔名稱666: {subFile.FullName},{subFile.Attributes},{subFile.FullName}");
                }
                else
                {
                    Trace.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} no Exists 刪除檔名稱000: {subFile.FullName}");
                    //subFile.Delete();
                }
            }
            foreach (DirectoryInfo sub in directory.GetDirectories())
            {
                if (sub.GetFiles().Length > 0 || sub.GetDirectories().Length > 0)
                    RemoveSubDirectory(sub);
                sub.Delete(true);
                Logger($"要刪除的目錄資訊 sub:{sub}");
            }
            Logger($"目錄成功");
        }

        /// <summary>
        /// 將應用程式設為或不設為開機啟動
        /// </summary>
        /// <param name="onOff">自啟開關</param>
        /// <param name="appName">應用程式名</param>
        /// <param name="appPath">應用程式完全路徑</param>
        public static bool SetAutoStart(bool onOff, string appName, string appPath)
        {
            Logger($"登錄檔設定的開機啟動項:{onOff},{appName},{appPath}");
            return true;
            #region MyRegion
            //bool isOk = true;
            ////如果從沒有設為開機啟動設定到要設為開機啟動
            //if (!IsExistKey(appName) && onOff)
            //{
            //    Logger($"------設定登錄檔自動啟動----不存在開機啟動項,即將新增開機啟動項------");
            //    isOk = SelfRunning(onOff, appName, @appPath);
            //}
            ////如果從設為開機啟動設定到不要設為開機啟動
            //else if (IsExistKey(appName) && !onOff)
            //{
            //    Logger($"------設定登錄檔自動啟動----存在開機啟動項,但未開啟,即將開啟啟動項------");
            //    isOk = SelfRunning(onOff, appName, @appPath);
            //}
            //return isOk;
            #endregion
        }

        /// <summary>
        /// 判斷註冊鍵值對是否存在,即是否處於開機啟動狀態
        /// </summary>
        /// <param name="keyName">鍵值名</param>
        /// <returns></returns>
        private static bool IsExistKey(string keyName)
        {
            try
            {
                bool _exist = false;
                RegistryKey local = Registry.LocalMachine;
                RegistryKey runs = local.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", true);
                if (runs == null)
                {
                    RegistryKey key2 = local.CreateSubKey("SOFTWARE");
                    RegistryKey key3 = key2.CreateSubKey("Microsoft");
                    RegistryKey key4 = key3.CreateSubKey("Windows");
                    RegistryKey key5 = key4.CreateSubKey("CurrentVersion");
                    RegistryKey key6 = key5.CreateSubKey("Run");
                    runs = key6;
                }
                string[] runsName = runs.GetValueNames();
                foreach (string strName in runsName)
                {
                    if (strName.ToUpper() == keyName.ToUpper())
                    {
                        _exist = true;
                        return _exist;
                    }
                }
                return _exist;

            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// 寫入或刪除登錄檔鍵值對,即設為開機啟動或開機不啟動
        /// </summary>
        /// <param name="isStart">是否開機啟動</param>
        /// <param name="exeName">應用程式名</param>
        /// <param name="path">應用程式路徑帶程式名</param>
        /// <returns></returns>
        private static bool SelfRunning(bool isStart, string exeName, string path)
        {
            try
            {
                RegistryKey local = Registry.LocalMachine;
                RegistryKey key = local.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", true);
                if (key == null)
                {
                    local.CreateSubKey("SOFTWARE//Microsoft//Windows//CurrentVersion//Run");
                }
                //若開機自啟動則新增鍵值對
                if (isStart)
                {
                    key.SetValue(exeName, path);
                    key.Close();
                    Logger("------設定登錄檔自動啟動----開啟----成功------");
                }
                else//否則刪除鍵值對
                {
                    string[] keyNames = key.GetValueNames();
                    foreach (string keyName in keyNames)
                    {
                        if (keyName.ToUpper() == exeName.ToUpper())
                        {
                            key.DeleteValue(exeName);
                            key.Close();
                            Logger("------設定登錄檔自動啟動----關閉----成功------");
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Logger($"------設定登錄檔自動啟動----異常----原因{ex}------");
                return false;
            }
            return true;
        }

        /// <summary>
        /// 記錄日誌
        /// </summary>
        /// <param name="message"></param>
        private static void Logger(string message)
        {
            try
            {
                string fileName = @"F:\Test\log.txt";
                if (!File.Exists(fileName))
                {
                    File.Create(fileName);
                    Trace.Listeners.Clear();
                    Trace.AutoFlush = true;
                    Trace.Listeners.Add(new TextWriterTraceListener(fileName));
                }
                Trace.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss}" + message);
            }
            catch (Exception ex)
            {
                Trace.Listeners.Clear();
                Trace.AutoFlush = true;
                Trace.Listeners.Add(new TextWriterTraceListener(@"F:\Test\log.txt"));
                Trace.WriteLine($"Logger出錯,錯誤資訊:{ex}");
            }
        }
    }
}

5、Advanced Installer15.2 製作安裝包,本文使用的是Advanced Installer15.2版本

開啟Advanced Installer,點選左上角的 檔案 -新建,建立新的專案,通用模板-安裝專案 選擇 企業版 語言選擇 中文簡體,點選 建立專案

選擇 左側的 資源-檔案和資料夾,可以看到需要打包的檔案以及資料夾資訊

建立桌面快捷方式和選單程式

選擇 桌面 新增桌面快捷方式和應用程式快捷資料夾圖示

開機啟動

新增開機啟動,2種方式,1、種如下操作,複製新增桌面快捷方式 貼上到開始選單 啟動項中,2、選擇資源 登錄檔,新增對應的資訊,如下

登錄檔新增開機啟動

安裝必備元件

安裝必備元件,選擇 需求 執行環境預定義的執行環境,勾選時會自動下載

Advanced Installer支援直接在安裝包中包含.net Framework,這樣在安裝軟體的時候會檢測你的PC上面是否安裝了對應的framework的版本,如果沒有,則會自動給你安裝Framework。並且支援離線和線上兩種方式的安裝如下圖

這裡勾選的系統版本即為安裝必須的系統版本,除此之外,Advanced Installer還支援安裝的時候檢測機器上面的瀏覽器版本、sql server資料庫版本、IIS版本等選項。如果當前安裝機器上面的版本低於要求的配置,就會提示錯誤。

需要注意的是離線和線上,各有好處,離線安裝方便,但是安裝檔案可能會很大,線上安裝需要安裝時有網路否則無法安裝,具體使用,自己根據需要來選擇。

自定義安裝配置介面------使用者介面

選擇 使用者介面 對話方塊 msi 程式包 首次安裝 選擇右鍵新建對話方塊或者新增對話方塊,選擇新建對話方塊就可以了

新建對話方塊

EDIT_1_PROP 這個屬性名稱的文字框的值,在自定義的intsaller安裝類中會使用,其他的文字框的名稱也可以修改成自己需要的名稱,但是必須要和下文中要用的名稱一致才行,否則在installer自定義安裝類中就獲取不到了安裝介面設定的值了

自定義安裝配置介面------自定義行為

選擇 自定義行為 自定義操作 新增自定義操作 .NET Iintsaller Class action 選擇自定義的intsaller類庫,該類庫必須繼承System.Configuration.Install.Installer實現,本文使用的是TestCustomInstallerLib,

獲取使用者自定義安裝介面時,輸入的資訊,這裡只舉例獲取prop的,其他的都是一樣的,prop是給自定義的安裝程式installer中使用的,可以通過Context.Parameters["prop"]來獲取,通過EDIT_1_PROP這個屬性名稱獲取使用者介面輸入的文字框的值,

這樣通過prop與[EDIT_1_PROP]就可以在Context.Parameters["prop"]來獲取介面輸入的值了

注意,如果要在C#裡面讀取安裝檔案的根目錄

1、C#裡面讀取安裝檔案的根目錄

Assembly asm = Assembly.GetExecutingAssembly();
string asmpath = asm.Location.Remove(asm.Location.LastIndexOf("\\")) + "\\";

2、定義一個變數來接受安裝檔案的根目錄,C#裡面讀取安裝檔案的根目錄

string path = this.Context.Parameters["targetdir"].ToString();

另外使用者介面還可以新增其他的對話方塊,例如資料庫連線測試,在安裝的過程中,元件會自動檢測我們系統的sql Server例項,我們輸入使用者名稱密碼之後,點選“Test Sql Connection”按鈕,可以檢測是否連線成功。

修改TestCustomInstallerLib,CustomInstaller中的

//獲取自定義安裝使用者介面上的埠值
string portId = this.Context.Parameters["prop"];
string path = this.Context.Parameters["targetdir"];

點選左上角的,全部重新構建,即可。

補充:使用者介面的控制元件都可以修改的,可以根據需要修改成自己的,包安裝的主題背景圖片等,如下: