更新到最新dotnet运行时版本
大石头 authored at 2024-08-29 18:23:57
29.13 KiB
LuckyClover
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Reflection;
using System.Security.Cryptography;
using Microsoft.Win32;
using NewLife;
using NewLife.Log;

namespace Installer;

public class NetRuntime
{
    #region 静态定义
    public const String Version6 = "6.0.33";
    public const String Version7 = "7.0.20";
    public const String Version8 = "8.0.8";
    #endregion

    #region 属性
    public String BaseUrl { get; set; }

    public Boolean Silent { get; set; }

    /// <summary>应用安装目录</summary>
    public String InstallPath { get; set; }

    /// <summary>缓存目录</summary>
    public String CachePath { get; set; } = "../Cache";

    /// <summary>是否强制。如果true,则已安装版本存在也强制安装。默认false</summary>
    public Boolean Force { get; set; }

    public IDictionary<String, String> Hashs { get; set; }

    public ITracer Tracer { get; set; }
    #endregion

    #region 核心方法
    public Boolean Install(String fileName, String baseUrl = null, String arg = null)
    {
        using var span = Tracer?.NewSpan($"Install-{Path.GetFileNameWithoutExtension(fileName)}", new { fileName, baseUrl });

        XTrace.WriteLine("下载 {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}";
            XTrace.WriteLine("正在下载:{0}", url);

            fullFile.EnsureDirectory(true);

            var http = new WebClient();
            http.DownloadFile(url, fullFile);
            XTrace.WriteLine("MD5: {0}", GetMD5(fullFile));
        }

        // 解压缩
        if (fileName.EndsWithIgnoreCase(".zip"))
        {
            XTrace.WriteLine("正在解压缩到:{0}", InstallPath);

            fullFile.AsFile().Extract(InstallPath.EnsureDirectory(false), true);

            return true;
        }

        // 执行安装包
        if (String.IsNullOrEmpty(arg)) arg = "/passive /promptrestart";
        if (!Silent) arg = null;

        XTrace.WriteLine("正在安装:{0} {1}", fullFile, arg);
        var p = Process.Start(fullFile, arg);
        if (p.WaitForExit(600_000))
        {
            if (p.ExitCode == 0)
                XTrace.WriteLine("安装完成!");
            else
                XTrace.WriteLine("安装失败!ExitCode={0}", p.ExitCode);
            Environment.ExitCode = p.ExitCode;
            return p.ExitCode == 0;
        }
        else
        {
            XTrace.WriteLine("安装超时!");
            Environment.ExitCode = 400;
            return false;
        }
    }

    static Version GetLast(IList<VerInfo> vers, String prefix = null, String suffix = null)
    {
        var ver = new Version();
        if (vers.Count > 0)
        {
            //XTrace.WriteLine("已安装版本:");
            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;
                }

                //XTrace.WriteLine(item.Name);
            }
            //XTrace.WriteLine("");
        }

        return ver;
    }

    public Boolean InstallNet40()
    {
        using var span = Tracer?.NewSpan(nameof(InstallNet40), null);

        var vers = new List<VerInfo>();
        vers.AddRange(Get1To45VersionFromRegistry());
        vers.AddRange(Get45PlusFromRegistry());

        var ver = GetLast(vers, null);

        // 目标版本
        var target = new Version("4.0");
        if (ver >= target)
        {
            XTrace.WriteLine("已安装最新版 v{0}", ver);
            return true;
        }


        var rs = Install("dotNetFx40_Full_x86_x64.exe", null);
        if (!rs)
        {
            XTrace.WriteLine("安装NET4失败,准备清理环境后重新安装!");

            CleanForNet();

            rs = Install("dotNetFx40_Full_x86_x64.exe", null);
        }

        return rs;
    }

    public Boolean InstallNet45()
    {
        using var span = Tracer?.NewSpan(nameof(InstallNet45), null);

        var vers = new List<VerInfo>();
        vers.AddRange(Get1To45VersionFromRegistry());
        vers.AddRange(Get45PlusFromRegistry());

        var ver = GetLast(vers, null);

        // 目标版本
        var target = new Version("4.5");
        if (ver >= target)
        {
            XTrace.WriteLine("已安装最新版 v{0}", ver);
            return false;
        }

        var rs = Install("NDP452-KB2901907-x86-x64-AllOS-ENU.exe");
        if (rs) Install("NDP452-KB2901907-x86-x64-AllOS-CHS.exe");

        if (!rs)
        {
            XTrace.WriteLine("安装NET45失败,准备清理环境后重新安装!");

            CleanForNet();

            rs = Install("NDP452-KB2901907-x86-x64-AllOS-ENU.exe");
            Install("NDP452-KB2901907-x86-x64-AllOS-CHS.exe");
        }

        return rs;
    }

    public Boolean InstallNet48()
    {
        using var span = Tracer?.NewSpan(nameof(InstallNet48), null);

        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 (ver >= target)
        {
            XTrace.WriteLine("已安装最新版 v{0}", ver);
            return true;
        }

        var is64 = IntPtr.Size == 8;

        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");
            if (rs) Install("ndp481-x86-x64-allos-chs.exe", null, "/passive /promptrestart /showfinalerror");
        }
        else
        {
            rs = Install("ndp48-x86-x64-allos-enu.exe", null, "/passive /promptrestart /showfinalerror");
            if (rs) 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)
    {
        using var span = Tracer?.NewSpan(nameof(InstallNet6), 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", target);
                    rs = Install($"aspnetcore-runtime-{target}-win-x64.exe", target);
                    break;
                case "desktop":
                    rs = Install($"windowsdesktop-runtime-{target}-win-x64.exe", target);
                    break;
                case "host":
                    rs = Install($"dotnet-hosting-{target}-win.exe", target);
                    break;
                default:
                    rs = Install($"dotnet-runtime-{target}-win-x64.exe", target);
                    break;
            }
        }
        else
        {
            switch (kind)
            {
                case "aspnet":
                    rs = Install($"dotnet-runtime-{target}-win-x86.exe", target);
                    rs = Install($"aspnetcore-runtime-{target}-win-x86.exe", target);
                    break;
                case "desktop":
                    rs = Install($"windowsdesktop-runtime-{target}-win-x86.exe", target);
                    break;
                case "host":
                    rs = Install($"dotnet-hosting-{target}-win.exe", target);
                    break;
                default:
                    rs = Install($"dotnet-runtime-{target}-win-x86.exe", target);
                    break;
            }
        }

        return rs;
    }

    public Boolean InstallNet7(String target, String? kind = null)
    {
        using var span = Tracer?.NewSpan(nameof(InstallNet7), 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)
        {
            XTrace.WriteLine("已安装最新版 v{0}", ver);
            return false;
        }

        var is64 = IntPtr.Size == 8;

        // win7需要vc2019运行时
        var osVer = Environment.OSVersion.Version;
        var isWin7 = osVer.Major == 6 && osVer.Minor == 1;
        if (isWin7)
        {
            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", target);
                    rs = Install($"aspnetcore-runtime-{target}-win-x64.exe", target);
                    break;
                case "desktop":
                    rs = Install($"windowsdesktop-runtime-{target}-win-x64.exe", target);
                    break;
                case "host":
                    rs = Install($"dotnet-hosting-{target}-win.exe", target);
                    break;
                default:
                    rs = Install($"dotnet-runtime-{target}-win-x64.exe", target);
                    break;
            }
        }
        else
        {
            switch (kind)
            {
                case "aspnet":
                    rs = Install($"dotnet-runtime-{target}-win-x86.exe", target);
                    rs = Install($"aspnetcore-runtime-{target}-win-x86.exe", target);
                    break;
                case "desktop":
                    rs = Install($"windowsdesktop-runtime-{target}-win-x86.exe", target);
                    break;
                case "host":
                    rs = Install($"dotnet-hosting-{target}-win.exe", target);
                    break;
                default:
                    rs = Install($"dotnet-runtime-{target}-win-x86.exe", target);
                    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)
    {
        using var span = Tracer?.NewSpan(nameof(InstallNet8), 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");
            }
        }

        if (is64)
        {
            switch (kind)
            {
                case "aspnet":
                    Install($"dotnet-runtime-{target}-win-x64.exe", target);
                    Install($"aspnetcore-runtime-{target}-win-x64.exe", target);
                    break;
                case "desktop":
                    Install($"windowsdesktop-runtime-{target}-win-x64.exe", target);
                    break;
                case "host":
                    Install($"dotnet-hosting-{target}-win.exe", target);
                    break;
                default:
                    Install($"dotnet-runtime-{target}-win-x64.exe", target);
                    break;
            }
        }
        else
        {
            switch (kind)
            {
                case "aspnet":
                    Install($"dotnet-runtime-{target}-win-x86.exe", target);
                    Install($"aspnetcore-runtime-{target}-win-x86.exe", target);
                    break;
                case "desktop":
                    Install($"windowsdesktop-runtime-{target}-win-x86.exe", target);
                    break;
                case "host":
                    Install($"dotnet-hosting-{target}-win.exe", target);
                    break;
                default:
                    Install($"dotnet-runtime-{target}-win-x86.exe", target);
                    break;
            }
        }

        return true;
    }

    /// <summary>获取所有已安装版本</summary>
    /// <returns></returns>
    public IList<VerInfo> GetVers()
    {
        var vers = new List<VerInfo>();
        vers.AddRange(Get1To45VersionFromRegistry());
        vers.AddRange(Get45PlusFromRegistry());
        vers.AddRange(GetNetCore());

        return vers;
    }

    public static IList<VerInfo> Get1To45VersionFromRegistry()
    {
        var list = new List<VerInfo>();
#if !NETCOREAPP3_1
        // 注册表查找 .NET Framework
        using var ndpKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\");

        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 = (String)versionKey.GetValue("Version", "");
            // 获取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
            {
                foreach (var subKeyName in versionKey.GetSubKeyNames())
                {
                    var subKey = versionKey.OpenSubKey(subKeyName);
                    ver = (String)subKey.GetValue("Version", "");
                    if (!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;
    }

    public static IList<VerInfo> Get45PlusFromRegistry()
    {
        var list = new List<VerInfo>();
#if !NETCOREAPP3_1
        const String subkey = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\";

        using var ndpKey = 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"));

        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;
    }

    public static IList<VerInfo> GetNetCore()
    {
        var list = new List<VerInfo>();

        var dir = "";
        if (Environment.OSVersion.Platform <= PlatformID.WinCE)
        {
            dir = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
            if (String.IsNullOrEmpty(dir)) return null;
            dir += "\\dotnet\\shared";
        }
        else if (Environment.OSVersion.Platform == PlatformID.Unix)
            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 (item.Name.Contains("AspNet"))
                        name += "-aspnet";
                    else if (item.Name.Contains("Desktop"))
                        name += "-desktop";
                    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);
        }

        // 通用处理
        {
            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 (ver.Contains("AspNet"))
                            name += "-aspnet";
                        else if (ver.Contains("Desktop"))
                            name += "-desktop";

                        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.WaitForExit(3_000))
            {
                process.Kill();
                return null;
            }

            return process.StandardOutput.ReadToEnd();
        }
        catch { return null; }
    }
    #endregion

    #region 辅助
    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);
        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>为安装NET清理环境</summary>
    public static void CleanForNet()
    {
        {
            // 解决“一般信任关系失败”问题

            Process.Start("regsvr32", "/s Softpub.dll").WaitForExit(15_000);
            Process.Start("regsvr32", "/s Wintrust.dll").WaitForExit(15_000);
            Process.Start("regsvr32", "/s Initpki.dll").WaitForExit(15_000);
            Process.Start("regsvr32", "/s Mssip32.dll").WaitForExit(15_000);
        }

        {
#if !NETCOREAPP3_1
            using var reg = Registry.CurrentUser.OpenSubKey(@"\Software\Microsoft\Windows\CurrentVersion\WinTrust\Trust Providers\Software Publishing", true);
            if (reg != null)
            {
                var v = (Int32)reg.GetValue("State");
                if (v != 0x23c00) reg.SetValue("State", 0x23c00);
            }
#endif
        }

        {
            // https://support.microsoft.com/zh-cn/sbs/windows/%E4%BF%AE%E5%A4%8D-windows-%E6%9B%B4%E6%96%B0%E9%94%99%E8%AF%AF-18b693b5-7818-5825-8a7e-2a4a37d6d787
            Process.Start("net", "stop bits").WaitForExit(15_000);
            Process.Start("net", "stop wuauserv").WaitForExit(15_000);

            var dirs = new[] {
                @"C:\Windows\SoftwareDistribution",
                @"C:\Windows\System32\catroot2",
            };
            foreach (var dir in dirs)
            {
                if (Directory.Exists(dir))
                {
                    try
                    {
                        Directory.Move(dir, dir + "_bak");
                    }
                    catch (Exception ex)
                    {
                        XTrace.WriteLine(ex.Message);
                    }
                }
            }

            Process.Start("net", "start bits").WaitForExit(15_000);
            Process.Start("net", "start wuauserv").WaitForExit(15_000);
        }

        {
            //!!! 以下代码,能够破坏掉已经安装好的.NET4.0
            //var files = new[] {
            //    @"C:\Windows\System32\msvcr100_clr0400.dll",
            //    @"C:\Windows\SysWOW64\msvcr100_clr0400.dll",
            //    @"C:\Windows\System32\msvcr110_clr0400.dll",
            //    @"C:\Windows\SysWOW64\msvcr110_clr0400.dll",
            //    @"C:\Windows\System32\msvcp110_clr0400.dll",
            //    @"C:\Windows\SysWOW64\msvcp110_clr0400.dll",
            //};
            //foreach (var file in files)
            //{
            //    if (File.Exists(file))
            //    {
            //        try
            //        {
            //            XTrace.WriteLine("清理:{0}", file);
            //            File.Move(file, file + ".bak");
            //        }
            //        catch
            //        {
            //            XTrace.WriteLine("重命名文件失败,请手工重命名:{0}", file);
            //        }
            //    }
            //}
        }

        InstallCert();
    }

    public static Boolean InstallCert()
    {
        XTrace.WriteLine("准备安装微软根证书");

        // 释放文件
        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);
            var buf = ms.ReadBytes(-1);

            File.WriteAllBytes(name.GetFullPath(), 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)
        {
            XTrace.WriteLine(ex.Message);
            return false;
        }
        finally
        {
            if (File.Exists(cert)) File.Delete(cert);
            if (File.Exists(exe)) File.Delete(exe);
        }
    }
    #endregion

    #region 日志
    /// <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);
    #endregion
}