ServerManagers/src/ServerManager.Updater/Program.cs
2021-01-07 16:23:23 +10:00

293 lines
10 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 = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.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)
{
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}");
}
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.Length != 1)
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;
}
}
}