mirror of
https://github.com/tribufu/ServerManagers
synced 2026-05-06 15:17:34 +00:00
- Removed TLS13 from the list of accepted security protocols, as not all OS support TLS13.
301 lines
11 KiB
C#
301 lines
11 KiB
C#
using System;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Reflection;
|
|
|
|
namespace ServerManagerTool.Updater
|
|
{
|
|
public class Program
|
|
{
|
|
private static string[] ApplicationArgs
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
private static string DownloadUrl
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
private static string Prefix
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public static void Main(string[] args)
|
|
{
|
|
Console.Title = "Server Manager Updater";
|
|
|
|
ServicePointManager.SecurityProtocol = SecurityUtils.GetSecurityProtocol(0xC00); // TLS12
|
|
|
|
var updaterArgs = args;
|
|
ApplicationArgs = null;
|
|
DownloadUrl = null;
|
|
Prefix = null;
|
|
|
|
try
|
|
{
|
|
// check if the updater is already running
|
|
if (ProcessUtils.IsAlreadyRunning())
|
|
throw new Exception("The updater is already running.");
|
|
|
|
var process = Validate(updaterArgs);
|
|
CloseApplication(process);
|
|
process = null;
|
|
|
|
Update();
|
|
RestartApplication();
|
|
|
|
Environment.ExitCode = 0;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (ex.InnerException != null)
|
|
{
|
|
OutputError(ex.InnerException.Message, null);
|
|
}
|
|
OutputError(ex.Message, ex.StackTrace);
|
|
Environment.ExitCode = 1;
|
|
|
|
OutputMessage("Press any key to continue.");
|
|
Console.ReadKey(true);
|
|
}
|
|
}
|
|
|
|
private static void CloseApplication(Process process)
|
|
{
|
|
OutputMessage("Closing application...");
|
|
|
|
if (process == null || process.HasExited)
|
|
throw new Exception("The process is not associated with a running application.");
|
|
|
|
// send a close to the application
|
|
process.CloseMainWindow();
|
|
|
|
// wait until the application has been closed
|
|
DateTime start = DateTime.Now;
|
|
while (!process.HasExited)
|
|
{
|
|
if (start.AddMinutes(5) < DateTime.Now)
|
|
{
|
|
process.Kill();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!process.HasExited)
|
|
throw new Exception("The application could not be closed.");
|
|
}
|
|
|
|
private static void OutputError(string error, string stackTrace)
|
|
{
|
|
Console.WriteLine($"ERROR: {error}");
|
|
#if DEBUG
|
|
if (!string.IsNullOrWhiteSpace(stackTrace))
|
|
Console.WriteLine(stackTrace);
|
|
#endif
|
|
}
|
|
|
|
private static void OutputMessage(string message)
|
|
{
|
|
Console.WriteLine(message);
|
|
}
|
|
|
|
private static void RestartApplication()
|
|
{
|
|
OutputMessage("Restarting application...");
|
|
|
|
var file = ApplicationArgs[0];
|
|
var arguments = string.Empty;
|
|
if (ApplicationArgs.Length > 1)
|
|
{
|
|
string[] argumentArray = new string[ApplicationArgs.Length - 1];
|
|
Array.Copy(ApplicationArgs, 1, argumentArray, 0, argumentArray.Length);
|
|
arguments = String.Join(" ", argumentArray);
|
|
}
|
|
|
|
ProcessStartInfo info = new ProcessStartInfo()
|
|
{
|
|
FileName = file.AsQuoted(),
|
|
Arguments = arguments,
|
|
UseShellExecute = true,
|
|
CreateNoWindow = true,
|
|
};
|
|
|
|
var process = Process.Start(info);
|
|
if (process == null)
|
|
throw new Exception("Could not restart application.");
|
|
}
|
|
|
|
private static void Update()
|
|
{
|
|
if (ApplicationArgs == null || ApplicationArgs.Length == 0)
|
|
throw new ArgumentNullException(nameof(ApplicationArgs), "The application args cannot be null or empty.");
|
|
if (string.IsNullOrWhiteSpace(DownloadUrl))
|
|
throw new ArgumentNullException(nameof(DownloadUrl), "The download url cannot be null or empty.");
|
|
if (string.IsNullOrWhiteSpace(Prefix))
|
|
throw new ArgumentNullException(nameof(Prefix), "The prefix cannot be null or empty.");
|
|
|
|
OutputMessage("Starting upgrade process...");
|
|
|
|
var currentInstallPath = Path.GetDirectoryName(ApplicationArgs[0]);
|
|
var upgradeStagingPath = Path.GetTempPath();
|
|
var applicationZip = Path.Combine(upgradeStagingPath, $"{Prefix}Latest.zip");
|
|
var extractPath = Path.Combine(upgradeStagingPath, $"{Prefix}Latest");
|
|
|
|
// Download the latest version
|
|
using (var client = new WebClient())
|
|
{
|
|
OutputMessage("Downloading latest version file...");
|
|
#if DEBUG
|
|
OutputMessage($"Url: {DownloadUrl}");
|
|
OutputMessage($"Zip: {applicationZip}");
|
|
#endif
|
|
client.DownloadFile(DownloadUrl, applicationZip);
|
|
}
|
|
|
|
// Unblock the downloaded file
|
|
OutputMessage("Preparing downloaded file...");
|
|
#if DEBUG
|
|
OutputMessage($"Zip: {applicationZip}");
|
|
#endif
|
|
IOUtils.Unblock(applicationZip);
|
|
|
|
try
|
|
{
|
|
// Delete the old extraction directory
|
|
if (Directory.Exists(extractPath))
|
|
{
|
|
#if DEBUG
|
|
OutputMessage("Deleting extract directory...");
|
|
OutputMessage($"Dir: {extractPath}");
|
|
#endif
|
|
Directory.Delete(extractPath, true);
|
|
}
|
|
}
|
|
catch { }
|
|
|
|
// Extract latest version to extraction directory
|
|
OutputMessage("Extracting latest version to staging area...");
|
|
#if DEBUG
|
|
OutputMessage($"Zip: {applicationZip}");
|
|
OutputMessage($"Dir: {extractPath}");
|
|
#endif
|
|
ZipFile.ExtractToDirectory(applicationZip, extractPath);
|
|
|
|
// build a list of files to be updated
|
|
var latestFiles = Directory.GetFiles(extractPath, "*.*", SearchOption.AllDirectories).ToList();
|
|
|
|
// build a list of files to be removed
|
|
var deleteFiles = Directory.GetFiles(currentInstallPath, "*.*", SearchOption.AllDirectories).ToList();
|
|
foreach (var latestFile in latestFiles)
|
|
{
|
|
var file = Path.Combine(currentInstallPath, latestFile.Replace($"{extractPath}\\", ""));
|
|
if (deleteFiles.Contains(file))
|
|
deleteFiles.Remove(file);
|
|
}
|
|
|
|
OutputMessage("Upgrading application...");
|
|
|
|
// delete the obsolete files
|
|
foreach (var deleteFile in deleteFiles)
|
|
{
|
|
if (File.Exists(deleteFile))
|
|
{
|
|
#if DEBUG
|
|
OutputMessage($"Delete: {deleteFile}");
|
|
#endif
|
|
//File.Delete(deleteFile);
|
|
}
|
|
}
|
|
|
|
// remove the updater from the files to be updated
|
|
var assemblyFile = Assembly.GetEntryAssembly().Location;
|
|
var assemblyPath = Path.GetDirectoryName(assemblyFile);
|
|
|
|
var updaterFile = Path.Combine(extractPath, assemblyFile.Replace($"{assemblyPath}\\", ""));
|
|
#if DEBUG
|
|
OutputMessage($"Checking: {updaterFile}");
|
|
#endif
|
|
if (latestFiles.Contains(updaterFile))
|
|
latestFiles.Remove(updaterFile);
|
|
|
|
var updaterConfigFile = Path.Combine(updaterFile, ".config");
|
|
#if DEBUG
|
|
OutputMessage($"Checking: {updaterConfigFile}");
|
|
#endif
|
|
if (latestFiles.Contains(updaterConfigFile))
|
|
latestFiles.Remove(updaterConfigFile);
|
|
|
|
// Replace the current installation
|
|
foreach (var latestFile in latestFiles)
|
|
{
|
|
var file = Path.Combine(currentInstallPath, latestFile.Replace($"{extractPath}\\", ""));
|
|
if (File.Exists(latestFile))
|
|
{
|
|
#if DEBUG
|
|
OutputMessage($"Copy: {latestFile} to {file}");
|
|
#endif
|
|
// check if the directory exists
|
|
var filePath = Path.GetDirectoryName(file);
|
|
if (!Directory.Exists(filePath))
|
|
Directory.CreateDirectory(filePath);
|
|
|
|
// copy over the file
|
|
File.Copy(latestFile, file, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static Process Validate(string[] args)
|
|
{
|
|
OutputMessage("Validating update...");
|
|
|
|
// argument format - PID, DownloadUrl, Prefix
|
|
if (args == null)
|
|
throw new ArgumentNullException(nameof(args), "The arguments cannot be null or empty");
|
|
if (args == null || args.Length != 3)
|
|
throw new ArgumentOutOfRangeException(nameof(args), "The arguments do not contain valid information.");
|
|
|
|
// check if the passed pid is valid
|
|
if (!int.TryParse(args[0], out int pid))
|
|
throw new InvalidCastException("The process id value is not valid.");
|
|
|
|
DownloadUrl = args[1];
|
|
if (string.IsNullOrWhiteSpace(DownloadUrl))
|
|
throw new ArgumentNullException(nameof(DownloadUrl), "The download url cannot be null or empty.");
|
|
|
|
Prefix = args[2];
|
|
if (string.IsNullOrWhiteSpace(Prefix))
|
|
throw new ArgumentNullException(nameof(Prefix), "The prefix cannot be null or empty.");
|
|
|
|
// get the process associated with the pid
|
|
var process = ProcessUtils.GetProcess(pid);
|
|
if (process == null)
|
|
throw new Exception("The process id value is not associated with a running application.");
|
|
|
|
// get a list of the running processes with the same name and file location
|
|
var executablePath = ProcessUtils.GetMainModuleFilepath(pid);
|
|
var processes = ProcessUtils.GetProcesses(process.ProcessName, executablePath);
|
|
|
|
// check if there is more than one instance of the application running
|
|
if (!processes.HasOne())
|
|
throw new Exception("The application to be updated has more than one instance running.");
|
|
|
|
// get the command line of the process
|
|
var commandLine = ProcessUtils.GetCommandLineForProcess(pid);
|
|
ApplicationArgs = ProcessUtils.CommandLineToArgs(commandLine);
|
|
|
|
if (ApplicationArgs == null || ApplicationArgs.Length == 0)
|
|
throw new ArgumentNullException(nameof(ApplicationArgs), "The application args cannot be null or empty.");
|
|
|
|
return process;
|
|
}
|
|
}
|
|
}
|