- 方案流程总结
- 安装NSIS
- 1.安装NSIS
- 2.安装HM NIS EDIT
- 制作NSIS脚本模板
- 解析NSIS脚本
- Unity发布程序的自动安装包制作实现
前段时间,应公司要求,研究了一下把Unity发布后的程序自动做成安装包的实现方案。经过一段时间的研究,最终使用NSIS开源工具基本实现了这个需求。为了防止以后自己忘了这部分的东西,特此记录下实现思路,方便以后回顾。研究下来之后,我觉得可能很多流氓软件可能背后都有NSIS这个工具的功劳,哼哼~。 方案流程总结
整体思路: 基于NSIS的脚本模板,使用C#实现IPostprocessBuildWithReport接口的编辑器脚本,用Unity内部的Player Setting信息,以及打包好的程序信息,替换NSIS脚本模板中的对应信息,再自动执行makensis命令制作安装包。
安装NSIS 1.安装NSISNSIS (Nullsoft Scriptable Install System) 是一个专业开源的制作 windows 安装程序的工具。详细介绍就不多说了,愿意详细了解的,参考:NSIS百度百科 。下载链接:NSIS
安装完成之后把NSIS目录添加到系统环境变量,用于后续C#脚本调用,添加完成后可以使用makensis命令测试,如下图:
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.运行制作好的安装包,安装程序,运行安装好的程序,再卸载程序,测试整个流程。
1.程序信息
这里包含程序的基本信息,程序名称、版本号、发布者(公司名称)、产品网址、注册表信息等。
2.安装包设置:
这里包含安装包的设置信息,包含图标设置、页面设置、语言设置等。
3.安装信息
这里包含程序的安装设置信息,包含安装包名称、安装路径、安装注册表设置,以及要安装的文件及文件夹等。
4.卸载信息
这里包含程序的卸载设置信息,包含卸载程序设置、卸载页面提示,以及要删除的文件及文件夹设置等。
分析完NSIS脚本之后,基本的用代码制作安装包的思路就出来了。具体如下:
1. 将Unity的PlayerSetting里面的程序名、发布者、版本号等信息替换模板中对应的信息;
2. 遍历Unity打包出来的程序目录,将里面的文件和文件夹路径写入到安装信息里面;
3. 类似第二步,改写卸载信息中对应的删除的文件和文件夹路径信息;
4. 保存生成好的nsi脚本,调用makensis命令生成安装包。
首先,因为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 "SoftwareMicrosoftWindowsCurrentVersionApp Paths${PRODUCT_NAME}.exe"
!define PRODUCT_UNINST_KEY "SoftwareMicrosoftWindowsCurrentVersionUninstall${PRODUCT_NAME}"
!define PRODUCT_UNINST_ROOT_KEY "HKLM"
; MUI 1.67 compatible ------
!include "MUI.nsh"
; MUI Settings
!define MUI_ABORTWARNING
!define MUI_ICON "${NSISDIR}ContribGraphicsIconsorange-install.ico"
!define MUI_UNICON "${NSISDIR}ContribGraphicsIconsorange-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" "$INSTDIRuninst.exe"
SectionEnd
Section -Post
WriteUninstaller "$INSTDIRuninst.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" "$INSTDIRuninst.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 outList = new List();
public void OnPostprocessBuild(BuildReport report)
{
UnityEngine.Debug.Log("MyCustomBuildProcessor.OnPostprocessBuild at path " + report.summary.outputPath);
path = report.summary.outputPath;
CreateNsisScr(path.Replace("/","\"));
}
///
/// 床脚NSIS脚本
///
///
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 textList = new List(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);
}
///
/// 路径处理
///
///
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完之后,这个脚本就会在打包目录下创建一个安装包了。



