前言
最近不是在学CAPE-OPEN单元模块开发的C#实现嘛,做完单元模块之后就寻思能不能直接做个安装包,安装之后就加载dll文件注册,卸载就进行反注册,这样发给别人或者在其他机器里测试的时候就不用来回使用命令行 regasm.exe /tlb or /u CapeOpen.dll
这种形式了,毕竟太麻烦。
能点点点谁还用cmd啊,是吧()
VS Installer Projects
Windows Installer 部署使您能够创建要分发给用户的安装程序包;用户运行安装文件并逐步完成向导以安装应用程序。这是通过向解决方案中添加 Setup 项目来实现的。构建后,项目将创建一个分发给用户的安装文件;用户运行安装文件并逐步完成向导以安装应用程序。
Microsoft Windows Installer 是一种数据驱动的安装和配置服务,作为 Windows 系统的一部分提供。Windows Installer 维护一个数据库,其中包含有关已安装的每个应用程序的信息,包括文件、注册表项和组件。卸载应用程序时,将检查数据库以确保在删除文件、注册表项或组件之前没有其他应用程序依赖于该文件、注册表项或组件。这可以防止删除一个应用程序会破坏另一个应用程序。
官方文档:Visual Studio Installer Deployment | Microsoft Learn 。
扩展下载链接:Microsoft Visual Studio Installer Projects 2022 - Visual Studio Marketplace 。
或者在Visual Studio扩展商店搜索下载即可,需要注意的是,在扩展商店点击安装之后会提示关闭VS之后开始安装,这个时候直接关闭VS即可进入扩展安装程序。
简单来说,就是把C#项目打包为一个Windows安装包。
添加项目
演示版本:Visual Studio 2022 Perview。
安装好插件之后,在解决方案中选择添加新项目,选择 Setup Project:
选择好项目名称,创建完成之后的项目界面如下:
下面这几个文件是我之前添加的,忽略即可。
如果没显示这个界面,可以右键新建的项目,选择 view - 文件系统:
默认工程文件中有三个文件夹:
- Application Folder:存放打包项目包含的所有文件;
- User’s Desktop:存放安装后要置于桌面的文件;
- User’s Programs Menu:存放安装后在“开始菜单”内显示的文件;
然后双击Application Folder,在空白的位置右键,Add:
四个选项说明:
- Folder:在当前文件添加文件夹,例如我之前添加的Icon目录;
- 项目输出:添加项目的输出文件,比如类库输出(dll),控制台程序输出或其他应用程序输出(exe);
- 文件:一些自定义的的脚本、外部添加的dll依赖文件、系统本身的程序(例如
cmd.exe
、powershell.exe
等); - 程序集:添加项目依赖项,当前电脑系统中安装的一些环境依赖,比如System命名空间之类的。
额外说明:
- 一般情况下,只会用到Folder和项目输出这两个;
- 添加目录是为了整理项目的一些静态文件什么的,为了好看和方便固定路径调用,不添加也没事,全放在一个目录中也是可以的;
- 只需要添加项目输出即可,VS会自动检索项目输出所需要的依赖文件然后自动添加进来,除非自己需要额外的防止一些静态文件或者手动添加依赖才需要用到文件和程序集;
- 添加了项目输出,并不是直接就把添加的项目输出文件复制过来,而是在编译的时候自动按照顺序来编译原始项目,复制项目生成,再编译安装包;
- 如果项目B依赖于项目A,那么只需要添加项目B即可,项目A的生成文件会自动进行添加,同时添加可能会产生冲突。
我们这里以类库为例,添加项目输出:
可以看到我的主要项目就是MyMixerTest类库,然后他依赖于项目CapeOpen,所以自动导入了CapeOpen项目输出的文件 CapeOpen.dll
和一些系统依赖。
MyMixerTest项目只是输出一个dll文件,并不能实现自动注册,所以需要再导入我写好的两个生成可执行文件的项目,所以是三个主输出。
这里要说一下我这里的ComRegister和ComUnRegister是主要做什么用的,顾名思义,一个是注册COM的dll文件,一个是反注册(卸载),这两个项目里我都分别写了对应的注册脚本,然后输出为exe文件,这样用户只需要双击就可以进行注册了,而不用打开命令行再去调用对应的regasm命令,实际上写一个bat脚本也可以,但是我是强迫症,所以写了两个exe文件。
写一个也行的,通过选项来选择是要注册还是反注册,后面我会优化。
这里只是举个例子而已,实际上你的安装包里应该包括至少一个exe或者可执行程序,用作你的程序主入口来让用户交互,当然这是推荐做法,如果你只是实现一些例如注册表注册之类的功能,是可以不放可执行程序,使用Install类(下文中详细说明)来进行相关自定义行为。
然后在User’s Programs Menu目录中新建一个文件夹,命名为自己安装程序的名字,这里就是在开始菜单中要添加的文件夹名字:
然后回到Application Folder中,右键已添加的项目主输出,点击Create Shortcut:
注意,这里创建快捷方式一定是能生成可执行程序的项目,否则创建一个类库dll文件的快捷方式也没啥用是吧,双击也不会作用,用户还以为程序坏了呢。
然后将添加的快捷方式重新命名,重命名之后直接拖入或者剪切到刚才在开始菜单中新建的文件夹中,效果如图:
这里的卸载整个程序下文会说如何实现。
加载和卸载模块就对应我刚才添加进来的ComRegister和ComUnRegister项目 主输出。
然后回到Application Folder中,重复创建快捷方式,重命名的操作,将所需要的快捷方式拖入或剪切到User’s Desktop目录中,也就是安装完之后创建桌面快捷方式:
因为在这个工程中,是不允许进行复制的,所以只能重新创建快捷方式,然后拖动或者剪切。
添加卸载
回到Application Folder中,右键Add - 文件,找到这个文件:
C:\Windows\System32\msiexec.exe
然后添加进来,右键msiexec,选择Create Shortcut,将创建的快捷方式重命名为卸载程序或者unistall之类的,拖入开始菜单中添加的文件夹,或者你愿意的话也可以放在桌面快捷方式中都行:
然后右键创建好的快捷方式,点击属性窗口:
将该msiexec的快捷方式的Arguments值设置为:
/x {ProductCode}
注意x和花括号之间有个英文空格,注意要粘贴ProductCode的值,而不是这个属性名称,ProductCode值的获取:
首先点一下右边这个属性栏右上角的图钉按钮(默认是固定的话就不用),将该属性栏固定在当前窗口,然后 左键单击 解决方案资源管理器中的安装包创建项目:
如果直接右键该项目,选择项目属性,看到的只会是项目的输出配置,而不是项目的安装属性,所以必须通过这个方法来查看项目的安装属性。
但是在安装项目内部,直接右键快捷方式或者已添加的文件,点击属性窗口,是可以直接查看这个属性界面的,也算是VS的老bug了。
就可以看到这个ProductCode值啦,这样当执行这个快捷方式的时候,系统就会执行卸载程序,当然在控制面板中对这个程序右键卸载,也是会自动调用这个快捷方式。
添加图标
首先回到Application Folder中,将准备好的 *.ico
图标文件(必须ico格式)添加进来,在任何一个创建好的快捷方式上右键 - 属性窗口,选择Icon - browse,然后选择添加进来的ico文件,点击确定即可:
这样就设置好了快捷方式或者可执行文件的图标啦。
保持属性窗口打开,单击解决方案资源管理器中的安装包项目,打开安装属性,选择AddRemoveProgramslcon项,选择browse,选择自己的ico文件,这里设置的是应用程序在控制面板中的图标:
安装路径
回到文件系统视图中,右键Application Folder - 属性窗口,修改DefaultLocation属性值,即为默认的安装路径:
路径中的参数定义:
[ProgramFilesFolder]\[Manufacturer]\[ProductName]
- ProgramFilesFolder:系统默认的安装位置,64位为C盘下Program Files文件夹,32位为C盘下Program Files (x86)文件夹;
- Manufacturer:安装程序所定义的程序作者或程序制造商名称;
- ProductName:该安装程序项目本身的名称;
路径遵循Windows安装路径规则,允许使用空格,例如:
C:\Program Files\[Manufacturer]\[ProductName]
启动条件
顾名思义,就是运行该安装程序所需要的依赖环境,比如 .NET Core或Framework等,右键解决方案资源管理器中的安装项目 - view - 启动条件:
右键 .NET Framework,属性窗口,选择对应的Version值:
选择之后,当运行 setup.exe
安装程序之后就会首先检测用户电脑上是否安装了 .NET Framework环境,如果没有安装,则会弹出InstallUrl属性的值,也就是依赖环境下载链接,
这种方法的缺点就是需要用户的电脑联网,不能将依赖环境打包进安装包中(也是考虑到打包依赖环境的话安装包体积会很大),所以下面的可选设置就可以提供将依赖环境直接打包进安装包中,
在解决方案资源管理器中,右键安装项目,选择项目属性:
选择Prerequisites:
选择目标版本,将安装位置更改为“从与我的应用程序相同的位置下载系统必备组件”:
点击确定即可,然后去微软官网下载对应版本 英语和中文 两种语言的 离线安装包,例如:
这是 .NET Framework 4.7.2的版本,4.8.0版本的包名是:
注意,这里必须是AllOS的脱机安装包,运行时或者开发工具都是不可以的,4.8版本微软只提供了英文,中文没提供,所以选择4.8版本会有警告,所以4.8版本不推荐使用这种方式。
然后将下载好的离线安装包放到下面两个目录中:
中文路径:
C:\Program Files (x86)\Microsoft SDKs\ClickOnce Bootstrapper\Packages\DotNetFX472\zh-Hans
英文路径:
C:\Program Files (x86)\Microsoft SDKs\ClickOnce Bootstrapper\Packages\DotNetFX472\
注意路径中的 DotNetFX472
就是对应的版本号,472后缀就是4.7.2版本,不要放错版本。
如果中文和英文不全,或者版本不一致,就会导致编译时有警告,无法通过。
这样就完成了依赖添加,当项目编译的时候会自动将其复制到安装包生成的文件夹中,安装的时候 setup.exe
如果检测到没有依赖则会自动安装。
实际上自己在发布安装包的时候,顺手也可以直接将 .NET 安装包手动打个压缩包一起分发,也能实现类似的效果,只不过需要用户手动安装一下而已。
项目设置
保持右边的属性页面打开,左键单击解决方案资源管理器中的安装项目,可以看到有很多属性,常用的几个如下:
- AddRemoveProgramslcon:应用程序在控制面板中的显示图标;
- Author:程序的制作者、制作商或公司名;
- Description:程序的简短描述或说明;
- InsallAllUsers:是否默认使用管理员权限安装(如果不想显示用户级别安装,参见进阶部分);
- Localization:默认安装语言;
- Manufacturer:同Author,程序的制作者、制作商或公司名,可作为安装路径的参数选项;
- ProductCode:程序安装唯一GUID,自动生成,默认不需要修改,在卸载程序中作为参数传入;
- ProductName:程序名称,可作为安装路径的参数选项;
- TargetPlatform:目标安装平台,即x86或x64;
- Title:安装程序启动后程序窗口的标题;
- Version:程序版本号,格式必须为
##.##.####
,前两个区域不得超过两位,最后一个区域不得超过四位,例如0.1.0519
;
需要注意的是,ProductCode在Version版本号更改之后会自动发生改变,但是卸载程序的快捷方式中的Arguments参数不会自动修改,需要手动复制粘贴一下。
如图:
其他的:
上述只是 安装项目本身 的设置,还有安装项目中的项目主输出,也就是 实际业务项目 的相关信息,可以在解决方案资源管理器中右键项目 - 属性 - 应用程序 - 程序集信息中进行设置:
需要注意的是,这里的版本号不会影响安装项目的ProductCode变化,但是会影响 程序覆盖更新 的文件新旧问题,在下文中会有详细说明。
项目生成
当上述的步骤都没什么问题之后,就可以生成项目了,不过在此之间,还需要检查以下几个设置,首先是安装程序的属性中的发布平台:
各个项目输出的目标平台:
启动条件中的依赖环境:
各个项目输出的目标框架:
我这里只列举了一个项目,记得要检查所有添加进去的项目的目标平台与目标框架是否一致。
检查完毕之后在解决方案资源管理器中右键安装项目,点击重新生成:
重新生成开始于 16:03...
1>------ 已启动全部重新生成: 项目: ComUnRegister, 配置: Debug Any CPU ------
2>------ 已启动全部重新生成: 项目: ComRegister, 配置: Debug Any CPU ------
3>------ 已启动全部重新生成: 项目: CapeOpen, 配置: Debug Any CPU ------
2> ComRegister -> D:\Code\CSharp\CapeOpen-CSharp\MyMixerTest\ComRegister\bin\Debug\ComRegister.exe
1> ComUnRegister -> D:\Code\CSharp\CapeOpen-CSharp\MyMixerTest\ComUnRegister\bin\Debug\ComUnRegister.exe
3> CapeOpen -> D:\Code\CSharp\CapeOpen-CSharp\MyMixerTest\CapeOpen\bin\Debug\CapeOpen.dll
4>------ 已启动全部重新生成: 项目: MyMixerTest, 配置: Debug Any CPU ------
4> MyMixerTest -> D:\Code\CSharp\CapeOpen-CSharp\MyMixerTest\MyMixerTest\bin\Debug\MyMixerTest.dll
------ Starting pre-build validation for project 'MyMixerTestSetup' ------
------ Pre-build validation for project 'MyMixerTestSetup' completed ------
5>------ 已启动全部重新生成: 项目: MyMixerTestSetup, 配置: Debug ------
Building file 'D:\Code\CSharp\CapeOpen-CSharp\MyMixerTest\MyMixerTestSetup\Debug\MyMixerTestSetup.msi'...
WARNING: 'msiexec.exe' should be excluded because its source file 'C:\Windows\System32\msiexec.exe' is under Windows System File Protection.
Packaging file 'ComUnRegister.exe.config'...
Packaging file 'System.Net.Http.dll'...
Packaging file 'ComRegister.exe'...
Packaging file 'Microsoft.VisualStudio.Interop.dll'...
Packaging file 'MyMixerTest.dll'...
Packaging file 'Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime.dll'...
Packaging file 'CapeOpen.dll'...
Packaging file 'ComRegister.exe.config'...
Packaging file 'ComUnRegister.exe'...
Packaging file 'epa_seal_medium.ico'...
Packaging file 'msiexec.exe'...
========== 全部重新生成: 5 成功,0 失败,0 已跳过 ==========
========== 重新生成 于 16:03 完成,耗时 08.005 秒 ==========
可以看到,安装程序会自动先生成项目主输出的依赖,再生成主输出,最后再打包生成安装程序。
生成的文件如下:
我这里没有选择打包依赖环境,因为我没找到4.8版本的离线安装包中文版,所以无法生成,如果选择了打包依赖环境,则会多一个文件夹:
图源自CSDN,如有侵权,请联系站长删除。
这两个文件的区别:MSI文件是Windows Installer开发出来的程序安装文件,它可以让你安装、修改、卸载你所安装的程序,也就是说VS工具打包生成的MSI文件就是Windows Installer的数据包,把所有和安装文件相关的内容封装在一个包里。VS工具打包生成的exe文件是主要是用于检查安装的环境(即系统必备),当安装的环境检查成功后,会自动再安装MSI文件。
到这里基本上就完成了,可以将生成的安装包双击安装进行测试了,安装成功之后可以在控制面板和开始菜单里看到卸载的选项,测试一下是否可以卸载,卸载完毕之后安装目录中是否有文件残留。
进阶操作
1、直接覆盖更新
覆盖更新的意思就是在安装了当前应用程序的情况下,不需要卸载原来的旧程序,直接安装新程序即可完成应用程序的更新。
首先需要修改安装项目的Version版本号:
版本号必须大于原版本号,版本号格式见前文,需要特别注意的是安装程序的ProductCode值在Version版本号更改之后会自动发生改变,但是卸载程序的快捷方式中的Arguments参数不会自动修改,需要手动复制粘贴一下,具体操作可以参考前文。
然后需要修改 实际业务项目 的版本号,可以在解决方案资源管理器中右键项目 - 属性 - 应用程序 - 程序集信息中进行设置:
文件版本与程序集版本建议一致,且必须大于旧版本号,当然,只需要修改更新的项目即可,比如我这里有四个项目,其他三个依赖项目并没有更新,只更新了主项目,那么就只需要更新这一个的版本号即可,这样覆盖更新就只更新主项目了。
同时还需要注意的是,安装包项目的属性中,DetectNewerInstalledVersion和RemovePreviousVersions必须要设置为True:
更改完成之后保存,重新生成安装程序即可。
2、Installer类与自定义操作
Installer类是微软官方提供的一个自定义安装程序各个阶段操作的类,文档地址:
Installer 类 (System.Configuration.Install) | Microsoft Learn
在要添加的项目主输出的业务逻辑项目中,我这里是MyMixerTest,右键添加 - 新建项,选择 安装程序类:
命名为 MyInstaller.cs
,点击添加,进入代码视图,这个Install类继承自 System.Cconfiguration.Instal.Installer
,里面就一个构造函数。
安装程序不能完成的功能,都可以在这个类写代码来自己实现,其主要有以下几个方法:
// Override the 'Install' method.
public override void Install(IDictionary savedState)
{
base.Install(savedState);
}
// Override the 'Commit' method.
public override void Commit(IDictionary savedState)
{
base.Commit(savedState);
}
// Override the 'Rollback' method.
public override void Rollback(IDictionary savedState)
{
base.Rollback(savedState);
}
// Override the 'Uninstall' method.
public override void Uninstall(IDictionary savedState)
{
base.Uninstall(savedState);
}
需要特别注意的是,执行自定义操作的逻辑一定要考虑权限问题,否则会导致安装失败回滚。
以及一些衍生方法:
详见前文中的文档,具体示例可以参考:
我自己写了一些COM注册的方法,然后发现其实并没有生效,所以这一块还没研究明白。
20250520更新:研究明白了,详见下文;
写好自己需要的逻辑之后,右键安装项目 - view - 自定义操作:
可以看到对应的四个阶段,然后在需要添加操作的阶段右键 - 添加自定义操作,然后选择Installer类对应的项目主输出即可:
右键添加的项目主输出 - 属性窗口:
为了使自定义操作被视为安装组件,必须将InstallerClass属性设置为True(该属性默认为True),同时需要注意运行平台,要与自己项目的目标平台一致。
CustomActionData属性是自定义操作的附加数据,读取自定义操作的安装信息,需采用 /name=value
的格式。多个值必须以单个空格隔开: /name1=value1 /name2=value2
,如果值内有一个空格,则必须加引号: /name="a value"
。
多个值是空格隔开还是英文分号隔开我忘了,没测试。
例如可以在Installer类中获取安装路径: /targetdir="[TARGETDIR]\"
,在Installer类中引用这个参数: this.Context.Parameters["targetdir"]
。
注意!!!如果是获取安装目录,那么
/targetdir="[TARGETDIR]\"
最后的这个 反斜杠,一定不能少!!!否则安装的时候会报如下错误:
Error 1001.在初始化安装时发生异常:System.I0.FileNotFoundException:未能加载文件或程序集“file:///C:\Windows\system32\UnitOpera\MyMixerTestSetup\ComRegister.exe”或它的某一个依赖项。系统找不到指定的文件。
这里的TARGETDIR即就是定义的安装路径参数:
同样的,提交、回滚、卸载的自定义操作也可以在Installer类中写好逻辑之后将该项目主输出添加到对应的文件夹中即可。
20250520更新
在自定义操作页面,必须在四个阶段都要添加Installer类项目的主输出:
且如果要获取安装路径或者其他参数,必须在 每个阶段 都要进行传参:
然后对应的 ComInstaller.cs
示例代码如下:
// 自定义 dll 注册操作,OnCommitted 阶段是安装完毕之后提交阶段
// 权限为完全管理员
protected override void OnCommitted(IDictionary savedState)
{
base.OnCommitted(savedState);
MessageBox.Show("开始执行类库注册...");
// 拿到 dll 文件路径
var paths = GetDllPath();
var coPath = paths.GetCoPath;
var opPath = paths.GetOpPath;
// 执行 CapeOpen.dll 和 MyMixerTest.dll 注册
RegistrationServices regSvcs = new RegistrationServices();
Assembly coAsm = Assembly.LoadFrom(coPath); // CapeOpen.dll
regSvcs.RegisterAssembly(coAsm, AssemblyRegistrationFlags.SetCodeBase);
Thread.Sleep(1000);
Assembly opAsm = Assembly.LoadFrom(opPath); // MyMixerTest.dll
regSvcs.RegisterAssembly(opAsm, AssemblyRegistrationFlags.SetCodeBase);
MessageBox.Show("类库注册完毕!");
}
// 自定义 dll 反注册操作,OnBeforeUninstall 阶段是在执行卸载程序之前
// 权限为完全管理员
protected override void OnBeforeUninstall(IDictionary savedState)
{
base.OnBeforeUninstall(savedState);
MessageBox.Show("开始执行类库卸载...");
// 拿到 dll 文件路径
var paths = GetDllPath();
var coPath = paths.GetCoPath;
var opPath = paths.GetOpPath;
// 执行 CapeOpen.dll 和 MyMixerTest.dll 反注册
RegistrationServices regSvcs = new RegistrationServices();
Assembly opAsm = Assembly.LoadFrom(opPath); // MyMixerTest.dll
regSvcs.UnregisterAssembly(opAsm);
Thread.Sleep(1000);
Assembly coAsm = Assembly.LoadFrom(coPath); // CapeOpen.dll
regSvcs.UnregisterAssembly(coAsm);
MessageBox.Show("类库卸载完毕!");
}
// 获取安装路径和 DLL 文件路径
public (string GetCoPath, string GetOpPath) GetDllPath()
{
// 获取原始安装路径,它可能包含末尾反斜杠,也可能因 CustomActionData 解析问题而有双反斜杠
var setupDirRaw = this.Context.Parameters["targetdir"];
// 使用 GetFullPath 规范化路径。它可以处理双反斜杠、相对路径等问题。
string setupDir = Path.GetFullPath(setupDirRaw);
// 检查路径是否为空(虽然 Installer Project 通常会提供,但防御性检查是好的)
if (string.IsNullOrEmpty(setupDir))
{
// 在自定义操作中抛出异常会导致安装回滚
// 如果是在 Commit 或 OnAfterInstall,可能不会导致回滚,但仍然不推荐
// 更好的做法是记录错误或显示一个警告
MessageBox.Show("错误:未能获取安装路径!", "安装错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
//return; // 退出方法
}
// 经过 Path.GetFullPath 处理后,setupDir 通常会是一个规范化的路径,通常不包含多余的末尾反斜杠
// 但为了安全起见,确保它以反斜杠结尾(对于目录路径通常是需要的)
if (!setupDir.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
setupDir += Path.DirectorySeparatorChar;
}
MessageBox.Show($"读取到安装路径: {setupDir}");
// 使用 Path.Combine() 拼接目录和文件名。
// Path.Combine 会智能处理分隔符,如果 setupDir 已经是规范的,这里就不会出现双反斜杠。
string coFileName = "CapeOpen.dll";
string opFileName = "MyMixerTest.dll";
// 使用 Path.Combine() 拼接目录和文件名
string coPath = Path.Combine(setupDir, coFileName);
string opPath = Path.Combine(setupDir, opFileName);
var pathInfo = new string[]{
$"安装路径: {setupDir}",
$"CapeOpen.dll的完整路径: {coPath}",
$"MyMixerTest.dll的完整路径: {opPath}"
};
MessageBox.Show(string.Join(Environment.NewLine, pathInfo),
"路径信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
// 测试文件是否存在
if (File.Exists(coPath))
{
MessageBox.Show("文件存在。");
}
else
{
MessageBox.Show("文件不存在。");
}
return (coPath, opPath);
}
这里需要注意的是,COM 注册是要修改 ROOT 级别的注册表,所以在 OnAfterInstall 阶段会因为权限不足导致注册失败而安装回滚,OnCommitted 阶段就没有权限问题。
3、修改安装界面
右键安装项目 - view - 用户界面:
在这里可以对安装过程中的界面进行修改,比如增加用户协议界面,增加一些按钮什么的,或者删除安装文件夹,这样就只能安装在默认的路径中。
如果想隐藏用户权限安装,直接默认使用管理员安装,需要确保安装包项目的InsallAllUsers属性为True:
和用户界面中User级别安装文件夹的InstallAllUsersVisible属性为False即可:
就OK了,然后还可以添加一个自述文件或者说明,文件必须为 *.rtf
格式,这是微软自家的富文本类型的,直接编辑一个即可,然后在两个阶段都要添加上:
这里要注意的就是rtf格式文件必须要是word创建的,然后另存为rtf格式,如果直接用txt格式更改后缀是显示不出来文字的。
参考链接
- C# 打包项目,.生成安装包 - 剑小秀 - 博客园
- VS程序打包(使用InstallerProjects制作安装包)、自定义安装程序类、不用卸载旧包直接覆盖升级_vs code能使用install projects-CSDN博客
- C#制作打包安装程序,安装程序类使用_c#制作安装程序包-CSDN博客
- VS2022打包C#安装包(最新、最全)_vs2022安装包-CSDN博客
- C#打包辅助类Installer使用总结 - 卡萨丁·周 - 博客园
MSIX打包
注意,不推荐这种方式,只是提供一个思路!!!
在VS2022中,新建项目时有一个 Windows应用程序打包 项目,简介就是可以使用 MSIX 打包现有的 Windows 应用程序和游戏。
微软官方文档和操作步骤地址:
使用 Visual Studio 从源代码打包桌面应用 - MSIX | Microsoft Learn
在解决方案中添加一个新建项目,选择Windows应用程序打包(C#)项目:
点击下一步后输入名称,我这里设置为MyMixerTestMsix,然后会弹出来弹窗让确认目标平台和最低平台,根据自己的需要进行选择,选择好之后会进入项目界面:
如果项目中的依赖项的包有黄色感叹号,只是提示你没有安装对应的sdk包,在nuget管理器中更新或者安装一下即可。
在解决方案资源管理器中,右键刚才新建的程序打包项目中的依赖项,选择添加引用:
将自己要添加进来的项目输出打勾,然后点击确定:
需要特别注意的是,这里只能选择 能生成可执行文件 的项目进行打包,也就是说我这两个输出dll文件的类库项目是无法添加的。
所以我选择手动添加一个现有项来把两个项目生成的dll文件添加进来:
然后在应用程序处,选择主要的程序,我这里就是ComRegister,右键 - 设置为入口:
设为入口点的意思就是,程序安装完毕之后的自动创建的快捷方式所直接打开的程序。
其他程序例如我这里的ComUnRegister就只能去安装目录下进行手动执行。
设置完毕之后,在解决方案资源管理器中右键程序打包项目 - 发布 - 创建应用程序包:
然后选择旁加载 - 跳过证书验证 - 选择自己的目标平台 - 输出:
========== 发布: 1 个成功,0 个失败,0 个已跳过 ==========
========== 发布 于 21:08 完成,耗时 05.166 秒 ==========
已成功为 Debug (x64) 生成一个包。
========== 包: 1 个成功,0 个失败 ===========
已成功为 Debug (x64) 生成一个应用程序捆绑包。
========== 应用程序捆绑包: 成功 1 个,失败 0 个 ===========
然后就可以在对应的目录下找到生成的安装脚本和包了。
需要注意的是,我没测试这种方式是否可行,而且必须要开启开发者模式才可以安装。