mirror of
https://github.com/immense/Remotely.git
synced 2025-10-26 11:27:15 +00:00
365 lines
15 KiB
C#
365 lines
15 KiB
C#
using Microsoft.Win32;
|
|
using Remotely.Shared.Models;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Configuration.Install;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Reflection;
|
|
using System.Security.Principal;
|
|
using System.ServiceProcess;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Web.Script.Serialization;
|
|
using System.Windows;
|
|
|
|
namespace Remotely.Agent.Installer.Win.Services
|
|
{
|
|
public class InstallerService
|
|
{
|
|
public event EventHandler<string> ProgressMessageChanged;
|
|
public event EventHandler<int> ProgressValueChanged;
|
|
|
|
private string InstallPath => Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), "Program Files", "Remotely");
|
|
private string Platform => Environment.Is64BitOperatingSystem ? "x64" : "x86";
|
|
private JavaScriptSerializer Serializer { get; } = new JavaScriptSerializer();
|
|
public async Task<bool> Install(string serverUrl,
|
|
string organizationId,
|
|
string deviceGroup,
|
|
string deviceAlias)
|
|
{
|
|
try
|
|
{
|
|
Logger.Write("Install started.");
|
|
if (!CheckIsAdministrator())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
await InstallDesktpRuntimeIfNeeded();
|
|
|
|
StopService();
|
|
|
|
BackupOrCreateDirectory();
|
|
|
|
var connectionInfo = GetConnectionInfo(organizationId, serverUrl);
|
|
|
|
ClearInstallDirectory();
|
|
|
|
await DownloadRemotelyAgent(serverUrl);
|
|
|
|
File.WriteAllText(Path.Combine(InstallPath, "ConnectionInfo.json"), Serializer.Serialize(connectionInfo));
|
|
|
|
File.Copy(Assembly.GetExecutingAssembly().Location, Path.Combine(InstallPath, "Remotely_Installer.exe"));
|
|
|
|
CreateDeviceSetupOptions(deviceGroup, deviceAlias);
|
|
|
|
AddFirewallRule();
|
|
|
|
InstallService();
|
|
|
|
CreateUninstallKey();
|
|
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Write(ex);
|
|
RestoreBackup();
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
public async Task<bool> Uninstall()
|
|
{
|
|
try
|
|
{
|
|
if (!CheckIsAdministrator())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
StopService();
|
|
|
|
ProcessWrapper.StartHidden("cmd.exe", "/c sc delete Remotely_Service").WaitForExit();
|
|
|
|
|
|
ProgressMessageChanged?.Invoke(this, "Stopping Remotely processes.");
|
|
var procs = Process.GetProcessesByName("Remotely_Agent").Concat(Process.GetProcessesByName("Remotely_ScreenCast"));
|
|
|
|
foreach (var proc in procs)
|
|
{
|
|
proc.Kill();
|
|
}
|
|
|
|
await Task.Delay(500);
|
|
|
|
ProgressMessageChanged?.Invoke(this, "Deleting files.");
|
|
ClearInstallDirectory();
|
|
ProcessWrapper.StartHidden("cmd.exe", $"/c timeout 5 & rd /s /q \"{InstallPath}\"");
|
|
|
|
ProcessWrapper.StartHidden("netsh", "advfirewall firewall delete rule name=\"Remotely ScreenCast\"").WaitForExit();
|
|
|
|
Registry.LocalMachine.DeleteSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Remotely", false);
|
|
|
|
return true;
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Write(ex);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private void AddFirewallRule()
|
|
{
|
|
var screenCastPath = Path.Combine(InstallPath, "ScreenCast", "Remotely_ScreenCast.exe");
|
|
ProcessWrapper.StartHidden("netsh", "advfirewall firewall delete rule name=\"Remotely ScreenCast\"").WaitForExit();
|
|
ProcessWrapper.StartHidden("netsh", $"advfirewall firewall add rule name=\"Remotely ScreenCast\" program=\"{screenCastPath}\" protocol=any dir=in enable=yes action=allow profile=Private,Domain description=\"The agent that allows screen sharing and remote control for Remotely.\"").WaitForExit();
|
|
}
|
|
|
|
private void BackupOrCreateDirectory()
|
|
{
|
|
if (Directory.Exists(InstallPath))
|
|
{
|
|
Logger.Write("Backing up current installation.");
|
|
ProgressMessageChanged?.Invoke(this, "Backing up current installation.");
|
|
var backupPath = Path.Combine(Path.GetTempPath(), "Remotely_Backup.zip");
|
|
if (File.Exists(backupPath))
|
|
{
|
|
File.Delete(backupPath);
|
|
}
|
|
ZipFile.CreateFromDirectory(InstallPath, backupPath, CompressionLevel.Fastest, false);
|
|
}
|
|
else
|
|
{
|
|
Directory.CreateDirectory(InstallPath);
|
|
}
|
|
}
|
|
|
|
private bool CheckIsAdministrator()
|
|
{
|
|
var identity = WindowsIdentity.GetCurrent();
|
|
var principal = new WindowsPrincipal(identity);
|
|
var result = principal.IsInRole(WindowsBuiltInRole.Administrator);
|
|
if (!result)
|
|
{
|
|
MessageBoxWrapper.Show("Elevated privileges are required. Please restart the installer using 'Run as administrator'.", "Elevation Required", MessageBoxButton.OK, MessageBoxImage.Warning);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private void ClearInstallDirectory()
|
|
{
|
|
if (Directory.Exists(InstallPath))
|
|
{
|
|
foreach (var entry in Directory.GetFileSystemEntries(InstallPath))
|
|
{
|
|
try
|
|
{
|
|
if (File.Exists(entry))
|
|
{
|
|
File.Delete(entry);
|
|
}
|
|
else if (Directory.Exists(entry))
|
|
{
|
|
Directory.Delete(entry, true);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Write(ex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CreateDeviceSetupOptions(string deviceGroup, string deviceAlias)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(deviceGroup) ||
|
|
!string.IsNullOrWhiteSpace(deviceAlias))
|
|
{
|
|
var setupOptions = new
|
|
{
|
|
DeviceGroup = deviceGroup,
|
|
DeviceAlias = deviceAlias
|
|
};
|
|
|
|
File.WriteAllText(Path.Combine(InstallPath, "DeviceSetupOptions.json"), Serializer.Serialize(setupOptions));
|
|
}
|
|
}
|
|
|
|
private void CreateUninstallKey()
|
|
{
|
|
var remotelyKey = Registry.LocalMachine.CreateSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Remotely", true);
|
|
remotelyKey.SetValue("DisplayIcon", Path.Combine(InstallPath, "Remotely_Agent.exe"));
|
|
remotelyKey.SetValue("DisplayName", "Remotely");
|
|
remotelyKey.SetValue("DisplayVersion", Assembly.GetExecutingAssembly().GetName().Version.ToString());
|
|
remotelyKey.SetValue("InstallDate", DateTime.Now.ToShortDateString());
|
|
remotelyKey.SetValue("Publisher", "Translucency Software");
|
|
remotelyKey.SetValue("VersionMajor", Assembly.GetExecutingAssembly().GetName().Version.Major.ToString(), RegistryValueKind.DWord);
|
|
remotelyKey.SetValue("VersionMinor", Assembly.GetExecutingAssembly().GetName().Version.Minor.ToString(), RegistryValueKind.DWord);
|
|
remotelyKey.SetValue("UninstallString", Path.Combine(InstallPath, "Remotely_Installer.exe -uninstall"));
|
|
remotelyKey.SetValue("QuietUninstallString", Path.Combine(InstallPath, "Remotely_Installer.exe -uninstall -quiet"));
|
|
}
|
|
private async Task DownloadRemotelyAgent(string serverUrl)
|
|
{
|
|
ProgressMessageChanged.Invoke(this, "Downloading Remotely agent.");
|
|
var client = new WebClient();
|
|
client.DownloadProgressChanged += (sender, args) =>
|
|
{
|
|
ProgressValueChanged?.Invoke(this, args.ProgressPercentage);
|
|
};
|
|
var targetFile = Path.Combine(Path.GetTempPath(), $"Remotely-Agent.zip");
|
|
await client.DownloadFileTaskAsync($"{serverUrl}/Downloads/Remotely-Win10-{Platform}.zip", targetFile);
|
|
|
|
ProgressMessageChanged.Invoke(this, "Extracting Remotely files.");
|
|
ProgressValueChanged?.Invoke(this, 0);
|
|
await Task.Run(() => { ZipFile.ExtractToDirectory(targetFile, InstallPath); });
|
|
}
|
|
|
|
private ConnectionInfo GetConnectionInfo(string organizationId, string serverUrl)
|
|
{
|
|
ConnectionInfo connectionInfo;
|
|
var connectionInfoPath = Path.Combine(InstallPath, "ConnectionInfo.json");
|
|
if (File.Exists(connectionInfoPath))
|
|
{
|
|
connectionInfo = Serializer.Deserialize<ConnectionInfo>(File.ReadAllText(connectionInfoPath));
|
|
connectionInfo.ServerVerificationToken = null;
|
|
}
|
|
else
|
|
{
|
|
connectionInfo = new ConnectionInfo()
|
|
{
|
|
DeviceID = Guid.NewGuid().ToString()
|
|
};
|
|
}
|
|
|
|
connectionInfo.OrganizationID = organizationId;
|
|
connectionInfo.Host = serverUrl;
|
|
return connectionInfo;
|
|
}
|
|
|
|
private async Task InstallDesktpRuntimeIfNeeded()
|
|
{
|
|
Logger.Write("Checking for .NET Core runtime.");
|
|
var uninstallKeys = new List<RegistryKey>();
|
|
var runtimeInstalled = false;
|
|
|
|
foreach (var subkeyName in Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\", false).GetSubKeyNames())
|
|
{
|
|
var subkey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\" + subkeyName, false);
|
|
if (subkey?.GetValue("DisplayName")?.ToString()?.Contains("Microsoft Windows Desktop Runtime - 3.1.2") == true)
|
|
{
|
|
runtimeInstalled = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!runtimeInstalled)
|
|
{
|
|
Logger.Write("Downloading .NET Core runtime.");
|
|
ProgressMessageChanged.Invoke(this, "Downloading the .NET Core runtime.");
|
|
var client = new WebClient();
|
|
client.DownloadProgressChanged += (sender, args) =>
|
|
{
|
|
ProgressValueChanged?.Invoke(this, args.ProgressPercentage);
|
|
};
|
|
var downloadUrl = string.Empty;
|
|
if (Environment.Is64BitOperatingSystem)
|
|
{
|
|
downloadUrl = "https://download.visualstudio.microsoft.com/download/pr/3240250e-6fe0-4258-af69-85abef6c00de/e01ee0af6c65d894f4a02bdf6705ec7b/windowsdesktop-runtime-3.1.2-win-x64.exe";
|
|
}
|
|
else
|
|
{
|
|
downloadUrl = "https://download.visualstudio.microsoft.com/download/pr/b824906f-bd6e-4067-86a6-95c61620674d/cfcdab84a01cee94fdaa31271c3d4d47/windowsdesktop-runtime-3.1.2-win-x86.exe";
|
|
}
|
|
var targetFile = Path.Combine(Path.GetTempPath(), "windowsdesktop-runtime.exe");
|
|
await client.DownloadFileTaskAsync(downloadUrl, targetFile);
|
|
|
|
Logger.Write("Installing .NET Core runtime.");
|
|
ProgressMessageChanged?.Invoke(this, "Installing the .NET Core runtime.");
|
|
ProgressValueChanged?.Invoke(this, 0);
|
|
|
|
await Task.Run(() => { ProcessWrapper.StartHidden(targetFile, "/install /quiet /norestart").WaitForExit(); });
|
|
}
|
|
else
|
|
{
|
|
Logger.Write(".NET Core runtime already installed.");
|
|
}
|
|
}
|
|
|
|
private void InstallService()
|
|
{
|
|
Logger.Write("Installing service.");
|
|
ProgressMessageChanged?.Invoke(this, "Installing Remotely service.");
|
|
var serv = ServiceController.GetServices().FirstOrDefault(ser => ser.ServiceName == "Remotely_Service");
|
|
if (serv == null)
|
|
{
|
|
var command = new string[] { "/assemblypath=" + Path.Combine(InstallPath, "Remotely_Agent.exe") };
|
|
var serviceInstaller = new ServiceInstaller();
|
|
var context = new InstallContext("", command);
|
|
serviceInstaller.Context = context;
|
|
serviceInstaller.DisplayName = "Remotely Service";
|
|
serviceInstaller.Description = "Background service that maintains a connection to the Remotely server. The service is used for remote support and maintenance by this computer's administrators.";
|
|
serviceInstaller.ServiceName = "Remotely_Service";
|
|
serviceInstaller.StartType = ServiceStartMode.Automatic;
|
|
serviceInstaller.Parent = new ServiceProcessInstaller();
|
|
|
|
var state = new System.Collections.Specialized.ListDictionary();
|
|
serviceInstaller.Install(state);
|
|
Logger.Write("Service installed.");
|
|
serv = ServiceController.GetServices().FirstOrDefault(ser => ser.ServiceName == "Remotely_Service");
|
|
|
|
ProcessWrapper.StartHidden("cmd.exe", "/c sc.exe failure \"Remotely_Service\" reset=5 actions=restart/5000");
|
|
}
|
|
if (serv.Status != ServiceControllerStatus.Running)
|
|
{
|
|
serv.Start();
|
|
}
|
|
Logger.Write("Service started.");
|
|
}
|
|
|
|
private void RestoreBackup()
|
|
{
|
|
try
|
|
{
|
|
var backupPath = Path.Combine(Path.GetTempPath(), "Remotely_Backup.zip");
|
|
if (File.Exists(backupPath))
|
|
{
|
|
Logger.Write("Restoring backup.");
|
|
ClearInstallDirectory();
|
|
ZipFile.ExtractToDirectory(backupPath, InstallPath);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Write(ex);
|
|
}
|
|
}
|
|
private void StopService()
|
|
{
|
|
try
|
|
{
|
|
var remotelyService = ServiceController.GetServices().FirstOrDefault(x => x.ServiceName == "Remotely_Service");
|
|
if (remotelyService != null)
|
|
{
|
|
Logger.Write("Stopping existing Remotely service.");
|
|
ProgressMessageChanged?.Invoke(this, "Stopping existing Remotely service.");
|
|
remotelyService.Stop();
|
|
remotelyService.WaitForStatus(ServiceControllerStatus.Stopped);
|
|
}
|
|
}
|
|
catch(Exception ex)
|
|
{
|
|
Logger.Write(ex);
|
|
}
|
|
}
|
|
}
|
|
}
|