基于NSIS的Unity发布后程序的自动制作安装包工具实现


前段时间,应公司要求,研究了一下把Unity发布后的程序自动做成安装包的实现方案。经过一段时间的研究,最终使用NSIS开源工具基本实现了这个需求。为了防止以后自己忘了这部分的东西,特此记录下实现思路,方便以后回顾。研究下来之后,我觉得可能很多流氓软件可能背后都有NSIS这个工具的功劳,哼哼~。

方案流程总结

安装NSIS
制作NSIS脚本模板
解析模板
Unity内C#自动生成NSIS脚本
使用生成的NSIS脚本制作安装包

整体思路: 基于NSIS的脚本模板,使用C#实现IPostprocessBuildWithReport接口的编辑器脚本,用Unity内部的Player Setting信息,以及打包好的程序信息,替换NSIS脚本模板中的对应信息,再自动执行makensis命令制作安装包。

安装NSIS

1.安装NSIS

NSIS (Nullsoft Scriptable Install System) 是一个专业开源的制作 windows 安装程序的工具。详细介绍就不多说了,愿意详细了解的,参考:NSIS百度百科 。下载链接:NSIS

安装完成之后把NSIS目录添加到系统环境变量,用于后续C#脚本调用,添加完成后可以使用makensis命令测试,如下图:
![](https://img-blog.csdnimg.cn/692cbf95cb704371858af8e51eb7d61b.png?x-oss-proc

2.安装HM NIS EDIT

HM NIS EDIT 是一个免费的NSIS脚本编辑器IDE。下载链接:HM NIS EDIT

制作NSIS脚本模板

1.在Unity内打包一个简易的程序程序,用于制作NSIS脚本,以及打包测试。
2.使用HM NIS EDIT的“新建脚本向导”,创建Unity打包后程序的的NSIS脚本,并保存下来,保存的脚本后缀为“nsi”。参考:方便快捷的客户端打包工具“HM NIS Edit”。因为这里没有授权文件,所以删除脚本中关于授权的内容,如下图:
在这里插入图片描述
3.使用生成的NSIS脚本测试NSIS制作安装包。命令为 makensis xxxx.nsi
在这里插入图片描述
4.运行制作好的安装包,安装程序,运行安装好的程序,再卸载程序,测试整个流程。
在这里插入图片描述

解析NSIS脚本

1.程序信息
在这里插入图片描述
这里包含程序的基本信息,程序名称、版本号、发布者(公司名称)、产品网址、注册表信息等。

2.安装包设置:
在这里插入图片描述
这里包含安装包的设置信息,包含图标设置、页面设置、语言设置等。

3.安装信息
在这里插入图片描述
这里包含程序的安装设置信息,包含安装包名称、安装路径、安装注册表设置,以及要安装的文件及文件夹等。

4.卸载信息
在这里插入图片描述
这里包含程序的卸载设置信息,包含卸载程序设置、卸载页面提示,以及要删除的文件及文件夹设置等。

分析完NSIS脚本之后,基本的用代码制作安装包的思路就出来了。具体如下:
1. 将Unity的PlayerSetting里面的程序名、发布者、版本号等信息替换模板中对应的信息;
2. 遍历Unity打包出来的程序目录,将里面的文件和文件夹路径写入到安装信息里面;
3. 类似第二步,改写卸载信息中对应的删除的文件和文件夹路径信息;
4. 保存生成好的nsi脚本,调用makensis命令生成安装包。

Unity发布程序的自动安装包制作实现

首先,因为nsi脚本中的很多信息都是用代码去添加填写,为了避免出错和冲突,方便后面代码去改写,可以将模板脚本中一些没用的信息清除掉,以及进行修改。具体如下:

  • 将所有用到产品名称的地方都用${PRODUCT_NAME}替换,你可以理解为这是表示产品名称的一个常量,涉及的部分有:在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 去掉Section “MainSection” SEC01 和下一个SectionEnd之间的安装文件信息,这些信息将在C#脚本里自动填写。
    在这里插入图片描述

  • 去掉Section Uninstall和 RMDir "$INSTDIR"之间的文件删除信息,这些信息将在C#脚本里自动填写。
    在这里插入图片描述
    修改完成后的nsi脚本如下:

; Script generated by the HM NIS Edit Script Wizard.

; HM NIS Edit Wizard helper defines
!define PRODUCT_NAME "NsisTool"
!define PRODUCT_VERSION "1.0"
!define PRODUCT_PUBLISHER "MyCompany, Inc."
!define PRODUCT_DIR_REGKEY "Software\Microsoft\Windows\CurrentVersion\App Paths\${PRODUCT_NAME}.exe"
!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
!define PRODUCT_UNINST_ROOT_KEY "HKLM"

; MUI 1.67 compatible ------
!include "MUI.nsh"

; MUI Settings
!define MUI_ABORTWARNING
!define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\orange-install.ico"
!define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\orange-uninstall.ico"

; Welcome page
!insertmacro MUI_PAGE_WELCOME
; Directory page
!insertmacro MUI_PAGE_DIRECTORY
; Instfiles page
!insertmacro MUI_PAGE_INSTFILES
; Finish page
!define MUI_FINISHPAGE_RUN "$INSTDIR\${PRODUCT_NAME}.exe"
!insertmacro MUI_PAGE_FINISH

; Uninstaller pages
!insertmacro MUI_UNPAGE_INSTFILES

; Language files
!insertmacro MUI_LANGUAGE "SimpChinese"

; MUI end ------

Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
OutFile "${PRODUCT_NAME}_Setup.exe"
InstallDir "$PROGRAMFILES\${PRODUCT_NAME}"
InstallDirRegKey HKLM "${PRODUCT_DIR_REGKEY}" ""
ShowInstDetails show
ShowUnInstDetails show

Section "MainSection" SEC01

SectionEnd

Section -AdditionalIcons
  CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\Uninstall.lnk" "$INSTDIR\uninst.exe"
SectionEnd

Section -Post
  WriteUninstaller "$INSTDIR\uninst.exe"
  WriteRegStr HKLM "${PRODUCT_DIR_REGKEY}" "" "$INSTDIR\${PRODUCT_NAME}.exe"
  WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayName" "$(^Name)"
  WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\uninst.exe"
  WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_NAME}.exe"
  WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayVersion" "${PRODUCT_VERSION}"
  WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
SectionEnd


Function un.onUninstSuccess
  HideWindow
  MessageBox MB_ICONINFORMATION|MB_OK "$(^Name) 已成功地从你的计算机移除。"
FunctionEnd

Function un.onInit
  MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 "你确实要完全移除 $(^Name) ,其及所有的组件?" IDYES +2
  Abort
FunctionEnd

Section Uninstall

  RMDir "$INSTDIR"

  DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}"
  DeleteRegKey HKLM "${PRODUCT_DIR_REGKEY}"
  SetAutoClose true
SectionEnd`

将这个脚本放到工程里的StreamingAssets目录下,我的命名为Template_NSIS.nsi。在这里插入图片描述

其次,因为这个脚本是要在Unity打包完成后编辑器自动运行的,所以脚本要放在Editor目录下,并且要实现IPostprocessBuildWithReport接口。
在这里插入图片描述
然后,上代码:

using UnityEngine;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using System.IO;
using System.Diagnostics;
using System.Text;
using System.Collections.Generic;

public class NsisTool : IPostprocessBuildWithReport
{
    //要查询nsi脚本中的关键key
    const string ProductText = "!define PRODUCT_NAME ";//产品名称
    const string VersionText = "!define PRODUCT_VERSION ";//版本号
    const string PublisherText = "!define PRODUCT_PUBLISHER ";//出版商
    const string OutFileText = "OutFile \"";//安装输出路径
    public int callbackOrder => 0;

    private static StringBuilder sb;
    private string path;
    private static int rootLength;
    private static List<string> outList = new List<string>();
    

    public void OnPostprocessBuild(BuildReport report)
    {
        UnityEngine.Debug.Log("MyCustomBuildProcessor.OnPostprocessBuild at path " + report.summary.outputPath);
        path = report.summary.outputPath;
        CreateNsisScr(path.Replace("/","\\"));
    }

    /// <summary>
    /// 床脚NSIS脚本
    /// </summary>
    /// <param name="path"></param>
    public static void CreateNsisScr(string path)
    {
        if (string.IsNullOrEmpty(path))
        {
            return;
        }

        string dir = Path.GetDirectoryName(path);
        rootLength = dir.Length;

        sb = new StringBuilder();
        string temScr =Path.Combine(Application.streamingAssetsPath,"Template_NSIS.nsi");

        List<string> textList = new List<string>(File.ReadAllLines(temScr, Encoding.UTF8));

        foreach (var item in textList)
        {
            if (item.StartsWith(ProductText))//写入软件名
            {
                sb.AppendLine(item.Remove(ProductText.Length) + "\"" + Application.productName + "\"");
            }
            else if (item.StartsWith(VersionText))//写入版本号
            {
                sb.AppendLine(item.Remove(VersionText.Length) + "\"" + Application.version + "\"");
            }
            else if (item.StartsWith(PublisherText))//写入公司名
            {
                sb.AppendLine(item.Remove(PublisherText.Length) + "\"" + Application.companyName + "\"");
            }
            else if (item.StartsWith(OutFileText))//写入输出路径
            {
                sb.AppendLine(item.Insert(OutFileText.Length, dir + "\\"));
            }
            else if (item == "Section \"MainSection\" SEC01")//写入安装文件列表
            {
                sb.AppendLine(item);
                sb.AppendLine("  SetOutPath \"$INSTDIR\"");
                sb.AppendLine("  SetOverwrite ifnewer");
                sb.AppendLine("  File \"" + path + "\"");
                sb.AppendLine("  CreateDirectory \"$SMPROGRAMS\\${PRODUCT_NAME}\"");
                sb.AppendLine("  CreateShortCut \"$SMPROGRAMS\\${PRODUCT_NAME}\\${PRODUCT_NAME}.lnk\" \"$INSTDIR\\${PRODUCT_NAME}.exe\"");
                sb.AppendLine("  CreateShortCut \"$DESKTOP\\${PRODUCT_NAME}.lnk\" \"$INSTDIR\\${PRODUCT_NAME}.exe\"");
                sb.AppendLine("  SetOverwrite try");
                //string file= Path.GetFileName(path);
                HandDir(dir);
            }
            else if (item == "Section Uninstall")//写入卸载文件列表
            {
                sb.AppendLine(item);
                sb.AppendLine(
                    "  Delete \"$INSTDIR\\uninst.exe\"\n" +
                    "  Delete \"$SMPROGRAMS\\${PRODUCT_NAME}\\Uninstall.lnk\"\n" +
                    "  Delete \"$DESKTOP\\${PRODUCT_NAME}.lnk\"\n" +
                    "  Delete \"$SMPROGRAMS\\${PRODUCT_NAME}\\${PRODUCT_NAME}.lnk\"\n" +
                    "  RMDir \"$SMPROGRAMS\\${PRODUCT_NAME}\"\n");

                foreach (var str in outList)
                {
                    sb.AppendLine(str);
                }
            }
            else
            {
                sb.AppendLine(item);
            }
        }

        //Console.WriteLine(sb.ToString());
        //输出nsi脚本
        string output = Path.Combine(Application.streamingAssetsPath,"Output_NSIS.nsi");
        File.WriteAllText(output, sb.ToString(), Encoding.UTF8);
        //使用makensis命令生成安装包
        Process.Start("makensis", output);
    }

    /// <summary>
    /// 路径处理
    /// </summary>
    /// <param name="path"></param>
    static void HandDir(string path)
    {
        string dir = path.Remove(0, rootLength);

        foreach (var item in Directory.EnumerateDirectories(path))
        {
            //Console.WriteLine(item);
            HandDir(item);
        }
        string str = "\"$INSTDIR" + dir + "\"";
        sb.AppendLine("  SetOutPath " + str);//添加文件夹安装信息
        outList.Add($"  RMDir {str}");//添加文件夹卸载信息

        foreach (var item in Directory.EnumerateFiles(path))
        {
            string file = item.Remove(0, rootLength);
            sb.AppendLine("  File \"" + item + "\"");//添加文件安装信息
            outList.Add("  Delete \"$INSTDIR" + file + "\"");//添加文件卸载信息
            //Console.WriteLine(item);
        }
    }
}

然后,在Unity编辑器里面Build完之后,这个脚本就会在打包目录下创建一个安装包了。
Alt

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值