WPF开发-检测软件的运行环境及运行库下载

前言

WPF开发的基于.NET环境的应用运行时必须要有对应的环境,有时程序还需要VC环境,所以我们可以做一个检测环境的程序。

不要在自己的程序内检测,没有环境我们的程序压根运行不起来,所以我们写的环境监测的程序所依赖的.NET环境一定要尽可能低,保证在Windows上都能运行,我这里基本只考虑Win7以上所以用的.NET3.5版本

环境监测

工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
using Microsoft.Win32;

using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;

namespace env_monitor.Utils
{
/// <summary>
/// 以下是判断vc++版本函数
/// </summary>
public class EnvCheckUtil
{
[DllImport("msi.dll")]
private static extern INSTALLSTATE MsiQueryProductState(string product);

private enum INSTALLSTATE
{
INSTALLSTATE_NOTUSED = -7, // component disabled
INSTALLSTATE_BADCONFIG = -6, // configuration data corrupt
INSTALLSTATE_INCOMPLETE = -5, // installation suspended or in progress
INSTALLSTATE_SOURCEABSENT = -4, // run from source, source is unavailable
INSTALLSTATE_MOREDATA = -3, // return buffer overflow
INSTALLSTATE_INVALIDARG = -2, // invalid function argument
INSTALLSTATE_UNKNOWN = -1, // unrecognized product or feature
INSTALLSTATE_BROKEN = 0, // broken
INSTALLSTATE_ADVERTISED = 1, // advertised feature
INSTALLSTATE_REMOVED = 1, // component being removed (action state, not settable)
INSTALLSTATE_ABSENT = 2, // uninstalled (or action state absent but clients remain)
INSTALLSTATE_LOCAL = 3, // installed on local drive
INSTALLSTATE_SOURCE = 4, // run from source, CD or net
INSTALLSTATE_DEFAULT = 5, // use default, local or source
}

public static bool IsInstallVc()
{
bool result = false;
if (MsiQueryProductState("{01FAEC41-B3BC-44F4-B185-5E8475AEB855}") == INSTALLSTATE.INSTALLSTATE_DEFAULT)
result = true;
else if (MsiQueryProductState("{77EB1EA9-8E1B-459D-8CDC-1984D0FF15B6}") == INSTALLSTATE.INSTALLSTATE_DEFAULT)
result = true;
return result;
}

/// <summary>
/// 根据名称判断应用是否安装
/// </summary>
/// <param name="displayName">
/// </param>
/// <returns>
/// </returns>
public static bool IsInstallVcByName(string displayName)
{
try
{
string bit32 = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";

RegistryKey localMachine = Registry.LocalMachine;
RegistryKey unistall = localMachine.OpenSubKey(bit32, true);

string[] subNames = unistall.GetSubKeyNames();

foreach (string subkey in subNames)
{
RegistryKey product = unistall.OpenSubKey(subkey);
if (product.GetValueNames().Any(n => n == "DisplayName") == true)
{
object tempDisplayNameObj = product.GetValue("DisplayName");
if (tempDisplayNameObj != null)
{
string tempDisplayName = tempDisplayNameObj.ToString();
if (tempDisplayName.Contains(displayName))
{
return true;
}
}
}
}
}
catch (Exception)
{
return false;
}

return false;
}

/// <summary>
/// 根据名称获取GUID
/// </summary>
/// <param name="displayName">
/// </param>
/// <returns>
/// </returns>
public static string GetProductGuid(string displayName)
{
string productGuid = string.Empty;

try
{
string bit32 = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";

RegistryKey localMachine = Registry.LocalMachine;
RegistryKey unistall = localMachine.OpenSubKey(bit32, true);

string[] subNames = unistall.GetSubKeyNames();

foreach (string subkey in subNames)
{
RegistryKey product = unistall.OpenSubKey(subkey);
if (product.GetValueNames().Any(n => n == "DisplayName") == true)
{
string tempDisplayName = product.GetValue("DisplayName").ToString();

if (tempDisplayName.Contains(displayName) && product.GetValueNames().Any(n => n == "UninstallString"))
{
string unitstallStr = product.GetValue("UninstallString").ToString();

string[] strs = unitstallStr.Split(new char[2] { '{', '}' });
if (strs.Length >= 2)
{
productGuid = strs[1];
break;
}
}
}
}
}
catch (Exception)
{
return productGuid;
}

return productGuid;
}

/// <summary>
/// 判断.Net Framework的Version是否符合需要 (.Net Framework 版本在2.0及以上)
/// </summary>
/// <param name="version">
/// 需要的版本 version = 4.5
/// </param>
/// <returns>
/// </returns>
public static bool IsInstallDotNet(string version)
{
string oldname = "0";
using (RegistryKey ndpKey =
RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, "").
OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\"))
{
foreach (string versionKeyName in ndpKey.GetSubKeyNames())
{
if (versionKeyName.StartsWith("v"))
{
RegistryKey versionKey = ndpKey.OpenSubKey(versionKeyName);
string newname = (string)versionKey.GetValue("Version", "");
if (string.Compare(newname, oldname) > 0)
{
oldname = newname;
}
if (newname != "")
{
continue;
}
foreach (string subKeyName in versionKey.GetSubKeyNames())
{
RegistryKey subKey = versionKey.OpenSubKey(subKeyName);
newname = (string)subKey.GetValue("Version", "");
if (string.Compare(newname, oldname) > 0)
{
oldname = newname;
}
}
}
}
}
return string.Compare(oldname, version) > 0 ? true : false;
}

/// <summary>
/// 打开系统软件卸载页面
/// </summary>
public static void openAppUninstallPage()
{
Process.Start("appwiz.cpl");
}
}
}

注意:

IsInstallVc方法中对应的产品ID根据自己实际情况设置,获取方式可以参考这篇文章的第一小节:https://www.psvmc.cn/article/2021-07-17-innosetup-check.html

使用

1
2
3
4
5
var isInstallVc = EnvCheckUtil.IsInstallVc();
Console.WriteLine("是否安装VC:" + isInstallVc);

var isInstallNet = EnvCheckUtil.IsInstallDotNet("4.5");
Console.WriteLine("是否安装.Net4.5:" + isInstallNet);

更好的VC检测方式

上面的方式是根据对应的产品ID进行检测,但是VC同样的产品对应的产品ID可能不一样。

下面这种方式根据库的名称进行检测,所以会更好点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ThreadPool.QueueUserWorkItem(
s =>
{
bool isInstallVC = EnvCheckUtil.IsInstallVcByName("Microsoft Visual C++ 2015-2019 Redistributable (x86)");
Console.WriteLine("isInstallVC:" + isInstallVC);
Dispatcher.Invoke(
new Action(
() =>
{
if (isInstallVC)
{
}
else
{
}
})
);
}
);

注意

使用这种方式程序必须要有管理员权限运行,否则会报异常,没有注册表的访问权限。

设置方式可以参考:https://www.psvmc.cn/article/2020-07-31-wpf-run-admin.html

环境库下载

工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
using System;
using System.ComponentModel;
using System.IO;
using System.Net;

namespace env_monitor.Utils
{
internal class FileDownloadUtil
{
private Action<long, long, int, string> progressAction = null;
private Action<string, string> finishAction = null;
private Action<string, string> errorAction = null;
private string savepathAll = "";
private string filetag = "";

public static string getFilename(string url)
{
string filename = "";
string[] url_arr = url.Split('/');
if (url_arr.Length > 0)
{
filename = url_arr[url_arr.Length - 1];
}

return filename;
}

public void downfile(
string url,
string savepath,
string filename,
string filetag,
Action<long, long, int, string> progressAction,
Action<string, string> finishAction,
Action<string, string> errorAction
)
{
this.filetag = filetag;
this.progressAction = progressAction;
this.finishAction = finishAction;
this.errorAction = errorAction;
if (filename == null || filename == "")
{
filename = getFilename(url);
}

if (filename == null || filename == "")
{
this.errorAction("没有文件名!", this.filetag);
return;
}
savepathAll = Path.Combine(savepath, filename);
WebClient client = new WebClient();
client.DownloadFileCompleted += client_DownloadFileCompleted;
client.DownloadProgressChanged += client_DownloadProgressChanged;
client.DownloadFileAsync(new Uri(url), savepathAll);
}

private void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
finishAction(savepathAll, filetag);
}

private void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
long Total = e.TotalBytesToReceive;
long Received = e.BytesReceived;
int ProgressPercentage = e.ProgressPercentage;
progressAction(Total, Received, ProgressPercentage, filetag);
}
}
}

下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
string mfileurl = "https://xhkjedu.oss-cn-huhehaote.aliyuncs.com/runtime/vc_redist.x86.exe";
string msavepath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string mfilename = FileDownloadUtil.getFilename(mfileurl);
string mfiletag = "testfile";
string mfilepathAll = System.IO.Path.Combine(msavepath, mfilename);
FileInfo fi = new FileInfo(mfilepathAll);
if (!fi.Exists)
{
new FileDownloadUtil().downfile(
mfileurl,
msavepath,
mfilename,
mfiletag,
(total, receive, progress, tag) =>
{
Console.WriteLine(tag + " 下载进度:" + progress);
},
(filepathAll, tag) =>
{
Console.WriteLine(tag + " 下载完成:" + filepathAll);
Process.Start(filepathAll);
},
(err, tag) =>
{
Console.WriteLine(tag + " 下载出错:" + err);
}
);
}
else
{
Console.WriteLine("文件已下载:" + mfilepathAll);
Process.Start(mfilepathAll);
}

这里提供两个下载地址

VC:https://xhkjedu.oss-cn-huhehaote.aliyuncs.com/runtime/vc_redist.x86.exe

.Net4.5.2:https://xhkjedu.oss-cn-huhehaote.aliyuncs.com/runtime/NDP452-KB2901907-x86-x64-AllOS-ENU.exe

安装

1
2
3
Process pr = new Process();//声明一个进程类对象
pr.StartInfo.FileName = mfilepathAll;
pr.Start();

还可以简单点:Process的静态方法Start();

1
2
3
4
//filiName 是你要运行的程序名,是物理路径
Process.Start(String fileName);
//filiName 是你要运行的程序名,是物理路径;arguments启动改程序时传递的命令行参数
Process.Start(String fileName,string arguments)

打开系统软件卸载界面

1
2
3
4
5
6
7
/// <summary>
/// 打开系统软件卸载页面
/// </summary>
public static void openAppUninstallPage()
{
Process.Start("appwiz.cpl");
}