Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions src/Squirrel.Client/InstallManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
using ReactiveUIMicro;
using Squirrel.Client.Extensions;
using Squirrel.Core;
using System.IO.Pipes;
using System.Security.Principal;
using System.Diagnostics;

namespace Squirrel.Client
{
Expand Down Expand Up @@ -165,12 +168,111 @@ async Task<List<string>> executeInstall(

public IObservable<Unit> ExecuteUninstall(Version version = null)
{
// Run uninstall
var updateManager = new UpdateManager("http://lol", BundledRelease.PackageName, FrameworkVersion.Net40, TargetRootDirectory);

return updateManager.FullUninstall(version)
.ObserveOn(RxApp.DeferredScheduler)
.Log(this, "Full uninstall")
.Finally(updateManager.Dispose);
}

public IObservable<bool> RequestExitAndWait(int connectionTimeout = 1000, int waitForExitTimeout = 1000)
{
return Observable.Start(() => requestExitAndWait(connectionTimeout, waitForExitTimeout), RxApp.TaskpoolScheduler);
}

bool requestExitAndWait(int connectionTimeout, int waitForExitTimeout)
{
// TODO: what if the app is running but doesn't respond in time?
// currently it's interpreted as the app being closed so the uninstall will proceed as usual

var pipeClient = new NamedPipeClientStream(".", GetPipeName(BundledRelease.PackageName), PipeDirection.InOut, PipeOptions.None);
bool connected = false;
try {
pipeClient.Connect(connectionTimeout);
connected = true;
} catch (TimeoutException) { }

if (connected) {
var buffer = new byte[validateBytes.Length];
pipeClient.Read(buffer, 0, validateBytes.Length);

if (buffer.SequenceEqual(validateBytes)) {
pipeClient.Write(exitMessage, 0, exitMessage.Length);
log.Info("Attempted to request running instance to exit");

var processIdBuffer = new byte[4];
pipeClient.Read(processIdBuffer, 0, processIdBuffer.Length);
int processId = BitConverter.ToInt32(processIdBuffer, 0);
log.Info("Going to wait for running instance (" + processId + ") to exit");

var process = default(Process);
try {
process = Process.GetProcessById(processId);
} catch (ArgumentException) { } // Process specified by processId is not running

// Check for id 0 because GetProcessById might actually succeed even though the app closed
// The resulting Process has an id of 0 and will throw a Win32Exception on most access attempts
if (process == null || process.Id == 0 || process.HasExited || process.WaitForExit(waitForExitTimeout)) {
log.Info("Running instance exited");
return true;
} else {
log.Info("Running instance did not exit in time");
return false;
}
} else {
log.Info("Connected to a named pipe but didn't respond or wasn't Squirrel");
return true;
}
} else {
log.Info("No running instances found to request exit");
return true;
}
}

// TODO: Clean up
// TODO: Should the client validate the server or the other way around? Should there be validation?
private static readonly byte[] validateBytes = new[] { 'S', 'Q', 'U', 'I', 'R', 'R', 'E', 'L' }.Select(x => (byte)x).ToArray();
private static readonly byte[] exitMessage = new[] { 'E', 'X', 'I', 'T' }.Select(x => (byte)x).ToArray();
public static IObservable<Unit> ListenForExitRequest(string applicationName)
{
var log = LogManager.GetLogger<InstallManager>();

var subject = new Subject<Unit>();

Task.Factory.StartNew(() => {
while (true) {
var pipeServer = new NamedPipeServerStream(GetPipeName(applicationName), PipeDirection.InOut);
pipeServer.WaitForConnection();
try {
pipeServer.Write(validateBytes, 0, validateBytes.Length);
var message = new byte[8];
pipeServer.Read(message, 0, message.Length);
if (message.Take(exitMessage.Length).SequenceEqual(exitMessage)) {
int processId = Process.GetCurrentProcess().Id;
var processBytes = BitConverter.GetBytes(processId);
pipeServer.Write(processBytes, 0, processBytes.Length);
subject.OnNext(Unit.Default);
log.Info("Named pipe server received exit request; sent back process id");
} else {
log.Info("Named pipe server received unknown message");
}
} catch (IOException ex) {
log.ErrorException("Named pipe server failed", ex);
} finally {
pipeServer.Close();
}
}
});

return subject;
}

// TODO: Clean up
private static string GetPipeName(string applicationName)
{
return "SquirrelPipe " + applicationName;
}
}
}
8 changes: 8 additions & 0 deletions src/Squirrel.WiXUi/WixUiBootstrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,14 @@ public WixUiBootstrapper(
}

if (wixEvents.Action == LaunchAction.Uninstall) {
// Try and get the app closed if it's listening for exit requests
// If it isn't, or fails to exit in time, we'll mark it for deletion on restart anyway
//
// TODO: if it was listening for an exit request make a ui to ask
// whether to cancel/try again/continue anyway (restart needed)
// and/or tell the user to close the app
installManager.RequestExitAndWait().Wait();

var task = installManager.ExecuteUninstall(BundledRelease.Version);
task.Subscribe(
_ => wixEvents.Engine.Apply(wixEvents.MainWindowHwnd),
Expand Down