Add service install and uninstall methods.

This commit is contained in:
Jared Goodwin 2020-02-21 19:59:14 -08:00
parent c6b19120d9
commit c032a5d8b3
11 changed files with 383 additions and 90 deletions

View File

@ -73,6 +73,7 @@
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="Models\InstallerSettings.cs" />
<Compile Include="Services\Executor.cs" />
<Compile Include="Services\InstallerService.cs" />
<Compile Include="Services\Logger.cs" />
<Compile Include="ViewModels\MainWindowViewModel.cs" />

View File

@ -1,8 +1,8 @@
<Application x:Class="Agent.Installer.Win.App"
<Application x:Class="Remotely.Agent.Installer.Win.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Remotely.Agent.Installer.Win"
StartupUri="MainWindow.xaml">
StartupUri="MainWindow.xaml" Startup="App_Startup">
<Application.Resources>
<Style x:Key="TitlebarButton" TargetType="Button">
<Setter Property="Background" Value="#FF464646"></Setter>

View File

@ -1,8 +1,12 @@
using System;
using Remotely.Agent.Installer.Win.Services;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
@ -13,5 +17,24 @@ namespace Remotely.Agent.Installer.Win
/// </summary>
public partial class App : Application
{
public void App_Startup(object sender, StartupEventArgs e)
{
if (e.Args.Contains("-uninstall"))
{
try
{
Logger.Write("Uninstall command received. Preparing temp directory.");
var targetPath = Path.Combine(Path.GetTempPath(), "Remotely_Installer.exe");
File.Copy(Assembly.GetExecutingAssembly().Location, targetPath, true);
Logger.Write("Launching uninstaller.");
Process.Start(targetPath, "-rununinstall");
Environment.Exit(0);
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
}
}
}

View File

@ -10,7 +10,7 @@
ResizeMode="NoResize"
MouseLeftButtonDown="Window_MouseLeftButtonDown"
Loaded="Window_Loaded"
Title="Remotely Installer" Height="350" Width="500" Icon="Assets/favicon.ico">
Title="Remotely Installer" Height="325" Width="500" Icon="Assets/favicon.ico">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
@ -33,56 +33,53 @@
</DockPanel>
</Border>
<Grid Grid.Row="1" Margin="10,15,10,0">
<StackPanel Visibility="{Binding IsServiceMissing, Converter={StaticResource BooleanToVisibilityConverter}}">
<TextBlock Style="{StaticResource SectionHeader}" Text="{Binding HeaderMessage}"></TextBlock>
<TextBlock TextWrapping="Wrap" Margin="25,5,0,0">
<Run>Organization: </Run>
<Run Text="{Binding OrganizationName}"></Run>
</TextBlock>
<TextBlock TextWrapping="Wrap" Margin="25,5,0,0">
<Run>Server URL: </Run>
<Run Text="{Binding ServerUrl}"></Run>
</TextBlock>
<TextBlock TextWrapping="Wrap" Margin="0,15,0,0" Text="{Binding SubMessage}"></TextBlock>
<ProgressBar Height="25" Margin="0,25,0,0" Value="{Binding Progress}"></ProgressBar>
<TextBlock TextWrapping="Wrap" Margin="0,15,0,0" Text="{Binding StatusMessage}"></TextBlock>
</StackPanel>
<StackPanel Visibility="{Binding IsServiceInstalled, Converter={StaticResource BooleanToVisibilityConverter}}">
<StackPanel>
<TextBlock Style="{StaticResource SectionHeader}" Text="{Binding HeaderMessage}"></TextBlock>
<TextBlock TextWrapping="Wrap" Margin="25,5,0,0" Visibility="{Binding IsServiceMissing, Converter={StaticResource BooleanToVisibilityConverter}}">
<Run>Organization: </Run>
<TextBlock TextWrapping="Wrap" Margin="25,5,0,0">
<Run>Provider: </Run>
<Run Text="{Binding OrganizationName}"></Run>
</TextBlock>
<TextBlock TextWrapping="Wrap" Margin="25,5,0,0" Visibility="{Binding IsServiceMissing, Converter={StaticResource BooleanToVisibilityConverter}}">
<TextBlock TextWrapping="Wrap" Margin="25,5,0,0">
<Run>Server URL: </Run>
<Run Text="{Binding ServerUrl}"></Run>
</TextBlock>
<TextBlock TextWrapping="Wrap" Margin="0,15,0,0" Text="{Binding SubMessage}"></TextBlock>
<ProgressBar Height="25" Margin="0,25,0,0" Value="{Binding Progress}" Visibility="{Binding IsProgressVisible, Converter={StaticResource BooleanToVisibilityConverter}}"></ProgressBar>
<TextBlock TextWrapping="Wrap" Margin="0,15,0,0" Text="{Binding StatusMessage}"></TextBlock>
</StackPanel>
</Grid>
<DockPanel Margin="10" Grid.Row="2">
<Button DockPanel.Dock="Left"
HorizontalAlignment="Left"
Style="{StaticResource NormalButton}"
Margin="5,0,0,0"
Command="{Binding OpenLogsCommand}">
Logs
</Button>
<Button DockPanel.Dock="Right"
HorizontalAlignment="Right"
Style="{StaticResource NormalButton}"
Margin="5,0,0,0">
Margin="5,0,0,0"
Click="CloseButton_Click">
Close
</Button>
<Button DockPanel.Dock="Right"
HorizontalAlignment="Right"
Style="{StaticResource NormalButton}"
Margin="5,0,0,0"
Visibility="{Binding IsServiceMissing, Converter={StaticResource BooleanToVisibilityConverter}}">
Visibility="{Binding IsServiceMissing, Converter={StaticResource BooleanToVisibilityConverter}}"
Command="{Binding InstallCommand}">
Install
</Button>
<Button DockPanel.Dock="Right"
HorizontalAlignment="Right"
Style="{StaticResource NormalButton}"
Margin="5,0,0,0"
Visibility="{Binding IsServiceInstalled, Converter={StaticResource BooleanToVisibilityConverter}}">
Visibility="{Binding IsServiceInstalled, Converter={StaticResource BooleanToVisibilityConverter}}"
Command="{Binding UninstallCommand}">
Uninstall
</Button>
</DockPanel>

View File

@ -31,9 +31,9 @@ namespace Remotely.Agent.Installer.Win
DragMove();
}
private async void Window_Loaded(object sender, RoutedEventArgs e)
private void Window_Loaded(object sender, RoutedEventArgs e)
{
await (DataContext as MainWindowViewModel).Init();
(DataContext as MainWindowViewModel).Init();
}
private void CloseButton_Click(object sender, RoutedEventArgs e)

View File

@ -12,5 +12,11 @@ namespace Remotely.Agent.Installer.Win
{
[DataMember]
public string OrganizationID { get; set; }
[DataMember]
public string OrganizationName { get; set; }
[DataMember]
public string ServerUrl { get; set; }
}
}

View File

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace Remotely.Agent.Installer.Win.Services
{
public class Executor : ICommand
{
public Executor(Action<object> executeAction, Predicate<object> isExecutable = null)
{
ExecuteAction = executeAction;
IsExecutable = isExecutable;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
private Action<object> ExecuteAction { get; set; }
private Predicate<object> IsExecutable { get; set; }
public bool CanExecute(object parameter)
{
if (IsExecutable == null)
{
return true;
}
return IsExecutable.Invoke(parameter);
}
public void Execute(object parameter)
{
ExecuteAction.Invoke(parameter);
}
}
}

View File

@ -2,51 +2,106 @@
using System.Collections.Generic;
using System.Configuration.Install;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Principal;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace Remotely.Agent.Installer.Win.Services
{
public class InstallerService
{
private void InstallService(List<string> args)
public bool InstallService()
{
try
{
if (!CheckIsAdministrator())
{
return false;
}
Logger.Write("Install started.");
var installPath = System.Reflection.Assembly.GetExecutingAssembly().Location;
var serv = ServiceController.GetServices().FirstOrDefault(ser => ser.ServiceName == "Remotely_Service");
if (serv == null)
{
string[] command = new String[] { "/assemblypath=" + installPath };
ServiceInstaller ServiceInstallerObj = new ServiceInstaller();
InstallContext Context = new InstallContext("", command);
ServiceInstallerObj.Context = Context;
ServiceInstallerObj.DisplayName = "Remotely Service";
ServiceInstallerObj.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.";
ServiceInstallerObj.ServiceName = "Remotely_Service";
ServiceInstallerObj.StartType = ServiceStartMode.Automatic;
ServiceInstallerObj.DelayedAutoStart = true;
ServiceInstallerObj.Parent = new ServiceProcessInstaller();
var command = new string[] { "/assemblypath=" + installPath };
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();
System.Collections.Specialized.ListDictionary state = new System.Collections.Specialized.ListDictionary();
ServiceInstallerObj.Install(state);
var state = new System.Collections.Specialized.ListDictionary();
serviceInstaller.Install(state);
Logger.Write("Service installed.");
}
serv = ServiceController.GetServices().FirstOrDefault(ser => ser.ServiceName == "Remotely_Service");
if (serv != null && serv.Status != ServiceControllerStatus.Running)
if (serv.Status != ServiceControllerStatus.Running)
{
serv.Start();
}
Logger.Write("Service started.");
var psi = new ProcessStartInfo("cmd.exe", "/c sc.exe failure \"Remotely_Service\" reset=5 actions=restart/5000");
psi.WindowStyle = ProcessWindowStyle.Hidden;
Process.Start(psi).WaitForExit();
return true;
}
catch (Exception ex)
{
Logger.Write(ex);
return false;
}
}
public async Task<bool> Uninstall()
{
try
{
if (!CheckIsAdministrator())
{
return false;
}
Process.Start("cmd.exe", "/c sc delete Remotely_Service").WaitForExit();
var procs = Process.GetProcessesByName("Remotely_Agent").Concat(Process.GetProcessesByName("Remotely_ScreenCast"));
foreach (var proc in procs)
{
proc.Kill();
}
await Task.Delay(500);
Directory.Delete(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Remotely"), true);
return true;
}
catch (Exception ex)
{
Logger.Write(ex);
return false;
}
}
private bool CheckIsAdministrator()
{
var identity = WindowsIdentity.GetCurrent();
var principal = new WindowsPrincipal(identity);
var result = principal.IsInRole(WindowsBuiltInRole.Administrator);
if (!result)
{
MessageBox.Show("Elevated privileges are required. Please restart the installer using 'Run as administrator'.", "Elevation Required", MessageBoxButton.OK, MessageBoxImage.Warning);
}
return result;
}
}
}

View File

@ -1,23 +1,41 @@
using System;
using Remotely.Agent.Installer.Win.Services;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization.Json;
using System.Security.Principal;
using System.ServiceProcess;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
namespace Remotely.Agent.Installer.Win.ViewModels
{
public class MainWindowViewModel : ViewModelBase
{
private string headerMessage = "Install the Remotely service.";
private bool isServiceInstalled;
private bool isServiceMissing = true;
private string subMessage = "Installing the Remotely service will allow remote access by the above service provider.";
private string statusMessage;
private int progress = 50;
private string headerMessage;
private bool isServiceInstalled;
private string organizationName;
private int progress;
private string serverUrl;
private string statusMessage;
private string subMessage;
public MainWindowViewModel()
{
Installer = new InstallerService();
}
public string HeaderMessage
{
get
@ -31,19 +49,9 @@ namespace Remotely.Agent.Installer.Win.ViewModels
}
}
public ICommand InstallCommand => new Executor(Install);
public int Progress
{
get
{
return progress;
}
set
{
progress = value;
FirePropertyChanged(nameof(Progress));
}
}
public bool IsProgressVisible => Progress > 0;
public bool IsServiceInstalled
{
@ -55,30 +63,68 @@ namespace Remotely.Agent.Installer.Win.ViewModels
{
isServiceInstalled = value;
FirePropertyChanged(nameof(IsServiceInstalled));
}
}
public bool IsServiceMissing
{
get
{
return isServiceMissing;
}
set
{
isServiceMissing = value;
FirePropertyChanged(nameof(IsServiceMissing));
}
}
public string SubMessage
public bool IsServiceMissing => !isServiceInstalled;
public ICommand OpenLogsCommand
{
get
{
return subMessage;
return new Executor(param =>
{
var logPath = Path.Combine(Path.GetTempPath(), "Remotely_Installer.log");
if (File.Exists(logPath))
{
Process.Start(logPath);
}
else
{
MessageBox.Show("Log file doesn't exist.", "No Logs", MessageBoxButton.OK, MessageBoxImage.Information);
}
});
}
}
public string OrganizationID { get; set; }
public string OrganizationName
{
get
{
return organizationName;
}
set
{
subMessage = value;
FirePropertyChanged(nameof(SubMessage));
organizationName = value;
FirePropertyChanged(nameof(OrganizationName));
}
}
public int Progress
{
get
{
return progress;
}
set
{
progress = value;
FirePropertyChanged(nameof(Progress));
FirePropertyChanged(nameof(IsProgressVisible));
}
}
public string ServerUrl
{
get
{
return serverUrl;
}
set
{
serverUrl = value;
FirePropertyChanged(nameof(ServerUrl));
}
}
@ -94,9 +140,46 @@ namespace Remotely.Agent.Installer.Win.ViewModels
FirePropertyChanged(nameof(StatusMessage));
}
}
public string SubMessage
{
get
{
return subMessage;
}
set
{
subMessage = value;
FirePropertyChanged(nameof(SubMessage));
}
}
public ICommand UninstallCommand => new Executor(async (param) => { await Uninstall(param); });
private InstallerService Installer { get; }
public async Task Init()
{
IsServiceInstalled = ServiceController.GetServices().Any(x => x.ServiceName == "Remotely_Service");
if (IsServiceInstalled)
{
HeaderMessage = "Install the Remotely service.";
SubMessage = "Installing the Remotely service will allow remote access by the above service provider.";
}
else
{
HeaderMessage = "Uninstall the Remotely service.";
SubMessage = "Uninstalling the Remotely service will remove all remote acess for the above service provider.";
}
var installerSettings = ReadInstallerSettings();
OrganizationName = installerSettings?.OrganizationName;
ServerUrl = installerSettings?.ServerUrl;
OrganizationID = installerSettings?.OrganizationID;
if (Environment.GetCommandLineArgs().Contains("-rununinstaller"))
{
await Uninstall(null);
}
}
public InstallerSettings ReadInstallerSettings()
@ -104,26 +187,113 @@ namespace Remotely.Agent.Installer.Win.ViewModels
try
{
var fileBytes = File.ReadAllBytes(Assembly.GetExecutingAssembly().Location);
using (var readStream = new MemoryStream(fileBytes))
using (var br = new BinaryReader(readStream))
using (var peStream = new MemoryStream(fileBytes))
using (var br = new BinaryReader(peStream))
{
readStream.Seek(-4, SeekOrigin.End);
peStream.Seek(-4, SeekOrigin.End);
var payloadSize = br.ReadInt32();
readStream.Seek(-4 - payloadSize, SeekOrigin.End);
peStream.Seek(-4 - payloadSize, SeekOrigin.End);
var payloadBytes = br.ReadBytes(payloadSize);
using (var writeStream = new MemoryStream(payloadBytes))
using (var settingsStream = new MemoryStream(payloadBytes))
{
writeStream.Seek(0, SeekOrigin.Begin);
settingsStream.Seek(0, SeekOrigin.Begin);
var serializer = new DataContractJsonSerializer(typeof(InstallerSettings));
var installerSettings = (InstallerSettings)serializer.ReadObject(writeStream);
var installerSettings = (InstallerSettings)serializer.ReadObject(settingsStream);
return installerSettings;
}
}
}
catch (Exception ex)
{
Logger.Write(ex);
MessageBox.Show("There was an error reading the installer settings. Try re-downloading the installer.", "Configuration Error", MessageBoxButton.OK, MessageBoxImage.Error);
return null;
}
}
private bool CheckIsAdministrator()
{
var identity = WindowsIdentity.GetCurrent();
var principal = new WindowsPrincipal(identity);
var result = principal.IsInRole(WindowsBuiltInRole.Administrator);
if (!result)
{
MessageBox.Show("Elevated privileges are required. Please restart the installer using 'Run as administrator'.", "Elevation Required", MessageBoxButton.OK, MessageBoxImage.Warning);
}
return result;
}
private bool CheckParams()
{
var result = !string.IsNullOrWhiteSpace(OrganizationID) && !string.IsNullOrWhiteSpace(ServerUrl);
if (!result)
{
MessageBox.Show("Required settings are missing. Try re-downloading the installer.", "Invalid Installer", MessageBoxButton.OK, MessageBoxImage.Error);
}
return result;
}
private void Install(object param)
{
try
{
if (!CheckParams())
{
return;
}
if (Installer.InstallService())
{
IsServiceInstalled = true;
Progress = 0;
HeaderMessage = "Installation completed.";
SubMessage = "Remotely has been installed. You can now close this window.";
}
else
{
Progress = 0;
HeaderMessage = "An error occurred during installation.";
SubMessage = "There was an error during installation. Check the logs for details.";
}
if (!CheckIsAdministrator())
{
return;
}
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
private async Task Uninstall(object param)
{
try
{
if (!CheckParams())
{
return;
}
if (await Installer.Uninstall())
{
IsServiceInstalled = false;
Progress = 0;
HeaderMessage = "Uninstall completed.";
SubMessage = "Remotely has been uninstalled. You can now close this window.";
}
else
{
Progress = 0;
HeaderMessage = "An error occurred during uninstall.";
SubMessage = "There was an error during uninstall. Check the logs for details.";
}
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
}
}

View File

@ -27,12 +27,6 @@ namespace Remotely.Agent
{
BuildServices();
if (Environment.CommandLine.Contains("-uninstall"))
{
Services.GetRequiredService<Uninstaller>().UninstallAgent();
return;
}
Task.Run(() => { Init(); });
Thread.Sleep(Timeout.Infinite);

View File

@ -12,5 +12,11 @@ namespace Remotely.Shared.Models
{
[DataMember]
public string OrganizationID { get; set; }
[DataMember]
public string OrganizationName { get; set; }
[DataMember]
public string ServerUrl { get; set; }
}
}