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;

namespace MyCustomLib
{
    [RunInstaller(true)]
    public partial class CustomeInstaller : Installer
    {

        public CustomeInstaller()
        {
            InitializeComponent();
            Logger($"日志记录开始:{DateTime.Now:yyyy-MM-dd HH:mm:ss}");
        }

        /// <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($"targetdir:{path}");
            Logger($"OnAfterInstall添加 savedState:{path}");
            //开机启动 1、硬编码,2设置Setup Projects的注册表编辑器
            //1、安装完成以后可以把硬编码把该软件写到注册表中,这样可以设置开机启动,
            //2、当然还有另外一种开机启动的方式是可以使用Setup Projects的注册表编辑器的来进行注册
            savedState.Add("savedState", path);
            Logger($"OnAfterInstall添加成功");
            SetAutoStart(true, "MyTestWinFrm", path + "MyTestWinFrm.exe");
            base.OnAfterInstall(savedState);
        }

        /// <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。
            base.OnAfterUninstall(savedState);

            var savedStateValue = savedState.Contains("savedState") ? savedState["savedState"] : "未获取到安装的目录";
            Logger($"OnAfterUninstall从OnAfterInstall获取 savedState,值为:{savedStateValue}");
            string path = this.Context.Parameters["targetdir"];
            Logger($"targetdir:{path}");
            Logger($"开始删除目录:{path}");
            if (Directory.Exists(path))
            {
                RemoveSubDirectory(new DirectoryInfo(path));
                Logger($"删除目录:{path} 成功");
            }
            //Logger("OnAfterUninstall 进入。。。。");
            Logger("OnAfterUninstall  完成了。。。。");
        }

        /// <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安装程序自定义安装以及设置开机启动和卸载时删除多余的文件的整个过程,

上述步骤完成以后可以重新生成安装程序,进行安装使用。

猜你喜欢

转载自blog.csdn.net/LongtengGensSupreme/article/details/107102442
今日推荐