-
Notifications
You must be signed in to change notification settings - Fork 5
Description
Thank you for publishing this - it has been helpful in pointing me in the right direction to setting up a controller for single instance apps that will pass on the command line from and retry attempts to the running process.
My code is massively changed from your to make it more generic as a controller for single instance apps so it isn't really possible for me to just do a PR. I did find a number of issues that other users of your code should be aware of though. Listing here for the record more than expecting you to change your code which is just an example anyway.
1. Terminal Servers etc.
Your code does not work "logically" on these - it will actually limit the app to one per server not one per user session. There may be some requirement for this (once per server, not once per user session) but I suspect mostly it would be once per user session. Fixing this was less that simple. In my code I added a property that needs to be set before you start monitoring that indicates if you are restricting to one per server or not. Mutex part was realtively easy to fix - as Mutex's have a built in selectability using "Local" at the start of the name make it one per session, "Global" one per server. Named pipe part was a lot harder to fix as even if the pipe was restricted from the other sessions the system would not allow a new pipe with the same name (so name is always global one per server). However any change of name per session neeeds to be repeatable without any extra info so the next run can work out the unique pipe name in order to post on the command line to the first app instance on the pipe.
My solution was to use the session ID appended to the pipe name (I added a _ between) for the pipe name - from
int sessionId = Process.GetCurrentProcess().SessionId)I also used the assembly Guid for the base part of my mutex and pipe names (so that same code could be included in different apps without issue - retrieved using:
System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
AssemblyGuid = ((System.Runtime.InteropServices.GuidAttribute)Attribute.GetCustomAttribute(assembly, typeof(System.Runtime.InteropServices.GuidAttribute), false)).Value.ToUpper();So for anyone stil lreading the summary was:
One Instance Per Server:
MutexName = "Global\" + AssemblyGuid;
PipeName = AssemblyGuid;One Instance per Session:
MutexName = "Local\" + AssemblyGuid;
PipeName = $"{AssemblyGuid}_{sessionId}";2. Pipe Security
In NamedPipeServerCreateServer you had
// Alow Everyone to read/write
accessRule = new PipeAccessRule(sidWorld, PipeAccessRights.ReadWrite, AccessControlType.Allow);
pipeSecurity.AddAccessRule(accessRule);This is only needed if you want "one instance per server" as one per session the session user already owns the pipe and you gave the m full control rights anyway. So I used my flag to only apply as needed. Also changed to use WellKnownSidType.AuthenticatedUserSid because I can't see you would want any one else being able to potentially inject a command line over the pipe.
3. Shutdown
I was seeing some null object exceptions when shutting down - I managed to trace these to NamedPipeServerConnectionCallback. This was getting called when the pipe server stream was disposed and so actually came in with _namedPipeServerStream null. So I wrapped the main block of code (in the try) is an check that _namedPipeServerStream != null.
Then I noticed another funny - because the call in NamedPipeServerConnectionCallback to create the next pipe was getting called even when it was entered as part of shutting down - so I wrapped this call:
// Create a new pipe for next connection
NamedPipeServerCreateServer();in a check of a bool flag I set when shutting down so it doesn't try to start a new pipe stream when you are actually on the way out.
Talking of shutdown I didn't see the need to have a shutdown flag (as you do in your XML) seeing as that came off the command line anyway - I just passed on the whole command line from Environment.GetCommandLineArgs() - and if a flag needed supporting it would be one of those args anyway. Also I didn't see the need for the custom XML class - just serialised the string[] of Environment.GetCommandLineArgs() and deserialised it on read.
4. Thanks
Anyway - once I sorted the issues that affected my use this method has worked well - so thanks for pointing me in the correct direction.