VS制作C#程序windows安装程序
目录
C#编写一个Windows Services,需要做成安装程序
开发环境 Visualstudio 2019
一、安装插件
安装插件名称
Microsoft Visual Studio Installer Projects
VS 工具 → 扩展 → 管理扩展 → 联机 搜索 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]