using System.Diagnostics;
using System.Net;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using NewLife;
using NewLife.Log;
using NewLife.Remoting.Clients;
namespace Stardust.Managers;
/// <summary>dotNet运行时</summary>
public class NetRuntime
{
#region 属性
/// <summary>基准路径</summary>
public String BaseUrl { get; set; } = "http://x.newlifex.com/dotnet";
/// <summary>静默安装</summary>
public Boolean Silent { get; set; }
/// <summary>缓存目录</summary>
public String? CachePath { get; set; }
/// <summary>是否强制。如果true,则已安装版本存在也强制安装。默认false</summary>
public Boolean Force { get; set; }
/// <summary>文件哈希。用于校验下载文件的完整性</summary>
public IDictionary<String, String>? Hashs { get; set; }
/// <summary>事件客户端</summary>
public IEventProvider? EventProvider { get; set; }
#endregion
#region 构造
/// <summary>实例化</summary>
public NetRuntime()
{
var set = NewLife.Setting.Current;
if (!set.PluginServer.IsNullOrEmpty())
{
BaseUrl = set.PluginServer.Split(',').First().TrimEnd('/') + "/dotnet";
}
}
#endregion
#region 核心方法
/// <summary>下载文件</summary>
/// <param name="fileName"></param>
/// <param name="baseUrl"></param>
/// <returns></returns>
public String Download(String fileName, String? baseUrl = null)
{
WriteLog("下载 {0}", fileName);
var fullFile = fileName;
if (!String.IsNullOrEmpty(CachePath)) fullFile = Path.Combine(CachePath, fileName);
var hash = "";
if (Hashs == null || !Hashs.TryGetValue(fileName, out hash)) hash = null;
// 检查已存在文件的MD5哈希,不正确则重新下载
var fi = new FileInfo(fullFile);
if (fi.Exists && fi.Length < 1024 && !String.IsNullOrEmpty(hash) && GetMD5(fullFile) != hash)
{
fi.Delete();
fi = null;
}
if (fi == null || !fi.Exists)
{
if (String.IsNullOrEmpty(baseUrl))
baseUrl = BaseUrl?.TrimEnd('/');
else
baseUrl = BaseUrl?.TrimEnd('/') + '/' + baseUrl.TrimStart('/').TrimEnd('/');
var url = $"{baseUrl}/{fileName}";
WriteLog("正在下载:{0}", url);
var dir = Path.GetDirectoryName(fullFile);
if (!String.IsNullOrEmpty(dir) && !Directory.Exists(dir)) Directory.CreateDirectory(dir);
// 独立区域,下载完成后释放连接和文件句柄
{
#if NET6_0_OR_GREATER
using var http = new System.Net.Http.HttpClient();
var hs = http.GetStreamAsync(url).Result;
using var fs = new FileStream(fullFile, FileMode.CreateNew, FileAccess.Write);
hs.CopyTo(fs);
#else
using var http = new WebClient();
http.DownloadFile(url, fullFile);
#endif
}
WriteLog("MD5: {0}", GetMD5(fullFile));
//// 在windows系统上,下载完成以后,等待一会再安装,避免文件被占用(可能是安全扫描),提高安装成功率
//if (Runtime.Windows) Thread.Sleep(15_000);
}
return fullFile;
}
/// <summary>安装</summary>
/// <param name="fileName"></param>
/// <param name="baseUrl"></param>
/// <param name="arg"></param>
/// <returns></returns>
public Boolean Install(String fileName, String? baseUrl = null, String? arg = null)
{
var fullFile = Download(fileName, baseUrl);
if (String.IsNullOrEmpty(fullFile)) return false;
if (IsWindows && String.IsNullOrEmpty(arg)) arg = "/passive /promptrestart";
if (!Silent) arg = null;
WriteLog("正在安装:{0} {1}", fullFile, arg);
if (IsWindows)
return InstallOnWindows(fullFile, arg);
else
return InstallOnLinux(fullFile, arg);
}
Boolean InstallOnWindows(String fullFile, String? arg)
{
var p = Process.Start(fullFile, arg + "");
if (p.WaitForExit(600_000))
{
if (p.ExitCode == 0)
WriteLog("安装完成!");
else
WriteLog("安装失败!ExitCode={0}", p.ExitCode);
Environment.ExitCode = p.ExitCode;
return p.ExitCode == 0;
}
else
{
WriteLog("安装超时!");
Environment.ExitCode = 400;
return false;
}
}
Boolean InstallOnLinux(String fullFile, String? arg)
{
// 建立目录
var target = "/usr/share/dotnet";
//target.EnsureDirectory(false);
if (!Directory.Exists(target)) Directory.CreateDirectory(target);
// 解压缩
WriteLog($"解压缩[{fullFile}]到[{target}]");
Process.Start(new ProcessStartInfo("tar", $"-xzf {fullFile} -C {target}") { UseShellExecute = true });
// 建立链接
var link = "/usr/bin/dotnet";
if (Force && File.Exists(link)) File.Delete(link);
if (!File.Exists(link))
{
WriteLog($"创建[{target}/dotnet]的软连接到[{link}]");
Process.Start(new ProcessStartInfo("ln", $"{target}/dotnet {link} -s") { UseShellExecute = true });
}
WriteLog("安装完成!");
return true;
}
static Version GetLast(IList<VerInfo> vers, String? prefix = null, String? suffix = null)
{
var ver = new Version();
if (vers.Count > 0)
{
//WriteLog("已安装版本:");
foreach (var item in vers)
{
if ((String.IsNullOrEmpty(prefix) || item.Name.StartsWith(prefix)) &&
(String.IsNullOrEmpty(suffix) || item.Name.EndsWith(suffix)))
{
var str = item.Name.Trim('v');
var p = str.IndexOf('-');
if (p > 0) str = str.Substring(0, p);
var v = new Version(str);
if (v > ver) ver = v;
}
//WriteLog(item.Name);
}
//WriteLog("");
}
return ver;
}
/// <summary>安装.NET4.0</summary>
#if NET5_0_OR_GREATER
[SupportedOSPlatform("windows")]
#endif
public Boolean InstallNet40()
{
var vers = new List<VerInfo>();
vers.AddRange(Get1To45VersionFromRegistry());
var ver = GetLast(vers, "v4.0");
// 目标版本
var target = new Version("4.0");
if (!Force && ver >= target)
{
WriteLog("已安装最新版 v{0}", ver);
return false;
}
var rs = Install("dotNetFx40_Full_x86_x64.exe", null);
if (!rs)
{
// 解决“一般信任关系失败”问题
Process.Start("regsvr32", "/s Softpub.dll").WaitForExit(5_000);
Process.Start("regsvr32", "/s Wintrust.dll").WaitForExit(5_000);
Process.Start("regsvr32", "/s Initpki.dll").WaitForExit(5_000);
Process.Start("regsvr32", "/s Mssip32.dll").WaitForExit(5_000);
#if NET45_OR_GREATER || NET6_0_OR_GREATER
using var reg = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"\Software\Microsoft\Windows\CurrentVersion\WinTrust\Trust Providers\Software Publishing", true);
if (reg != null)
{
var v = (Int32)(reg.GetValue("State") ?? 0);
if (v != 0x23c00) reg.SetValue("State", 0x23c00);
}
#endif
rs = Install("dotNetFx40_Full_x86_x64.exe", null);
}
return rs;
}
/// <summary>安装.NET4.5</summary>
#if NET5_0_OR_GREATER
[SupportedOSPlatform("windows")]
#endif
public Boolean InstallNet45()
{
var vers = new List<VerInfo>();
vers.AddRange(Get1To45VersionFromRegistry());
var ver = GetLast(vers, "v4.5");
// 目标版本
var target = new Version("4.5");
if (!Force && ver >= target)
{
WriteLog("已安装最新版 v{0}", ver);
return false;
}
var rs = Install("NDP452-KB2901907-x86-x64-AllOS-ENU.exe");
Install("NDP452-KB2901907-x86-x64-AllOS-CHS.exe");
return rs;
}
/// <summary>安装.NET4.8</summary>
#if NET5_0_OR_GREATER
[SupportedOSPlatform("windows")]
#endif
public Boolean InstallNet48()
{
var vers = new List<VerInfo>();
vers.AddRange(Get1To45VersionFromRegistry());
vers.AddRange(Get45PlusFromRegistry());
var ver = GetLast(vers, null);
// 目标版本。win10起支持4.8.1
var osVer = Environment.OSVersion.Version;
var target = osVer.Major >= 10 ? new Version("4.8.1") : new Version("4.8");
if (!Force && ver >= target)
{
WriteLog("已安装最新版 v{0}", ver);
return false;
}
#if NET20
var is64 = IntPtr.Size == 8;
#else
var is64 = Environment.Is64BitOperatingSystem;
#endif
var isWin7 = osVer.Major == 6 && osVer.Minor == 1;
if (isWin7)
{
//if (is64)
//{
// Install("Windows6.1-KB3063858-x64.msu", "/win7", "/quiet /norestart");
//}
//else
//{
// Install("Windows6.1-KB3063858-x86.msu", "/win7", "/quiet /norestart");
//}
InstallCert();
}
// win10/win11 中安装 .NET4.8.1
var rs = false;
if (osVer.Major >= 10)
{
rs = Install("ndp481-x86-x64-allos-enu.exe", null, "/passive /promptrestart /showfinalerror");
Install("ndp481-x86-x64-allos-chs.exe", null, "/passive /promptrestart /showfinalerror");
}
else
{
rs = Install("ndp48-x86-x64-allos-enu.exe", null, "/passive /promptrestart /showfinalerror");
Install("ndp48-x86-x64-allos-chs.exe", null, "/passive /promptrestart /showfinalerror");
}
return rs;
}
/// <summary>安装.NET6.0</summary>
/// <param name="target">目标版本。包括子版本,如6.0.15</param>
/// <param name="kind">安装类型。如aspnet/desktop/host</param>
public Boolean InstallNet6(String target, String? kind = null)
{
var vers = GetNetCore();
var suffix = "";
if (!String.IsNullOrEmpty(kind)) suffix = "-" + kind;
var ver = GetLast(vers, "v6.0", suffix);
// 目标版本
var targetVer = new Version(target);
if (!Force && ver >= targetVer)
{
WriteLog("已安装最新版 v{0}", ver);
return false;
}
#if NET20
var is64 = IntPtr.Size == 8;
#else
var is64 = Environment.Is64BitOperatingSystem;
#endif
// win7需要vc2019运行时
var osVer = Environment.OSVersion.Version;
var isWin7 = osVer.Major == 6 && osVer.Minor == 1;
if (isWin7 && ver.Major < 6)
{
if (is64)
{
Install("Windows6.1-KB3063858-x64.msu", "/win7", "/quiet /norestart");
Install("VC_redist.x64.exe", "/vc2019", "/passive");
}
else
{
Install("Windows6.1-KB3063858-x86.msu", "/win7", "/quiet /norestart");
Install("VC_redist.x86.exe", "/vc2019", "/passive");
}
}
var rs = false;
if (is64)
{
switch (kind)
{
case "aspnet":
rs = Install($"dotnet-runtime-{target}-win-x64.exe");
rs = Install($"aspnetcore-runtime-{target}-win-x64.exe");
break;
case "desktop":
rs = Install($"windowsdesktop-runtime-{target}-win-x64.exe");
break;
case "host":
rs = Install($"dotnet-hosting-{target}-win.exe");
break;
default:
rs = Install($"dotnet-runtime-{target}-win-x64.exe");
break;
}
}
else
{
switch (kind)
{
case "aspnet":
rs = Install($"dotnet-runtime-{target}-win-x86.exe");
rs = Install($"aspnetcore-runtime-{target}-win-x86.exe");
break;
case "desktop":
rs = Install($"windowsdesktop-runtime-{target}-win-x86.exe");
break;
case "host":
rs = Install($"dotnet-hosting-{target}-win.exe");
break;
default:
rs = Install($"dotnet-runtime-{target}-win-x86.exe");
break;
}
}
return rs;
}
/// <summary>安装.NET7.0</summary>
/// <param name="target">目标版本。包括子版本,如6.0.15</param>
/// <param name="kind">安装类型。如aspnet/desktop/host</param>
public Boolean InstallNet7(String target, String? kind = null)
{
var vers = GetNetCore();
var suffix = "";
if (!String.IsNullOrEmpty(kind)) suffix = "-" + kind;
var ver = GetLast(vers, "v7.0", suffix);
// 目标版本
var targetVer = new Version(target);
if (!Force && ver >= targetVer)
{
WriteLog("已安装最新版 v{0}", ver);
return false;
}
#if NET20
var is64 = IntPtr.Size == 8;
#else
var is64 = Environment.Is64BitOperatingSystem;
#endif
// win7需要vc2019运行时
var osVer = Environment.OSVersion.Version;
var isWin7 = osVer.Major == 6 && osVer.Minor == 1;
if (isWin7 && ver.Major < 6)
{
if (is64)
{
Install("Windows6.1-KB3063858-x64.msu", "/win7", "/quiet /norestart");
Install("VC_redist.x64.exe", "/vc2019", "/passive");
}
else
{
Install("Windows6.1-KB3063858-x86.msu", "/win7", "/quiet /norestart");
Install("VC_redist.x86.exe", "/vc2019", "/passive");
}
}
var rs = false;
if (is64)
{
switch (kind)
{
case "aspnet":
rs = Install($"dotnet-runtime-{target}-win-x64.exe");
rs = Install($"aspnetcore-runtime-{target}-win-x64.exe");
break;
case "desktop":
rs = Install($"windowsdesktop-runtime-{target}-win-x64.exe");
break;
case "host":
rs = Install($"dotnet-hosting-{target}-win.exe");
break;
default:
rs = Install($"dotnet-runtime-{target}-win-x64.exe");
break;
}
}
else
{
switch (kind)
{
case "aspnet":
rs = Install($"dotnet-runtime-{target}-win-x86.exe");
rs = Install($"aspnetcore-runtime-{target}-win-x86.exe");
break;
case "desktop":
rs = Install($"windowsdesktop-runtime-{target}-win-x86.exe");
break;
case "host":
rs = Install($"dotnet-hosting-{target}-win.exe");
break;
default:
rs = Install($"dotnet-runtime-{target}-win-x86.exe");
break;
}
}
return rs;
}
/// <summary>安装.NET8.0</summary>
/// <param name="target">目标版本。包括子版本,如6.0.15</param>
/// <param name="kind">安装类型。如aspnet/desktop/host</param>
public Boolean InstallNet8(String target, String? kind = null)
{
var vers = GetNetCore();
var suffix = "";
if (!String.IsNullOrEmpty(kind)) suffix = "-" + kind;
var ver = GetLast(vers, "v8.0", suffix);
// 目标版本
var targetVer = new Version(target);
if (!Force && ver >= targetVer)
{
WriteLog("已安装最新版 v{0}", ver);
return false;
}
#if NET20
var is64 = IntPtr.Size == 8;
#else
var is64 = Environment.Is64BitOperatingSystem;
#endif
// win7需要vc2019运行时
var osVer = Environment.OSVersion.Version;
var isWin7 = osVer.Major == 6 && osVer.Minor == 1;
if (isWin7 && ver.Major < 6)
{
if (is64)
{
Install("Windows6.1-KB3063858-x64.msu", "/win7", "/quiet /norestart");
Install("VC_redist.x64.exe", "/vc2019", "/passive");
}
else
{
Install("Windows6.1-KB3063858-x86.msu", "/win7", "/quiet /norestart");
Install("VC_redist.x86.exe", "/vc2019", "/passive");
}
}
var rs = false;
if (is64)
{
switch (kind)
{
case "aspnet":
rs = Install($"dotnet-runtime-{target}-win-x64.exe");
rs = Install($"aspnetcore-runtime-{target}-win-x64.exe");
break;
case "desktop":
rs = Install($"windowsdesktop-runtime-{target}-win-x64.exe");
break;
case "host":
rs = Install($"dotnet-hosting-{target}-win.exe");
break;
default:
rs = Install($"dotnet-runtime-{target}-win-x64.exe");
break;
}
}
else
{
switch (kind)
{
case "aspnet":
rs = Install($"dotnet-runtime-{target}-win-x86.exe");
rs = Install($"aspnetcore-runtime-{target}-win-x86.exe");
break;
case "desktop":
rs = Install($"windowsdesktop-runtime-{target}-win-x86.exe");
break;
case "host":
rs = Install($"dotnet-hosting-{target}-win.exe");
break;
default:
rs = Install($"dotnet-runtime-{target}-win-x86.exe");
break;
}
}
return rs;
}
/// <summary>在Linux上安装.NET运行时</summary>
/// <param name="target">目标版本。包括子版本,如6.0.15</param>
/// <param name="kind">安装类型。如aspnet</param>
public void InstallNetOnLinux(String target, String? kind = null)
{
var vers = GetNetCore();
var suffix = "";
if (!String.IsNullOrEmpty(kind)) suffix = "-" + kind;
var ver = GetLast(vers, "v" + target.Substring(0, 3), suffix);
// 目标版本
var targetVer = new Version(target);
if (!Force && ver >= targetVer)
{
WriteLog("已安装最新版 v{0}", ver);
return;
}
#if NETSTANDARD ||NETCOREAPP
var arch = RuntimeInformation.ProcessArchitecture.ToString().ToLower();
// 在x64架构的centos系统中,需要检查更新libstdc++库
if (arch == "x64") UpgradeLibStdCxx();
// Alpine基于uClibc和Busybox,对应dotnet运行时带有musl
if (IsAlpine()) arch = "musl-" + arch;
switch (kind)
{
case "aspnet":
Install($"aspnetcore-runtime-{target}-linux-{arch}.tar.gz");
break;
default:
Install($"dotnet-runtime-{target}-linux-{arch}.tar.gz");
break;
}
#endif
}
/// <summary>更新Linux中的libstdc++库</summary>
public void UpgradeLibStdCxx()
{
#if NET40_OR_GREATER || NETCOREAPP
//var mi = MachineInfo.GetCurrent();
//var osName = (mi.OSName ?? "").ToLower();
var osName = Environment.OSVersion.Platform.ToString().ToLower();
var osr = "/etc/os-release";
if (File.Exists(osr))
{
var lines = File.ReadAllLines(osr);
foreach (var item in lines)
{
if (item.StartsWith("ID="))
{
osName = item.Substring("ID=".Length).Trim('"').ToLower();
break;
}
}
}
if (String.IsNullOrEmpty(osName) || !osName.Contains("centos") && !osName.Contains("linx")) return;
var verLib = new Version("6.0.26");
var dir = "/usr/lib64";
var libstd = $"{dir}/libstdc++.so.6";
if (!File.Exists(libstd))
{
dir = "/usr/lib/x86_64-linux-gnu";
libstd = $"{dir}/libstdc++.so.6";
}
var libsrc = $"{dir}/libstdc++.so.{verLib}";
if (File.Exists(libstd) && !File.Exists(libsrc))
{
// 检查lib64目录下是否有比6.0.26版本更高的libstdc++库,如果有,则不需要更新
Version? max = null;
foreach (var item in Directory.GetFiles(dir))
{
if (item.StartsWith("libstdc++.so."))
{
var name = item.Substring("libstdc++.so.".Length);
if (Version.TryParse(name, out var v) && (max == null || max < v))
max = v;
}
}
if (max == null || max < verLib)
{
WriteLog("更新libstdc++,原版本{0},更新到{1}", max, verLib);
var file = Download($"libstdcpp.{verLib}.so", null);
if (!String.IsNullOrEmpty(file) && File.Exists(file))
{
File.Copy(file, libsrc);
Process.Start("chmod", "+x " + libsrc).WaitForExit(5_000);
File.Delete(libstd);
Process.Start("ln", $"-s {libsrc} {libstd}").WaitForExit(5_000);
}
}
}
#endif
}
/// <summary>Alpine基于uClibc和Busybox,对应dotnet运行时带有musl</summary>
/// <returns></returns>
public Boolean IsAlpine()
{
var file = "/proc/version";
if (File.Exists(file))
{
var txt = File.ReadAllText(file);
if (txt.Contains("-musl-") || txt.Contains("Alpine")) return true;
}
var dir = "/lib";
if (!Directory.Exists(dir)) return false;
var fis = Directory.GetFiles(dir, "*-musl-*");
if (fis.Length > 0) return true;
return false;
}
/// <summary>获取所有已安装版本</summary>
/// <returns></returns>
public IList<VerInfo> GetVers()
{
var vers = new List<VerInfo>();
#if NET5_0_OR_GREATER
if (OperatingSystem.IsWindows())
{
vers.AddRange(Get1To45VersionFromRegistry());
vers.AddRange(Get45PlusFromRegistry());
}
#else
vers.AddRange(Get1To45VersionFromRegistry());
vers.AddRange(Get45PlusFromRegistry());
#endif
vers.AddRange(GetNetCore());
return vers;
}
/// <summary>获取Net45以下版本</summary>
/// <returns></returns>
#if NET5_0_OR_GREATER
[SupportedOSPlatform("windows")]
#endif
public static IList<VerInfo> Get1To45VersionFromRegistry()
{
var list = new List<VerInfo>();
if (!IsWindows) return list;
#if NET45_OR_GREATER || NET6_0_OR_GREATER
// 注册表查找 .NET Framework
using var ndpKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\");
if (ndpKey == null) return list;
foreach (var versionKeyName in ndpKey.GetSubKeyNames())
{
// 跳过 .NET Framework 4.5
if (versionKeyName == "v4") continue;
if (!versionKeyName.StartsWith("v")) continue;
var versionKey = ndpKey.OpenSubKey(versionKeyName);
// 获取 .NET Framework 版本
var ver = versionKey?.GetValue("Version", "") as String;
// 获取SP数字
var sp = versionKey?.GetValue("SP", "")?.ToString();
if (!String.IsNullOrEmpty(ver))
{
// 获取 installation flag, or an empty string if there is none.
var install = versionKey?.GetValue("Install", "")?.ToString();
if (String.IsNullOrEmpty(install)) // No install info; it must be in a child subkey.
list.Add(new VerInfo { Name = versionKeyName, Version = ver, Sp = sp });
else if (!String.IsNullOrEmpty(sp) && install == "1")
list.Add(new VerInfo { Name = versionKeyName, Version = ver, Sp = sp });
}
else if (versionKey != null)
{
foreach (var subKeyName in versionKey.GetSubKeyNames())
{
var subKey = versionKey.OpenSubKey(subKeyName);
ver = subKey?.GetValue("Version", "") as String;
if (subKey != null && !String.IsNullOrEmpty(ver))
{
var name = ver;
while (name.Length > 3 && name.Substring(name.Length - 2) == ".0")
name = name.Substring(0, name.Length - 2);
if (name[0] != 'v') name = 'v' + name;
sp = subKey.GetValue("SP", "")?.ToString();
var install = subKey.GetValue("Install", "")?.ToString();
if (String.IsNullOrEmpty(install)) //No install info; it must be later.
list.Add(new VerInfo { Name = name, Version = ver, Sp = sp });
else if (!String.IsNullOrEmpty(sp) && install == "1")
list.Add(new VerInfo { Name = name, Version = ver, Sp = sp });
else if (install == "1")
list.Add(new VerInfo { Name = name, Version = ver, Sp = sp });
}
}
}
}
#endif
return list;
}
/// <summary>获取Net45版本</summary>
/// <returns></returns>
#if NET5_0_OR_GREATER
[SupportedOSPlatform("windows")]
#endif
public static IList<VerInfo> Get45PlusFromRegistry()
{
var list = new List<VerInfo>();
if (!IsWindows) return list;
#if NET45_OR_GREATER || NET6_0_OR_GREATER
const String subkey = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\";
using var ndpKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(subkey);
if (ndpKey == null) return list;
//First check if there's an specific version indicated
var name = "";
var value = "";
var ver = ndpKey.GetValue("Version");
if (ver != null) name = ver.ToString();
var release = ndpKey.GetValue("Release");
if (release != null)
value = CheckFor45PlusVersion((Int32)(ndpKey.GetValue("Release") ?? 0));
if (String.IsNullOrEmpty(name)) name = value;
if (String.IsNullOrEmpty(value)) value = name;
if (!String.IsNullOrEmpty(name)) list.Add(new VerInfo { Name = "v" + value, Version = name });
// Checking the version using >= enables forward compatibility.
static String CheckFor45PlusVersion(Int32 releaseKey) => releaseKey switch
{
>= 533325 => "4.8.1",
>= 528040 => "4.8",
>= 461808 => "4.7.2",
>= 461308 => "4.7.1",
>= 460798 => "4.7",
>= 394802 => "4.6.2",
>= 394254 => "4.6.1",
>= 393295 => "4.6",
>= 379893 => "4.5.2",
>= 378675 => "4.5.1",
>= 378389 => "4.5",
_ => ""
};
#endif
return list;
}
/// <summary>获取NetCore版本</summary>
/// <returns></returns>
public static IList<VerInfo> GetNetCore(Boolean exact = true)
{
var list = new List<VerInfo>();
var dir = "";
if (IsWindows)
{
dir = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
if (String.IsNullOrEmpty(dir)) return list;
dir += "\\dotnet\\shared";
}
else
dir = "/usr/share/dotnet/shared";
var dic = new SortedDictionary<String, VerInfo>();
var di = new DirectoryInfo(dir);
if (di.Exists)
{
foreach (var item in di.GetDirectories())
{
foreach (var elm in item.GetDirectories())
{
var name = "v" + elm.Name;
if (exact)
{
if (item.Name.Contains("AspNet"))
name += "-aspnet";
else if (item.Name.Contains("Desktop"))
name += "-desktop";
}
else if (name.Contains("-"))
continue;
if (!dic.ContainsKey(name))
{
dic.Add(name, new VerInfo { Name = name, Version = item.Name + " " + elm.Name });
}
}
}
}
foreach (var item in dic)
{
list.Add(item.Value);
}
// 通用处理
if (list.Count == 0)
{
var infs = Execute("dotnet", "--list-runtimes")?.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
if (infs != null)
{
foreach (var line in infs)
{
var ss = line.Split(' ');
if (ss.Length >= 2)
{
var name = "v" + ss[1];
var ver = $"{ss[0]} {ss[1]}";
if (exact)
{
if (ver.Contains("AspNet"))
name += "-aspnet";
else if (ver.Contains("Desktop"))
name += "-desktop";
}
else if (name.Contains("-"))
continue;
VerInfo? vi = null;
foreach (var item in list)
{
if (item.Name == name)
{
vi = item;
break;
}
}
if (vi == null)
{
vi = new VerInfo { Name = name, Version = ver };
list.Add(vi);
}
if (vi.Version.Length < ver.Length) vi.Version = ver;
}
}
}
}
return list;
}
private static String? Execute(String cmd, String? arguments = null)
{
try
{
var psi = new ProcessStartInfo(cmd, arguments ?? "")
{
// UseShellExecute 必须 false,以便于后续重定向输出流
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
RedirectStandardOutput = true,
//RedirectStandardError = true,
};
var process = Process.Start(psi);
if (process == null) return null;
if (!process.WaitForExit(3_000))
{
process.Kill();
return null;
}
return process.StandardOutput.ReadToEnd();
}
catch { return null; }
}
#endregion
#region 辅助
/// <summary>是否Windows</summary>
public static Boolean IsWindows => Environment.OSVersion.Platform <= PlatformID.WinCE;
/// <summary>获取文件MD5</summary>
/// <param name="fileName"></param>
/// <returns></returns>
public static String GetMD5(String fileName)
{
var fi = new FileInfo(fileName);
var md5 = MD5.Create();
using var fs = fi.OpenRead();
var buf = md5.ComputeHash(fs);
var hex = BitConverter.ToString(buf).Replace("-", null);
return hex;
}
/// <summary>加载内嵌的文件MD5信息</summary>
/// <returns></returns>
public static IDictionary<String, String> LoadMD5s()
{
var asm = Assembly.GetExecutingAssembly();
var ms = asm.GetManifestResourceStream(typeof(NetRuntime).Namespace + ".res.md5.txt");
var dic = new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase);
if (ms == null) return dic;
using var reader = new StreamReader(ms);
while (!reader.EndOfStream)
{
var line = reader.ReadLine()?.Trim();
if (String.IsNullOrEmpty(line)) continue;
var ss = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (ss.Length >= 2)
{
dic[ss[0]] = ss[1];
}
}
return dic;
}
/// <summary>安装微软根证书</summary>
/// <returns></returns>
public Boolean InstallCert()
{
WriteLog("准备安装微软根证书");
// 释放文件
var asm = Assembly.GetExecutingAssembly();
var names = new[] { "CertMgr.Exe", "MicrosoftRootCertificateAuthority2011.cer" };
foreach (var name in names)
{
var ms = asm.GetManifestResourceStream(typeof(NetRuntime).Namespace + ".res." + name);
if (ms != null)
{
var buf = new Byte[ms.Length];
ms.Read(buf, 0, buf.Length);
File.WriteAllBytes(name, buf);
}
}
var exe = names[0];
var cert = names[1];
if (!File.Exists(exe) || !File.Exists(cert)) return false;
// 执行
try
{
var p = Process.Start(exe, $"-add \"{cert}\" -s -r localMachine AuthRoot");
return p.WaitForExit(30_000) && p.ExitCode == 0;
}
catch (Exception ex)
{
WriteLog(ex.Message);
return false;
}
finally
{
if (File.Exists(cert)) File.Delete(cert);
if (File.Exists(exe)) File.Delete(exe);
}
}
#endregion
#region 日志
///// <summary>性能追踪</summary>
//public ITracer Tracer { get; set; }
/// <summary>日志</summary>
public ILog Log { get; set; } = Logger.Null;
/// <summary>写日志</summary>
/// <param name="format"></param>
/// <param name="args"></param>
public void WriteLog(String format, params Object?[] args)
{
Log?.Info($"[NetRuntime]{format}", args);
var msg = (args == null || args.Length == 0) ? format : String.Format(format, args);
DefaultSpan.Current?.AppendTag(msg);
if (format.Contains("错误") || format.Contains("失败"))
EventProvider?.WriteErrorEvent(nameof(NetRuntime), msg);
else
EventProvider?.WriteInfoEvent(nameof(NetRuntime), msg);
}
#endregion
}
|