[feat] 增加安装助手,包括net20/net40版本,用于安装主流dotNet运行时大石头 authored at 2024-01-21 19:22:47
diff --git a/Installer/Advapi32.cs b/Installer/Advapi32.cs
new file mode 100644
index 0000000..6d568f7
--- /dev/null
+++ b/Installer/Advapi32.cs
@@ -0,0 +1,250 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Installer;
+
+internal class SafeServiceHandle : SafeHandle
+{
+ public override Boolean IsInvalid
+ {
+ get
+ {
+ if (!(DangerousGetHandle() == IntPtr.Zero))
+ {
+ return DangerousGetHandle() == new IntPtr(-1);
+ }
+ return true;
+ }
+ }
+
+ internal SafeServiceHandle(IntPtr handle)
+ : base(IntPtr.Zero, true) => SetHandle(handle);
+
+ protected override Boolean ReleaseHandle() => Advapi32.CloseServiceHandle(handle);
+}
+
+internal class Advapi32
+{
+ [Flags]
+ public enum ServiceType
+ {
+ Adapter = 0x4,
+ FileSystemDriver = 0x2,
+ InteractiveProcess = 0x100,
+ KernelDriver = 0x1,
+ RecognizerDriver = 0x8,
+ Win32OwnProcess = 0x10,
+ Win32ShareProcess = 0x20
+ }
+
+ internal enum ControlOptions
+ {
+ Stop = 1,
+ Pause = 2,
+ Continue = 3,
+ Interrogate = 4,
+ Shutdown = 5,
+
+ PowerEvent = 13,
+ SessionChange = 14,
+ TimeChange = 16,
+ }
+
+ internal class ServiceOptions
+ {
+ internal const Int32 SERVICE_QUERY_CONFIG = 1;
+
+ internal const Int32 SERVICE_CHANGE_CONFIG = 2;
+
+ internal const Int32 SERVICE_QUERY_STATUS = 4;
+
+ internal const Int32 SERVICE_ENUMERATE_DEPENDENTS = 8;
+
+ internal const Int32 SERVICE_START = 16;
+
+ internal const Int32 SERVICE_STOP = 32;
+
+ internal const Int32 SERVICE_PAUSE_CONTINUE = 64;
+
+ internal const Int32 SERVICE_INTERROGATE = 128;
+
+ internal const Int32 SERVICE_USER_DEFINED_CONTROL = 256;
+
+ internal const Int32 SERVICE_ALL_ACCESS = 983551;
+
+ internal const Int32 STANDARD_RIGHTS_DELETE = 65536;
+
+ internal const Int32 STANDARD_RIGHTS_REQUIRED = 983040;
+ }
+
+ internal class ServiceControllerOptions
+ {
+ internal const Int32 SC_ENUM_PROCESS_INFO = 0;
+
+ internal const Int32 SC_MANAGER_CONNECT = 1;
+
+ internal const Int32 SC_MANAGER_CREATE_SERVICE = 2;
+
+ internal const Int32 SC_MANAGER_ENUMERATE_SERVICE = 4;
+
+ internal const Int32 SC_MANAGER_LOCK = 8;
+
+ internal const Int32 SC_MANAGER_MODIFY_BOOT_CONFIG = 32;
+
+ internal const Int32 SC_MANAGER_QUERY_LOCK_STATUS = 16;
+
+ internal const Int32 SC_MANAGER_ALL = 983103;
+ }
+
+ internal struct SERVICE_STATUS
+ {
+ public ServiceType serviceType;
+
+ public ServiceControllerStatus currentState;
+
+ public ControlsAccepted controlsAccepted;
+
+ public Int32 win32ExitCode;
+
+ public Int32 serviceSpecificExitCode;
+
+ public Int32 checkPoint;
+
+ public Int32 waitHint;
+ }
+
+ public enum ServiceControllerStatus : Int32
+ {
+ ContinuePending = 5,
+ Paused = 7,
+ PausePending = 6,
+ Running = 4,
+ StartPending = 2,
+ Stopped = 1,
+ StopPending = 3
+ }
+
+ [Flags]
+ public enum ControlsAccepted : Int32
+ {
+ NetBindChange = 0x00000010,
+ ParamChange = 0x00000008,
+ CanPauseAndContinue = 0x00000002,
+ /// <summary>系统将关闭</summary>
+ PreShutdown = 0x00000100,
+ CanShutdown = 0x00000004,
+ CanStop = 0x00000001,
+
+ //supported only by HandlerEx
+ HardwareProfileChange = 0x00000020,
+ /// <summary>电源状态更改</summary>
+ CanHandlePowerEvent = 0x00000040,
+ /// <summary>会话状态发生更改</summary>
+ CanHandleSessionChangeEvent = 0x00000080,
+ /// <summary>系统时间已更改</summary>
+ TimeChange = 0x00000200,
+ /// <summary>注册为已发生事件的服务触发事件</summary>
+ TriggerEvent = 0x00000400,
+ /// <summary>用户已开始重新启动</summary>
+ UserModeReboot = 0x00000800
+ }
+
+ public delegate void ServiceMainCallback(Int32 argCount, IntPtr argPointer);
+
+ [StructLayout(LayoutKind.Sequential)]
+ public class SERVICE_TABLE_ENTRY
+ {
+ public IntPtr name;
+
+ public ServiceMainCallback callback;
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ public struct SERVICE_DESCRIPTION
+ {
+ public IntPtr Description;
+ }
+
+ public enum PowerBroadcastStatus
+ {
+ BatteryLow = 9,
+ OemEvent = 11,
+ PowerStatusChange = 10,
+ QuerySuspend = 0,
+ QuerySuspendFailed = 2,
+ ResumeAutomatic = 18,
+ ResumeCritical = 6,
+ ResumeSuspend = 7,
+ Suspend = 4
+ }
+
+ public enum SessionChangeReason
+ {
+ ConsoleConnect = 1,
+ ConsoleDisconnect,
+ RemoteConnect,
+ RemoteDisconnect,
+ SessionLogon,
+ SessionLogoff,
+ SessionLock,
+ SessionUnlock,
+ SessionRemoteControl
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ public class WTSSESSION_NOTIFICATION
+ {
+ public Int32 size;
+
+ public Int32 sessionId;
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ public class SERVICE_TIMECHANGE_INFO
+ {
+ public Int64 NewTime;
+ public Int64 OldTime;
+ }
+
+ public delegate Int32 ServiceControlCallbackEx(ControlOptions control, Int32 eventType, IntPtr eventData, IntPtr eventContext);
+
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ internal static extern Boolean CloseServiceHandle(IntPtr handle);
+
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ internal static extern unsafe Boolean ControlService(SafeServiceHandle serviceHandle, ControlOptions control, SERVICE_STATUS* pStatus);
+
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, EntryPoint = "OpenSCManagerW", SetLastError = true)]
+ internal static extern IntPtr OpenSCManager(String machineName, String databaseName, Int32 access);
+
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, EntryPoint = "OpenServiceW", SetLastError = true)]
+ internal static extern IntPtr OpenService(SafeServiceHandle databaseHandle, String serviceName, Int32 access);
+
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ internal static extern IntPtr CreateService(SafeServiceHandle databaseHandle, String lpSvcName, String lpDisplayName,
+ Int32 dwDesiredAccess, Int32 dwServiceType, Int32 dwStartType,
+ Int32 dwErrorControl, String lpPathName, String lpLoadOrderGroup,
+ Int32 lpdwTagId, String lpDependencies, String lpServiceStartName,
+ String lpPassword);
+
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ public static extern Int32 DeleteService(SafeServiceHandle serviceHandle);
+
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ internal static extern unsafe Boolean QueryServiceStatus(SafeServiceHandle serviceHandle, SERVICE_STATUS* pStatus);
+
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, EntryPoint = "StartServiceW", SetLastError = true)]
+ internal static extern Boolean StartService(SafeServiceHandle serviceHandle, Int32 argNum, IntPtr argPtrs);
+
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ public static extern unsafe Boolean SetServiceStatus(IntPtr serviceStatusHandle, SERVICE_STATUS* status);
+
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ public static extern IntPtr RegisterServiceCtrlHandlerEx(String serviceName, ServiceControlCallbackEx callback, IntPtr userData);
+
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ public static extern Boolean StartServiceCtrlDispatcher(IntPtr entry);
+
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ public static extern Boolean ChangeServiceConfig2(SafeServiceHandle serviceHandle, Int32 dwInfoLevel, IntPtr pInfo);
+}
\ No newline at end of file
diff --git a/Installer/app.manifest b/Installer/app.manifest
new file mode 100644
index 0000000..2e0ad7a
--- /dev/null
+++ b/Installer/app.manifest
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+ <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
+ <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+ <security>
+ <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
+ <!-- UAC 清单选项
+ 如果想要更改 Windows 用户帐户控制级别,请使用
+ 以下节点之一替换 requestedExecutionLevel 节点。
+
+ <requestedExecutionLevel level="asInvoker" uiAccess="false" />
+ <requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
+ <requestedExecutionLevel level="highestAvailable" uiAccess="false" />
+
+ 指定 requestedExecutionLevel 元素将禁用文件和注册表虚拟化。
+ 如果你的应用程序需要此虚拟化来实现向后兼容性,则移除此
+ 元素。
+ -->
+ <requestedExecutionLevel level="asInvoker" uiAccess="false" />
+ </requestedPrivileges>
+ </security>
+ </trustInfo>
+
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!-- 设计此应用程序与其一起工作且已针对此应用程序进行测试的
+ Windows 版本的列表。取消评论适当的元素,
+ Windows 将自动选择最兼容的环境。 -->
+
+ <!-- Windows Vista -->
+ <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
+
+ <!-- Windows 7 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
+
+ <!-- Windows 8 -->
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
+
+ <!-- Windows 8.1 -->
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
+
+ <!-- Windows 10 -->
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
+
+ </application>
+ </compatibility>
+
+ <!-- 指示该应用程序可感知 DPI 且 Windows 在 DPI 较高时将不会对其进行
+ 自动缩放。Windows Presentation Foundation (WPF)应用程序自动感知 DPI,无需
+ 选择加入。选择加入此设置的 Windows 窗体应用程序(面向 .NET Framework 4.6)还应
+ 在其 app.config 中将 "EnableWindowsFormsHighDpiAutoResizing" 设置设置为 "true"。
+
+ 将应用程序设为感知长路径。请参阅 https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
+
+ <application xmlns="urn:schemas-microsoft-com:asm.v3">
+ <windowsSettings>
+ <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
+ <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
+ </windowsSettings>
+ </application>
+
+
+ <!-- 启用 Windows 公共控件和对话框的主题(Windows XP 和更高版本) -->
+ <!--
+ <dependency>
+ <dependentAssembly>
+ <assemblyIdentity
+ type="win32"
+ name="Microsoft.Windows.Common-Controls"
+ version="6.0.0.0"
+ processorArchitecture="*"
+ publicKeyToken="6595b64144ccf1df"
+ language="*"
+ />
+ </dependentAssembly>
+ </dependency>
+ -->
+
+</assembly>
diff --git a/Installer/DownloadHelper.cs b/Installer/DownloadHelper.cs
new file mode 100644
index 0000000..704c697
--- /dev/null
+++ b/Installer/DownloadHelper.cs
@@ -0,0 +1,85 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using NewLife;
+using NewLife.Http;
+using NewLife.Log;
+
+namespace BuildTool;
+
+public class DownloadHelper
+{
+ public String BaseUrl { get; set; } = "http://x.newlifex.com/dotnet/";
+
+ public String CachePath { get; set; }
+
+ public async Task DownloadAllAsync()
+ {
+ XTrace.WriteLine("开始检测下载所有组件");
+
+ await Check("dotnet-runtime-7.0.9-win-x64.exe");
+ await Check("dotnet-runtime-7.0.9-win-x86.exe");
+ await Check("dotnet-runtime-6.0.19-win-x64.exe");
+ await Check("dotnet-runtime-6.0.19-win-x86.exe");
+
+ await Check("ndp481-x86-x64-allos-enu.exe");
+ await Check("ndp481-x86-x64-allos-chs.exe");
+ await Check("ndp48-x86-x64-allos-enu.exe");
+ await Check("ndp48-x86-x64-allos-chs.exe");
+
+ await Check("dotNetFx40_Full_x86_x64.exe");
+ await Check("NetFx20SP2_x86.exe");
+
+ await Check("vc2019/VC_redist.x64.exe");
+ await Check("vc2019/VC_redist.x86.exe");
+ await Check("win7/Windows6.1-KB3063858-x64.msu");
+ await Check("win7/Windows6.1-KB3063858-x86.msu");
+ await Check("win7/MsRootCert.zip");
+
+ await Check("aspnetcore-runtime-6.0.20-1-loongarch64.deb");
+ await Check("aspnetcore-runtime-7.0.9-linux-arm64.tar.gz");
+ await Check("aspnetcore-runtime-7.0.9-linux-x64.tar.gz");
+ await Check("dotnet-sdk-3.1.7-rc1-mips64el.deb");
+
+ XTrace.WriteLine("所有组件下载完成!");
+ }
+
+ public async Task DownloadAppsAsync()
+ {
+ XTrace.WriteLine("开始检测下载所有应用");
+
+ await Check("star/staragent80.zip");
+ await Check("star/staragent60.zip");
+ await Check("star/staragent45.zip");
+
+ XTrace.WriteLine("所有应用下载完成!");
+ }
+
+ async Task Check(String file, String url = null)
+ {
+ XTrace.WriteLine("检查:{0}", file);
+
+ var fi = CachePath.CombinePath(file);
+ if (File.Exists(fi)) return;
+
+ var p = Path.GetDirectoryName(fi);
+ if (!Directory.Exists(p)) Directory.CreateDirectory(p);
+
+ if (String.IsNullOrEmpty(url)) url = file;
+ if (!url.StartsWith("http")) url = BaseUrl.EnsureEnd("/") + url;
+
+ XTrace.WriteLine("下载:{0}", url);
+
+#if NET40_OR_GREATER || NETCOREAPP
+ using var client = new HttpClient();
+ await client.DownloadFileAsync(url, fi);
+#else
+ using var client = new WebClient();
+ client.DownloadFile(url, fi);
+#endif
+
+ XTrace.WriteLine("下载完成!");
+ }
+}
diff --git a/Installer/FrmMain.cs b/Installer/FrmMain.cs
new file mode 100644
index 0000000..ab186b1
--- /dev/null
+++ b/Installer/FrmMain.cs
@@ -0,0 +1,745 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Reflection;
+using System.Web;
+using System.Windows.Forms;
+using BuildTool;
+using NewLife;
+using NewLife.Log;
+using NewLife.Reflection;
+using NewLife.Serialization;
+using NewLife.Threading;
+using NewLife.Web;
+using Timer = System.Threading.Timer;
+#if !NET40
+using TaskEx = System.Threading.Tasks.Task;
+#endif
+
+namespace Installer;
+
+#if NET40
+#else
+//delegate void Func();
+delegate void Action();
+#endif
+
+public partial class FrmMain : Form
+{
+ private MachineInfo _info;
+ private Boolean _x64;
+ private String _installPath;
+ private String _serviceName = "StarAgent";
+ private ITracer _tracer;
+
+ public FrmMain()
+ {
+ InitializeComponent();
+
+#if NET45
+ _tracer = DefaultTracer.Instance;
+#endif
+ }
+
+ private void FrmMain_Load(Object sender, EventArgs e)
+ {
+ rtbLog.UseWinFormControl();
+
+ using var span = _tracer?.NewSpan(nameof(FrmMain_Load));
+
+ var asm = AssemblyX.Create(Assembly.GetExecutingAssembly());
+ Text = String.Format("{2} v{0} {1:HH:mm:ss}", asm.FileVersion, asm.Compile, Text);
+
+ var mi = new MachineInfo();
+ mi.Init();
+ span?.SetTag(mi);
+ XTrace.WriteLine(mi.ToJson(true));
+ _info = mi;
+
+ txtMachince.Text = Environment.MachineName;
+ txtUser.Text = Environment.UserName;
+
+ txtOS.Text = mi.OSName;
+ txtVersion.Text = mi.OSVersion;
+
+ txtProduct.Text = mi.Product;
+ txtSerial.Text = mi.Serial;
+
+ _x64 = IntPtr.Size == 8;
+
+ var f = "server.txt";
+ if (File.Exists(f))
+ {
+ var url = File.ReadAllText(f)?.Trim();
+ if (!url.IsNullOrWhiteSpace()) txtServer.Text = url;
+ }
+
+ _timer = new Timer(Detect, null, 0, 30_000);
+
+ ThreadPoolX.QueueUserWorkItem(DetectTarget);
+ }
+
+ #region 运行时环境
+ Timer _timer;
+ private Boolean _firstDetect;
+ private void Detect(Object state)
+ {
+ using var span = _tracer?.NewSpan(nameof(Detect));
+
+ var net = new NetRuntime();
+ var vers = net.GetVers();
+ span?.SetTag(vers);
+
+ if (!_firstDetect)
+ {
+ _firstDetect = true;
+
+ XTrace.WriteLine("已安装NET运行时版本:");
+ foreach (var item in vers)
+ {
+ if (String.IsNullOrEmpty(item.Sp))
+ XTrace.WriteLine("{0,-10} {1}", item.Name, item.Version);
+ else
+ XTrace.WriteLine("{0,-10} {1} Sp{2}", item.Name, item.Version, item.Sp);
+ }
+ }
+
+ this.Invoke(new Action(() =>
+ {
+ if (vers.Any(e => e.Name.StartsWith("v2.0")))
+ cbNET2.Checked = true;
+
+ if (vers.Any(e => e.Name.StartsWith("v4.0")))
+ cbNET4.Checked = true;
+ else
+ btnNET4.Enabled = true;
+
+ if (vers.Any(e => e.Name.StartsWith("v4.8")))
+ cbNET48.Checked = true;
+ else
+ btnNET48.Enabled = true;
+
+ if (vers.Any(e => e.Name.StartsWith("v6.")))
+ cbNET6.Checked = true;
+ else
+ btnNET6.Enabled = true;
+
+ if (vers.Any(e => e.Name.StartsWith("v7.")))
+ cbNET7.Checked = true;
+ else
+ btnNET7.Enabled = true;
+ }));
+
+ var svc = new WindowsService();
+ if (svc.IsInstalled(_serviceName))
+ {
+ this.Invoke(new Action(() =>
+ {
+ btnUninstall.Enabled = true;
+ }));
+
+ var bRunning = svc.IsRunning(_serviceName);
+ XTrace.WriteLine("软件{0}已安装!{1}", _serviceName, bRunning ? "正在运行!" : "未运行!");
+ }
+ else
+ {
+ XTrace.WriteLine("软件{0}未安装!", _serviceName);
+ }
+ }
+
+ NetRuntime GetNetRuntime(Boolean silent = false)
+ {
+ var net = new NetRuntime
+ {
+ BaseUrl = txtServer.Text.EnsureEnd("/") + "dotnet",
+ Silent = silent,
+ InstallPath = _installPath,
+ Hashs = NetRuntime.LoadMD5s(),
+ };
+#if NET45
+ net.Tracer = DefaultTracer.Instance;
+#endif
+
+ return net;
+ }
+
+ void ShowResult(Boolean rs)
+ {
+ if (rs)
+ MessageBox.Show("安装成功!", "", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ else
+ MessageBox.Show("安装失败!", "", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+
+ private void btnNET4_Click(Object sender, EventArgs e)
+ {
+ var net = GetNetRuntime();
+ ThreadPoolX.QueueUserWorkItem(() =>
+ {
+ var rs = net.InstallNet40();
+
+ _timer.Change(0, 30_000);
+
+ ShowResult(rs);
+ });
+ }
+
+ private void btnNET48_Click(Object sender, EventArgs e)
+ {
+ var net = GetNetRuntime();
+ ThreadPoolX.QueueUserWorkItem(() =>
+ {
+ var rs = net.InstallNet48();
+
+ _timer.Change(0, 30_000);
+
+ ShowResult(rs);
+ });
+ }
+
+ private void btnNET6_Click(Object sender, EventArgs e)
+ {
+ var net = GetNetRuntime();
+ ThreadPoolX.QueueUserWorkItem(() =>
+ {
+ var rs = net.InstallNet6();
+
+ _timer.Change(0, 30_000);
+
+ ShowResult(rs);
+ });
+ }
+
+ private void btnNET7_Click(Object sender, EventArgs e)
+ {
+ var net = GetNetRuntime();
+ ThreadPoolX.QueueUserWorkItem(() =>
+ {
+ var rs = net.InstallNet7();
+
+ _timer.Change(0, 30_000);
+
+ ShowResult(rs);
+ });
+ }
+
+ private void btnFinal_Click(Object sender, EventArgs e)
+ {
+ var rs = MessageBox.Show("实在安装不上NET4时的终极大招,可能对系统文件有损坏!\r\n修复过程中可能自动重启系统,是否确定继续?", "危险提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning);
+ if (rs == DialogResult.Cancel) return;
+
+ var net = GetNetRuntime();
+ ThreadPoolX.QueueUserWorkItem(() =>
+ {
+ net.Install("netfxrepairtool.exe", null, "/repair /passive");
+
+ _timer.Change(0, 30_000);
+
+ MessageBox.Show("专家模式修复完成,请重新安装.NET运行时!");
+ });
+ }
+ #endregion
+
+ #region 服务通信
+ private void btnNetwork_Click(Object sender, EventArgs e)
+ {
+ var url = txtServer.Text;
+ if (url.IsNullOrEmpty()) return;
+
+ using var span = _tracer?.NewSpan(nameof(btnNetwork_Click));
+ try
+ {
+ url = url.EnsureEnd("/") + "api";
+ var client = new WebClientX();
+ var html = client.GetHtml(url);
+ if (html.IsNullOrEmpty()) return;
+
+ XTrace.WriteLine(html);
+
+ MessageBox.Show("测试通过");
+ }
+ catch (Exception ex)
+ {
+ span?.SetError(ex, null);
+ throw;
+ }
+ }
+
+ private void btnDownloadAll_Click(Object sender, EventArgs e)
+ {
+ var net = GetNetRuntime();
+ XTrace.WriteLine("目标目录:{0}", net.CachePath);
+
+ // 检测缓存目录所有文件,如果哈希不对,直接删除
+ foreach (var item in net.CachePath.AsDirectory().GetFiles("*.*", SearchOption.AllDirectories))
+ {
+ if (net.Hashs.TryGetValue(item.Name, out var hash))
+ {
+ var md5 = NetRuntime.GetMD5(item.FullName);
+ if (hash != md5)
+ {
+ XTrace.WriteLine("文件MD5校验失败,删除:{0}", item.FullName);
+ File.Delete(item.FullName);
+ }
+ }
+ }
+
+ TaskEx.Run(async () =>
+ {
+ var helper = new DownloadHelper { CachePath = net.CachePath };
+ await helper.DownloadAllAsync();
+
+ helper.BaseUrl = net.BaseUrl;
+ await helper.DownloadAppsAsync();
+ });
+ }
+ #endregion
+
+ #region 自动安装软件
+ void DetectTarget()
+ {
+ using var span = _tracer?.NewSpan(nameof(DetectTarget));
+
+ var osVer = Environment.OSVersion.Version;
+ var str = "";
+
+ //A方案,NET7
+ //B方案,NET6
+ //C方案,NET4
+ //D方案,NET2
+
+ Invoke(new Action(() =>
+ {
+ var btns = new[] { btnInstallA, btnInstallB, btnInstallC, btnInstallD };
+ foreach (var btn in btns)
+ {
+ btn.Enabled = false;
+ }
+
+ // WinXP
+ if (osVer.Major <= 5)
+ {
+ btnInstallC.Enabled = true;
+ btnInstallD.Enabled = true;
+ str = "WinXP";
+ }
+ // Vista
+ else if (osVer.Major == 6 && osVer.Minor == 0)
+ {
+ btnInstallC.Enabled = true;
+ btnInstallD.Enabled = true;
+ str = "Vista";
+ }
+ else if (osVer.Major == 6 && osVer.Minor == 1)
+ {
+ // Win7
+ if (osVer.Revision <= 7600)
+ {
+ btnInstallC.Enabled = true;
+ btnInstallD.Enabled = true;
+ str = "Win7";
+ }
+ else
+ // Win7Sp1
+ {
+ btnInstallB.Enabled = true;
+ btnInstallC.Enabled = true;
+ btnInstallD.Enabled = true;
+ str = "Win7Sp1";
+ }
+ }
+ // Win10/Win11
+ else if (osVer.Major >= 10)
+ {
+ btnInstallA.Enabled = true;
+ btnInstallB.Enabled = true;
+ //btnInstallC.Enabled = true;
+ str = "Win10/Win11";
+ }
+ else
+ {
+ btnInstallA.Enabled = true;
+ btnInstallB.Enabled = true;
+ //btnInstallC.Enabled = true;
+ str = txtOS.Text;
+ }
+
+ // 在.NET4.0中,禁止安装D方案(.NET2.0)
+#if NET40
+ btnInstallD.Enabled = false;
+#endif
+
+ if (_x64)
+ str += "(64位)";
+ else
+ str += "(32位)";
+ txtTarget.Text = str;
+
+ span?.SetTag(str);
+ }));
+
+ // 检测磁盘,决定安装目录
+ var dis = DriveInfo.GetDrives();
+ if (dis.Any(e => e.Name == @"D:\" && e.DriveType == DriveType.Fixed))
+ _installPath = @"D:\agent\";
+ else if (dis.Any(e => e.Name == @"C:\" && e.DriveType == DriveType.Fixed))
+ _installPath = @"C:\agent\";
+ else
+ _installPath = dis[0].Name.CombinePath("agent");
+
+ XTrace.WriteLine("安装路径:{0}", _installPath);
+ }
+
+ /// <summary>安装服务。启动进程</summary>
+ /// <param name="svc"></param>
+ /// <returns></returns>
+ Boolean InstallService(WindowsService svc, String url)
+ {
+ var file = @"C:\agent\StarAgent.exe";
+ if (!File.Exists(file))
+ {
+ XTrace.WriteLine("找不到客户端文件");
+ return false;
+ }
+
+ // 强制使用外部url地址控制内部文件
+ var txt = Path.GetDirectoryName(file).CombinePath("server.txt");
+ //if (!File.Exists(txt) && !url.IsNullOrEmpty())
+ if (!url.IsNullOrEmpty())
+ {
+ File.WriteAllText(txt, url);
+ }
+
+ var si = new ProcessStartInfo
+ {
+ FileName = file,
+ Arguments = "-install",
+
+ WindowStyle = ProcessWindowStyle.Hidden,
+ CreateNoWindow = true,
+
+ UseShellExecute = true,
+ //RedirectStandardOutput = true,
+ //RedirectStandardError = true,
+ };
+
+ var p = new Process { StartInfo = si };
+ //p.OutputDataReceived += (s, e) => { if (e.Data != null) XTrace.WriteLine(e.Data); };
+ //p.ErrorDataReceived += (s, e) => { if (e.Data != null) XTrace.Log.Error(e.Data); };
+
+ p.Start();
+ //p.BeginOutputReadLine();
+ //p.BeginErrorReadLine();
+
+ if (!p.WaitForExit(15_000)) return false;
+
+ return true;
+ }
+
+ private void btnInstallA_Click(Object sender, EventArgs e)
+ {
+ var svc = new WindowsService();
+ if (!svc.IsAdministrator())
+ {
+ MessageBox.Show("需要管理员权限运行本软件!");
+ return;
+ }
+
+ groupBox3.Enabled = false;
+ var url = txtServer.Text;
+
+ var server = txtServer.Text;
+ var net = GetNetRuntime(true);
+ ThreadPoolX.QueueUserWorkItem(() =>
+ {
+ net.InstallNet7();
+ _timer.Change(1000, 30_000);
+ //net.InstallApp("gbt-agent7.zip");
+
+ try
+ {
+ if (svc.IsRunning(_serviceName)) svc.Stop(_serviceName);
+ }
+ catch (Exception ex)
+ {
+ XTrace.WriteException(ex);
+ }
+
+ var rs = net.Install("gbt-win10-mini.exe", null);
+ if (rs) rs = InstallService(svc, url);
+ _timer.Change(1000, 30_000);
+
+ PostLog(server, nameof(btnInstallA_Click));
+
+ if (!rs)
+ MessageBox.Show("安装失败!");
+ else
+ Invoke(new Action(() =>
+ {
+ groupBox3.Enabled = true;
+
+ MessageBox.Show("安装完成!");
+ }));
+ });
+ }
+
+ private void btnInstallB_Click(Object sender, EventArgs e)
+ {
+ var svc = new WindowsService();
+ if (!svc.IsAdministrator())
+ {
+ MessageBox.Show("需要管理员权限运行本软件!");
+ return;
+ }
+
+ groupBox3.Enabled = false;
+ var url = txtServer.Text;
+
+ var server = txtServer.Text;
+ var net = GetNetRuntime(true);
+ ThreadPoolX.QueueUserWorkItem(() =>
+ {
+ net.InstallNet6();
+ _timer.Change(1000, 30_000);
+ //net.InstallApp("gbt-agent6.zip");
+
+ try
+ {
+ if (svc.IsRunning(_serviceName)) svc.Stop(_serviceName);
+ }
+ catch (Exception ex)
+ {
+ XTrace.WriteException(ex);
+ }
+
+ var rs = false;
+ if (_x64)
+ rs = net.Install("gbt-win7-mini.exe", null);
+ else
+ rs = net.Install("gbt-win7x86-mini.exe", null);
+
+ if (rs) rs = InstallService(svc, url);
+ _timer.Change(1000, 30_000);
+
+ PostLog(server, nameof(btnInstallB_Click));
+
+ if (!rs)
+ MessageBox.Show("安装失败!");
+ else
+ Invoke(new Action(() =>
+ {
+ groupBox3.Enabled = true;
+
+ MessageBox.Show("安装完成!");
+ }));
+ });
+ }
+
+ private void btnInstallC_Click(Object sender, EventArgs e)
+ {
+ var svc = new WindowsService();
+ if (!svc.IsAdministrator())
+ {
+ MessageBox.Show("需要管理员权限运行本软件!");
+ return;
+ }
+
+ groupBox3.Enabled = false;
+ var url = txtServer.Text;
+
+ var server = txtServer.Text;
+ var net = GetNetRuntime(true);
+ ThreadPoolX.QueueUserWorkItem(() =>
+ {
+ net.InstallNet40();
+ _timer.Change(1000, 30_000);
+ //net.InstallApp("gbt-agent4.zip");
+
+ try
+ {
+ if (svc.IsRunning(_serviceName)) svc.Stop(_serviceName);
+ }
+ catch (Exception ex)
+ {
+ XTrace.WriteException(ex);
+ }
+
+ var rs = net.Install("gbt-winxp-mini.exe", null);
+ if (rs) rs = InstallService(svc, url);
+ _timer.Change(1000, 30_000);
+
+ PostLog(server, nameof(btnInstallC_Click));
+
+ if (!rs)
+ MessageBox.Show("安装失败!");
+ else
+ Invoke(new Action(() =>
+ {
+ groupBox3.Enabled = true;
+
+ MessageBox.Show("安装完成!");
+ }));
+ });
+ }
+
+ private void btnInstallD_Click(Object sender, EventArgs e)
+ {
+ var svc = new WindowsService();
+ if (!svc.IsAdministrator())
+ {
+ MessageBox.Show("需要管理员权限运行本软件!");
+ return;
+ }
+
+ groupBox3.Enabled = false;
+ var url = txtServer.Text;
+
+ var server = txtServer.Text;
+ var net = GetNetRuntime(true);
+ ThreadPoolX.QueueUserWorkItem(() =>
+ {
+ //net.InstallNet40();
+ //_timer.Change(1000, 30_000);
+
+ try
+ {
+ if (svc.IsRunning(_serviceName)) svc.Stop(_serviceName);
+ }
+ catch (Exception ex)
+ {
+ XTrace.WriteException(ex);
+ }
+
+ var rs = net.Install("gbt-agent20-mini.exe", null);
+ if (rs) rs = InstallService(svc, url);
+ _timer.Change(1000, 30_000);
+
+ PostLog(server, nameof(btnInstallD_Click));
+
+ if (!rs)
+ MessageBox.Show("安装失败!");
+ else
+ Invoke(new Action(() =>
+ {
+ groupBox3.Enabled = true;
+
+ MessageBox.Show("安装完成!");
+ }));
+ });
+ }
+
+ private void btnUninstall_Click(Object sender, EventArgs e)
+ {
+ var svc = new WindowsService();
+ if (!svc.IsAdministrator())
+ {
+ MessageBox.Show("需要管理员权限运行本软件!");
+ return;
+ }
+
+ var server = txtServer.Text;
+ using var span = _tracer?.NewSpan(nameof(btnUninstall_Click));
+ try
+ {
+ try
+ {
+ svc.Stop(_serviceName);
+ }
+ catch (Exception ex)
+ {
+ XTrace.WriteException(ex);
+ }
+ try
+ {
+ svc.Remove(_serviceName);
+ }
+ catch (Exception ex)
+ {
+ XTrace.WriteException(ex);
+ }
+
+ _timer.Change(1000, 30_000);
+
+ PostLog(server, nameof(btnUninstall_Click));
+ }
+ catch (Exception ex)
+ {
+ span?.SetError(ex, null);
+ //XTrace.WriteException(ex);
+ throw;
+ }
+ }
+ #endregion
+
+ #region 辅助
+ private void btnReport_Click(Object sender, EventArgs e)
+ {
+ PostLog(txtServer.Text, nameof(btnReport_Click));
+ }
+
+ void PostLog(String server, String action)
+ {
+ // 上传客户端日志
+ var di = @"C:\agent\Log".AsDirectory();
+ if (di.Exists)
+ {
+ var fi = di.GetFiles("*.log").OrderByDescending(e => e.LastWriteTime).FirstOrDefault();
+ if (fi != null) PostLog(server, fi, action);
+ }
+
+ // 上传自己的日志
+ di = @".\Log".AsDirectory();
+ if (di.Exists)
+ {
+ var fi = di.GetFiles("*.log").OrderByDescending(e => e.LastWriteTime).FirstOrDefault();
+ if (fi != null) PostLog(server, fi, action);
+ }
+ }
+
+ void PostLog(String server, FileInfo fi, String action)
+ {
+ using var fs = new FileStream(fi.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+
+ // 取最后100K
+ var retain = fs.Length - 100 * 1024;
+ if (retain > 0) fs.Seek(retain, SeekOrigin.Begin);
+
+ XTrace.WriteLine("发现日志:{0}", fi.FullName);
+ XTrace.WriteLine("日志大小:{0}", fs.Length);
+
+ var buf = new Byte[fs.Length - fs.Position];
+ var count = fs.Read(buf, 0, buf.Length);
+
+ buf = buf.Compress();
+ XTrace.WriteLine("压缩大小:{0}", buf.Length);
+
+#if NET40_OR_GREATER
+ var http = new HttpClient { BaseAddress = new Uri(server) };
+ var headers = http.DefaultRequestHeaders;
+#else
+ var http = new WebClientX { BaseAddress = server };
+ var headers = http.Headers;
+#endif
+ headers.Add("X-Action", action);
+ headers.Add("X-ClientId", _info.UUID);
+ headers.Add("X-MachineName", Environment.MachineName);
+ headers.Add("X-UserName", Environment.UserName);
+ headers.Add("X-IP", NetHelper.MyIP() + "");
+
+#if NET40_OR_GREATER
+ var content = new ByteArrayContent(buf);
+ var rs = http.PostAsync("/log", content).Result;
+ rs.EnsureSuccessStatusCode();
+ var html = rs.Content.ReadAsStringAsync().Result;
+#else
+ var rs = http.UploadData("/log", buf);
+ var html = rs.ToStr();
+#endif
+
+ XTrace.WriteLine("上传完成!{0}", html);
+ }
+ #endregion
+}
\ No newline at end of file
diff --git a/Installer/FrmMain.Designer.cs b/Installer/FrmMain.Designer.cs
new file mode 100644
index 0000000..73529ad
--- /dev/null
+++ b/Installer/FrmMain.Designer.cs
@@ -0,0 +1,687 @@
+namespace Installer
+{
+ partial class FrmMain
+ {
+ /// <summary>
+ /// 必需的设计器变量。
+ /// </summary>
+ private System.ComponentModel.IContainer components = null;
+
+ /// <summary>
+ /// 清理所有正在使用的资源。
+ /// </summary>
+ /// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows 窗体设计器生成的代码
+
+ /// <summary>
+ /// 设计器支持所需的方法 - 不要修改
+ /// 使用代码编辑器修改此方法的内容。
+ /// </summary>
+ private void InitializeComponent()
+ {
+ this.components = new System.ComponentModel.Container();
+ this.groupBox1 = new System.Windows.Forms.GroupBox();
+ this.txtSerial = new System.Windows.Forms.TextBox();
+ this.label5 = new System.Windows.Forms.Label();
+ this.txtProduct = new System.Windows.Forms.TextBox();
+ this.label6 = new System.Windows.Forms.Label();
+ this.txtVersion = new System.Windows.Forms.TextBox();
+ this.label3 = new System.Windows.Forms.Label();
+ this.txtOS = new System.Windows.Forms.TextBox();
+ this.label4 = new System.Windows.Forms.Label();
+ this.txtUser = new System.Windows.Forms.TextBox();
+ this.label2 = new System.Windows.Forms.Label();
+ this.txtMachince = new System.Windows.Forms.TextBox();
+ this.label1 = new System.Windows.Forms.Label();
+ this.groupBox2 = new System.Windows.Forms.GroupBox();
+ this.btnFinal = new System.Windows.Forms.Button();
+ this.cbNET48 = new System.Windows.Forms.CheckBox();
+ this.lbNET48 = new System.Windows.Forms.Label();
+ this.btnNET48 = new System.Windows.Forms.Button();
+ this.label9 = new System.Windows.Forms.Label();
+ this.btnNET7 = new System.Windows.Forms.Button();
+ this.btnNET6 = new System.Windows.Forms.Button();
+ this.btnNET4 = new System.Windows.Forms.Button();
+ this.cbNET7 = new System.Windows.Forms.CheckBox();
+ this.lbNET7 = new System.Windows.Forms.Label();
+ this.cbNET6 = new System.Windows.Forms.CheckBox();
+ this.lbNET6 = new System.Windows.Forms.Label();
+ this.cbNET4 = new System.Windows.Forms.CheckBox();
+ this.lbNET4 = new System.Windows.Forms.Label();
+ this.cbNET2 = new System.Windows.Forms.CheckBox();
+ this.lbNET2 = new System.Windows.Forms.Label();
+ this.groupBox3 = new System.Windows.Forms.GroupBox();
+ this.btnDownloadAll = new System.Windows.Forms.Button();
+ this.btnInstallD = new System.Windows.Forms.Button();
+ this.btnReport = new System.Windows.Forms.Button();
+ this.label10 = new System.Windows.Forms.Label();
+ this.btnUninstall = new System.Windows.Forms.Button();
+ this.btnInstallC = new System.Windows.Forms.Button();
+ this.btnInstallB = new System.Windows.Forms.Button();
+ this.btnInstallA = new System.Windows.Forms.Button();
+ this.txtTarget = new System.Windows.Forms.TextBox();
+ this.label8 = new System.Windows.Forms.Label();
+ this.btnNetwork = new System.Windows.Forms.Button();
+ this.txtServer = new System.Windows.Forms.TextBox();
+ this.label7 = new System.Windows.Forms.Label();
+ this.groupBox4 = new System.Windows.Forms.GroupBox();
+ this.rtbLog = new System.Windows.Forms.RichTextBox();
+ this.toolTip1 = new System.Windows.Forms.ToolTip(this.components);
+ this.groupBox1.SuspendLayout();
+ this.groupBox2.SuspendLayout();
+ this.groupBox3.SuspendLayout();
+ this.groupBox4.SuspendLayout();
+ this.SuspendLayout();
+ //
+ // groupBox1
+ //
+ this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
+ | System.Windows.Forms.AnchorStyles.Right)));
+ this.groupBox1.Controls.Add(this.txtSerial);
+ this.groupBox1.Controls.Add(this.label5);
+ this.groupBox1.Controls.Add(this.txtProduct);
+ this.groupBox1.Controls.Add(this.label6);
+ this.groupBox1.Controls.Add(this.txtVersion);
+ this.groupBox1.Controls.Add(this.label3);
+ this.groupBox1.Controls.Add(this.txtOS);
+ this.groupBox1.Controls.Add(this.label4);
+ this.groupBox1.Controls.Add(this.txtUser);
+ this.groupBox1.Controls.Add(this.label2);
+ this.groupBox1.Controls.Add(this.txtMachince);
+ this.groupBox1.Controls.Add(this.label1);
+ this.groupBox1.Location = new System.Drawing.Point(14, 14);
+ this.groupBox1.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.groupBox1.Name = "groupBox1";
+ this.groupBox1.Padding = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.groupBox1.Size = new System.Drawing.Size(1005, 157);
+ this.groupBox1.TabIndex = 0;
+ this.groupBox1.TabStop = false;
+ this.groupBox1.Text = "系统信息";
+ //
+ // txtSerial
+ //
+ this.txtSerial.Location = new System.Drawing.Point(623, 103);
+ this.txtSerial.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.txtSerial.Name = "txtSerial";
+ this.txtSerial.ReadOnly = true;
+ this.txtSerial.Size = new System.Drawing.Size(360, 28);
+ this.txtSerial.TabIndex = 11;
+ //
+ // label5
+ //
+ this.label5.AutoSize = true;
+ this.label5.Location = new System.Drawing.Point(541, 109);
+ this.label5.Name = "label5";
+ this.label5.Size = new System.Drawing.Size(80, 18);
+ this.label5.TabIndex = 10;
+ this.label5.Text = "序列号:";
+ //
+ // txtProduct
+ //
+ this.txtProduct.Location = new System.Drawing.Point(127, 103);
+ this.txtProduct.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.txtProduct.Name = "txtProduct";
+ this.txtProduct.ReadOnly = true;
+ this.txtProduct.Size = new System.Drawing.Size(360, 28);
+ this.txtProduct.TabIndex = 9;
+ //
+ // label6
+ //
+ this.label6.AutoSize = true;
+ this.label6.Location = new System.Drawing.Point(62, 109);
+ this.label6.Name = "label6";
+ this.label6.Size = new System.Drawing.Size(62, 18);
+ this.label6.TabIndex = 8;
+ this.label6.Text = "产品:";
+ //
+ // txtVersion
+ //
+ this.txtVersion.Location = new System.Drawing.Point(623, 66);
+ this.txtVersion.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.txtVersion.Name = "txtVersion";
+ this.txtVersion.ReadOnly = true;
+ this.txtVersion.Size = new System.Drawing.Size(360, 28);
+ this.txtVersion.TabIndex = 7;
+ //
+ // label3
+ //
+ this.label3.AutoSize = true;
+ this.label3.Location = new System.Drawing.Point(558, 72);
+ this.label3.Name = "label3";
+ this.label3.Size = new System.Drawing.Size(62, 18);
+ this.label3.TabIndex = 6;
+ this.label3.Text = "版本:";
+ //
+ // txtOS
+ //
+ this.txtOS.Location = new System.Drawing.Point(127, 66);
+ this.txtOS.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.txtOS.Name = "txtOS";
+ this.txtOS.ReadOnly = true;
+ this.txtOS.Size = new System.Drawing.Size(360, 28);
+ this.txtOS.TabIndex = 5;
+ //
+ // label4
+ //
+ this.label4.AutoSize = true;
+ this.label4.Location = new System.Drawing.Point(28, 72);
+ this.label4.Name = "label4";
+ this.label4.Size = new System.Drawing.Size(98, 18);
+ this.label4.TabIndex = 4;
+ this.label4.Text = "操作系统:";
+ //
+ // txtUser
+ //
+ this.txtUser.Location = new System.Drawing.Point(623, 29);
+ this.txtUser.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.txtUser.Name = "txtUser";
+ this.txtUser.ReadOnly = true;
+ this.txtUser.Size = new System.Drawing.Size(360, 28);
+ this.txtUser.TabIndex = 3;
+ //
+ // label2
+ //
+ this.label2.AutoSize = true;
+ this.label2.Location = new System.Drawing.Point(558, 35);
+ this.label2.Name = "label2";
+ this.label2.Size = new System.Drawing.Size(62, 18);
+ this.label2.TabIndex = 2;
+ this.label2.Text = "用户:";
+ //
+ // txtMachince
+ //
+ this.txtMachince.Location = new System.Drawing.Point(127, 29);
+ this.txtMachince.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.txtMachince.Name = "txtMachince";
+ this.txtMachince.ReadOnly = true;
+ this.txtMachince.Size = new System.Drawing.Size(360, 28);
+ this.txtMachince.TabIndex = 1;
+ //
+ // label1
+ //
+ this.label1.AutoSize = true;
+ this.label1.Location = new System.Drawing.Point(45, 35);
+ this.label1.Name = "label1";
+ this.label1.Size = new System.Drawing.Size(80, 18);
+ this.label1.TabIndex = 0;
+ this.label1.Text = "机器名:";
+ //
+ // groupBox2
+ //
+ this.groupBox2.Controls.Add(this.btnFinal);
+ this.groupBox2.Controls.Add(this.cbNET48);
+ this.groupBox2.Controls.Add(this.lbNET48);
+ this.groupBox2.Controls.Add(this.btnNET48);
+ this.groupBox2.Controls.Add(this.label9);
+ this.groupBox2.Controls.Add(this.btnNET7);
+ this.groupBox2.Controls.Add(this.btnNET6);
+ this.groupBox2.Controls.Add(this.btnNET4);
+ this.groupBox2.Controls.Add(this.cbNET7);
+ this.groupBox2.Controls.Add(this.lbNET7);
+ this.groupBox2.Controls.Add(this.cbNET6);
+ this.groupBox2.Controls.Add(this.lbNET6);
+ this.groupBox2.Controls.Add(this.cbNET4);
+ this.groupBox2.Controls.Add(this.lbNET4);
+ this.groupBox2.Controls.Add(this.cbNET2);
+ this.groupBox2.Controls.Add(this.lbNET2);
+ this.groupBox2.Location = new System.Drawing.Point(755, 179);
+ this.groupBox2.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.groupBox2.Name = "groupBox2";
+ this.groupBox2.Padding = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.groupBox2.Size = new System.Drawing.Size(263, 274);
+ this.groupBox2.TabIndex = 1;
+ this.groupBox2.TabStop = false;
+ this.groupBox2.Text = "运行时环境(专家级)";
+ //
+ // btnFinal
+ //
+ this.btnFinal.Location = new System.Drawing.Point(162, 49);
+ this.btnFinal.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.btnFinal.Name = "btnFinal";
+ this.btnFinal.Size = new System.Drawing.Size(82, 36);
+ this.btnFinal.TabIndex = 19;
+ this.btnFinal.Text = "补天";
+ this.toolTip1.SetToolTip(this.btnFinal, "实在安装不上NET4时的终极大招,可能对系统文件有损坏!");
+ this.btnFinal.UseVisualStyleBackColor = true;
+ this.btnFinal.Click += new System.EventHandler(this.btnFinal_Click);
+ //
+ // cbNET48
+ //
+ this.cbNET48.AutoSize = true;
+ this.cbNET48.Enabled = false;
+ this.cbNET48.Location = new System.Drawing.Point(114, 144);
+ this.cbNET48.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.cbNET48.Name = "cbNET48";
+ this.cbNET48.Size = new System.Drawing.Size(22, 21);
+ this.cbNET48.TabIndex = 18;
+ this.cbNET48.UseVisualStyleBackColor = true;
+ //
+ // lbNET48
+ //
+ this.lbNET48.AutoSize = true;
+ this.lbNET48.Location = new System.Drawing.Point(18, 145);
+ this.lbNET48.Name = "lbNET48";
+ this.lbNET48.Size = new System.Drawing.Size(71, 18);
+ this.lbNET48.TabIndex = 17;
+ this.lbNET48.Text = ".NET4.8";
+ //
+ // btnNET48
+ //
+ this.btnNET48.Enabled = false;
+ this.btnNET48.Location = new System.Drawing.Point(162, 136);
+ this.btnNET48.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.btnNET48.Name = "btnNET48";
+ this.btnNET48.Size = new System.Drawing.Size(82, 36);
+ this.btnNET48.TabIndex = 16;
+ this.btnNET48.Text = "安装";
+ this.toolTip1.SetToolTip(this.btnNET48, "安装.NET4.8");
+ this.btnNET48.UseVisualStyleBackColor = true;
+ this.btnNET48.Click += new System.EventHandler(this.btnNET48_Click);
+ //
+ // label9
+ //
+ this.label9.AutoSize = true;
+ this.label9.Location = new System.Drawing.Point(18, 25);
+ this.label9.Name = "label9";
+ this.label9.Size = new System.Drawing.Size(152, 18);
+ this.label9.TabIndex = 15;
+ this.label9.Text = "打勾表示已安装!";
+ //
+ // btnNET7
+ //
+ this.btnNET7.Enabled = false;
+ this.btnNET7.Location = new System.Drawing.Point(162, 222);
+ this.btnNET7.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.btnNET7.Name = "btnNET7";
+ this.btnNET7.Size = new System.Drawing.Size(82, 36);
+ this.btnNET7.TabIndex = 14;
+ this.btnNET7.Text = "安装";
+ this.toolTip1.SetToolTip(this.btnNET7, "安装.NET7.0");
+ this.btnNET7.UseVisualStyleBackColor = true;
+ this.btnNET7.Click += new System.EventHandler(this.btnNET7_Click);
+ //
+ // btnNET6
+ //
+ this.btnNET6.Enabled = false;
+ this.btnNET6.Location = new System.Drawing.Point(162, 179);
+ this.btnNET6.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.btnNET6.Name = "btnNET6";
+ this.btnNET6.Size = new System.Drawing.Size(82, 36);
+ this.btnNET6.TabIndex = 13;
+ this.btnNET6.Text = "安装";
+ this.toolTip1.SetToolTip(this.btnNET6, "安装.NET6.0");
+ this.btnNET6.UseVisualStyleBackColor = true;
+ this.btnNET6.Click += new System.EventHandler(this.btnNET6_Click);
+ //
+ // btnNET4
+ //
+ this.btnNET4.Enabled = false;
+ this.btnNET4.Location = new System.Drawing.Point(162, 92);
+ this.btnNET4.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.btnNET4.Name = "btnNET4";
+ this.btnNET4.Size = new System.Drawing.Size(82, 36);
+ this.btnNET4.TabIndex = 12;
+ this.btnNET4.Text = "安装";
+ this.toolTip1.SetToolTip(this.btnNET4, "安装.NET4.0");
+ this.btnNET4.UseVisualStyleBackColor = true;
+ this.btnNET4.Click += new System.EventHandler(this.btnNET4_Click);
+ //
+ // cbNET7
+ //
+ this.cbNET7.AutoSize = true;
+ this.cbNET7.Enabled = false;
+ this.cbNET7.Location = new System.Drawing.Point(114, 230);
+ this.cbNET7.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.cbNET7.Name = "cbNET7";
+ this.cbNET7.Size = new System.Drawing.Size(22, 21);
+ this.cbNET7.TabIndex = 7;
+ this.cbNET7.UseVisualStyleBackColor = true;
+ //
+ // lbNET7
+ //
+ this.lbNET7.AutoSize = true;
+ this.lbNET7.Location = new System.Drawing.Point(18, 232);
+ this.lbNET7.Name = "lbNET7";
+ this.lbNET7.Size = new System.Drawing.Size(71, 18);
+ this.lbNET7.TabIndex = 6;
+ this.lbNET7.Text = ".NET7.0";
+ //
+ // cbNET6
+ //
+ this.cbNET6.AutoSize = true;
+ this.cbNET6.Enabled = false;
+ this.cbNET6.Location = new System.Drawing.Point(114, 187);
+ this.cbNET6.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.cbNET6.Name = "cbNET6";
+ this.cbNET6.Size = new System.Drawing.Size(22, 21);
+ this.cbNET6.TabIndex = 5;
+ this.cbNET6.UseVisualStyleBackColor = true;
+ //
+ // lbNET6
+ //
+ this.lbNET6.AutoSize = true;
+ this.lbNET6.Location = new System.Drawing.Point(18, 188);
+ this.lbNET6.Name = "lbNET6";
+ this.lbNET6.Size = new System.Drawing.Size(71, 18);
+ this.lbNET6.TabIndex = 4;
+ this.lbNET6.Text = ".NET6.0";
+ //
+ // cbNET4
+ //
+ this.cbNET4.AutoSize = true;
+ this.cbNET4.Enabled = false;
+ this.cbNET4.Location = new System.Drawing.Point(114, 101);
+ this.cbNET4.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.cbNET4.Name = "cbNET4";
+ this.cbNET4.Size = new System.Drawing.Size(22, 21);
+ this.cbNET4.TabIndex = 3;
+ this.cbNET4.UseVisualStyleBackColor = true;
+ //
+ // lbNET4
+ //
+ this.lbNET4.AutoSize = true;
+ this.lbNET4.Location = new System.Drawing.Point(18, 102);
+ this.lbNET4.Name = "lbNET4";
+ this.lbNET4.Size = new System.Drawing.Size(71, 18);
+ this.lbNET4.TabIndex = 2;
+ this.lbNET4.Text = ".NET4.0";
+ //
+ // cbNET2
+ //
+ this.cbNET2.AutoSize = true;
+ this.cbNET2.Enabled = false;
+ this.cbNET2.Location = new System.Drawing.Point(114, 58);
+ this.cbNET2.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.cbNET2.Name = "cbNET2";
+ this.cbNET2.Size = new System.Drawing.Size(22, 21);
+ this.cbNET2.TabIndex = 1;
+ this.cbNET2.UseVisualStyleBackColor = true;
+ //
+ // lbNET2
+ //
+ this.lbNET2.AutoSize = true;
+ this.lbNET2.Location = new System.Drawing.Point(18, 59);
+ this.lbNET2.Name = "lbNET2";
+ this.lbNET2.Size = new System.Drawing.Size(71, 18);
+ this.lbNET2.TabIndex = 0;
+ this.lbNET2.Text = ".NET2.0";
+ //
+ // groupBox3
+ //
+ this.groupBox3.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
+ | System.Windows.Forms.AnchorStyles.Right)));
+ this.groupBox3.Controls.Add(this.btnDownloadAll);
+ this.groupBox3.Controls.Add(this.btnInstallD);
+ this.groupBox3.Controls.Add(this.btnReport);
+ this.groupBox3.Controls.Add(this.label10);
+ this.groupBox3.Controls.Add(this.btnUninstall);
+ this.groupBox3.Controls.Add(this.btnInstallC);
+ this.groupBox3.Controls.Add(this.btnInstallB);
+ this.groupBox3.Controls.Add(this.btnInstallA);
+ this.groupBox3.Controls.Add(this.txtTarget);
+ this.groupBox3.Controls.Add(this.label8);
+ this.groupBox3.Controls.Add(this.btnNetwork);
+ this.groupBox3.Controls.Add(this.txtServer);
+ this.groupBox3.Controls.Add(this.label7);
+ this.groupBox3.Location = new System.Drawing.Point(14, 179);
+ this.groupBox3.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.groupBox3.Name = "groupBox3";
+ this.groupBox3.Padding = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.groupBox3.Size = new System.Drawing.Size(735, 272);
+ this.groupBox3.TabIndex = 2;
+ this.groupBox3.TabStop = false;
+ this.groupBox3.Text = "软件安装(普通用户)";
+ //
+ // btnDownloadAll
+ //
+ this.btnDownloadAll.Location = new System.Drawing.Point(369, 85);
+ this.btnDownloadAll.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.btnDownloadAll.Name = "btnDownloadAll";
+ this.btnDownloadAll.Size = new System.Drawing.Size(102, 52);
+ this.btnDownloadAll.TabIndex = 23;
+ this.btnDownloadAll.Text = "下载缓存";
+ this.toolTip1.SetToolTip(this.btnDownloadAll, "在网络较好的电脑上把所需要使用的所有组件下载下来");
+ this.btnDownloadAll.UseVisualStyleBackColor = true;
+ this.btnDownloadAll.Click += new System.EventHandler(this.btnDownloadAll_Click);
+ //
+ // btnInstallD
+ //
+ this.btnInstallD.Enabled = false;
+ this.btnInstallD.Location = new System.Drawing.Point(454, 191);
+ this.btnInstallD.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.btnInstallD.Name = "btnInstallD";
+ this.btnInstallD.Size = new System.Drawing.Size(146, 68);
+ this.btnInstallD.TabIndex = 22;
+ this.btnInstallD.Text = "安装精简版\r\n(WinXP/Win7)";
+ this.btnInstallD.UseVisualStyleBackColor = true;
+ this.btnInstallD.Click += new System.EventHandler(this.btnInstallD_Click);
+ //
+ // btnReport
+ //
+ this.btnReport.Location = new System.Drawing.Point(587, 85);
+ this.btnReport.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.btnReport.Name = "btnReport";
+ this.btnReport.Size = new System.Drawing.Size(102, 52);
+ this.btnReport.TabIndex = 21;
+ this.btnReport.Text = "上传日志";
+ this.toolTip1.SetToolTip(this.btnReport, "直接向平台注册登记本计算机,仅用于无法安装客户端时使用");
+ this.btnReport.UseVisualStyleBackColor = true;
+ this.btnReport.Click += new System.EventHandler(this.btnReport_Click);
+ //
+ // label10
+ //
+ this.label10.AutoSize = true;
+ this.label10.ForeColor = System.Drawing.Color.Blue;
+ this.label10.Location = new System.Drawing.Point(6, 154);
+ this.label10.Name = "label10";
+ this.label10.Size = new System.Drawing.Size(710, 18);
+ this.label10.TabIndex = 16;
+ this.label10.Text = "以下按钮亮起即表示可以安装,优先左边,某个版本安装失败时可以尝试安装其它版本。";
+ //
+ // btnUninstall
+ //
+ this.btnUninstall.Enabled = false;
+ this.btnUninstall.Location = new System.Drawing.Point(615, 191);
+ this.btnUninstall.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.btnUninstall.Name = "btnUninstall";
+ this.btnUninstall.Size = new System.Drawing.Size(111, 68);
+ this.btnUninstall.TabIndex = 20;
+ this.btnUninstall.Text = "卸载软件";
+ this.btnUninstall.UseVisualStyleBackColor = true;
+ this.btnUninstall.Click += new System.EventHandler(this.btnUninstall_Click);
+ //
+ // btnInstallC
+ //
+ this.btnInstallC.Enabled = false;
+ this.btnInstallC.Location = new System.Drawing.Point(306, 191);
+ this.btnInstallC.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.btnInstallC.Name = "btnInstallC";
+ this.btnInstallC.Size = new System.Drawing.Size(135, 68);
+ this.btnInstallC.TabIndex = 19;
+ this.btnInstallC.Text = "安装WinXP版\r\n(兼容Win7)";
+ this.btnInstallC.UseVisualStyleBackColor = true;
+ this.btnInstallC.Click += new System.EventHandler(this.btnInstallC_Click);
+ //
+ // btnInstallB
+ //
+ this.btnInstallB.Enabled = false;
+ this.btnInstallB.Location = new System.Drawing.Point(158, 191);
+ this.btnInstallB.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.btnInstallB.Name = "btnInstallB";
+ this.btnInstallB.Size = new System.Drawing.Size(135, 68);
+ this.btnInstallB.TabIndex = 18;
+ this.btnInstallB.Text = "安装Win7版\r\n(兼容Win10)";
+ this.btnInstallB.UseVisualStyleBackColor = true;
+ this.btnInstallB.Click += new System.EventHandler(this.btnInstallB_Click);
+ //
+ // btnInstallA
+ //
+ this.btnInstallA.Enabled = false;
+ this.btnInstallA.Location = new System.Drawing.Point(8, 191);
+ this.btnInstallA.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.btnInstallA.Name = "btnInstallA";
+ this.btnInstallA.Size = new System.Drawing.Size(135, 68);
+ this.btnInstallA.TabIndex = 13;
+ this.btnInstallA.Text = "安装Win10版\r\n(兼容Win11)";
+ this.btnInstallA.UseVisualStyleBackColor = true;
+ this.btnInstallA.Click += new System.EventHandler(this.btnInstallA_Click);
+ //
+ // txtTarget
+ //
+ this.txtTarget.Location = new System.Drawing.Point(124, 92);
+ this.txtTarget.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.txtTarget.Name = "txtTarget";
+ this.txtTarget.ReadOnly = true;
+ this.txtTarget.Size = new System.Drawing.Size(222, 28);
+ this.txtTarget.TabIndex = 12;
+ //
+ // label8
+ //
+ this.label8.AutoSize = true;
+ this.label8.Location = new System.Drawing.Point(21, 98);
+ this.label8.Name = "label8";
+ this.label8.Size = new System.Drawing.Size(98, 18);
+ this.label8.TabIndex = 17;
+ this.label8.Text = "当前系统:";
+ //
+ // btnNetwork
+ //
+ this.btnNetwork.Location = new System.Drawing.Point(587, 25);
+ this.btnNetwork.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.btnNetwork.Name = "btnNetwork";
+ this.btnNetwork.Size = new System.Drawing.Size(102, 52);
+ this.btnNetwork.TabIndex = 16;
+ this.btnNetwork.Text = "网络检测";
+ this.toolTip1.SetToolTip(this.btnNetwork, "检测本机到平台的网络畅通情况");
+ this.btnNetwork.UseVisualStyleBackColor = true;
+ this.btnNetwork.Click += new System.EventHandler(this.btnNetwork_Click);
+ //
+ // txtServer
+ //
+ this.txtServer.Location = new System.Drawing.Point(117, 36);
+ this.txtServer.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.txtServer.Name = "txtServer";
+ this.txtServer.Size = new System.Drawing.Size(427, 28);
+ this.txtServer.TabIndex = 1;
+ this.txtServer.Text = "http://s.newlifex.com:6600";
+ //
+ // label7
+ //
+ this.label7.AutoSize = true;
+ this.label7.Location = new System.Drawing.Point(21, 42);
+ this.label7.Name = "label7";
+ this.label7.Size = new System.Drawing.Size(80, 18);
+ this.label7.TabIndex = 0;
+ this.label7.Text = "服务器:";
+ //
+ // groupBox4
+ //
+ this.groupBox4.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
+ | System.Windows.Forms.AnchorStyles.Left)
+ | System.Windows.Forms.AnchorStyles.Right)));
+ this.groupBox4.Controls.Add(this.rtbLog);
+ this.groupBox4.Location = new System.Drawing.Point(14, 460);
+ this.groupBox4.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.groupBox4.Name = "groupBox4";
+ this.groupBox4.Padding = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.groupBox4.Size = new System.Drawing.Size(1005, 409);
+ this.groupBox4.TabIndex = 3;
+ this.groupBox4.TabStop = false;
+ this.groupBox4.Text = "日志";
+ //
+ // rtbLog
+ //
+ this.rtbLog.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.rtbLog.Location = new System.Drawing.Point(3, 25);
+ this.rtbLog.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.rtbLog.Name = "rtbLog";
+ this.rtbLog.Size = new System.Drawing.Size(999, 380);
+ this.rtbLog.TabIndex = 0;
+ this.rtbLog.Text = "";
+ //
+ // FrmMain
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 18F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(1032, 883);
+ this.Controls.Add(this.groupBox4);
+ this.Controls.Add(this.groupBox3);
+ this.Controls.Add(this.groupBox2);
+ this.Controls.Add(this.groupBox1);
+ this.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
+ this.Name = "FrmMain";
+ this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+ this.Text = "dotNet安装助手(新生命团队)";
+ this.Load += new System.EventHandler(this.FrmMain_Load);
+ this.groupBox1.ResumeLayout(false);
+ this.groupBox1.PerformLayout();
+ this.groupBox2.ResumeLayout(false);
+ this.groupBox2.PerformLayout();
+ this.groupBox3.ResumeLayout(false);
+ this.groupBox3.PerformLayout();
+ this.groupBox4.ResumeLayout(false);
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.GroupBox groupBox1;
+ private System.Windows.Forms.GroupBox groupBox2;
+ private System.Windows.Forms.Label label1;
+ private System.Windows.Forms.TextBox txtUser;
+ private System.Windows.Forms.Label label2;
+ private System.Windows.Forms.TextBox txtMachince;
+ private System.Windows.Forms.TextBox txtSerial;
+ private System.Windows.Forms.Label label5;
+ private System.Windows.Forms.TextBox txtProduct;
+ private System.Windows.Forms.Label label6;
+ private System.Windows.Forms.TextBox txtVersion;
+ private System.Windows.Forms.Label label3;
+ private System.Windows.Forms.TextBox txtOS;
+ private System.Windows.Forms.Label label4;
+ private System.Windows.Forms.CheckBox cbNET2;
+ private System.Windows.Forms.Label lbNET2;
+ private System.Windows.Forms.CheckBox cbNET7;
+ private System.Windows.Forms.Label lbNET7;
+ private System.Windows.Forms.CheckBox cbNET6;
+ private System.Windows.Forms.Label lbNET6;
+ private System.Windows.Forms.CheckBox cbNET4;
+ private System.Windows.Forms.Label lbNET4;
+ private System.Windows.Forms.Button btnNET4;
+ private System.Windows.Forms.Button btnNET6;
+ private System.Windows.Forms.Button btnNET7;
+ private System.Windows.Forms.GroupBox groupBox3;
+ private System.Windows.Forms.TextBox txtServer;
+ private System.Windows.Forms.Label label7;
+ private System.Windows.Forms.Button btnNetwork;
+ private System.Windows.Forms.GroupBox groupBox4;
+ private System.Windows.Forms.RichTextBox rtbLog;
+ private System.Windows.Forms.TextBox txtTarget;
+ private System.Windows.Forms.Label label8;
+ private System.Windows.Forms.Button btnInstallB;
+ private System.Windows.Forms.Button btnInstallA;
+ private System.Windows.Forms.Button btnInstallC;
+ private System.Windows.Forms.Button btnUninstall;
+ private System.Windows.Forms.Label label9;
+ private System.Windows.Forms.Label label10;
+ private System.Windows.Forms.ToolTip toolTip1;
+ private System.Windows.Forms.Button btnNET48;
+ private System.Windows.Forms.CheckBox cbNET48;
+ private System.Windows.Forms.Label lbNET48;
+ private System.Windows.Forms.Button btnReport;
+ private System.Windows.Forms.Button btnFinal;
+ private System.Windows.Forms.Button btnInstallD;
+ private System.Windows.Forms.Button btnDownloadAll;
+ }
+}
+
diff --git a/Installer/FrmMain.resx b/Installer/FrmMain.resx
new file mode 100644
index 0000000..66e18f4
--- /dev/null
+++ b/Installer/FrmMain.resx
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <metadata name="toolTip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+ <value>17, 17</value>
+ </metadata>
+ <metadata name="toolTip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+ <value>17, 17</value>
+ </metadata>
+</root>
\ No newline at end of file
diff --git a/Installer/Helper.cs b/Installer/Helper.cs
new file mode 100644
index 0000000..9fa1e1b
--- /dev/null
+++ b/Installer/Helper.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Text;
+
+namespace Installer;
+
+public static class Helper
+{
+ /// <summary>去掉两头的0字节</summary>
+ /// <param name="value"></param>
+ /// <returns></returns>
+ public static String TrimZero(this String value) => value?.Trim().Trim('\0').Trim().Replace("\0", null);
+
+ /// <summary>
+ /// 获取可见字符串
+ /// </summary>
+ /// <param name="value"></param>
+ /// <returns></returns>
+ public static String GetInvisibleChar(this String value, Boolean isFirst = true)
+ {
+ if (String.IsNullOrEmpty(value)) return value;
+
+ var builder = new StringBuilder();
+
+ for (var i = 0; i < value.Length; i++)
+ {
+ if (value[i].IsInvisible())
+ builder.Append(value[i]);
+ else if (isFirst)
+ break;
+ }
+
+ return builder.ToString();
+ }
+
+ /// <summary>
+ /// 是否可见字符串
+ /// ASCII码中,第0~31号及第127号(共33个)是控制字符或通讯专用字符
+ /// </summary>
+ /// <param name="str"></param>
+ /// <returns></returns>
+ public static Boolean IsInvisible(this Char ch) => ch is > (Char)31 and not (Char)127;
+}
\ No newline at end of file
diff --git a/Installer/Installer.csproj b/Installer/Installer.csproj
new file mode 100644
index 0000000..dd9676b
--- /dev/null
+++ b/Installer/Installer.csproj
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{12503268-DD9B-4A68-B53C-EDCD71F2D485}</ProjectGuid>
+ <OutputType>WinExe</OutputType>
+ <RootNamespace>Installer</RootNamespace>
+ <AssemblyName>Installer</AssemblyName>
+ <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <Deterministic>false</Deterministic>
+ <LangVersion>latest</LangVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>..\Bin\Installer\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>..\Bin\Installer\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+ <PropertyGroup>
+ <ApplicationManifest>app.manifest</ApplicationManifest>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Microsoft.VisualBasic" />
+ <Reference Include="System" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Deployment" />
+ <Reference Include="System.Drawing" />
+ <Reference Include="System.Management" />
+ <Reference Include="System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>C:\Windows\Microsoft.NET\Framework64\v2.0.50727\System.Web.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Windows.Forms" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Advapi32.cs" />
+ <Compile Include="DownloadHelper.cs" />
+ <Compile Include="FrmMain.cs">
+ <SubType>Form</SubType>
+ </Compile>
+ <Compile Include="FrmMain.Designer.cs">
+ <DependentUpon>FrmMain.cs</DependentUpon>
+ </Compile>
+ <Compile Include="Helper.cs" />
+ <Compile Include="ITracer.cs" />
+ <Compile Include="NetRuntime.cs" />
+ <Compile Include="Program.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="VerInfo.cs" />
+ <Compile Include="WindowsService.cs" />
+ <EmbeddedResource Include="FrmMain.resx">
+ <DependentUpon>FrmMain.cs</DependentUpon>
+ </EmbeddedResource>
+ <EmbeddedResource Include="Properties\Resources.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+ <SubType>Designer</SubType>
+ </EmbeddedResource>
+ <Compile Include="Properties\Resources.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Resources.resx</DependentUpon>
+ </Compile>
+ <EmbeddedResource Include="..\LuckyClover\res\MicrosoftRootCertificateAuthority2011.cer">
+ <Link>res\MicrosoftRootCertificateAuthority2011.cer</Link>
+ </EmbeddedResource>
+ <None Include="app.manifest" />
+ <None Include="Properties\Settings.settings">
+ <Generator>SettingsSingleFileGenerator</Generator>
+ <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+ </None>
+ <Compile Include="Properties\Settings.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Settings.settings</DependentUpon>
+ <DesignTimeSharedInput>True</DesignTimeSharedInput>
+ </Compile>
+ </ItemGroup>
+ <ItemGroup>
+ <PackageReference Include="NewLife.Core">
+ <Version>10.6.2024.101-net20</Version>
+ </PackageReference>
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="..\LuckyClover\res\CertMgr.Exe">
+ <Link>res\CertMgr.Exe</Link>
+ </EmbeddedResource>
+ <EmbeddedResource Include="..\LuckyClover\res\md5.txt">
+ <Link>res\md5.txt</Link>
+ </EmbeddedResource>
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>
\ No newline at end of file
diff --git a/Installer/ITracer.cs b/Installer/ITracer.cs
new file mode 100644
index 0000000..b4c1bc2
--- /dev/null
+++ b/Installer/ITracer.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Installer;
+
+public interface ITracer
+{
+ ISpan NewSpan(String name, Object tag = null);
+}
+
+public interface ISpan : IDisposable
+{
+ void SetTag(Object tag);
+ void SetError(Exception ex, Object tag = null);
+}
diff --git a/Installer/MachineInfo.cs b/Installer/MachineInfo.cs
new file mode 100644
index 0000000..f097cd7
--- /dev/null
+++ b/Installer/MachineInfo.cs
@@ -0,0 +1,360 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Management;
+using System.Runtime.InteropServices;
+using System.Security;
+using Microsoft.VisualBasic.Devices;
+using Microsoft.Win32;
+using NewLife.Log;
+
+namespace NewLife;
+
+/// <summary>机器信息</summary>
+/// <remarks>
+/// 文档 https://newlifex.com/core/machine_info
+///
+/// 刷新信息成本较高,建议采用单例模式
+/// </remarks>
+public class MachineInfo
+{
+ #region 属性
+ /// <summary>系统名称</summary>
+ [DisplayName("系统名称")]
+ public String OSName { get; set; }
+
+ /// <summary>系统版本</summary>
+ [DisplayName("系统版本")]
+ public String OSVersion { get; set; }
+
+ /// <summary>产品名称。制造商</summary>
+ [DisplayName("产品名称")]
+ public String Product { get; set; }
+
+ /// <summary>处理器型号</summary>
+ [DisplayName("处理器型号")]
+ public String Processor { get; set; }
+
+ ///// <summary>处理器序列号。PC处理器序列号绝大部分重复,实际存储处理器的其它信息</summary>
+ //public String CpuID { get; set; }
+
+ /// <summary>硬件唯一标识。取主板编码,部分品牌存在重复</summary>
+ [DisplayName("硬件唯一标识")]
+ public String UUID { get; set; }
+
+ /// <summary>软件唯一标识。系统标识,操作系统重装后更新,Linux系统的machine_id,Android的android_id,Ghost系统存在重复</summary>
+ [DisplayName("软件唯一标识")]
+ public String Guid { get; set; }
+
+ /// <summary>计算机序列号。适用于品牌机,跟笔记本标签显示一致</summary>
+ [DisplayName("计算机序列号")]
+ public String Serial { get; set; }
+
+ /// <summary>主板。序列号或家族信息</summary>
+ [DisplayName("主板")]
+ public String Board { get; set; }
+
+ /// <summary>磁盘序列号</summary>
+ [DisplayName("磁盘序列号")]
+ public String DiskID { get; set; }
+
+ /// <summary>内存总量。单位Byte</summary>
+ [DisplayName("内存总量")]
+ public UInt64 Memory { get; set; }
+
+ /// <summary>可用内存。单位Byte</summary>
+ [DisplayName("可用内存")]
+ public UInt64 AvailableMemory { get; set; }
+
+ /// <summary>CPU占用率</summary>
+ [DisplayName("CPU占用率")]
+ public Single CpuRate { get; set; }
+
+ ///// <summary>网络上行速度。字节每秒,初始化后首次读取为0</summary>
+ //[DisplayName("网络上行速度")]
+ //public UInt64 UplinkSpeed { get; set; }
+
+ ///// <summary>网络下行速度。字节每秒,初始化后首次读取为0</summary>
+ //[DisplayName("网络下行速度")]
+ //public UInt64 DownlinkSpeed { get; set; }
+
+ ///// <summary>温度。单位度</summary>
+ //[DisplayName("温度")]
+ //public Double Temperature { get; set; }
+
+ ///// <summary>电池剩余。小于1的小数,常用百分比表示</summary>
+ //[DisplayName("电池剩余")]
+ //public Double Battery { get; set; }
+ #endregion
+
+ #region 构造
+ /// <summary>当前机器信息。默认null,在RegisterAsync后才能使用</summary>
+ public static MachineInfo Current { get; set; } = new MachineInfo();
+ #endregion
+
+ #region 方法
+ /// <summary>刷新</summary>
+ public void Init()
+ {
+ var osv = Environment.OSVersion;
+ if (OSVersion.IsNullOrEmpty()) OSVersion = osv.Version + "";
+ if (OSName.IsNullOrEmpty()) OSName = (osv + "").TrimStart("Microsoft").TrimEnd(OSVersion).Trim();
+ if (Guid.IsNullOrEmpty()) Guid = "";
+
+ try
+ {
+ LoadWindowsInfo();
+ }
+ catch (Exception ex)
+ {
+ if (XTrace.Log.Level <= LogLevel.Debug) XTrace.WriteException(ex);
+ }
+
+ // window+netcore 不方便读取注册表,随机生成一个guid,借助文件缓存确保其不变
+ if (Guid.IsNullOrEmpty()) Guid = "0-" + System.Guid.NewGuid().ToString();
+ if (UUID.IsNullOrEmpty()) UUID = "0-" + System.Guid.NewGuid().ToString();
+
+ try
+ {
+ Refresh();
+ }
+ catch (Exception ex)
+ {
+ if (XTrace.Log.Level <= LogLevel.Debug) XTrace.WriteException(ex);
+ }
+ }
+
+ private void LoadWindowsInfo()
+ {
+ var machine_guid = "";
+
+ var reg = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Cryptography");
+ if (reg != null) machine_guid = reg.GetValue("MachineGuid") + "";
+ if (machine_guid.IsNullOrEmpty())
+ {
+ //reg = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
+ //if (reg != null) machine_guid = reg.GetValue("MachineGuid") + "";
+ }
+
+ var ci = new ComputerInfo();
+ try
+ {
+ Memory = ci.TotalPhysicalMemory;
+
+ // 系统名取WMI可能出错
+ OSName = ci.OSFullName.TrimStart("Microsoft").Trim();
+ OSVersion = ci.OSVersion;
+ }
+ catch
+ {
+ try
+ {
+ var reg2 = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion");
+ if (reg2 != null)
+ {
+ OSName = reg2.GetValue("ProductName") + "";
+ OSVersion = reg2.GetValue("ReleaseId") + "";
+ }
+ }
+ catch (Exception ex)
+ {
+ if (XTrace.Log.Level <= LogLevel.Debug) XTrace.WriteException(ex);
+ }
+ }
+
+ Processor = GetInfo("Win32_Processor", "Name");
+ //CpuID = GetInfo("Win32_Processor", "ProcessorId");
+ var uuid = GetInfo("Win32_ComputerSystemProduct", "UUID");
+ Product = GetInfo("Win32_ComputerSystemProduct", "Name");
+ DiskID = GetInfo("Win32_DiskDrive", "SerialNumber");
+
+ var sn = GetInfo("Win32_BIOS", "SerialNumber");
+ if (!sn.IsNullOrEmpty() && !sn.EqualIgnoreCase("System Serial Number")) Serial = sn;
+ Board = GetInfo("Win32_BaseBoard", "SerialNumber");
+
+ // UUID取不到时返回 FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF
+ if (!uuid.IsNullOrEmpty() && !uuid.EqualIgnoreCase("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF")) UUID = uuid;
+
+ if (!machine_guid.IsNullOrEmpty()) Guid = machine_guid;
+ }
+
+ /// <summary>获取实时数据,如CPU、内存、温度</summary>
+ public void Refresh()
+ {
+ RefreshWindows();
+ }
+
+ private void RefreshWindows()
+ {
+ MEMORYSTATUSEX ms = default;
+ ms.Init();
+ if (GlobalMemoryStatusEx(ref ms))
+ {
+ Memory = ms.ullTotalPhys;
+ AvailableMemory = ms.ullAvailPhys;
+ }
+
+ GetSystemTimes(out var idleTime, out var kernelTime, out var userTime);
+
+ var current = new SystemTime
+ {
+ IdleTime = idleTime.ToLong(),
+ TotalTime = kernelTime.ToLong() + userTime.ToLong(),
+ };
+
+ var idle = current.IdleTime - (_systemTime?.IdleTime ?? 0);
+ var total = current.TotalTime - (_systemTime?.TotalTime ?? 0);
+ _systemTime = current;
+
+ CpuRate = total == 0 ? 0 : (Single)Math.Round((Single)(total - idle) / total, 4);
+ }
+ #endregion
+
+ #region 内存
+ [DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ [SecurityCritical]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern Boolean GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer);
+
+ internal struct MEMORYSTATUSEX
+ {
+ internal UInt32 dwLength;
+
+ internal UInt32 dwMemoryLoad;
+
+ internal UInt64 ullTotalPhys;
+
+ internal UInt64 ullAvailPhys;
+
+ internal UInt64 ullTotalPageFile;
+
+ internal UInt64 ullAvailPageFile;
+
+ internal UInt64 ullTotalVirtual;
+
+ internal UInt64 ullAvailVirtual;
+
+ internal UInt64 ullAvailExtendedVirtual;
+
+ internal void Init() => dwLength = checked((UInt32)Marshal.SizeOf(typeof(MEMORYSTATUSEX)));
+ }
+ #endregion
+
+ #region 磁盘
+ /// <summary>获取指定目录所在盘可用空间,默认当前目录</summary>
+ /// <param name="path"></param>
+ /// <returns>返回可用空间,字节,获取失败返回-1</returns>
+ public static Int64 GetFreeSpace(String path = null)
+ {
+ if (path.IsNullOrEmpty()) path = ".";
+
+ var driveInfo = new DriveInfo(Path.GetPathRoot(path.GetFullPath()));
+ if (driveInfo == null || !driveInfo.IsReady) return -1;
+
+ try
+ {
+ return driveInfo.AvailableFreeSpace;
+ }
+ catch
+ {
+ return -1;
+ }
+ }
+
+ /// <summary>获取指定目录下文件名,支持去掉后缀的去重,主要用于Linux</summary>
+ /// <param name="path"></param>
+ /// <param name="trimSuffix"></param>
+ /// <returns></returns>
+ public static ICollection<String> GetFiles(String path, Boolean trimSuffix = false)
+ {
+ var list = new List<String>();
+ if (path.IsNullOrEmpty()) return list;
+
+ var di = path.AsDirectory();
+ if (!di.Exists) return list;
+
+ var list2 = di.GetFiles().Select(e => e.Name).ToList();
+ foreach (var item in list2)
+ {
+ var line = item?.Trim();
+ if (!line.IsNullOrEmpty())
+ {
+ if (trimSuffix)
+ {
+ if (!list2.Any(e => e != line && line.StartsWith(e))) list.Add(line);
+ }
+ else
+ {
+ list.Add(line);
+ }
+ }
+ }
+
+ return list;
+ }
+ #endregion
+
+ #region Windows辅助
+ [DllImport("kernel32.dll", SetLastError = true)]
+ private static extern Boolean GetSystemTimes(out FILETIME idleTime, out FILETIME kernelTime, out FILETIME userTime);
+
+ private struct FILETIME
+ {
+ public UInt32 Low;
+
+ public UInt32 High;
+
+ public FILETIME(Int64 time)
+ {
+ Low = (UInt32)time;
+ High = (UInt32)(time >> 32);
+ }
+
+ public Int64 ToLong() => (Int64)(((UInt64)High << 32) | Low);
+ }
+
+ private class SystemTime
+ {
+ public Int64 IdleTime;
+ public Int64 TotalTime;
+ }
+
+ private SystemTime _systemTime;
+
+ /// <summary>获取WMI信息</summary>
+ /// <param name="path"></param>
+ /// <param name="property"></param>
+ /// <param name="nameSpace"></param>
+ /// <returns></returns>
+ public static String GetInfo(String path, String property, String nameSpace = null)
+ {
+ // Linux Mono不支持WMI
+ if (Runtime.Mono) return "";
+
+ var bbs = new List<String>();
+ try
+ {
+ var wql = $"Select {property} From {path}";
+ var cimobject = new ManagementObjectSearcher(nameSpace, wql);
+ var moc = cimobject.Get();
+ foreach (var mo in moc)
+ {
+ var val = mo?.Properties?[property]?.Value;
+ if (val != null) bbs.Add(val.ToString().Trim());
+ }
+ }
+ catch (Exception ex)
+ {
+ if (XTrace.Log.Level <= LogLevel.Debug) XTrace.WriteLine("WMI.GetInfo({0})失败!{1}", path, ex.Message);
+ return "";
+ }
+
+ bbs.Sort();
+
+ return bbs.Distinct().Join();
+ }
+ #endregion
+}
\ No newline at end of file
diff --git a/Installer/NetRuntime.cs b/Installer/NetRuntime.cs
new file mode 100644
index 0000000..74038e3
--- /dev/null
+++ b/Installer/NetRuntime.cs
@@ -0,0 +1,805 @@
+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 String BaseUrl { get; set; }
+
+ public Boolean Silent { get; set; }
+
+ /// <summary>应用安装目录</summary>
+ public String InstallPath { get; set; }
+
+ /// <summary>缓存目录</summary>
+ public String CachePath { get; set; } = "../Cache";
+
+ public IDictionary<String, String> Hashs { get; set; }
+
+ public ITracer Tracer { get; set; }
+ #endregion
+
+ #region 核心方法
+ public void AutoInstallNet()
+ {
+ var osVer = Environment.OSVersion.Version;
+
+ // WinXP
+ if (osVer.Major <= 5)
+ InstallNet40();
+ // Vista
+ else if (osVer.Major == 6 && osVer.Minor == 0)
+ InstallNet45();
+ else if (osVer.Major == 6 && osVer.Minor == 1)
+ {
+ // Win7
+ if (osVer.Revision <= 7600)
+ InstallNet45();
+ else
+ // Win7Sp1
+ {
+ InstallNet48();
+ InstallNet6();
+ }
+ }
+ // Win10/Win11
+ else if (osVer.Major >= 10)
+ {
+ InstallNet7();
+ }
+ else
+ {
+ InstallNet48();
+ InstallNet7();
+ }
+ }
+
+ 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;
+ else
+ baseUrl = BaseUrl + baseUrl;
+
+ 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 (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;
+ }
+
+ public Boolean InstallNet6(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 target = new Version("6.0");
+ if (ver >= target)
+ {
+ 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-6.0.16-win-x64.exe");
+ Install("aspnetcore-runtime-6.0.16-win-x64.exe");
+ break;
+ case "desktop":
+ rs = Install("windowsdesktop-runtime-6.0.16-win-x64.exe");
+ break;
+ case "host":
+ rs = Install("dotnet-hosting-6.0.16-win.exe");
+ break;
+ default:
+ rs = Install("dotnet-runtime-6.0.16-win-x64.exe");
+ break;
+ }
+ }
+ else
+ {
+ switch (kind)
+ {
+ case "aspnet":
+ rs = Install("dotnet-runtime-6.0.16-win-x86.exe");
+ rs = Install("aspnetcore-runtime-6.0.16-win-x86.exe");
+ break;
+ case "desktop":
+ rs = Install("windowsdesktop-runtime-6.0.16-win-x86.exe");
+ break;
+ case "host":
+ rs = Install("dotnet-hosting-6.0.16-win.exe");
+ break;
+ default:
+ rs = Install("dotnet-runtime-6.0.16-win-x86.exe");
+ break;
+ }
+ }
+
+ return rs;
+ }
+
+ public Boolean InstallNet7(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 target = new Version("7.0");
+ if (ver >= target)
+ {
+ 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-7.0.9-win-x64.exe");
+ rs = Install("aspnetcore-runtime-7.0.9-win-x64.exe");
+ break;
+ case "desktop":
+ rs = Install("windowsdesktop-runtime-7.0.9-win-x64.exe");
+ break;
+ case "host":
+ rs = Install("dotnet-hosting-7.0.9-win.exe");
+ break;
+ default:
+ rs = Install("dotnet-runtime-7.0.9-win-x64.exe");
+ break;
+ }
+ }
+ else
+ {
+ switch (kind)
+ {
+ case "aspnet":
+ rs = Install("dotnet-runtime-7.0.9-win-x86.exe");
+ rs = Install("aspnetcore-runtime-7.0.9-win-x86.exe");
+ break;
+ case "desktop":
+ rs = Install("windowsdesktop-runtime-7.0.9-win-x86.exe");
+ break;
+ case "host":
+ rs = Install("dotnet-hosting-7.0.9-win.exe");
+ break;
+ default:
+ rs = Install("dotnet-runtime-7.0.9-win-x86.exe");
+ break;
+ }
+ }
+
+ return rs;
+ }
+
+ /// <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
+}
\ No newline at end of file
diff --git a/Installer/Program.cs b/Installer/Program.cs
new file mode 100644
index 0000000..a05fa32
--- /dev/null
+++ b/Installer/Program.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Windows.Forms;
+using NewLife.Log;
+
+namespace Installer;
+
+internal static class Program
+{
+ /// <summary>
+ /// 应用程序的主入口点。
+ /// </summary>
+ [STAThread]
+ static void Main()
+ {
+ XTrace.UseWinForm();
+
+ Application.EnableVisualStyles();
+ Application.SetCompatibleTextRenderingDefault(false);
+ Application.Run(new FrmMain());
+ }
+}
diff --git a/Installer/Properties/AssemblyInfo.cs b/Installer/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..bc5c59c
--- /dev/null
+++ b/Installer/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// 有关程序集的一般信息由以下
+// 控制。更改这些特性值可修改
+// 与程序集关联的信息。
+[assembly: AssemblyTitle("Installer")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Installer")]
+[assembly: AssemblyCopyright("Copyright © NewLife 2023")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 将 ComVisible 设置为 false 会使此程序集中的类型
+//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
+//请将此类型的 ComVisible 特性设置为 true。
+[assembly: ComVisible(false)]
+
+// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
+[assembly: Guid("12503268-dd9b-4a68-b53c-edcd71f2d485")]
+
+// 程序集的版本信息由下列四个值组成:
+//
+// 主版本
+// 次版本
+// 生成号
+// 修订号
+//
+//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
+//通过使用 "*",如下所示:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.7.*")]
+[assembly: AssemblyFileVersion("1.7.2023.0531")]
diff --git a/Installer/Properties/Resources.Designer.cs b/Installer/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..badea14
--- /dev/null
+++ b/Installer/Properties/Resources.Designer.cs
@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// 此代码由工具生成。
+// 运行时版本: 4.0.30319.42000
+//
+// 对此文件的更改可能导致不正确的行为,如果
+// 重新生成代码,则所做更改将丢失。
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Installer.Properties
+{
+
+
+ /// <summary>
+ /// 强类型资源类,用于查找本地化字符串等。
+ /// </summary>
+ // 此类是由 StronglyTypedResourceBuilder
+ // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
+ // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
+ // (以 /str 作为命令选项),或重新生成 VS 项目。
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources
+ {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources()
+ {
+ }
+
+ /// <summary>
+ /// 返回此类使用的缓存 ResourceManager 实例。
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager
+ {
+ get
+ {
+ if ((resourceMan == null))
+ {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Installer.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// 重写当前线程的 CurrentUICulture 属性,对
+ /// 使用此强类型资源类的所有资源查找执行重写。
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture
+ {
+ get
+ {
+ return resourceCulture;
+ }
+ set
+ {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/Installer/Properties/Resources.resx b/Installer/Properties/Resources.resx
new file mode 100644
index 0000000..af7dbeb
--- /dev/null
+++ b/Installer/Properties/Resources.resx
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+</root>
\ No newline at end of file
diff --git a/Installer/Properties/Settings.Designer.cs b/Installer/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..a25727d
--- /dev/null
+++ b/Installer/Properties/Settings.Designer.cs
@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Installer.Properties
+{
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+ {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default
+ {
+ get
+ {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/Installer/Properties/Settings.settings b/Installer/Properties/Settings.settings
new file mode 100644
index 0000000..3964565
--- /dev/null
+++ b/Installer/Properties/Settings.settings
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
+ <Profiles>
+ <Profile Name="(Default)" />
+ </Profiles>
+ <Settings />
+</SettingsFile>
diff --git a/Installer/VerInfo.cs b/Installer/VerInfo.cs
new file mode 100644
index 0000000..b20c7ae
--- /dev/null
+++ b/Installer/VerInfo.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Installer;
+
+public class VerInfo
+{
+ public String Name { get; set; }
+
+ public String Version { get; set; }
+
+ public String Sp { get; set; }
+
+ public override String ToString() => String.IsNullOrEmpty(Sp) ? $"{Name} {Version}" : $"{Name} {Version} Sp{Sp}";
+}
diff --git a/Installer/WindowsService.cs b/Installer/WindowsService.cs
new file mode 100644
index 0000000..4c66879
--- /dev/null
+++ b/Installer/WindowsService.cs
@@ -0,0 +1,290 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Security.Principal;
+using System.Text;
+using NewLife;
+using NewLife.Log;
+using static Installer.Advapi32;
+
+namespace Installer;
+
+public class WindowsService
+{
+ #region 服务状态和控制
+ /// <summary>服务是否已安装</summary>
+ /// <param name="serviceName">服务名</param>
+ /// <returns></returns>
+ public Boolean IsInstalled(String serviceName)
+ {
+ using var manager = new SafeServiceHandle(OpenSCManager(null, null, ServiceControllerOptions.SC_MANAGER_CONNECT));
+ if (manager == null || manager.IsInvalid) return false;
+
+ using var service = new SafeServiceHandle(OpenService(manager, serviceName, ServiceOptions.SERVICE_QUERY_CONFIG));
+ return service != null && !service.IsInvalid;
+ }
+
+ /// <summary>服务是否已启动</summary>
+ /// <param name="serviceName">服务名</param>
+ /// <returns></returns>
+ public unsafe Boolean IsRunning(String serviceName)
+ {
+ using var manager = new SafeServiceHandle(OpenSCManager(null, null, ServiceControllerOptions.SC_MANAGER_CONNECT));
+ if (manager == null || manager.IsInvalid) return false;
+
+ using var service = new SafeServiceHandle(OpenService(manager, serviceName, ServiceOptions.SERVICE_QUERY_STATUS));
+ if (service == null || service.IsInvalid) return false;
+
+ SERVICE_STATUS status = default;
+ return !QueryServiceStatus(service, &status)
+ ? throw new Win32Exception(Marshal.GetLastWin32Error())
+ : status.currentState == ServiceControllerStatus.Running;
+ }
+
+ /// <summary>安装服务</summary>
+ /// <param name="serviceName">服务名</param>
+ /// <param name="displayName"></param>
+ /// <param name="binPath"></param>
+ /// <param name="description"></param>
+ /// <returns></returns>
+ public Boolean Install(String serviceName, String displayName, String binPath, String description)
+ {
+ XTrace.WriteLine("{0}.Install {1}, {2}, {3}, {4}", GetType().Name, serviceName, displayName, binPath, description);
+
+ using var manager = new SafeServiceHandle(OpenSCManager(null, null, ServiceControllerOptions.SC_MANAGER_CREATE_SERVICE));
+ if (manager.IsInvalid)
+ {
+ XTrace.WriteLine("安装Windows服务要求以管理员运行");
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+
+ using var service = new SafeServiceHandle(CreateService(manager, serviceName, displayName, ServiceOptions.SERVICE_ALL_ACCESS, 0x10, 2, 1, binPath, null, 0, null, null, null));
+ if (service.IsInvalid) throw new Win32Exception(Marshal.GetLastWin32Error());
+
+ // 设置描述信息
+ if (!description.IsNullOrEmpty())
+ {
+ SERVICE_DESCRIPTION sd;
+ sd.Description = Marshal.StringToHGlobalUni(description);
+ var lpInfo = Marshal.AllocHGlobal(Marshal.SizeOf(sd));
+
+ try
+ {
+ Marshal.StructureToPtr(sd, lpInfo, false);
+
+ const Int32 SERVICE_CONFIG_DESCRIPTION = 1;
+ ChangeServiceConfig2(service, SERVICE_CONFIG_DESCRIPTION, lpInfo);
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(lpInfo);
+ Marshal.FreeHGlobal(sd.Description);
+ }
+ }
+
+ return true;
+ }
+
+ /// <summary>卸载服务</summary>
+ /// <param name="serviceName">服务名</param>
+ /// <returns></returns>
+ public unsafe Boolean Remove(String serviceName)
+ {
+ XTrace.WriteLine("{0}.Remove {1}", GetType().Name, serviceName);
+
+ if (!IsAdministrator()) return RunAsAdministrator("-uninstall");
+
+ using var manager = new SafeServiceHandle(OpenSCManager(null, null, ServiceControllerOptions.SC_MANAGER_ALL));
+ if (manager.IsInvalid)
+ {
+ XTrace.WriteLine("卸载Windows服务要求以管理员运行");
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+
+ using var service = new SafeServiceHandle(OpenService(manager, serviceName, ServiceOptions.SERVICE_STOP | ServiceOptions.STANDARD_RIGHTS_DELETE));
+ if (service.IsInvalid) throw new Win32Exception(Marshal.GetLastWin32Error());
+
+ SERVICE_STATUS status = default;
+ ControlService(service, ControlOptions.Stop, &status);
+
+ return DeleteService(service) == 0 ? throw new Win32Exception(Marshal.GetLastWin32Error()) : true;
+ }
+
+ /// <summary>启动服务</summary>
+ /// <param name="serviceName">服务名</param>
+ /// <returns></returns>
+ public Boolean Start(String serviceName)
+ {
+ XTrace.WriteLine("{0}.Start {1}", GetType().Name, serviceName);
+
+ if (!IsAdministrator()) return RunAsAdministrator("-start");
+
+ using var manager = new SafeServiceHandle(OpenSCManager(null, null, ServiceControllerOptions.SC_MANAGER_CONNECT));
+ if (manager.IsInvalid)
+ {
+ XTrace.WriteLine("启动Windows服务要求以管理员运行");
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+
+ using var service = new SafeServiceHandle(OpenService(manager, serviceName, ServiceOptions.SERVICE_START));
+ return service.IsInvalid
+ ? throw new Win32Exception(Marshal.GetLastWin32Error())
+ : !StartService(service, 0, IntPtr.Zero) ? throw new Win32Exception(Marshal.GetLastWin32Error()) : true;
+ }
+
+ /// <summary>停止服务</summary>
+ /// <param name="serviceName">服务名</param>
+ /// <returns></returns>
+ public unsafe Boolean Stop(String serviceName)
+ {
+ XTrace.WriteLine("{0}.Stop {1}", GetType().Name, serviceName);
+
+ if (!IsAdministrator()) return RunAsAdministrator("-stop");
+
+ using var manager = new SafeServiceHandle(OpenSCManager(null, null, ServiceControllerOptions.SC_MANAGER_ALL));
+ if (manager.IsInvalid)
+ {
+ XTrace.WriteLine("停止Windows服务要求以管理员运行");
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+
+ using var service = new SafeServiceHandle(OpenService(manager, serviceName, ServiceOptions.SERVICE_STOP));
+ if (service.IsInvalid) throw new Win32Exception(Marshal.GetLastWin32Error());
+
+ SERVICE_STATUS status = default;
+ return !ControlService(service, ControlOptions.Stop, &status) ? throw new Win32Exception(Marshal.GetLastWin32Error()) : true;
+ }
+
+ /// <summary>重启服务</summary>
+ /// <param name="serviceName">服务名</param>
+ public Boolean Restart(String serviceName)
+ {
+ XTrace.WriteLine("{0}.Restart {1}", GetType().Name, serviceName);
+
+ if (!IsAdministrator()) return RunAsAdministrator("-restart");
+
+ //if (InService)
+ {
+ var cmd = $"/c net stop {serviceName} & ping 127.0.0.1 -n 5 & net start {serviceName}";
+ Process.Start("cmd.exe", cmd);
+ }
+ //else
+ //{
+ // Process.Start(Service.GetExeName(), "-run -delay");
+ //}
+
+ //// 在临时目录生成重启服务的批处理文件
+ //var filename = "重启.bat".GetFullPath();
+ //if (File.Exists(filename)) File.Delete(filename);
+
+ //File.AppendAllText(filename, "net stop " + serviceName);
+ //File.AppendAllText(filename, Environment.NewLine);
+ //File.AppendAllText(filename, "ping 127.0.0.1 -n 5 > nul ");
+ //File.AppendAllText(filename, Environment.NewLine);
+ //File.AppendAllText(filename, "net start " + serviceName);
+
+ ////执行重启服务的批处理
+ ////RunCmd(filename, false, false);
+ //var p = new Process();
+ //var si = new ProcessStartInfo
+ //{
+ // FileName = filename,
+ // UseShellExecute = true,
+ // CreateNoWindow = true
+ //};
+ //p.StartInfo = si;
+
+ //p.Start();
+
+ ////if (File.Exists(filename)) File.Delete(filename);
+
+ return true;
+ }
+
+ static Boolean RunAsAdministrator(String argument)
+ {
+ var exe = ExecutablePath;
+ if (exe.IsNullOrEmpty()) return false;
+
+ if (exe.EndsWithIgnoreCase(".dll"))
+ {
+ var exe2 = Path.ChangeExtension(exe, ".exe");
+ if (File.Exists(exe2)) exe = exe2;
+ }
+
+ var startInfo = exe.EndsWithIgnoreCase(".dll") ?
+ new ProcessStartInfo
+ {
+ FileName = "dotnet",
+ Arguments = $"{Path.GetFileName(exe)} {argument}",
+ WorkingDirectory = Path.GetDirectoryName(exe),
+ Verb = "runas",
+ UseShellExecute = true,
+ } :
+ new ProcessStartInfo
+ {
+ FileName = exe,
+ Arguments = argument,
+ Verb = "runas",
+ UseShellExecute = true,
+ };
+
+ var p = Process.Start(startInfo);
+ return !p.WaitForExit(5_000) || p.ExitCode == 0;
+ }
+
+ static String _executablePath;
+ static String ExecutablePath
+ {
+ get
+ {
+ if (_executablePath == null)
+ {
+ var entryAssembly = Assembly.GetEntryAssembly();
+ if (entryAssembly != null)
+ {
+ var codeBase = entryAssembly.CodeBase;
+ var uri = new Uri(codeBase);
+ _executablePath = uri.IsFile ? uri.LocalPath + Uri.UnescapeDataString(uri.Fragment) : uri.ToString();
+ }
+ else
+ {
+ var moduleFileNameLongPath = GetModuleFileNameLongPath(new HandleRef(null, IntPtr.Zero));
+ _executablePath = moduleFileNameLongPath.ToString().GetFullPath();
+ }
+ }
+
+ return _executablePath;
+ }
+ }
+
+ static StringBuilder GetModuleFileNameLongPath(HandleRef hModule)
+ {
+ var sb = new StringBuilder(260);
+ var num = 1;
+ var num2 = 0;
+ while ((num2 = GetModuleFileName(hModule, sb, sb.Capacity)) == sb.Capacity && Marshal.GetLastWin32Error() == 122 && sb.Capacity < 32767)
+ {
+ num += 2;
+ var capacity = (num * 260 < 32767) ? (num * 260) : 32767;
+ sb.EnsureCapacity(capacity);
+ }
+ sb.Length = num2;
+ return sb;
+ }
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ static extern Int32 GetModuleFileName(HandleRef hModule, StringBuilder buffer, Int32 length);
+
+ public Boolean IsAdministrator()
+ {
+ var current = WindowsIdentity.GetCurrent();
+ var windowsPrincipal = new WindowsPrincipal(current);
+ return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator);
+ }
+ #endregion
+}
diff --git a/Installer4/app.manifest b/Installer4/app.manifest
new file mode 100644
index 0000000..2e0ad7a
--- /dev/null
+++ b/Installer4/app.manifest
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+ <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
+ <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+ <security>
+ <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
+ <!-- UAC 清单选项
+ 如果想要更改 Windows 用户帐户控制级别,请使用
+ 以下节点之一替换 requestedExecutionLevel 节点。
+
+ <requestedExecutionLevel level="asInvoker" uiAccess="false" />
+ <requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
+ <requestedExecutionLevel level="highestAvailable" uiAccess="false" />
+
+ 指定 requestedExecutionLevel 元素将禁用文件和注册表虚拟化。
+ 如果你的应用程序需要此虚拟化来实现向后兼容性,则移除此
+ 元素。
+ -->
+ <requestedExecutionLevel level="asInvoker" uiAccess="false" />
+ </requestedPrivileges>
+ </security>
+ </trustInfo>
+
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!-- 设计此应用程序与其一起工作且已针对此应用程序进行测试的
+ Windows 版本的列表。取消评论适当的元素,
+ Windows 将自动选择最兼容的环境。 -->
+
+ <!-- Windows Vista -->
+ <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
+
+ <!-- Windows 7 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
+
+ <!-- Windows 8 -->
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
+
+ <!-- Windows 8.1 -->
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
+
+ <!-- Windows 10 -->
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
+
+ </application>
+ </compatibility>
+
+ <!-- 指示该应用程序可感知 DPI 且 Windows 在 DPI 较高时将不会对其进行
+ 自动缩放。Windows Presentation Foundation (WPF)应用程序自动感知 DPI,无需
+ 选择加入。选择加入此设置的 Windows 窗体应用程序(面向 .NET Framework 4.6)还应
+ 在其 app.config 中将 "EnableWindowsFormsHighDpiAutoResizing" 设置设置为 "true"。
+
+ 将应用程序设为感知长路径。请参阅 https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
+
+ <application xmlns="urn:schemas-microsoft-com:asm.v3">
+ <windowsSettings>
+ <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
+ <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
+ </windowsSettings>
+ </application>
+
+
+ <!-- 启用 Windows 公共控件和对话框的主题(Windows XP 和更高版本) -->
+ <!--
+ <dependency>
+ <dependentAssembly>
+ <assemblyIdentity
+ type="win32"
+ name="Microsoft.Windows.Common-Controls"
+ version="6.0.0.0"
+ processorArchitecture="*"
+ publicKeyToken="6595b64144ccf1df"
+ language="*"
+ />
+ </dependentAssembly>
+ </dependency>
+ -->
+
+</assembly>
diff --git a/Installer4/Installer4.csproj b/Installer4/Installer4.csproj
new file mode 100644
index 0000000..6eb22ff
--- /dev/null
+++ b/Installer4/Installer4.csproj
@@ -0,0 +1,61 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <OutputType>WinExe</OutputType>
+ <TargetFramework>net4.0</TargetFramework>
+ <AssemblyName>Installer</AssemblyName>
+ <RootNamespace>Installer</RootNamespace>
+ <AssemblyTitle>安装助手</AssemblyTitle>
+ <Description>辅助客户端打包,指定下载需要的文件</Description>
+ <Company>新生命开发团队</Company>
+ <Copyright>©2002-2023 NewLife</Copyright>
+ <VersionPrefix>1.7</VersionPrefix>
+ <VersionSuffix>$([System.DateTime]::Now.ToString(`yyyy.MMdd`))</VersionSuffix>
+ <Version>$(VersionPrefix).$(VersionSuffix)</Version>
+ <FileVersion>$(Version)</FileVersion>
+ <AssemblyVersion>$(VersionPrefix).*</AssemblyVersion>
+ <Deterministic>false</Deterministic>
+ <OutputPath>..\Bin\Installer4</OutputPath>
+ <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <LangVersion>latest</LangVersion>
+ <UseWindowsForms>true</UseWindowsForms>
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
+ <ApplicationManifest>app.manifest</ApplicationManifest>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <Compile Include="..\Installer\Advapi32.cs" Link="Advapi32.cs" />
+ <Compile Include="..\Installer\DownloadHelper.cs" Link="DownloadHelper.cs" />
+ <Compile Include="..\Installer\FrmMain.cs" Link="FrmMain.cs" />
+ <Compile Include="..\Installer\FrmMain.Designer.cs" Link="FrmMain.Designer.cs" />
+ <Compile Include="..\Installer\Helper.cs" Link="Helper.cs" />
+ <Compile Include="..\Installer\NetRuntime.cs" Link="NetRuntime.cs" />
+ <Compile Include="..\Installer\VerInfo.cs" Link="VerInfo.cs" />
+ <Compile Include="..\Installer\WindowsService.cs" Link="WindowsService.cs" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <EmbeddedResource Include="..\LuckyClover\res\md5.txt" Link="res\md5.txt" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <EmbeddedResource Include="..\Installer\FrmMain.resx" Link="FrmMain.resx" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <PackageReference Include="NewLife.Core" Version="10.6.2024.101-net40" />
+ <PackageReference Include="NewLife.Stardust" Version="2.9.2024.101" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Folder Include="res\" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <EmbeddedResource Include="..\LuckyClover\res\CertMgr.Exe" Link="res\CertMgr.Exe" />
+ <EmbeddedResource Include="..\LuckyClover\res\MicrosoftRootCertificateAuthority2011.cer" Link="res\MicrosoftRootCertificateAuthority2011.cer" />
+ </ItemGroup>
+
+</Project>
\ No newline at end of file
diff --git a/Installer4/Program.cs b/Installer4/Program.cs
new file mode 100644
index 0000000..79ee8d2
--- /dev/null
+++ b/Installer4/Program.cs
@@ -0,0 +1,23 @@
+using NewLife.Log;
+using Stardust;
+
+namespace Installer;
+
+internal static class Program
+{
+ /// <summary>
+ /// 应用程序的主入口点。
+ /// </summary>
+ [STAThread]
+ static void Main()
+ {
+ XTrace.UseWinForm();
+
+ var star = new StarFactory("http://s.newlifex.com:6600", null, null);
+ DefaultTracer.Instance = star.Tracer;
+
+ Application.EnableVisualStyles();
+ Application.SetCompatibleTextRenderingDefault(false);
+ Application.Run(new FrmMain());
+ }
+}
\ No newline at end of file
diff --git a/LuckyClover.sln b/LuckyClover.sln
index 245613f..1d1f1c5 100644
--- a/LuckyClover.sln
+++ b/LuckyClover.sln
@@ -10,10 +10,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Others", "Others", "{52E5A5
Readme.MD = Readme.MD
EndProjectSection
EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Clover", "Clover\Clover.vcxproj", "{85063414-B2A7-4AC8-B7EF-AB17237A96FC}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Clover", "Clover\Clover.vcxproj", "{8A06ED40-1CEA-4217-868E-050970F7ACEB}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CloverLinux", "CloverLinux\CloverLinux.vcxproj", "{9F1E9996-58AA-4148-A809-2E12D67D236B}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Installer", "Installer\Installer.csproj", "{12503268-DD9B-4A68-B53C-EDCD71F2D485}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Installer4", "Installer4\Installer4.csproj", "{6E9F5946-AA92-4B5C-9FBD-5DCB68D6B47D}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -48,26 +52,26 @@ Global
{F1A1DD43-A06A-4ADD-8303-D33310CB0ED3}.Release|x64.Build.0 = Release|Any CPU
{F1A1DD43-A06A-4ADD-8303-D33310CB0ED3}.Release|x86.ActiveCfg = Release|Any CPU
{F1A1DD43-A06A-4ADD-8303-D33310CB0ED3}.Release|x86.Build.0 = Release|Any CPU
- {85063414-B2A7-4AC8-B7EF-AB17237A96FC}.Debug|Any CPU.ActiveCfg = Debug|x64
- {85063414-B2A7-4AC8-B7EF-AB17237A96FC}.Debug|Any CPU.Build.0 = Debug|x64
- {85063414-B2A7-4AC8-B7EF-AB17237A96FC}.Debug|ARM.ActiveCfg = Debug|x64
- {85063414-B2A7-4AC8-B7EF-AB17237A96FC}.Debug|ARM.Build.0 = Debug|x64
- {85063414-B2A7-4AC8-B7EF-AB17237A96FC}.Debug|ARM64.ActiveCfg = Debug|x64
- {85063414-B2A7-4AC8-B7EF-AB17237A96FC}.Debug|ARM64.Build.0 = Debug|x64
- {85063414-B2A7-4AC8-B7EF-AB17237A96FC}.Debug|x64.ActiveCfg = Debug|x64
- {85063414-B2A7-4AC8-B7EF-AB17237A96FC}.Debug|x64.Build.0 = Debug|x64
- {85063414-B2A7-4AC8-B7EF-AB17237A96FC}.Debug|x86.ActiveCfg = Debug|Win32
- {85063414-B2A7-4AC8-B7EF-AB17237A96FC}.Debug|x86.Build.0 = Debug|Win32
- {85063414-B2A7-4AC8-B7EF-AB17237A96FC}.Release|Any CPU.ActiveCfg = Release|x64
- {85063414-B2A7-4AC8-B7EF-AB17237A96FC}.Release|Any CPU.Build.0 = Release|x64
- {85063414-B2A7-4AC8-B7EF-AB17237A96FC}.Release|ARM.ActiveCfg = Release|x64
- {85063414-B2A7-4AC8-B7EF-AB17237A96FC}.Release|ARM.Build.0 = Release|x64
- {85063414-B2A7-4AC8-B7EF-AB17237A96FC}.Release|ARM64.ActiveCfg = Release|x64
- {85063414-B2A7-4AC8-B7EF-AB17237A96FC}.Release|ARM64.Build.0 = Release|x64
- {85063414-B2A7-4AC8-B7EF-AB17237A96FC}.Release|x64.ActiveCfg = Release|x64
- {85063414-B2A7-4AC8-B7EF-AB17237A96FC}.Release|x64.Build.0 = Release|x64
- {85063414-B2A7-4AC8-B7EF-AB17237A96FC}.Release|x86.ActiveCfg = Release|Win32
- {85063414-B2A7-4AC8-B7EF-AB17237A96FC}.Release|x86.Build.0 = Release|Win32
+ {8A06ED40-1CEA-4217-868E-050970F7ACEB}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {8A06ED40-1CEA-4217-868E-050970F7ACEB}.Debug|Any CPU.Build.0 = Debug|x64
+ {8A06ED40-1CEA-4217-868E-050970F7ACEB}.Debug|ARM.ActiveCfg = Debug|x64
+ {8A06ED40-1CEA-4217-868E-050970F7ACEB}.Debug|ARM.Build.0 = Debug|x64
+ {8A06ED40-1CEA-4217-868E-050970F7ACEB}.Debug|ARM64.ActiveCfg = Debug|x64
+ {8A06ED40-1CEA-4217-868E-050970F7ACEB}.Debug|ARM64.Build.0 = Debug|x64
+ {8A06ED40-1CEA-4217-868E-050970F7ACEB}.Debug|x64.ActiveCfg = Debug|x64
+ {8A06ED40-1CEA-4217-868E-050970F7ACEB}.Debug|x64.Build.0 = Debug|x64
+ {8A06ED40-1CEA-4217-868E-050970F7ACEB}.Debug|x86.ActiveCfg = Debug|Win32
+ {8A06ED40-1CEA-4217-868E-050970F7ACEB}.Debug|x86.Build.0 = Debug|Win32
+ {8A06ED40-1CEA-4217-868E-050970F7ACEB}.Release|Any CPU.ActiveCfg = Release|x64
+ {8A06ED40-1CEA-4217-868E-050970F7ACEB}.Release|Any CPU.Build.0 = Release|x64
+ {8A06ED40-1CEA-4217-868E-050970F7ACEB}.Release|ARM.ActiveCfg = Release|x64
+ {8A06ED40-1CEA-4217-868E-050970F7ACEB}.Release|ARM.Build.0 = Release|x64
+ {8A06ED40-1CEA-4217-868E-050970F7ACEB}.Release|ARM64.ActiveCfg = Release|x64
+ {8A06ED40-1CEA-4217-868E-050970F7ACEB}.Release|ARM64.Build.0 = Release|x64
+ {8A06ED40-1CEA-4217-868E-050970F7ACEB}.Release|x64.ActiveCfg = Release|x64
+ {8A06ED40-1CEA-4217-868E-050970F7ACEB}.Release|x64.Build.0 = Release|x64
+ {8A06ED40-1CEA-4217-868E-050970F7ACEB}.Release|x86.ActiveCfg = Release|Win32
+ {8A06ED40-1CEA-4217-868E-050970F7ACEB}.Release|x86.Build.0 = Release|Win32
{9F1E9996-58AA-4148-A809-2E12D67D236B}.Debug|Any CPU.ActiveCfg = Debug|x64
{9F1E9996-58AA-4148-A809-2E12D67D236B}.Debug|Any CPU.Build.0 = Debug|x64
{9F1E9996-58AA-4148-A809-2E12D67D236B}.Debug|Any CPU.Deploy.0 = Debug|x64
@@ -98,6 +102,46 @@ Global
{9F1E9996-58AA-4148-A809-2E12D67D236B}.Release|x86.ActiveCfg = Release|x86
{9F1E9996-58AA-4148-A809-2E12D67D236B}.Release|x86.Build.0 = Release|x86
{9F1E9996-58AA-4148-A809-2E12D67D236B}.Release|x86.Deploy.0 = Release|x86
+ {12503268-DD9B-4A68-B53C-EDCD71F2D485}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {12503268-DD9B-4A68-B53C-EDCD71F2D485}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {12503268-DD9B-4A68-B53C-EDCD71F2D485}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {12503268-DD9B-4A68-B53C-EDCD71F2D485}.Debug|ARM.Build.0 = Debug|Any CPU
+ {12503268-DD9B-4A68-B53C-EDCD71F2D485}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {12503268-DD9B-4A68-B53C-EDCD71F2D485}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {12503268-DD9B-4A68-B53C-EDCD71F2D485}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {12503268-DD9B-4A68-B53C-EDCD71F2D485}.Debug|x64.Build.0 = Debug|Any CPU
+ {12503268-DD9B-4A68-B53C-EDCD71F2D485}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {12503268-DD9B-4A68-B53C-EDCD71F2D485}.Debug|x86.Build.0 = Debug|Any CPU
+ {12503268-DD9B-4A68-B53C-EDCD71F2D485}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {12503268-DD9B-4A68-B53C-EDCD71F2D485}.Release|Any CPU.Build.0 = Release|Any CPU
+ {12503268-DD9B-4A68-B53C-EDCD71F2D485}.Release|ARM.ActiveCfg = Release|Any CPU
+ {12503268-DD9B-4A68-B53C-EDCD71F2D485}.Release|ARM.Build.0 = Release|Any CPU
+ {12503268-DD9B-4A68-B53C-EDCD71F2D485}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {12503268-DD9B-4A68-B53C-EDCD71F2D485}.Release|ARM64.Build.0 = Release|Any CPU
+ {12503268-DD9B-4A68-B53C-EDCD71F2D485}.Release|x64.ActiveCfg = Release|Any CPU
+ {12503268-DD9B-4A68-B53C-EDCD71F2D485}.Release|x64.Build.0 = Release|Any CPU
+ {12503268-DD9B-4A68-B53C-EDCD71F2D485}.Release|x86.ActiveCfg = Release|Any CPU
+ {12503268-DD9B-4A68-B53C-EDCD71F2D485}.Release|x86.Build.0 = Release|Any CPU
+ {6E9F5946-AA92-4B5C-9FBD-5DCB68D6B47D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6E9F5946-AA92-4B5C-9FBD-5DCB68D6B47D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6E9F5946-AA92-4B5C-9FBD-5DCB68D6B47D}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {6E9F5946-AA92-4B5C-9FBD-5DCB68D6B47D}.Debug|ARM.Build.0 = Debug|Any CPU
+ {6E9F5946-AA92-4B5C-9FBD-5DCB68D6B47D}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {6E9F5946-AA92-4B5C-9FBD-5DCB68D6B47D}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {6E9F5946-AA92-4B5C-9FBD-5DCB68D6B47D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {6E9F5946-AA92-4B5C-9FBD-5DCB68D6B47D}.Debug|x64.Build.0 = Debug|Any CPU
+ {6E9F5946-AA92-4B5C-9FBD-5DCB68D6B47D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {6E9F5946-AA92-4B5C-9FBD-5DCB68D6B47D}.Debug|x86.Build.0 = Debug|Any CPU
+ {6E9F5946-AA92-4B5C-9FBD-5DCB68D6B47D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6E9F5946-AA92-4B5C-9FBD-5DCB68D6B47D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6E9F5946-AA92-4B5C-9FBD-5DCB68D6B47D}.Release|ARM.ActiveCfg = Release|Any CPU
+ {6E9F5946-AA92-4B5C-9FBD-5DCB68D6B47D}.Release|ARM.Build.0 = Release|Any CPU
+ {6E9F5946-AA92-4B5C-9FBD-5DCB68D6B47D}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {6E9F5946-AA92-4B5C-9FBD-5DCB68D6B47D}.Release|ARM64.Build.0 = Release|Any CPU
+ {6E9F5946-AA92-4B5C-9FBD-5DCB68D6B47D}.Release|x64.ActiveCfg = Release|Any CPU
+ {6E9F5946-AA92-4B5C-9FBD-5DCB68D6B47D}.Release|x64.Build.0 = Release|Any CPU
+ {6E9F5946-AA92-4B5C-9FBD-5DCB68D6B47D}.Release|x86.ActiveCfg = Release|Any CPU
+ {6E9F5946-AA92-4B5C-9FBD-5DCB68D6B47D}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/LuckyClover/LuckyClover.csproj b/LuckyClover/LuckyClover.csproj
index b5469ab..b11259b 100644
--- a/LuckyClover/LuckyClover.csproj
+++ b/LuckyClover/LuckyClover.csproj
@@ -46,10 +46,17 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="PublishAotCompressed" Version="1.0.1" />
+ <None Remove="res\CertMgr.Exe" />
+ <None Remove="res\MicrosoftRootCertificateAuthority2011.cer" />
</ItemGroup>
<ItemGroup>
+ <PackageReference Include="PublishAotCompressed" Version="1.0.3" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <EmbeddedResource Include="res\CertMgr.Exe" />
<EmbeddedResource Include="res\md5.txt" />
+ <EmbeddedResource Include="res\MicrosoftRootCertificateAuthority2011.cer" />
</ItemGroup>
</Project>