VS制作C#程序windows安装程序

目录
C#编写一个Windows Services,需要做成安装程序
开发环境 Visualstudio 2019
一、安装插件
安装插件名称
Microsoft Visual Studio Installer ProjectsVS 工具 → 扩展 → 管理扩展 → 联机 搜索 Microsoft Visual Studio Installer Projects

二、VS新建安装项目

输入项目名称,点击创建

创建好安装项目后如图所示

三、配置安装项目
1)配置文件系统 View → 文件系统
右键 安装项目 选择 View → 文件系统

右键 文件系统 界面中 Application Folder 选择 Add → 项目输出

弹出界面中选择要打包为安装程序 的项目

2)配置自定义操作 View → 自定义操作
右键 项目 选择 View → 自定义操作

在界面中 右键 Custom Actions 选择 添加自定义操作

弹出框中选择如图

第二步,选择主输出项目

添加后截图

3)配置用户界面,安装时添加配置, View → 用户界面
右键项目 选择 View → 用户界面

用户界面截图

右键Start 选择 添加对话框

选择文本框A

右键 Start → 文本框(A) 选择属性

文本框配置如图所示,意思很容易就能理解

添加后 回到 View → 自定义操作
打开 Install 主输出的属性窗口

设置 CustomActionData 属性 值为:
/IPAddress="[IPADDRESS]" /Port="[PORT]" /targetdir="[TARGETDIR]/"[***] 参数要和用户界面中设置的属性 Edit*Property 值相同
CustomActionData 属性的格式取决于自定义操作的类型。
对于作为安装组件的自定义操作(ProjectInstaller 类),“CustomActionData”属性采用 /name=value 形式。其中的每个名称都必须是唯一的,并且仅有一个值。多个值之间必须用一个空格隔开:/name1=value1 /name2=value2。如果值本身有一个空格,则必须在该值两侧加上引号:/name="a value"。
使用加括号的语法:/name=[PROPERTYNAME],可以传递 Windows Installer 属性。对于像“[TARGETDIR]”这样返回目录的 Windows Installer 属性,除了加括号外,还必须加引号和尾部反斜杠:/name="[TARGETDIR]""。

三、Windows Services 对安装时配置数据的处理
windows Services项目中添加 XmlHandle 类

XmlHandle.cs 类内容
using System;
using System.Text;
using System.Xml;
namespace GZ.NetFWManager.Server
{
class XmlHandle
{
public void updatePort(string path, ConfigModel data)
{
try
{
XmlTextWriter xmlWriter = new XmlTextWriter(path + "\\server.config", Encoding.UTF8);
xmlWriter.WriteStartDocument();
xmlWriter.WriteStartElement("Server");
xmlWriter.WriteStartElement("ip", "");
xmlWriter.WriteString(data.IPAddress);
xmlWriter.WriteEndElement();
xmlWriter.WriteStartElement("port", "");
xmlWriter.WriteString(data.Port.ToString());
xmlWriter.WriteEndElement();
xmlWriter.WriteEndDocument();
xmlWriter.Close();
}
catch (Exception ex)
{
}
}
public static ConfigModel getConfig()
{
ConfigModel data = new ConfigModel()
{
IPAddress = "0.0.0.0",
Port = 18500
};
try
{
XmlDocument doc = new XmlDocument();
doc.Load(AppDomain.CurrentDomain.BaseDirectory + "\\server.config");
XmlNode xnRoot = doc.SelectSingleNode("Server");
XmlNodeList xnl = xnRoot.ChildNodes;
foreach (XmlNode xn in xnl)
{
XmlElement xe = (XmlElement)xn;
byte[] bts = Encoding.UTF8.GetBytes(xe.Name);
switch (xe.Name)
{
case "ip":
data.IPAddress = xe.InnerText;
break;
case "port":
data.Port = int.Parse(xe.InnerText);
break;
}
}
}
catch (Exception ex)
{
throw ex;
}
return data;
}
}
public class ConfigModel
{
public string IPAddress { get; set; }
public int Port { get; set; }
}
}修改 ProjectInstaller.cs
主要做三件事:
- 安装前,获取用户安装界面中的配置信息,IP和端口
- 安装后,保存用户安装时的配置信息,根据用户配置的端口,添加一个防火墙入站规则
- 卸载前,删除程序安装时创建的额防火墙入站规则
using System.Collections;
using System.ComponentModel;
namespace GZ.NetFWManager.Server
{
[RunInstaller(true)]
public partial class ProjectInstaller : System.Configuration.Install.Installer
{
public ProjectInstaller()
{
InitializeComponent();
this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem;
// 设定服务名称
this.serviceInstaller1.ServiceName = "GZ.NetFWManager";
this.serviceInstaller1.DisplayName = "GZ防火墙管理";
// 服务描述
this.serviceInstaller1.Description = "Windows防火墙管理程序";
// 自动启动
this.serviceInstaller1.StartType = System.ServiceProcess.ServiceStartMode.Automatic;
// 延时启动
this.serviceInstaller1.DelayedAutoStart = true;
}
ConfigModel config = new ConfigModel();
/// <summary>
/// 安装前运行
/// </summary>
/// <param name="savedState"></param>
protected override void OnBeforeInstall(IDictionary savedState)
{
//base.OnBeforeInstall(savedState);
//从用户界面获取的参数
config.IPAddress = Context.Parameters["IPAddress"];
config.Port = int.Parse(Context.Parameters["Port"]);
// 添加防火墙
var helper = new NetFWManager.Server.Business.FWHelper();
helper.CreateNetFWManagerRule(config.Port);
}
/// <summary>
/// 安装后运行
/// </summary>
/// <param name="savedState"></param>
protected override void OnAfterInstall(IDictionary savedState)
{
base.OnAfterInstall(savedState);
// 保存配置文件
string appPath = Context.Parameters["targetdir"];
XmlHandle xml = new XmlHandle();
xml.updatePort(appPath, config);
// 启动服务
System.ServiceProcess.ServiceController sc = new System.ServiceProcess.ServiceController(serviceInstaller1.ServiceName);
if (sc != null)
{
sc.Start();
}
}
/// <summary>
/// 卸载前运行
/// </summary>
/// <param name="savedState"></param>
protected override void OnBeforeUninstall(IDictionary savedState)
{
// 删除防火墙
var helper = new NetFWManager.Server.Business.FWHelper();
helper.DeleteNetFWManageRule();
base.OnBeforeUninstall(savedState);
}
}
}
四、生成安装文件并测试
1)生成安装文件
编译安装项目,生成安装文件如图

2)安装过程
运行 GZ.NetFWManagerSetup.msi






3)安装后系统变化
服务 GZ防火墙管理 被添加

windows防火墙 会多一个入站规则

五、版本增量更新配置
默认设置情况下,安装前需要手动去卸载旧版本

选中项目按F4打开属性
不要通过右键去打开属性这个方式打开的面板中找不到设置属性


正确的做法是通过键盘F4来打开,界面如下


- 1. Author : 一般填公司名,会使用其作为软件安装目录名
- 2. ProductName : 应用程序名称,会使用其作为软件安装目录名
- 3. Localization : 指定软件运行地语种
- 4. TargetPlatform : 指定软件目标平台 x86 or x64
- 5. Version :发布版本号
- 6. InstallAllUsers : True 效果:“控制面板”程序中会显示公司的名称,安装时默认为“任何人”
- 7. ProductCode : 默认给出无需修改,当 Version 变动时会提示更改
- 8. RemovePreviousVersions:删除之前版本
注意:如果不设置RemovePreviousVersions =True,则会在控制面板中显示多个安装版本
修改版本号 Version,会提示更改ProductCode,点击是

然后可以正常安装

如果出现新版本安装后,dll和exe还是旧版本,没有更新的问题
发布新版本的时候,需要修改dll文件版本,在AssemblyInfo.cs
// 程序集版本
[assembly: AssemblyVersion("10.22.1108.0")]
// 文件版本,不设置该值,升级过程中不会替换文件
[assembly: AssemblyFileVersion("10.22.1108.0")]
// Application.UserAppDataPath 获取应用数据路径的时候,会以版本号来区分版本文件夹
[assembly:AssemblyInformationalVersion("10.0.0.0")]注意如果程序中用了 Application.UserAppDtaPath来获取应用数据存放路径,得到的路径是:C:\Users\yw357\AppData\Roaming\GZ\GZ.CodeGenerate\10.0.0.0,观察路径信息可以看到路径尾部包含了版本号信息,因此如果
经过测试,这个路径版本号获取规则为:
AssemblyInformationalVersion > AssemblyFileVersion >AssemblyVersion
六、关于安装路径选择

官方参考:https://learn.microsoft.com/zh-cn/windows/win32/msi/localappdatafolder
[AppDataFolder]\[ProductName]

[LocalAppDataFolder]\[ProductName]

[ProgramFilesFolder]\[ProductName]



