Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#54 Updater flow refactor and improvements #76

Merged
merged 23 commits into from
Feb 18, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ae6df46
#54 Replaced the Win32 pipe handling with .NET
robinwassen Feb 4, 2016
168588f
#54 Increased the pipe timeout to 15s
robinwassen Feb 5, 2016
074039a
#54 More detailed error handling in LaunchProcessAndSendDto
robinwassen Feb 5, 2016
413b8eb
#54 Refactor of the updater
robinwassen Feb 5, 2016
c5ba68e
#54 Improved exception handling in Updater
robinwassen Feb 10, 2016
1b7e41f
#54 Code cleanup in the updater
robinwassen Feb 10, 2016
caa69aa
#54 Additional clean up of the updater
robinwassen Feb 10, 2016
9826ca0
#54 Commit of a csproj file that i forgot
robinwassen Feb 10, 2016
92da31e
#54 Refactored the process start options in updater.exe
robinwassen Feb 10, 2016
491be06
#54 Some further clean up of the updater
robinwassen Feb 10, 2016
8b752d0
#54 Removed my personal working notes
robinwassen Feb 10, 2016
b8a89a5
#54 Bumped up the updater.exe to .NET 3.5
robinwassen Feb 10, 2016
ed18e92
#54 Minor code cleanup
robinwassen Feb 10, 2016
504e5c3
#54 Changed the AfterRestart check
robinwassen Feb 10, 2016
52d99ed
#54 Pass the -nappupdate-afterrestart argument when relaunching appli…
Feb 11, 2016
398456e
#54 Increased target framework version of FeedBuilder to .NET 3.5
Feb 11, 2016
cd6048f
#54 The calls to Application.DoEvents are needed to make the console …
Feb 11, 2016
3a82629
#54 Pass HResult of exception as exit code if unhandled exceptions oc…
Feb 11, 2016
aa5c26b
#54 Instead of showing error message, always log unhandled exceptions…
Feb 11, 2016
f25af3c
#54 Simplified error handling logic of failed update tasks
Feb 11, 2016
95f9599
#54 Added the message box again but only show it if the application i…
Feb 11, 2016
f8267da
#54 Added check for null in Teardown
Feb 11, 2016
9d3f5ad
Merge pull request #75 from FWennerdahl/54-timeout-error-handling
robinwassen Feb 17, 2016
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
5 changes: 3 additions & 2 deletions FeedBuilder/FeedBuilder.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<AssemblyName>FeedBuilder</AssemblyName>
<FileAlignment>512</FileAlignment>
<MyType>WindowsForms</MyType>
<TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<TargetFrameworkProfile>
</TargetFrameworkProfile>
<NoWarn>1591</NoWarn>
Expand Down Expand Up @@ -136,6 +136,7 @@
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
Expand All @@ -153,4 +154,4 @@
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.Targets" />
</Project>
</Project>
2 changes: 1 addition & 1 deletion FeedBuilder/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions FeedBuilder/Properties/Settings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions FeedBuilder/app.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup><supportedRuntime version="v2.0.50727"/></startup></configuration>
4 changes: 3 additions & 1 deletion src/NAppUpdate.Framework/NAppUpdate.Framework.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>NAppUpdate.Framework</RootNamespace>
<AssemblyName>NAppUpdate.Framework</AssemblyName>
<TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>NAppUpdate.snk</AssemblyOriginatorKeyFile>
Expand All @@ -33,6 +33,7 @@
<IsWebBootstrapper>false</IsWebBootstrapper>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
Expand Down Expand Up @@ -101,6 +102,7 @@
<Compile Include="Utils\NauIpc.cs" />
<Compile Include="Utils\FileSystem.cs" />
<Compile Include="Utils\PermissionsCheck.cs" />
<Compile Include="Utils\ProcessStartFailedException.cs" />
<Compile Include="Utils\Reflection.cs" />
</ItemGroup>
<ItemGroup>
Expand Down
27 changes: 25 additions & 2 deletions src/NAppUpdate.Framework/UpdateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -506,10 +506,20 @@ public void ApplyUpdates(bool relaunchApplication, bool updaterDoLogging, bool u

public void ReinstateIfRestarted()
{
if (!IsAfterRestart())
{
return;
}

lock (UpdatesToApply)
{
var dto = NauIpc.ReadDto(Config.UpdateProcessName) as NauIpc.NauDto;
if (dto == null) return;
NauIpc.NauDto dto = NauIpc.ReadDto(Config.UpdateProcessName);

if (dto == null)
{
return;
}

Config = dto.Configs;
UpdatesToApply = dto.Tasks;
Logger = new Logger(dto.LogItems);
Expand Down Expand Up @@ -581,5 +591,18 @@ public void CleanUp()
ShouldStop = false;
}
}

private bool IsAfterRestart()
{
foreach (string arg in Environment.GetCommandLineArgs())
{
if (arg == "-nappupdate-afterrestart")
{
return true;
}
}

return false;
}
}
}
Binary file modified src/NAppUpdate.Framework/Updater/updater.exe
Binary file not shown.
171 changes: 45 additions & 126 deletions src/NAppUpdate.Framework/Utils/NauIpc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Runtime.Serialization.Formatters.Binary;
using NAppUpdate.Framework.Common;
using NAppUpdate.Framework.Tasks;
using System.IO.Pipes;

namespace NAppUpdate.Framework.Utils
{
Expand All @@ -29,158 +30,76 @@ internal class NauDto
public bool RelaunchApplication { get; set; }
}

[DllImport("kernel32.dll", SetLastError = true)]
private static extern SafeFileHandle CreateNamedPipe(
String pipeName,
uint dwOpenMode,
uint dwPipeMode,
uint nMaxInstances,
uint nOutBufferSize,
uint nInBufferSize,
uint nDefaultTimeOut,
IntPtr lpSecurityAttributes);

[DllImport("kernel32.dll", SetLastError = true)]
private static extern int ConnectNamedPipe(
SafeFileHandle hNamedPipe,
IntPtr lpOverlapped);

[DllImport("kernel32.dll", SetLastError = true)]
private static extern SafeFileHandle CreateFile(
String pipeName,
uint dwDesiredAccess,
uint dwShareMode,
IntPtr lpSecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
IntPtr hTemplate);

//private const uint DUPLEX = (0x00000003);
private const uint WRITE_ONLY = (0x00000002);
private const uint FILE_FLAG_OVERLAPPED = (0x40000000);

const uint GENERIC_READ = (0x80000000);
//static readonly uint GENERIC_WRITE = (0x40000000);
const uint OPEN_EXISTING = 3;

//Which really isn't an error...
const uint ERROR_PIPE_CONNECTED = 535;

internal static string GetPipeName(string syncProcessName)
{
return string.Format("\\\\.\\pipe\\{0}", syncProcessName);
}

private class State
{
public readonly EventWaitHandle eventWaitHandle;
public int result { get; set; }
public SafeFileHandle clientPipeHandle { get; set; }
public Exception exception;

public State()
{
eventWaitHandle = new ManualResetEvent(false);
}
}

internal static uint BUFFER_SIZE = 4096;
private const int PIPE_TIMEOUT = 15000;

/// <summary>
/// Launches the specifies process and sends the dto object to it using a named pipe
/// </summary>
/// <param name="dto">Dto object to send</param>
/// <param name="processStartInfo">Process info for the process to start</param>
/// <param name="syncProcessName">Name of the pipe to write to</param>
/// <returns>The started process</returns>
public static Process LaunchProcessAndSendDto(NauDto dto, ProcessStartInfo processStartInfo, string syncProcessName)
{
Process p;
State state = new State();

using (state.clientPipeHandle = CreateNamedPipe(
GetPipeName(syncProcessName),
WRITE_ONLY | FILE_FLAG_OVERLAPPED,
0,
1, // 1 max instance (only the updater utility is expected to connect)
BUFFER_SIZE,
BUFFER_SIZE,
0,
IntPtr.Zero))

using (NamedPipeServerStream pipe = new NamedPipeServerStream(syncProcessName, PipeDirection.Out, 1, PipeTransmissionMode.Message, PipeOptions.Asynchronous))
{
//failed to create named pipe
if (state.clientPipeHandle.IsInvalid)
p = Process.Start(processStartInfo);

if (p == null)
{
throw new Exception("Launch process client: Failed to create named pipe, handle is invalid.");
throw new ProcessStartFailedException("The process failed to start");
}

// This will throw Win32Exception if the user denies UAC
p = Process.Start(processStartInfo);

ThreadPool.QueueUserWorkItem(ConnectPipe, state);
//A rather arbitary five seconds, perhaps better to be user configurable at some point?
state.eventWaitHandle.WaitOne(10000);
var asyncResult = pipe.BeginWaitForConnection(null, null);

//failed to connect client pipe
if (state.result == 0)
if (asyncResult.AsyncWaitHandle.WaitOne(PIPE_TIMEOUT))
{
throw new Exception("Launch process client: Failed to connect to named pipe", state.exception);
pipe.EndWaitForConnection(asyncResult);

BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(pipe, dto);
}
else if (p.HasExited)
{
Type exceptionType = Marshal.GetExceptionForHR(p.ExitCode).GetType();

//client connection successfull
using (var fStream = new FileStream(state.clientPipeHandle, FileAccess.Write, (int)BUFFER_SIZE, true))
throw new TimeoutException(string.Format("The NamedPipeServerStream timed out waiting for a named pipe connection, " +
"but the process has exited with exit code: {0} ({1})", p.ExitCode, exceptionType.FullName));
}
else
{
new BinaryFormatter().Serialize(fStream, dto);
fStream.Flush();
fStream.Close();
throw new TimeoutException("The NamedPipeServerStream timed out waiting for a named pipe connection.");
}
}

return p;
}

internal static void ConnectPipe(object stateObject)
/// <summary>
/// Reads the dto object from the named pipe
/// </summary>
/// <param name="syncProcessName">Name of the pipe to read from</param>
/// <returns>The dto object read from the pipe</returns>
public static NauDto ReadDto(string syncProcessName)
{
State state = (State)stateObject;
NauDto dto;

try
{
state.result = ConnectNamedPipe(state.clientPipeHandle, IntPtr.Zero);
}
catch (Exception e)
using (NamedPipeClientStream pipe = new NamedPipeClientStream(".", syncProcessName, PipeDirection.In, PipeOptions.Asynchronous))
{
state.exception = e;
}
pipe.Connect(PIPE_TIMEOUT);

int error = Marshal.GetLastWin32Error();
//Check for the oddball: ERROR - PIPE CONNECTED
//Ref: http://msdn.microsoft.com/en-us/library/windows/desktop/aa365146%28v=vs.85%29.aspx
if (error == ERROR_PIPE_CONNECTED)
{
state.result = 1;
}
else if (error != 0)
{
state.exception = new Win32Exception(error);
BinaryFormatter formatter = new BinaryFormatter();
dto = formatter.Deserialize(pipe) as NauDto;
}

state.eventWaitHandle.Set(); // signal we're done
}


internal static object ReadDto(string syncProcessName)
{
using (SafeFileHandle pipeHandle = CreateFile(
GetPipeName(syncProcessName),
GENERIC_READ,
0,
IntPtr.Zero,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
IntPtr.Zero))
if (dto == null || dto.Configs == null)
{

if (pipeHandle.IsInvalid)
return null;

using (var fStream = new FileStream(pipeHandle, FileAccess.Read, (int)BUFFER_SIZE, true))
{
return new BinaryFormatter().Deserialize(fStream);
}
throw new Exception("Failed to read the dto from the pipe stream");
}

return dto;
}

internal static void ExtractUpdaterFromResource(string updaterPath, string hostExeName)
Expand Down Expand Up @@ -212,4 +131,4 @@ internal static void ExtractUpdaterFromResource(string updaterPath, string hostE
}
}
}
}
}
16 changes: 16 additions & 0 deletions src/NAppUpdate.Framework/Utils/ProcessStartFailedException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace NAppUpdate.Framework.Utils
{
/// <summary>
/// Thrown if the Process.Start() call returns null, e.g. the updater process fails to start at all.
/// </summary>
internal class ProcessStartFailedException : Exception
{
public ProcessStartFailedException(string message) : base(message) { }
}

}
Loading