Friday, January 23, 2009

Detecting if other instances of your app are running.

I got a question from a friend of mine the other day about why Process.GetProcesses() was returning an error when used under Terminal Services by a user that doesn't have the Debug Programs privilege.  It was because Process.GetProcesses() was trying to return the list of ALL processes running on the machine (including those not started by the current user).  This worked just fine when a single user was logged into the TS server, but when more than one user was logged in it failed.  My friend was very confused by this, until I told him that non-admin users need the Debug Programs privilege in order to open the process token of a process not created in their session.

Of course, his next question was "how do I get Process.GetProcesses() to return only the processes accessible to the current user's session?".  At that point I took a step back and asked "why?".

In fact, what he was trying to do was to detect whether another instance of his application was running, in order to gracefully tell the user that only one instance of the program can run at a time.  I asked him why he wasn't using a Mutex and the answer surprised me, just a bit.  He said "we were worried about what would happen if the process unexpectedly exited".  My next question was yet another "why?".

Apparently there's some confusion about how Mutexes work in Windows, as well as how the Mutex method that I've used for about 10 years to detect multiple instances of an app works.  I'm not sure where I first learned this technique from, but I've been using it since my Win32 programming days and it's worked like a charm ever since then.  I've now used it in several reincarnations (from VB6, Delphi, C++, and now .NET) and it's never failed me.

The first misconception that almost everyone has when hearing about this approach is that it has something to do with Mutexes (at least in the way they are used in multithreaded programs).  In fact, any Kernel named object could be used for this approach, including even a "delete on close" file.  However, I've always used mutexes because that's what was recommended to me, and I don't think it makes much difference since you never actually 'wait' on the mutex so it's not really important what type of object it is.  The idea of this technique is not to use a mutex, but to use the 'kernel namespace'.  Essentially, by creating a mutex in the naming scope appropriate for your purposes, you are 'registering' that name with the OS.  Then, if you were the first one to register the name, you continue, otherwise you show your message and quit.

The pattern looks like this:

bool createdNew;
MutexSecurity ms = new MutexSecurity();
MutexAccessRule mar = new MutexAccessRule(
new SecurityIdentifier(WellKnownSidType.WorldSid, null),
MutexRights.FullControl,
AccessControlType.Allow);
ms.AddAccessRule(mar);
Mutex m = new Mutex(
false,
@"Global\somenameuniquetomyapplication",
out createdNew,
ms);
if(!createdNew)
{
// show a message that only one instance is allowed and exit
return;
}

where the first part (the MutexSecurity stuff) guarantees that anybody can access the mutex (the security probably doesn't need FullControl, but I didn't want to think about what it really wants, so I left it at that, no real security risk here since the only thing that could happen is other apps could grab hold of this mutex and use it for their own purposes, but that's not really going to do much).  The point of this is that the other instances of your app may not be started by the same user - this guarantees that only one app per machine is startable (because I'm using the Global namespace for my mutex name).  If, on the other hand, you want a per-session limit of a single instance (rather than per-machine) you could use the local namespace (change Global\ to Local\), which is local to each TS session.

The second part of that mess is the mutex creation code.  This code (as written) will create the mutex if it doesn't exist or return the existing one if one already exists with that name (and you have permissions to open it).  If you created it (because it didn't exist), then 'createdNew' will be set to True.  Otherwise, it will be false.  Either way, you get a valid Mutex object.

Then, you should keep this Mutex object alive for the lifetime of your application (i.e. save "m" in a variable that has the same lifetime as your application so that it doesn't get GCed and disappear on you).  A good way to do this is to make it a local variable in your Program.cs's main function.  When your application is done shutting down, you can close the mutex either as soon as you think it's ok for other instances to start, or let Windows reclaim the mutex for you.

Generally, I put this code in a separate library function that I can call from everywhere.  That function generally looks something like the following (in .NET):

/// <summary>
///
Attempts to create an application isolation mutex, and
/// return it to the caller. If the caller isn't the first
/// to create the mutex (i.e. it already exists) then we
/// return null to indicate that the caller "lost".
/// </summary>
/// <param name="objectName">
The name to use for the mutex,
/// should begin with Global\ if you want per-machine
/// isolation, or Local\ if you want per-session isolation.
/// If you want per-machine/per-user isolation (slightly
/// different from per-session) then you should mangle the
/// mutex name by putting the username in it somewhere.</param>
/// <returns>
Null if the mutex already existed, or the mutex
/// if it was created by this function. You should keep the
/// mutex in scope somewhere until you are ready to release
/// the isolation. You shouldn't use this mutex for locking
/// or anything else - just forget it's a mutex altogether.
/// </returns>
public IDisposable GetAppIsolationHandle(string objectName)
{
// setup the mutex security settings.
var ms = new MutexSecurity();
var sidWorld =
new SecurityIdentifier(WellKnownSidType.WorldSid, null);
var mar = new MutexAccessRule(
sidWorld,
MutexRights.FullControl,
AccessControlType.Allow);
ms.AddAccessRule(mar);

// create the mutex and return it if it's "ours".
bool createdNew;
var mutex = new Mutex(
false,
objectName,
out createdNew,
ms);
if (createdNew)
return mutex;

// return null if the mutex isn't "ours".
return null;
}

Then, you can call this code in your Program.cs as follows:

var isolationHandle
= GetAppIsolationHandle(@"Global\MyApp");
if (isolationHandle == null)
{
MessageBox.Show("Sorry, only one at a time!");
return;
}
using(isolationHandle)
{
Application.EnableVisualStyles();
Application.Run(new Form());
}

which, of course, looks really nice (at least in my opinion).  If you had special "shutdown" stuff to do after Run returns, you can put that outside the using block (assuming it doesn't need to be isolated).

No comments: