using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Data.SqlClient; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Net.NetworkInformation; using System.Windows.Forms; using System.Net; using System.Security.Cryptography; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.Diagnostics; using Microsoft.VisualBasic.Logging; using System.IO.Compression; using SharpCompress.Archives.Rar; using SharpCompress.Common; using SharpCompress.Archives; using System.Diagnostics.Eventing.Reader; using System.ServiceProcess; using static System.Windows.Forms.VisualStyles.VisualStyleElement; using System.Security.Policy; using static System.Windows.Forms.VisualStyles.VisualStyleElement.ToolBar; using System.Data.SqlTypes; using Serilog; using System.Xml.Linq; using System.IO; using Microsoft.Win32; using System.Web; namespace Start { public partial class Main : Form { private bool isCanDBConfig = true; private bool isPortTestOk = false; private bool isServiceTestOk = false; private bool isDBNameTestOK = false; private bool isRuntimeTestOk = false; public Main() { InitializeComponent(); this.MaximizeBox = false; var physicalAddressList = NetworkInterface.GetAllNetworkInterfaces().Select(t => t.GetPhysicalAddress().ToString()); this.machineTextBox.Text = physicalAddressList.FirstOrDefault()?.ToString(); //this.KeySecreteTextBox.Text = Md5($"{this.machineTextBox.Text}_XINGCANG"); } int apiPort = 7100; int vuePort = 9527; private async void connectButton_Click(object sender, EventArgs e) { string connectionString = $"Server={serverTextBox.Text};User Id={usernameTextBox.Text};Password={passwordTextBox.Text};"; using (SqlConnection connection = new SqlConnection(connectionString)) { try { await Task.Run(() => { connection.Open(); }); WinformLog("Database connection test succeeded.", Color.Green); isCanDBConfig = false; serverTextBox.Enabled = isCanDBConfig; usernameTextBox.Enabled = isCanDBConfig; passwordTextBox.Enabled = isCanDBConfig; connectButton.Enabled = false; selectPathBtn.Enabled = true; //portBtn.Enabled = true; //testDBBtn.Enabled = true; //testServicebtn.Enabled = true; } catch (Exception ex) { WinformLog($"Database connection test failed:{ex.Message}", Color.Red); } } } private void portBtn_Click(object sender, EventArgs e) { if (int.TryParse(nginxPortTBox.Text, out vuePort) == false || int.TryParse(apiPortTBox.Text, out apiPort) == false) { WinformLog($"Please enter a valid port number!", Color.Red); return; } if (IsPortInUse(vuePort)) { WinformLog($"The front-end port set by the service is already in use. Please choose another port!", Color.Red); return; } if (IsPortInUse(apiPort)) { WinformLog($"The back-end port set by the service is already in use. Please choose another port!", Color.Red); return; } WinformLog("Port test succeeded.", Color.Green); isPortTestOk = true; } private void testServicebtn_Click(object sender, EventArgs e) { if (ServiceController.GetServices().Any(t => t.ServiceName == serviceNameTBox.Text)) { WinformLog($"Name of the backend service already exists. Please change it!", Color.Red); return; } if (ServiceController.GetServices().Any(t => t.ServiceName == nginxServiceNameTbox.Text)) { WinformLog($"Name of the nginx service already exists. Please change it!", Color.Red); return; } WinformLog($"Service name test succeeded.", Color.Green); isServiceTestOk = true; } private void testNetcoreRuntime() { string runtimeListCommand = "dotnet --list-runtimes"; Process process = new Process(); process.StartInfo.FileName = "cmd.exe"; process.StartInfo.Arguments = "/c " + runtimeListCommand; process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.Start(); string output = process.StandardOutput.ReadToEnd(); process.WaitForExit(); bool isAspNetCoreRuntimeInstalled = false; bool isNETCoreRuntimeInstalled = false; var lineList = output.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).ToList(); foreach (string line in lineList) { if (line.Contains("Microsoft.AspNetCore.App") && !line.Contains("Preview")) { string versionString = line.Split(' ')[1]; Version version = new Version(versionString); if (version.Major > 6 || (version.Major == 6 && version.Minor >= 0)) { // 大于等于 6.0.0 的 .NET 运行时已安装 isAspNetCoreRuntimeInstalled = true; continue; } } if (line.Contains("Microsoft.NETCore.App") && !line.Contains("Preview")) { string versionString = line.Split(' ')[1]; Version version = new Version(versionString); if (version.Major > 6 || (version.Major == 6 && version.Minor >= 0)) { // 大于等于 6.0.0 的 .NET 运行时已安装 isNETCoreRuntimeInstalled = true; continue; } } } if (isAspNetCoreRuntimeInstalled && isNETCoreRuntimeInstalled) { // 执行逻辑,表示大于等于 6.0.0 的 .NET 运行时已安装 WinformLog("Netcore Runtime 6.0.0 or newer has been installed.", Color.Green); isRuntimeTestOk = true; } else { WinformLog("Netcore Runtime 6.0.0 or newer is not installed. Please install it before running the deployment software.", Color.Red); connectButton.Enabled = false; //await Task.Run(() => // { // WinformLog("Installing Netcore Runtime….", Color.Orange); // string runtimePath = Path.Combine(AppContext.BaseDirectory, "Resource/NetCoreRuntime/dotnet-hosting-6.0.0-win.exe"); // var processInfo = new ProcessStartInfo // { // FileName = runtimePath, // 安装程序的文件名 // Arguments = $"/S /v /qn REINSTALLMODE=vomus", // UseShellExecute = false // }; // var otherProcess = new Process { StartInfo = processInfo }; // otherProcess.Start(); // otherProcess.WaitForExit(); // WinformLog("Netcore 6.0.0 Runtime installation completed.", Color.Green); // }); } } private void testDBBtn_Click(object sender, EventArgs e) { if (string.IsNullOrWhiteSpace(dbNameTBox.Text)) { WinformLog($"Please enter the name of the deployed database!", Color.Red); return; } string connectionString = $"Server={serverTextBox.Text};User Id={usernameTextBox.Text};Password={passwordTextBox.Text};"; string sql = $"SELECT COUNT(*) FROM sys.databases WHERE name = '{dbNameTBox.Text.Trim()}';"; // 创建连接对象和命令对象 using (SqlConnection connection = new SqlConnection(connectionString)) using (SqlCommand command = new SqlCommand(sql, connection)) { // 打开连接 connection.Open(); // 执行查询,返回结果行数 int rowCount = (int)command.ExecuteScalar(); // 判断结果是否大于0,即是否存在该数据库 if (rowCount > 0) { WinformLog($"{dbNameTBox.Text.Trim()} This database already exists.", Color.Red); return; } } WinformLog($"Database name test succeeded. ", Color.Green); isDBNameTestOK = true; } private void confimDeployBtn_Click(object sender, EventArgs e) { portBtn_Click(null, null); testServicebtn_Click(null, null); testDBBtn_Click(null, null); if (string.IsNullOrWhiteSpace(this.folderPathTbox.Text)) { WinformLog($"Please select the deployment path ( Confirmation can be made only after port, database name, and service name pass the test.).", Color.Red); return; } if (isDBNameTestOK == false || isPortTestOk == false || isServiceTestOk == false) { WinformLog($"Confirmation can be made only after port, database name, and service name pass the test.", Color.Red); return; } WinformLog($"Deployment configuration confirmed successfully.", Color.Green); activeBtn.Enabled = true; confimDeployBtn.Enabled = false; selectPathBtn.Enabled = false; //portBtn.Enabled = false; //testDBBtn.Enabled = false; //testServicebtn.Enabled = false; nginxPortTBox.Enabled = false; apiPortTBox.Enabled = false; dbNameTBox.Enabled = false; serviceNameTBox.Enabled = false; serviceDisplayNameTBox.Enabled = false; nginxServiceNameTbox.Enabled = false; nginxServiceDisplayNameTbox.Enabled = false; } private static bool IsPortInUse(int port) { IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties(); IPEndPoint[] tcpEndPoints = ipProperties.GetActiveTcpListeners(); foreach (IPEndPoint endPoint in tcpEndPoints) { if (endPoint.Port == port) { return true; } } return false; } public static string Md5(string target) { using (MD5 md5 = MD5.Create()) { // MD5非线程安全 byte[] bytes = md5.ComputeHash(Encoding.UTF8.GetBytes(target)); StringBuilder sb = new StringBuilder(32); for (int i = 0; i < bytes.Length; ++i) sb.Append(bytes[i].ToString("x2")); return sb.ToString(); } } private void WinformLog(string message, Color color) { if (InvokeRequired) { Invoke(new Action(() => WinformLog(message, color))); return; } logTBox.SelectionColor = color; logTBox.AppendText($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}: {message}" + Environment.NewLine); Serilog.Log.Information(message); } private async Task ProcessStandardInputAsync(Process process, string cmd, string workDirectory = "") { if (!string.IsNullOrWhiteSpace(workDirectory)) { process.StartInfo.WorkingDirectory = workDirectory; } await process.StandardInput.WriteLineAsync(cmd); await process.StandardInput.FlushAsync(); //while (!process.StandardOutput.EndOfStream) //{ // var output = await process.StandardOutput.ReadLineAsync(); // if (string.IsNullOrWhiteSpace(output)) // { // break; // } //} } private async void activeBtn_Click(object sender, EventArgs e) { if (Md5($"{machineTextBox.Text}_XINGCANG") != KeySecreteTextBox.Text.Trim()) { WinformLog($"Secret key is not current. Please contact the vendor!", Color.Red); return; } this.activeBtn.Enabled = false; var serviceName = serviceNameTBox.Text; var serviceDisplayName = serviceDisplayNameTBox.Text; var nginxServiceName = nginxServiceNameTbox.Text; var nginxServiceDesName = nginxServiceDisplayNameTbox.Text; var deployFoder = this.folderPathTbox.Text; string nginxExtractName = "LiLiDICOMViewerWeb"; string apiExtractName = "LiLiDICOMViewerService"; string dataExtraName = "LiLiDICOMViewerData"; string resouceFolder = "Resource"; string nginxStartPath = Path.Combine(deployFoder, @$"{nginxExtractName}"); var nginxConfigPath = Path.Combine(nginxStartPath, @$"conf\nginx.conf"); var nginxEXEPath = Path.Combine(nginxStartPath, "nginx.exe"); string nginxServiceEXEPath = Path.Combine(nginxStartPath, "nginxService.exe"); string nginxServiceXMLPath = Path.Combine(nginxStartPath, "nginxService.xml"); var apiBinPath = Path.Combine(deployFoder, apiExtractName, "EI_Med_Viewer.exe"); var apiJsonConfigPath = Path.Combine(deployFoder, apiExtractName,"appsettings.Test_510K.json"); string nginxRarFilePath = Path.Combine(AppContext.BaseDirectory, resouceFolder, "LiLiDICOMViewerWeb.rar"); string apiRarFilePath = Path.Combine(AppContext.BaseDirectory, resouceFolder, "LiLiDICOMViewerService.rar"); string dataRarFilePath = Path.Combine(AppContext.BaseDirectory, resouceFolder, "LiLiDICOMViewerData.rar"); #region 创建进程执行命令 var startInfo = new ProcessStartInfo(); startInfo.FileName = "cmd.exe"; // 指定要启动的应用程序 startInfo.RedirectStandardInput = true; startInfo.UseShellExecute = false; startInfo.CreateNoWindow = true; // 创建新进程并启动 var process = new Process(); process.StartInfo = startInfo; process.Start(); #endregion await Task.Run(async () => { #region 解压nginx 和前端部署的文件 WinformLog($"Start extracting Nginx and front-end deployment compressed files...", Color.Green); string extractPath = Path.Combine(deployFoder, nginxExtractName); if (!Directory.Exists(extractPath)) { Directory.CreateDirectory(extractPath); } using (var archive = RarArchive.Open(nginxRarFilePath)) { foreach (var entry in archive.Entries) { if (!entry.IsDirectory) { entry.WriteToDirectory(extractPath, new ExtractionOptions() { ExtractFullPath = true, Overwrite = true }); } else { string directoryPath = Path.Combine(extractPath, entry.Key); Directory.CreateDirectory(directoryPath); } } } WinformLog($"Nginx and front-end deployment compressed files have been successfully extracted to: {extractPath}", Color.Green); #endregion #region 修改 nginx 配置文件 启动nginx if (!File.Exists(nginxConfigPath)) { WinformLog("No nginx exists under the default path.", Color.Red); } WinformLog("Prepare to write in Nginx configuration...", Color.Green); var nginxConfig = File.ReadAllText(nginxConfigPath); nginxConfig = nginxConfig.Replace("9520", vuePort.ToString()); nginxConfig = nginxConfig.Replace("7100", apiPort.ToString()); File.WriteAllText(nginxConfigPath, nginxConfig); WinformLog("Nginx deployment configuration write-in completed!", Color.Green); #region nginxservice WinformLog("Prepare Nginx self-starting service configuration file...", Color.Green); var nginxServiceConfig = File.ReadAllText(nginxServiceXMLPath); nginxServiceConfig = nginxServiceConfig.Replace("{nginxName}", nginxServiceName); nginxServiceConfig = nginxServiceConfig.Replace("{nginxDesName}", nginxServiceDesName); nginxServiceConfig = nginxServiceConfig.Replace("{nginxPath}", nginxEXEPath); nginxServiceConfig = nginxServiceConfig.Replace("{nginxStartPath}", nginxStartPath); File.WriteAllText(nginxServiceXMLPath, nginxServiceConfig); WinformLog("Nginx self-starting service configuration write-in completed", Color.Green); #endregion WinformLog($"Prepare to create self-starting nginx service...", Color.Green); var nginxCreateStr = $@"{nginxServiceEXEPath} install "; // 执行 sc create 命令来创建服务 await ProcessStandardInputAsync(process, nginxCreateStr); WinformLog($"The execution of creating Service {nginxServiceName} ends", Color.Green); await Task.Delay(2000); #region nginx 服务 WinformLog($"Start the deployed service {nginxServiceName}...", Color.Green); await ProcessStandardInputAsync(process, $"sc start {nginxServiceName}"); ServiceController scNginx = new ServiceController(nginxServiceName); if (scNginx.Status != ServiceControllerStatus.Running) { scNginx.Refresh(); await Task.Delay(3000); scNginx.Refresh(); await Task.Delay(2000); } if (scNginx.Status == ServiceControllerStatus.Running) { WinformLog($"Nginx service started successfully.", Color.Green); } else { WinformLog($"Nginx service failed to start.", Color.Red); } #endregion #region 命令行方式启动 废弃 //// 创建ProcessStartInfo对象,指定要启动的可执行文件及其参数 //ProcessStartInfo psi = new ProcessStartInfo(nginxEXEPath); //// 指定工作目录,即进入nginx.exe所在的目录 //psi.WorkingDirectory = nginxStartPath; //WinformLog(" Start nginx service...", Color.Green); //// 启动可执行文件 //Process.Start(psi); //if (Process.GetProcesses().Any(t => t.ProcessName.Contains("nginx") && t.MainModule.FileName.Contains(nginxStartPath))) //{ // WinformLog(" nginx service started successfully", Color.Green); //} //else //{ // WinformLog(" nginx service failed to start", Color.Red); // return; //} #endregion #endregion }); await Task.Run(() => { #region 解压后端部署的文件 WinformLog($"Start extracting the backend deployment compressed file...", Color.Green); string apiExtractPath = Path.Combine(deployFoder, apiExtractName); if (!Directory.Exists(apiExtractPath)) { Directory.CreateDirectory(apiExtractPath); } using (var archive = RarArchive.Open(apiRarFilePath)) { foreach (var entry in archive.Entries) { if (!entry.IsDirectory) { entry.WriteToDirectory(apiExtractPath, new ExtractionOptions() { ExtractFullPath = true, Overwrite = true }); } else { string directoryPath = Path.Combine(apiExtractPath, entry.Key); Directory.CreateDirectory(directoryPath); } } } WinformLog($"The backend deployment compressed file has been successfully extracted to: {apiExtractPath}", Color.Green); #endregion }); await Task.Run(() => { #region 解压模板文件 WinformLog($"Start extracting template files…", Color.Green); string extractPath = Path.Combine(deployFoder, dataExtraName); if (!Directory.Exists(extractPath)) { Directory.CreateDirectory(extractPath); } using (var archive = RarArchive.Open(dataRarFilePath)) { foreach (var entry in archive.Entries) { if (!entry.IsDirectory) { entry.WriteToDirectory(extractPath, new ExtractionOptions() { ExtractFullPath = true, Overwrite = true }); } } } WinformLog($"Template files have been successfully extracted to: {extractPath}", Color.Green); #endregion #region 文档等资源文件 var otherFileFolder = Path.Combine(AppContext.BaseDirectory, $@"Resource"); if (Directory.Exists(otherFileFolder)) { // 获取源目录下所有文件 string[] files = Directory.GetFiles(otherFileFolder); foreach (string file in files) { // 筛选掉文件夹 if (!File.GetAttributes(file).HasFlag(FileAttributes.Directory)) { string extension = Path.GetExtension(file); if (extension != ".zip" && extension != ".rar" && extension != ".7z" && extension != ".tar" && extension != ".gz") { // 获取文件名 string fileName = Path.GetFileName(file); // 拷贝文件到目标目录 File.Copy(file, Path.Combine(deployFoder, fileName), true); } } } } WinformLog($"Document resources have been released successfully.", Color.Green); #endregion }); await Task.Run(() => { #region 激活 try { string directoryPath = @"C:\ProgramData\.xingcang"; if (!Directory.Exists(directoryPath)) { Directory.CreateDirectory(directoryPath); } var configObj = new { key = this.machineTextBox.Text, value = this.KeySecreteTextBox.Text.Trim(), user = usernameTextBox.Text, server = serverTextBox.Text, password = passwordTextBox.Text, dbName = dbNameTBox.Text, deployFolder = deployFoder, nginxStartPath = nginxStartPath, serviceName = serviceName, nginxServiceName = nginxServiceName, nginxServiceEXEPath = nginxServiceEXEPath }; File.WriteAllText(Path.Combine(directoryPath, "config.json"), JsonConvert.SerializeObject(configObj)); WinformLog("Activation key writing has been completed.", Color.Green); } catch (Exception ex) { WinformLog("Activation key writing failed. Please launch the application in administrator's mode, and make sure that the application has sufficient permissions.", Color.Red); } #endregion }); await Task.Run(async () => { #region 创建服务 WinformLog($"Start creating a service {serviceName}...", Color.Green); var createTestStr = $"sc create {serviceName} binPath= \"{apiBinPath} --urls=http://*:{apiPort} --env Test_510K\" DisplayName= \"{serviceDisplayName}\" start= auto"; var createStr = $@"sc create {serviceName} binPath= ""{apiBinPath} --urls=http://127.0.0.1:{apiPort} --env Test_510K"" DisplayName= ""{serviceDisplayName}"" start= auto"; // 执行 sc create 命令来创建服务 await ProcessStandardInputAsync(process, createStr); WinformLog($"Creating {serviceName} service has ended.", Color.Green); #endregion #region 初始化数据库脚本 WinformLog($"Initialize database script...", Color.Green); //执行数据库脚本 await ProcessStandardInputAsync(process, $@" SQLCMD -S {serverTextBox.Text} -U {usernameTextBox.Text} -P {passwordTextBox.Text} -v dbName = ""{dbNameTBox.Text}"" -i ""{AppContext.BaseDirectory}Resource\Data\dbo.sql"" "); await Task.Delay(3000); await ProcessStandardInputAsync(process, $@" SQLCMD -S {serverTextBox.Text} -U {usernameTextBox.Text} -P {passwordTextBox.Text} -v dbName = ""{dbNameTBox.Text}"" -i ""{AppContext.BaseDirectory}Resource\Data\data.sql"" "); WinformLog($"Initializing database script has ended.", Color.Green); #endregion #region 部署网站配置文件写入 WinformLog("Start writing service configuration file…", Color.Green); var appsettingsJson = File.ReadAllText(apiJsonConfigPath); // 解析 JSON 字符串 var jObject = JObject.Parse(appsettingsJson); // 获取 UpdateConfig 属性所在的节点 var updateConfigNode = jObject["ConnectionStrings"]; var connectionString = $"Server={serverTextBox.Text};Database={dbNameTBox.Text};User ID={usernameTextBox.Text};Password={passwordTextBox.Text};TrustServerCertificate=true"; updateConfigNode["RemoteNew"] = connectionString; try { using (var connection = new SqlConnection(connectionString)) { connection.Open(); File.WriteAllText(apiJsonConfigPath, jObject.ToString()); } } catch (SqlException ex) { WinformLog($"Database connection string error", Color.Red); return; } WinformLog("Service configuration file writing & testing has been done.", Color.Green); #endregion }); await Task.Run(async () => { #region 启动后端服务 WinformLog($"Start the backend service.", Color.Green); await ProcessStandardInputAsync(process, $"sc start {serviceName}"); // 关闭进程流并等待进程退出 process.StandardInput.Close(); process.WaitForExit(); ServiceController sc = new ServiceController(serviceName); if (sc.Status != ServiceControllerStatus.Running) { sc.Refresh(); await Task.Delay(3000); sc.Refresh(); await Task.Delay(2000); } if (sc.Status == ServiceControllerStatus.Running) { WinformLog($"Backend service started successfully.", Color.Green); WinformLog($"Deployment has been completed.", Color.Green); try { WinformLog($"Ready to open the browser…", Color.Green); await Task.Delay(2000); // Use ProcessStartInfo class var start = new ProcessStartInfo($"http://127.0.0.1:{vuePort}") { UseShellExecute = true, Verb = "open" }; var urlProcess = Process.Start(start); urlProcess.WaitForExit(); // 等待进程结束 } catch (Exception ex) { // Handle exception MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } else { WinformLog($"Backend service failed to start. Please try to start {serviceDisplayName} manually. If a manual start still fails, please contact tech support to confirm the deployment environment.", Color.Red); } #endregion await Task.Delay(10000); Application.Exit(); }); } private void selectPathBtn_Click(object sender, EventArgs e) { FolderBrowserDialog dialog = new FolderBrowserDialog(); if (dialog.ShowDialog() == DialogResult.OK) { folderPathTbox.Text = dialog.SelectedPath; confimDeployBtn.Enabled = true; WinformLog($"Deployment path selection was successful.", Color.Green); } } private void btnCopy_Click(object sender, EventArgs e) { Clipboard.SetText(machineTextBox.Text); WinformLog("Machine code has been copied to the clipboard successfully. ", Color.Green); } private void Main_Load(object sender, EventArgs e) { Serilog.Log.Logger = new LoggerConfiguration() .WriteTo.File("logs\\log.txt", rollingInterval: RollingInterval.Day) .CreateLogger(); // 检查是否安装了 SQL Server bool isSqlServerInstalled = false; RegistryKey key = null; try { key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Microsoft SQL Server"); if (key != null) isSqlServerInstalled = true; } finally { key?.Close(); } if (isSqlServerInstalled) { // 获取 SQL Server 实例列表 key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL"); if (key != null) { foreach (var instanceName in key.GetValueNames()) { WinformLog($"An SQL Server instance already exists and its name is: {instanceName}", Color.Green); } } } else { WinformLog("SQL Server is not installed. Please install it before running the deployment program.", Color.Red); } key?.Close(); testNetcoreRuntime(); //this.TopMost = true; this.Activate(); } } }