using IWshRuntimeLibrary; using Microsoft.VisualBasic.FileIO; using Microsoft.Win32; using Remotely.Agent.Installer.Win.Utilities; using Remotely.Shared.Models; using System; 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.Threading.Tasks; using System.Web.Script.Serialization; using System.Windows; using FileIO = System.IO.File; namespace Remotely.Agent.Installer.Win.Services { public class InstallerService { private readonly string _installPath = Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), "Program Files", "Remotely"); private readonly string _platform = Environment.Is64BitOperatingSystem ? "x64" : "x86"; private readonly JavaScriptSerializer _serializer = new JavaScriptSerializer(); public event EventHandler ProgressMessageChanged; public event EventHandler ProgressValueChanged; public async Task Install(string serverUrl, string organizationId, string deviceGroup, string deviceAlias, string deviceUuid, bool createSupportShortcut) { try { Logger.Write("Install started."); if (!CheckIsAdministrator()) { return false; } StopService(); await StopProcesses(); BackupDirectory(); var connectionInfo = GetConnectionInfo(organizationId, serverUrl, deviceUuid); ClearInstallDirectory(); await DownloadRemotelyAgent(serverUrl); FileIO.WriteAllText(Path.Combine(_installPath, "ConnectionInfo.json"), _serializer.Serialize(connectionInfo)); FileIO.Copy(Assembly.GetExecutingAssembly().Location, Path.Combine(_installPath, "Remotely_Installer.exe")); await CreateDeviceOnServer(connectionInfo.DeviceID, serverUrl, deviceGroup, deviceAlias, organizationId); AddFirewallRule(); InstallService(); CreateUninstallKey(); CreateSupportShortcut(serverUrl, connectionInfo.DeviceID, createSupportShortcut); return true; } catch (Exception ex) { Logger.Write(ex); RestoreBackup(); return false; } } public async Task Uninstall() { try { if (!CheckIsAdministrator()) { return false; } StopService(); ProcessEx.StartHidden("cmd.exe", "/c sc delete Remotely_Service").WaitForExit(); await StopProcesses(); ProgressMessageChanged?.Invoke(this, "Deleting files."); ClearInstallDirectory(); ProcessEx.StartHidden("cmd.exe", $"/c timeout 5 & rd /s /q \"{_installPath}\""); ProcessEx.StartHidden("netsh", "advfirewall firewall delete rule name=\"Remotely Desktop Unattended\"").WaitForExit(); GetRegistryBaseKey().DeleteSubKeyTree(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Remotely", false); return true; } catch (Exception ex) { Logger.Write(ex); return false; } } private void AddFirewallRule() { var desktopExePath = Path.Combine(_installPath, "Desktop", "Remotely_Desktop.exe"); ProcessEx.StartHidden("netsh", "advfirewall firewall delete rule name=\"Remotely Desktop Unattended\"").WaitForExit(); ProcessEx.StartHidden("netsh", $"advfirewall firewall add rule name=\"Remotely Desktop Unattended\" program=\"{desktopExePath}\" protocol=any dir=in enable=yes action=allow description=\"The agent that allows screen sharing and remote control for Remotely.\"").WaitForExit(); } private void BackupDirectory() { 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 (FileIO.Exists(backupPath)) { FileIO.Delete(backupPath); } ZipFile.CreateFromDirectory(_installPath, backupPath, CompressionLevel.Fastest, false); } } private bool CheckIsAdministrator() { var identity = WindowsIdentity.GetCurrent(); var principal = new WindowsPrincipal(identity); var result = principal.IsInRole(WindowsBuiltInRole.Administrator); if (!result) { MessageBoxEx.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 (FileIO.Exists(entry)) { FileIO.Delete(entry); } else if (Directory.Exists(entry)) { Directory.Delete(entry, true); } } catch (Exception ex) { Logger.Write(ex); } } } } private async Task CreateDeviceOnServer(string deviceUuid, string serverUrl, string deviceGroup, string deviceAlias, string organizationId) { try { if (!string.IsNullOrWhiteSpace(deviceGroup) || !string.IsNullOrWhiteSpace(deviceAlias)) { var setupOptions = new DeviceSetupOptions() { DeviceID = deviceUuid, DeviceGroupName = deviceGroup, DeviceAlias = deviceAlias, OrganizationID = organizationId }; var wr = WebRequest.CreateHttp(serverUrl.TrimEnd('/') + "/api/devices"); wr.Method = "POST"; wr.ContentType = "application/json"; using (var rs = await wr.GetRequestStreamAsync()) using (var sw = new StreamWriter(rs)) { await sw.WriteAsync(_serializer.Serialize(setupOptions)); } using (var response = await wr.GetResponseAsync() as HttpWebResponse) { Logger.Write($"Create device response: {response.StatusCode}"); } } } catch (WebException ex) when ((ex.Response is HttpWebResponse response) && response.StatusCode == HttpStatusCode.BadRequest) { Logger.Write("Bad request when creating device. The device ID may already be created."); } catch (Exception ex) { Logger.Write(ex); } } private void CreateSupportShortcut(string serverUrl, string deviceUuid, bool createSupportShortcut) { var shell = new WshShell(); var shortcutLocation = Path.Combine(_installPath, "Get Support.lnk"); var shortcut = (IWshShortcut)shell.CreateShortcut(shortcutLocation); shortcut.Description = "Get IT support"; shortcut.IconLocation = Path.Combine(_installPath, "Remotely_Agent.exe"); shortcut.TargetPath = serverUrl.TrimEnd('/') + $"/GetSupport?deviceID={deviceUuid}"; shortcut.Save(); if (createSupportShortcut) { var systemRoot = Path.GetPathRoot(Environment.SystemDirectory); var publicDesktop = Path.Combine(systemRoot, "Users", "Public", "Desktop", "Get Support.lnk"); FileIO.Copy(shortcutLocation, publicDesktop, true); } } private void CreateUninstallKey() { var version = FileVersionInfo.GetVersionInfo(Path.Combine(_installPath, "Remotely_Agent.exe")); var baseKey = GetRegistryBaseKey(); var remotelyKey = baseKey.CreateSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Remotely", true); remotelyKey.SetValue("DisplayIcon", Path.Combine(_installPath, "Remotely_Agent.exe")); remotelyKey.SetValue("DisplayName", "Remotely"); remotelyKey.SetValue("DisplayVersion", version.FileVersion); remotelyKey.SetValue("InstallDate", DateTime.Now.ToShortDateString()); remotelyKey.SetValue("Publisher", "Immense Networks"); remotelyKey.SetValue("VersionMajor", version.FileMajorPart.ToString(), RegistryValueKind.DWord); remotelyKey.SetValue("VersionMinor", version.FileMinorPart.ToString(), RegistryValueKind.DWord); remotelyKey.SetValue("UninstallString", Path.Combine(_installPath, "Remotely_Installer.exe -uninstall -quiet")); remotelyKey.SetValue("QuietUninstallString", Path.Combine(_installPath, "Remotely_Installer.exe -uninstall -quiet")); } private async Task DownloadRemotelyAgent(string serverUrl) { var targetFile = Path.Combine(Path.GetTempPath(), $"Remotely-Agent.zip"); if (CommandLineParser.CommandLineArgs.TryGetValue("path", out var result) && FileIO.Exists(result)) { targetFile = result; } else { ProgressMessageChanged.Invoke(this, "Downloading Remotely agent."); using (var client = new WebClient()) { client.DownloadProgressChanged += (sender, args) => { ProgressValueChanged?.Invoke(this, args.ProgressPercentage); }; await client.DownloadFileTaskAsync($"{serverUrl}/Content/Remotely-Win10-{_platform}.zip", targetFile); } } ProgressMessageChanged.Invoke(this, "Extracting Remotely files."); ProgressValueChanged?.Invoke(this, 0); var tempDir = Path.Combine(Path.GetTempPath(), "RemotelyUpdate"); if (Directory.Exists(tempDir)) { Directory.Delete(tempDir, true); } Directory.CreateDirectory(_installPath); while (!Directory.Exists(_installPath)) { await Task.Delay(10); } var wr = WebRequest.CreateHttp($"{serverUrl}/Content/Remotely-Win10-{_platform}.zip"); wr.Method = "Head"; using (var response = (HttpWebResponse)await wr.GetResponseAsync()) { FileIO.WriteAllText(Path.Combine(_installPath, "etag.txt"), response.Headers["ETag"]); } ZipFile.ExtractToDirectory(targetFile, tempDir); var fileSystemEntries = Directory.GetFileSystemEntries(tempDir); for (var i = 0; i < fileSystemEntries.Length; i++) { try { ProgressValueChanged?.Invoke(this, (int)((double)i / (double)fileSystemEntries.Length * 100d)); var entry = fileSystemEntries[i]; if (FileIO.Exists(entry)) { FileIO.Copy(entry, Path.Combine(_installPath, Path.GetFileName(entry)), true); } else if (Directory.Exists(entry)) { FileSystem.CopyDirectory(entry, Path.Combine(_installPath, new DirectoryInfo(entry).Name), true); } await Task.Delay(1); } catch (Exception ex) { Logger.Write(ex); } } ProgressValueChanged?.Invoke(this, 0); } private ConnectionInfo GetConnectionInfo(string organizationId, string serverUrl, string deviceUuid) { ConnectionInfo connectionInfo; var connectionInfoPath = Path.Combine(_installPath, "ConnectionInfo.json"); if (FileIO.Exists(connectionInfoPath)) { connectionInfo = _serializer.Deserialize(FileIO.ReadAllText(connectionInfoPath)); connectionInfo.ServerVerificationToken = null; } else { connectionInfo = new ConnectionInfo() { DeviceID = Guid.NewGuid().ToString() }; } if (!string.IsNullOrWhiteSpace(deviceUuid)) { // Clear the server verification token if we're installing this as a new device. if (connectionInfo.DeviceID != deviceUuid) { connectionInfo.ServerVerificationToken = null; } connectionInfo.DeviceID = deviceUuid; } connectionInfo.OrganizationID = organizationId; connectionInfo.Host = serverUrl; return connectionInfo; } private RegistryKey GetRegistryBaseKey() { if (Environment.Is64BitOperatingSystem) { return RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64); } else { return RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32); } } 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 context = new InstallContext("", command); var serviceInstaller = new ServiceInstaller() { Context = context, DisplayName = "Remotely Service", 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.", ServiceName = "Remotely_Service", StartType = ServiceStartMode.Automatic, 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"); ProcessEx.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 (FileIO.Exists(backupPath)) { Logger.Write("Restoring backup."); ClearInstallDirectory(); ZipFile.ExtractToDirectory(backupPath, _installPath); var serv = ServiceController.GetServices().FirstOrDefault(ser => ser.ServiceName == "Remotely_Service"); if (serv?.Status != ServiceControllerStatus.Running) { serv?.Start(); } } } catch (Exception ex) { Logger.Write(ex); } } private async Task StopProcesses() { ProgressMessageChanged?.Invoke(this, "Stopping Remotely processes."); var procs = Process.GetProcessesByName("Remotely_Agent").Concat(Process.GetProcessesByName("Remotely_Desktop")); foreach (var proc in procs) { proc.Kill(); } await Task.Delay(500); } 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); } } } }