本文介绍的是利用Inno Setup 6将.net 8.0的Winforms项目打包成一个独立的安装包文件,实现安装包自动根据.net的安装情况选择是否安装.net运行时,并且生成自签名用于软件的数字签名。
以前打包C#项目安装程序一直用的是微软官方出的打包工具Microsoft Visual Studio Installer Projects,它能将软件打包成一个.msi安装程序,方便把项目部署至客户机器上。它的优点是操作简单,生成的安装程序也可以实现运行环境的检测。但是有两个算不上缺点的小地方,是我个人不太喜欢的。一个是生成的安装程序是msi格式,并不是常用的exe格式,这一点本身没什么,但是msi不具备检测运行环境的功能,所以如果想要实现离线安装运行环境,就会生成三个文件:1个msi安装程序、1个exe环境检测程序、1个运行环境的安装包,还得利用压缩软件将这些东西合并成一个自解压安装包,才能实现单安装文件交付;第二个就是它不能自动生成一个卸载程序,只能通过调用msiexec.exe建立快捷方式实现卸载,要么就自己写一个exe实现卸载功能,但这样就太复杂了。所以这次的项目就转向了Inno Setup,它能很好的解决上面的两个问题。
此外关于运行环境检测,因为现在.net Framework应用还是更广泛一些,所以能查到的大部分教程也都是针对.net Framework的检测,对应.net 8.0的方案基本找不到或用不了。这个得怪微软,随着.net版本更新,各版本在注册表里对应的位置跑来跑去,很难抓住,所以这次抛弃了注册表,直接检测文件。至于这个方法在.net 10或12还能不能用,只能到时候再说了。
关于数字签名,我面向的都是一些工业场景部署,所以把自签名证书往客户机器里安装一下就没问题了,下文介绍的是自签名证书的生成方式。如果需要商业证书,还是需要购买。
准备工作
要安装的软件
我找了一个以前做的调试小工具作为演示道具。在VS项目文件夹\bin\Release目录下找到调试生成的软件,删掉里面的.pdb文件,这个是调试用的,不需要交付。在D盘新建一个文件夹,就叫New Tools吧,用于放生成安装包所需的所有内容。将调试目录里所有的内容都拷贝过来。

运行环境安装包
在微软网站下载.net8.0的安装包,快捷传送门。每个版本的.net都包括一个SDK和三个运行时。SDK是给开发者用的,用户用不到,下载运行时就好了。运行时包括:ASP.NET Core 运行时,这个是给Web应用使用的;.NET 桌面运行时,这个是给Winforms和WPF用的,下载这个就行;还有一个是.NET 运行时,只包括控制台程序的组件,如果开发的程序没有界面,只是控制台应用程序的话可以下载这个。

.NET 桌面运行时有x64、x86、Arm64几个版本,取决于VS里的解决方案配置,Release平台选了x64或者Any CPU,就下载x64版本,选了x86或者Arm64,就下载对应版本。这里我下载的是windowsdesktop-runtime-8.0.11-win-x64.exe,下载好的安装包直接丢进前面准备好的文件夹里就OK,不用像Microsoft Visual Studio Installer Projects那样还得放到一个巨深的指定目录里。
Inno Setup安装
在官方网站下载Inno Setup 6的安装包,快捷传送门。项目位于GitHub,所以下载的时候需要一些小方法。下载并安装好之后需要额外下一个中文语言包。这个中文语言包不是软件的汉化包,软件使用时从头到尾都没中文,这个语言包是给客户安装时显示的,不下载的话用户运行的安装软件界面没有中文。
点击网站左侧小小的Translations链接,下载列表中的Chinese (Simplified)。下载页面上面有一句:“Note that official translations come bundled with your Inno Setup installation(官方翻译已随您的Inno Setup安装包提供)”,不要信!都是骗人的!根本没自带,还是得下。
下载好的文件放到Inno Setup安装目录下的Language文件夹里。

其他文件
然后还需要准备一个.ico图标文件作为安装程序的图标,和一个.txt许可协议(也可以没有),也放进New Tools文件夹。整理好的D:\New Tools\文件夹应该是这个样子的。
D:\New Tools\
├─ 📂 Drivers\ # 软件驱动文件
├─ 📂 Resources\ # 软件资源文件
├─ config.ini # 软件配置文件
├─ HslCommunication.dll # 软件依赖
├─ icon.ico # 图标
├─ license.txt # 许可协议
├─ S7.Net.dll
├─ Tools.deps.json
├─ Tools.dll
├─ Tools.exe # 应用入口
├─ Tools.runtimeconfig.json
└─ windowsdesktop-runtime-8.0.11-win-x64.exe # 运行环境安装包Plaintext安装程序脚本
开始生成安装程序的脚本。Inno Setup是通过脚本生成安装程序的,可以借助软件自带的向导Script Wizard生成基础的脚本,再根据需要修改。
生成脚本
运行Inno Setup 6,点击“Create a new script file using the Script Wizard”,点击OK。

不要勾选“Create a new empty script file”,点击Next。

第一行输入软件的名称,我这里就简单的叫Tools了;第二行输入版本号,输入1.0.0.0,注意版本号前不要加V;下面两行是开发者名称和网址,此处输入公司的信息。点击Next下一步。

选择默认的安装位置,选择“Program Files folder”的话,如果是32位程序,会默认装到C:\Program Files (x86)\软件名,如果是64位程序,会默认装到C:\Program Files\软件名。一般默认就可以了,不想用这个的话,可以选择“(Custom)”自定义,将希望的路径输入在下面的文本框里。再往下的“Application folder name:”是安装软件的文件夹的名称,默认是用软件名,也可以改成别的,比如改成“Tools v1.0”,那么安装路径就会变为C:\Program Files\Tools v1.0。再下面的第一个复选框是允许用户自己修改安装位置,勾上允许就好;第二个复选框是说本软件不需要文件夹,这个不要选。点击Next下一步。

这一步是添加安装包里的文件,最上面的是选择软件入口,点击“Browse…”,打开前面创建的安装程序文件夹D:\New Tools\,把软件的exe文件Tools.exe添加进去。下面的两个复选框,第一个是安装完成后显示一个立即启动软件的选项,按需勾选即可;第二个是本软件没有exe入口程序,这里不选。再下面是添加其他所有需要打包的文件。点击右边的“Add file(s)…”,把New Tools文件夹里除了Tools.exe以外的全部文件都选中导入。

像我这样软件里面还有文件夹的需要点“Add folder…”将下面的文件夹一个一个添加进去,弹出的提示是问是否一并添加子文件(夹),点是添加进去。上述添加文件过程不建议点“Add folder…”将New Tools整个文件夹一并导入,因为后续需要对个别文件单独设置安装的行为,一起添加进去不好改。添加完点击Next下一步。

这一步是关联拓展名,如果你的软件有指定的文件格式,例如pdf文件,就在这一也勾选上,下面分别输入“PDF文档”和“.pdf”,安装会自动把.pdf文件关联到你的软件打开。本例不涉及所以不选,点击Next下一步。

接下来是设置开始菜单。最上面的复选框是在开始菜单直接生成一个软件的快捷方式,不勾选的话可以在下面自定义开始菜单设置。在文本框里填写开始菜单文件夹的名字。下面的四个选项分别是:允许用户自己设置开始菜单文件夹名、允许用户不创建开始菜单选项、在开始菜单文件夹里添加你网站的链接、在开始菜单文件夹里添加卸载程序的快捷方式,此处按需选择。最下面的复选框是允许用户创建桌面快捷方式,这里不勾选的话,安装程序不会向用户显示创建桌面快捷方式的选项。点击Next下一步。

第一行是用户许可协议,用户需要点击同意许可协议才能进行后面的安装。点击右侧的“Browse…”,选择前面准备好的许可协议license.txt,没有协议也可以空着。下面两项分别是安装前和安装后向用户展示的内容。格式也是txt文本。点击Next下一步。

选择安装模式,从上往下分别是:为所有用户安装、只为当前用户安装、允许用户自己改安装模式、开始安装前让用户自己选安装模式。这里选第一个就行。点击Next下一步。

如果有安装时需要添加到的注册表内容,点击右侧“Browse…”导入。下面的四个选项分别是:卸载时删除注册表值为空的键、同时也删除不为空的键、卸载时删除所有值、至少是指定Windows版本以上才创建。我们这里用不到,直接点击Next下一步。

选择安装程序界面UI的显示语言,前面如果不下载那个语言包ChineseSimplified.isl的话,这里就没有中文可选。取消勾选English,勾选Chinese Simplified。至少选择一种语言,也可以多选,多选的话运行安装程序时会首先让用户选择一种语言。点击Next下一步。

进行一些编译的设置,第一项是编译好的安装程序输出位置,还选刚才的文件夹就行;第二项是安装程序的名字;第三项是设置安装程序图标,点击右侧的“Browse…”选择前面准备好的图标;第四项是设置安装密码,这个一般不设置。最下面的复选框是用上面的密码加密应用程序文件。点击Next下一步。

下一步是设置界面风格,我这里就用默认的风格了。点击Next下一步。

这个选项是用#define定义编译脚本用的变量,这样后续脚本需要修改的话修改定义位置就行,比较方便。勾选后点击Next下一步。

到这里就完成了生成脚本的步骤,点击Finish结束向导。

点击完成后提示是否直接编译,这里我们还没有添加环境检测等内容,先不要编译,点击否。另存为将生成的脚本保存到前面新建的文件夹D:\New Tools。

编辑脚本
用脚本向导生成的脚本分为几大区块:#define区块是定义变量、[Setup]区块是设置、[Languages]区块是语言设置、[Task]区块是安装附加任务、[Files]区块是安装文件的导入、[Icons]区块是设置图标、[Run]区块是安装时执行的任务,后面还会往里面添加[Code]区块,是需要运行的脚本。分号开头的是注释。
; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
; Non-commercial use only
#define MyAppName "Tools"
#define MyAppVersion "1.0.0.0"
#define MyAppPublisher "MOZE"
#define MyAppURL "https://imzy.ink/"
#define MyAppExeName "Tools.exe"
[Setup]
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{00460292-1CB4-40B2-8CC9-0782C4DEB64A}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={autopf}\{#MyAppName}
UninstallDisplayIcon={app}\{#MyAppExeName}
; "ArchitecturesAllowed=x64compatible" specifies that Setup cannot run
; on anything but x64 and Windows 11 on Arm.
ArchitecturesAllowed=x64compatible
; "ArchitecturesInstallIn64BitMode=x64compatible" requests that the
; install be done in "64-bit mode" on x64 or Windows 11 on Arm,
; meaning it should use the native 64-bit Program Files directory and
; the 64-bit view of the registry.
ArchitecturesInstallIn64BitMode=x64compatible
DefaultGroupName={#MyAppName}
AllowNoIcons=yes
LicenseFile=D:\New Tools\License.txt
; Uncomment the following line to run in non administrative install mode (install for current user only).
;PrivilegesRequired=lowest
OutputDir=D:\New Tools
OutputBaseFilename=Tools安装程序
SetupIconFile=D:\New Tools\icon.ico
SolidCompression=yes
WizardStyle=modern dynamic
[Languages]
Name: "chinesesimplified"; MessagesFile: "compiler:Languages\ChineseSimplified.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
[Files]
Source: "D:\New Tools\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "D:\New Tools\config.ini"; DestDir: "{app}"; Flags: ignoreversion
Source: "D:\New Tools\HslCommunication.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "D:\New Tools\icon.ico"; DestDir: "{app}"; Flags: ignoreversion
Source: "D:\New Tools\License.txt"; DestDir: "{app}"; Flags: ignoreversion
Source: "D:\New Tools\S7.Net.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "D:\New Tools\Tools.deps.json"; DestDir: "{app}"; Flags: ignoreversion
Source: "D:\New Tools\Tools.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "D:\New Tools\Tools.runtimeconfig.json"; DestDir: "{app}"; Flags: ignoreversion
Source: "D:\New Tools\windowsdesktop-runtime-8.0.11-win-x64.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "D:\New Tools\Resources\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "D:\New Tools\Drivers\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilentINI首先在#define部分后面添加两行定义,一行是AppId,一行是版权信息。AppId可以用[Setup]区块下面自动生成的那个,也可以点击软件菜单栏Tools-Generate GUID生成一个新的,也可以在VS里点击工具-创建GUID来生成一个新的。如果你的软件里已经有了之前生成的GUID,可以直接复制过来用。GUID用于系统对软件的识别,所以后续发布更新安装程序时,只可以修改版本号,不能修改GUID。版权信息会显示在安装程序的属性里。添加的内容如下:
; 这里改成自己的GUID
#define MyAppId "{00460292-1CB4-40B2-8CC9-0782C4DEB64A}"
; 这里改成自己的版权信息
#define MyAppCopyright "© 2026 MOZE All rights reserved."Pascal下面修改[Setup]区块。首先将AppId改为已经定义的变量,注意前面花括号有两个,但后面的花括号只有一个。然后再添加一行版权信息,一行文件版本。文件版本不添加的话安装程序的属性里文件版本一项会显示0.0.0.0。找到OutputBaseFilename这一项,将其改为{#MyAppName}-{#MyAppVersion}-x64-Setup或者其他自定义的内容,这样生成的安装程序名会自动跟着版本修改,不用每次去重命名了。
改完后的[Setup]区块如下:
[Setup]
; 所有原有注释都删掉了方便阅读
; 修改 注意AppId前后的花括号数量
AppId={{#MyAppId}
AppName={#MyAppName}
; 新增
AppCopyright={#MyAppCopyright}
AppVersion={#MyAppVersion}
; 新增
VersionInfoVersion={#MyAppVersion}
;
; ...中间不修改的部分省略...
;
OutputDir=D:\New Tools
; 修改
OutputBaseFilename={#MyAppName}-{#MyAppVersion}-x64-Setup
SetupIconFile=D:\New Tools\icon.ico
SolidCompression=yes
WizardStyle=modern dynamicINI[Languages]区块不需要修改。[Task]区块下面这行的意思是创建桌面快捷方式,后面的unchecked将这个选项默认为不勾选,如果你想让用户安装到这一步时默认选中创建桌面快捷方式,就把后面的一部分去掉,就像这样:
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"Pascal接下来是[Files]区块。先找到配置文件config.ini这一行,有些软件有类似于ini这类的配置文件,用于存储用户数据,卸载时希望能留下来或者更新时不覆盖,需要在后面添加skipifsourcedoesntexist(更新时不覆盖)或uninsneveruninstall(卸载时不删除)这两个参数,可以都添加或添加其中任意一个。如果软件里没有配置文件,就忽略这一步。
; 安装时覆盖,卸载时删除(默认)
Source: "D:\New Tools\config.ini"; DestDir: "{app}"; Flags: ignoreversion
; 安装时不覆盖 卸载时删除
Source: "D:\New Tools\config.ini"; DestDir: "{app}"; Flags: ignoreversion skipifsourcedoesntexist
; 安装时覆盖 卸载时不删除
Source: "D:\New Tools\config.ini"; DestDir: "{app}"; Flags: ignoreversion uninsneveruninstall
; 安装时不覆盖 卸载时不删除
Source: "D:\New Tools\config.ini"; DestDir: "{app}"; Flags: ignoreversion skipifsourcedoesntexist uninsneveruninstallPascal接下来找到运行环境这一行,这个安装包只用于安装必要的运行环境,装完没有留下来的必要,所以要把目标位置从{app}改为{tmp},并添加参数deleteafterinstall(安装完成后删除),这样.net安装包就不会留在安装完的程序目录里了。
; .net安装包安装时放在temp目录,安装后删除
Source: "D:\New Tools\windowsdesktop-runtime-8.0.11-win-x64.exe"; DestDir: "{tmp}"; Flags: ignoreversion deleteafterinstallPascal如果你的程序文件里存在文件夹,就比如我的程序中前面导入两个文件夹Resources和Drivers,可以看到他们的目标位置是{app},安装时安装程序会把文件夹内的子文件直接放在软件根目录,所以文件夹的目标位置要改成类似于{app}\Resources\这样。改完的[Files]区块如下:
[Files]
Source: "D:\New Tools\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
; 修改
Source: "D:\New Tools\config.ini"; DestDir: "{app}"; Flags: ignoreversion skipifsourcedoesntexist uninsneveruninstall
Source: "D:\New Tools\HslCommunication.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "D:\New Tools\icon.ico"; DestDir: "{app}"; Flags: ignoreversion
Source: "D:\New Tools\License.txt"; DestDir: "{app}"; Flags: ignoreversion
Source: "D:\New Tools\S7.Net.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "D:\New Tools\Tools.deps.json"; DestDir: "{app}"; Flags: ignoreversion
Source: "D:\New Tools\Tools.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "D:\New Tools\Tools.runtimeconfig.json"; DestDir: "{app}"; Flags: ignoreversion
; 修改
Source: "D:\New Tools\windowsdesktop-runtime-8.0.11-win-x64.exe"; DestDir: "{tmp}"; Flags: ignoreversion deleteafterinstall
; 修改
Source: "D:\New Tools\Resources\*"; DestDir: "{app}\Resources\"; Flags: ignoreversion recursesubdirs createallsubdirs
; 修改
Source: "D:\New Tools\Drivers\*"; DestDir: "{app}\Drivers\"; Flags: ignoreversion recursesubdirs createallsubdirsPascal[Icon]区块不需要修改。[Run]区块原有这一行的功能是启动软件的任务,用于安装完成后显示立即运行程序的选项,如果不想让用户安装完立即运行程序,就把这行整体删除。
[Run]
; 安装完成后的运行程序选项,不需要可以整行删除
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilentPascal按下键盘ctrl+H,或者点击菜单栏Edit-Replace…,打开替换,将前面准备好的文件的路径,我这里是D:\New Tools,全部替换为{#SourcePath}。这一步是把绝对路径替换为相对路径,{#SourcePath}的意思是脚本文件所在目录。修改后,如果将前面准备的文件夹D:\New Tools移动到其他地方,脚本也能正常运行,方便后续版本更新。

环境检测
下面为安装脚本添加检测运行环境功能。本文准备的软件是基于.net 8.0开发的Winforms应用,运行需要.net desktop runtime 8.0的任何版本。电脑上如果安装了.net 8.0 SDK(SDK自带runtime,不用另外安装)或者.net desktop runtime 8.0的话,在C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App\目录下可以找到已安装的全部desktop runtime的版本。如果是32位的.net,其位置在C:\Program Files (x86)\dotnet\shared\Microsoft.WindowsDesktop.App\。我们要检测的是这个文件夹下是否存在任何以“8.0.”开头的文件夹,若有,说明电脑已有所需运行环境,若没有,就安装我们打包好的安装包。

如上图,我电脑上能看到这个安装目录里有.net 6、7、8、9,说明至少这四个版本是可以用这个办法检测的。如果开发的是控制台应用程序或者Web应用,则需要后退一层目录,找到对应的runtime安装目录进行检测。目录如下:
64位安装路径
ASP.NET Core Runtime 用于Web应用
📂 C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App\
.NET Desktop Runtime 用于桌面应用Winforms和WPF
📂 C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App\
.NET Runtime 用于控制台应用
📂 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\
32位安装路径
ASP.NET Core Runtime 用于Web应用
📂 C:\Program Files (x86)\dotnet\shared\Microsoft.AspNetCore.App\
.NET Desktop Runtime 用于桌面应用Winforms和WPF
📂 C:\Program Files (x86)\dotnet\shared\Microsoft.WindowsDesktop.App\
.NET Runtime 用于控制台应用
📂 C:\Program Files (x86)\dotnet\shared\Microsoft.NETCore.App\Plaintext在脚本最下方新增[Code]区块,添加以下代码,用于判断运行环境是否已安装。其中检测路径中的{pf},会自动根据你的安装模式是64位还是32位自动定位到C:\Program Files或C:\Program Files (x86)目录,不需要手动判断。如果想强制要求检测C:\Program Files (x86),可以替换为{pf32}。
[Code]
function HasDotNet8DesktopX64(): Boolean;
var
basePath: string;
searchRec: TFindRec;
begin
Result := False;
// dotnet共享框架目录,如果是Web或控制台应用后面改成相应的文件夹
basePath := ExpandConstant('{pf}\dotnet\shared\Microsoft.WindowsDesktop.App');
if DirExists(basePath) then
begin
if FindFirst(basePath + '\*', searchRec) then
begin
repeat
if (searchRec.Name <> '.') and (searchRec.Name <> '..') then
begin
// 判断文件夹是否以“8.0.”开头
if Pos('8.0.', searchRec.Name) = 1 then
begin
Result := True;
Break;
end;
end;
until not FindNext(searchRec);
FindClose(searchRec);
end;
end;
end;Pascal在[Run]区块启动软件任务之前,添加一行安装任务,来实现安装运行环境。安装可以通过/quiet或/passive参数选择是否静默安装(不显示.net安装界面)。
[Run]
; 静默安装.net 安装完不需要重启
Filename: "{tmp}\windowsdesktop-runtime-8.0.11-win-x64.exe"; Parameters: "/quiet /norestart"; Check: not HasDotNet8DesktopX64(); StatusMsg: '正在安装 .NET Desktop Runtime 8.0...'
; 正常安装,显示.net安装界面 安装完不需要重启
Filename: "{tmp}\windowsdesktop-runtime-8.0.11-win-x64.exe"; Parameters: "/passive /norestart"; Check: not HasDotNet8DesktopX64(); StatusMsg: '正在安装 .NET Desktop Runtime 8.0 ...'
; 原启动软件任务
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilentPascal编辑完成后保存脚本。
数字签名
下面开始生成一个自签名证书。
准备工具
首先需要下载安装Windows SDK,快捷传送门。我下载的是Windows SDK for Windows 11 (10.0.28000.2705)。安装完成后打开目录C:\Program Files (x86)\Windows Kits\10\bin\10.0.28000.0\x64,我们所需的工具都在这里。这个路径中的10.0.28000.0这个文件夹名称对应的是你下载的Windows SDK的版本。用到的工具是这些:
C:\Program Files (x86)\Windows Kits\10\bin\10.0.28000.0\x64\
├─ cert2spc.exe
├─ certmgr.exe
├─ MakeCert.exe
├─ pvk2pfx.exe
└─ signtool.exePlaintext首先需要将这个目录添加到环境变量,方便后面使用。在“此电脑”上右键-属性,找到高级系统设置,在弹出的窗口点击最下面的“环境变量(N)…”。在下面系统变量里找到Path,双击打开编辑窗口,点击新建,将工具目录粘贴进去。点确定。

创建证书
在D盘新建一个放置用于生成证书的全部内容的文件夹,我这里就起名叫Cert了。以管理员身份运行命令提示符,用cd命令切换到新建的文件夹:
cd /d D:\CertBAT (Batchfile)执行下面的命令,以下命令的pvk、cer证书的名称可以自己命名,“CN=”部分改成你的公司名称,名称内不能含有逗号,名称可以是中文。
Makecert -sv MOZE.pvk -r -n "CN=MOZE.ink" MOZE.cerBAT (Batchfile)在弹出的窗口输入两次密码,密码可以自定义,但要记好,我这里设了123456。然后弹窗再输入一遍密码。成功后显示Succeeded。刚才的Cert文件夹里出现了创建的证书文件。如果命令不能执行,检查一下环境变量设置,然后重启。还不行的话就把上面提到的5个工具全部复制到D:\Cert中,再重新试一遍。

继续执行以下命令,cer文件名是上一步起的名字,spc文件名可以自定义:
cert2spc MOZE.cer MOZE.spcBAT (Batchfile)成功后显示Succeeded,文件夹里出现spc文件。

继续执行以下命令:
certmgr.exe -add -c MOZE.spc -s -r localMachine rootBAT (Batchfile)成功后显示CertMgr Succeeded。

继续执行以下命令,一共有4个参数:-pvk后面是pvk文件名,-pi后面是密码,-spc后面是spc文件名,-pfx后面起一个pfx证书的文件名。为了避免混淆我全部都用了一样的名字。
pvk2pfx -pvk MOZE.pvk -pi 123456 -spc MOZE.spc -pfx MOZE.pfx -fBAT (Batchfile)成功后文件夹内出现pfx证书。

添加签名
再次回到Inno Setup软件,点击菜单栏Tools-Configure Sign Tools…,开始配置签名。点击“Add…”新建,输入一个名字,我这里叫MOZE_Sign_Tool,记住这个名字,后面要用。

点击OK后弹出配置窗口,输入以下命令,其中需要将两个$q之间的pfx证书路径修改为实际的内容,将123456修改为创建证书时的密码。命令中摘要算法是sha256,后面那段是添加时间戳。
SignTool sign /fd sha256 /a /f $qD:\Cert\MOZE.pfx$q /p 123456 /t http://timestamp.digicert.com $fBAT (Batchfile)输好命令后保存,返回脚本编辑器,在[Setup]区块添加以下内容,将SignTool修改为刚才新建的那个:
[Setup]
; 修改为实际名称
SignTool=MOZE_Sign_Tool
SignedUninstaller=yesINI至此脚本编辑全部结束。保存一下脚本,然后点击编译按钮(菜单栏Build-Compile)。等下方进度条跑完之后,在最初的文件夹里(我这里是D:\New Tools)就能找到生成的安装程序了:Tools-1.0.0.0-x64-Setup.exe。右键安装程序,点击属性,点击数字签名,可以看到刚才做的数字签名。

顺便点击详细信息看一下,这里是脚本中[Setup]区块设置的属性。

安装与使用
安装证书
上面步骤生成的数字签名证书是自签名证书,并不会被系统信任,需要将自签名根证书安装到本机受信任根存储之后,才会被认为可信。
生成的证书中,.cer包含公钥,用于提供给客户机器安装,.pfx除公钥外还包含私钥,不可以提供给客户。将cer证书复制到需要安装的电脑,打开,点击“安装证书(I)…”。

存储位置选择本地计算机,点击下一步。选择“将所有的证书都放到下列存储(P)”,点击“浏览(R)…”,选择“受信任的根证书颁发机构”,点击确定,点击下一步。

点击完成结束安装。提示导入成功。
安装程序
将生成的安装程序复制到目标电脑上,运行。

如果之前选择了多种语言,最先显示的是选择语言界面,等待用户选择一种安装语言后继续。我只选了中文,所以直接进入了许可协议界面,同意后可以进行下一步。

然后是选择安装位置,因为软件是64位的,所以默认安装位置是C:\Program Files\目录。点击下一步。

下一步是开始菜单相关设置,因为前面选择了允许用户自己选择,所以这里的设置是可以修改的。

因为前面选择了允许创建桌面快捷方式,所以这里弹出了创建桌面快捷方式的附加任务。前面在脚本中去掉了unchecked,所以这里默认是勾选的状态。

用户可以自定义的内容确认完成就可以点击安装了。

安装时如果没有找到.net 8.0的安装,会自动开始安装.net。因为在脚本中没有设置静默安装,所以这里弹出了.net的安装界面。

安装完成。脚本中设置了安装完成后运行,这里就会多一个复选框提示可以运行软件。

安装结束后查看一下安装目录,所有文件都成功复制,.net安装包没有残留在安装目录。Inno Setup也自动生成了用于卸载的程序。

安装完成后Inno Setup会在注册表生成卸载数据,在系统程序与功能里可以看到刚刚安装的软件。

卸载后注册表、桌面图标、开始菜单和程序文件会被清除。但是脚本中设置了卸载时保留的配置文件会依然留在系统中。
大功告成。

