source code checkin

This commit is contained in:
Brett Hewitson 2021-01-07 16:23:23 +10:00
parent 5f8fb2c825
commit 7e57b72e35
675 changed files with 168433 additions and 0 deletions

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2"/>
</startup>
</configuration>

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,293 @@
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;
}
}
}

View file

@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ServerManager Updater")]
[assembly: AssemblyDescription("An application used to update the server managers.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Bletch1971")]
[assembly: AssemblyProduct("Server Managers")]
[assembly: AssemblyCopyright("Copyright © 2015-2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("3e0c9ee6-780f-4fef-ba03-e38062a5fdb6")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0")]
[assembly: AssemblyFileVersion("1.0.0.1")]

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<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 Manifest Options
If you want to change the Windows User Account Control level replace the
requestedExecutionLevel node with one of the following.
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
Specifying requestedExecutionLevel node will disable file and registry virtualization.
If you want to utilize File and Registry Virtualization for backward
compatibility then delete the requestedExecutionLevel node.
-->
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
<applicationRequestMinimum>
<defaultAssemblyRequest permissionSetReference="Custom" />
<PermissionSet class="System.Security.PermissionSet" version="1" ID="Custom" SameSite="site" Unrestricted="true" />
</applicationRequestMinimum>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of all Windows versions that this application is designed to work with.
Windows will automatically select the most compatible environment.-->
<!-- If your application is designed to work with Windows Vista, uncomment the following supportedOS node-->
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"></supportedOS>-->
<!-- If your application is designed to work with Windows 7, uncomment the following supportedOS node-->
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>-->
<!-- If your application is designed to work with Windows 8, uncomment the following supportedOS node-->
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"></supportedOS>-->
<!-- If your application is designed to work with Windows 8.1, uncomment the following supportedOS node-->
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>-->
</application>
</compatibility>
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
<!-- <dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>-->
</asmv1:assembly>

View file

@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Globals">
<SccProjectName>%24/Development/ServerManagers/Main/ServerManager.Updater</SccProjectName>
<SccProvider>{4CA58AB2-18FA-4F8D-95D4-32DDF27D184C}</SccProvider>
<SccAuxPath>https://dev.azure.com/bretthewitson</SccAuxPath>
<SccLocalPath>.</SccLocalPath>
</PropertyGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net462</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<ApplicationIcon>Art\favicon.ico</ApplicationIcon>
<StartupObject>ServerManagerTool.Updater.Program</StartupObject>
<AssemblyName>ServerManagerUpdater</AssemblyName>
<RootNamespace>ServerManagerTool.Updater</RootNamespace>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<Reference Include="System.Management" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,20 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace ServerManagerTool.Updater
{
public static class IOUtils
{
[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DeleteFile(string name);
public static bool Unblock(string fileName)
{
return DeleteFile(fileName + ":Zone.Identifier");
}
public static string NormalizePath(string path) => Path.GetFullPath(new Uri(path).LocalPath).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).ToLowerInvariant();
}
}

View file

@ -0,0 +1,113 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
namespace ServerManagerTool.Updater
{
public static class ProcessUtils
{
public static string FIELD_COMMANDLINE = "CommandLine";
public static string FIELD_EXECUTABLEPATH = "ExecutablePath";
public static string FIELD_PROCESSID = "ProcessId";
private static Mutex _mutex;
[DllImport("shell32.dll", SetLastError = true)]
static extern IntPtr CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, out int pNumArgs);
public static string[] CommandLineToArgs(string commandLine)
{
var argv = CommandLineToArgvW(commandLine, out int argc);
if (argv == IntPtr.Zero)
throw new Win32Exception();
try
{
var args = new string[argc];
for (var i = 0; i < args.Length; i++)
{
var p = Marshal.ReadIntPtr(argv, i * IntPtr.Size);
args[i] = Marshal.PtrToStringUni(p);
}
return args;
}
finally
{
Marshal.FreeHGlobal(argv);
}
}
public static string GetCommandLineForProcess(int processId)
{
var wmiQueryString = $"SELECT {FIELD_COMMANDLINE} FROM Win32_Process WHERE {FIELD_PROCESSID} = {processId}";
using (var searcher = new ManagementObjectSearcher(wmiQueryString))
{
using (var results = searcher.Get())
{
ManagementObject mo = results.Cast<ManagementObject>().FirstOrDefault();
if (mo != null)
return (string)mo[FIELD_COMMANDLINE];
}
}
return null;
}
public static string GetMainModuleFilepath(int processId)
{
var wmiQueryString = $"SELECT {FIELD_EXECUTABLEPATH} FROM Win32_Process WHERE {FIELD_PROCESSID} = {processId}";
using (var searcher = new ManagementObjectSearcher(wmiQueryString))
{
using (var results = searcher.Get())
{
ManagementObject mo = results.Cast<ManagementObject>().FirstOrDefault();
if (mo != null)
return (string)mo[FIELD_EXECUTABLEPATH];
}
}
return null;
}
public static Process GetProcess(int processId)
{
return Process.GetProcessById(processId);
}
public static Process[] GetProcesses(string processName, string executablePath)
{
var runningProcesses = Process.GetProcessesByName(processName).ToList();
for (var i = runningProcesses.Count - 1; i >= 0; i--)
{
var process = runningProcesses[i];
var runningPath = GetMainModuleFilepath(process.Id);
if (!string.Equals(executablePath, runningPath, StringComparison.OrdinalIgnoreCase))
runningProcesses.RemoveAt(i);
}
return runningProcesses.ToArray();
}
public static bool IsAlreadyRunning()
{
var assemblyLocation = Assembly.GetEntryAssembly().Location;
var name = $"Global::{Path.GetFileName(assemblyLocation)}";
_mutex = new Mutex(true, name, out bool createdNew);
if (createdNew)
_mutex.ReleaseMutex();
return !createdNew;
}
}
}

View file

@ -0,0 +1,15 @@
namespace ServerManagerTool.Updater
{
public static class ScriptUtils
{
public static string AsQuoted(this string parameter)
{
var newValue = parameter;
if (!newValue.StartsWith("\""))
newValue = "\"" + newValue;
if (!newValue.EndsWith("\""))
newValue = newValue + "\"";
return newValue;
}
}
}