Microsoft Visual Studio Installer Projects 安裝包的制作案例--------打包Winform安裝程序自定義安裝以及設置開機啟動和卸載時刪除多余的文件


前兩篇介紹了關於Microsoft Visual Studio Installer Projects 安裝包的制作案例的過程以及開機啟動和卸載等功能,具體可以參考

本文主要介紹篇使用Microsoft Visual Studio 2019(以下簡稱VS) 自定義安裝彈出以及開機啟動和卸載刪除多余的文件的功能,

主要用到了安裝程序類 System.Configuration.Install.Installer官方文檔,這是 .NET Framework 中所有自定義安裝程序的基類,繼承該類就可以在計算機上安裝應用程序的組件

通過 Installers 屬性,安裝程序包含作為子級的其他安裝程序的集合。 執行安裝程序時,它會循環調試其子級,並調用 InstallCommitRollback或 Uninstall。 有關 Installers 集合中對象的示例,請參閱 EventLogInstaller

Context 屬性包含有關安裝的信息。 例如,有關安裝的日志文件的位置的信息,保存 Uninstall 方法所需的信息的文件的位置,以及運行安裝可執行文件時輸入的命令行。 有關安裝可執行文件的示例,請參閱installutil.exe (安裝程序工具)

InstallCommitRollback和 Uninstall 方法並不總是在 Installer的同一實例上調用。 例如,你可以使用 Installer 來安裝和提交應用程序,然后釋放對該 Installer的引用。 

稍后,卸載應用程序會創建對 Installer的新引用,這意味着 Uninstall 方法在 Installer的其他實例上調用。 出於此原因,請不要在安裝程序中保存計算機的狀態。

相反,請使用跨調用保留並傳入 InstallCommitRollback和 Uninstall 方法的 IDictionary

 

Microsoft Visual Studio 2019(以下簡稱VS) 自定義安裝彈出以及開機啟動和卸載刪除多余的文件的功能,具體步驟如下:

1、新建解決方案QingLong,添加需要打包的項目MyTestWinFrm(Windows 窗體應用(.NET Framwork) 程序,本文是MyTestWinFrm生成的文件,用於打包),添加Setup Projects 打包程序Setup

2、創建自定義安裝程序,

    新建 類庫(.NET Framwork)  項目命名MyCustomLib,刪除默認的Class1.cs文件,新建 安裝程序類,命名CustomeInstaller

 

 

 

自定義安裝程序類CustomeInstaller中添加如下內容,添加安裝完成以后,添加注冊表,設置開機啟動以及卸載之后刪除多余的文件,當然其他的安裝,卸載,提交,回滾等也可以使用override進行重寫,以進行相應的處理

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 MyCustomLib
{
    [RunInstaller(true)]
    public partial class CustomeInstaller : Installer
    {

        public CustomeInstaller()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 安裝完成之后的操作,可以保留安裝路徑到
        /// 使用跨調用保留並傳入 Install、Commit、Rollback和 Uninstall 方法的 IDictionary。
        /// IDictionary savedState
        /// </summary>
        /// <param name="savedState"></param>
        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, "MyTestWinFrm", asmpath + "MyTestWinFrm.exe");
            //Process.Start(asmpath + "\\ServiceXStart.exe");//要執行的程序
            Process.Start(asmpath + "MyTestWinFrm.exe");//要執行的程序
            //Process.Start(path + "MyTestWinFrm.exe");//要執行的程序
            base.OnAfterInstall(savedState);
        }

        protected override void OnBeforeUninstall(IDictionary savedState)
        {
            base.OnBeforeUninstall(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("MyTestWinFrm");
            foreach (Process item in processes)
            {
                Trace.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} OnBeforeUninstall 進程:" + item);
                item.Kill();
                item.WaitForExit();
                item.Close();
            }
        }

        /// <summary>
        /// 卸載軟件的時候刪除多余的文件
        /// </summary>
        /// <param name="savedState"></param>
        protected override void OnAfterUninstall(IDictionary savedState)
        {
            //Install、Commit、Rollback和 Uninstall 方法並不總是在 Installer的同一實例上調用。 
            //例如,你可以使用 Installer 來安裝和提交應用程序,然后釋放對該 Installer的引用。 
            //稍后,卸載應用程序會創建對 Installer的新引用,這意味着 Uninstall 方法在 Installer的其他實例上調用。 
            //出於此原因,請不要在安裝程序中保存計算機的狀態。 
            //相反,請使用跨調用保留並傳入 Install、Commit、Rollback和 Uninstall 方法的 IDictionary。

            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");
            var savedStateValue = savedState.Contains("savedState") ? savedState["savedState"] : "未獲取到安裝的目錄";
            Trace.WriteLine($"OnAfterUninstall從OnAfterInstall獲取 savedState,值為:{savedStateValue}");
            string path = this.Context.Parameters["targetdir"];
            Trace.WriteLine($"targetdir:{path}");
            Trace.WriteLine($"開始刪除目錄:{path}");
            if (Directory.Exists(path))
            {
                RemoveSubDirectory(new DirectoryInfo(path));
                Trace.WriteLine($"刪除目錄:{path} 成功");
            }
            //Logger("OnAfterUninstall 進入。。。。");
            Trace.WriteLine("OnAfterUninstall  完成了。。。。");
            base.OnAfterUninstall(savedState);
            savedState.Add("xizai", true);
        }

        protected override void OnCommitted(IDictionary savedState)
        {
            base.OnCommitted(savedState);
            Trace.Listeners.Clear();
            Trace.AutoFlush = true;
            Trace.Listeners.Add(new TextWriterTraceListener(@"F:\Test\OnCommitted.txt"));
            Trace.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} OnCommitted");
            if (savedState==null)
            {
                Trace.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} savedState={savedState}");
            }
            else
            {
                Trace.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} 111 1111111savedState={savedState}");
            }
            var isxizai = savedState.Contains("xizai") ? savedState["xizai"] : "";
            var savedStateValue = savedState.Contains("savedState") ? savedState["savedState"] : "未獲取到安裝的目錄";
            Trace.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} isxizai:{isxizai}");
            Trace.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} savedStateValue:{savedStateValue}");
        }

        /// <summary>
        /// 卸載完成后刪除多余的文件
        /// </summary>
        /// <param name="uper"></param>
        private static void RemoveSubDirectory(DirectoryInfo directory)
        {
            Logger($"目錄信息 directory:{directory}");
            //foreach (FileInfo subFile in uper.GetFiles())
            //{
            //    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}");
            }
        }

    }
}

 

 

3、設置打包程序Setup

選擇打包程序Setup,右鍵 View  文件系統

 

文件系統右鍵 Add 項目輸出,選擇自定義安裝程序類CustomeInstaller所在項目MyCustomLib

 

 

 

 

文件系統添加項目輸出以后,接着,選擇打包程序Setup,右鍵 View  自定義操作

 

 

 

選擇 Custom Actions 右鍵 選擇添加定義操作,彈窗中雙擊 Application Folder,選擇自定義安裝程序類CustomeInstaller所在項目MyCustomLib

注意:Custom Actions 中選擇添加定義操作的不一定是一個實例,可以安裝是一個實例,而卸載是另外一個實例,

           如果它們之間有數據需要共享或者傳遞的話可以使用跨調用保留並傳入 InstallCommitRollback和 Uninstall 方法的 IDictionary來存儲和傳遞

 

 

 

 

4、設置打包程序Setup的自定義安裝用戶界面彈窗

選擇打包程序Setup,右鍵 View  用戶界面

 

用戶界面 中 選擇 start 右鍵 添加對話框(A)

 

選擇提供可以被自定義以控制安裝過程的文本框,點擊ok 即可編輯這對話框的內容,可以在右下角的屬性窗口編輯。

 

 

 

 

右下角的屬性窗口編輯這對話框的內容,設置對話框標題 BannerText  內容標題 BodyText 屬性標簽 Edit1Label 屬性名 Edit1Property   屬性值 Edit1Value  屬性的可見性 Edit1Visible,為了測試本文,Edit1Property =PORTID ,寫了內容如下

 

 

 

 實際安裝過程中的效果

 

 

 

 

5、設置自定義安裝程序類CustomeInstaller所在項目MyCustomLib獲取自定義安裝用戶界面彈窗輸入的參數信息

選擇打包程序Setup,右鍵 View  自定義操作,選擇 Install 下的 主輸出 from MyCustomLib (Active),右鍵 屬性,

右小角的屬性窗口中設置  CustomActionData  屬性賦值   

  /PortId="[PORTID]" /targetdir="[TARGETDIR]\"

等號前的PortId是項目MyCustomLib中的安裝程序類CustomeInstaller類中的Context.Parameters["PortId"];里面的參數名,后面的PORTID是用戶界面Edit1Property 的PORTID屬性的值,

上面等式中除了/PortId="[PORTID]",還有一個/targetdir="[TARGETDIR]\",這targetdir是安裝目錄,他們之間使用了空格分隔,如果需要用其他的Edit屬性值的話,他們之間 要用空格隔開,如:

 /PortId="[PORTID]" /targetdir="[TARGETDIR]\"  /PortId1="[PORTID1]" /targetdir1="[TARGETDIR1]\"

 

 

 

 

 

 

 

 

項目MyCustomLib中的安裝程序類CustomeInstaller類中的OnAfterInstall方法中添加

//獲取自定義安裝用戶界面上的端口值
string portId = this.Context.Parameters["PortId"];

這樣就可以獲取自定義安裝用戶界面上的端口值了,可以在需要使用的地方進行使用了,當然其他的參數也可以在這么配置,方法原理是一樣的

 

 

 

 

 上述就是Microsoft Visual Studio Installer Projects 安裝包的制作案例--------打包Winform安裝程序自定義安裝以及設置開機啟動和卸載時刪除多余的文件的整個過程,

上述步驟完成以后可以重新生成安裝程序,進行安裝使用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM