Monday, 22 August 2011

Cross-process application detection

Further to my previous post on detecting a terminal-service session, one may need to limit an application being started up twice within the same terminal-service session, but allow the application to be started up in different sessions (possibly started by the same user).

Normally (i.e. without TS), You can detect if your app is already running by using something like this:
public static bool IsAppAlreadyRunning()
{
    Process currentProcess = Process.GetCurrentProcess();
    return Process.GetProcessesByName(currentProcess.ProcessName).Length > 1;
}
Now obviously, this will find a running instance in a different TS session too - not what we want.

Enter the Mutex:

A mutex can control access to a shared resource, even across different processes. A mutex can be constructed like so:
new Mutex(true, QualifiedMutexName, out mutexCreated);
The value returned in the out parameter indicates whether this constructor actually created the mutex or not (it could have been created by another thread, even running in a different process). From here, detecting apps running in the Same TS session is a synch.

One thing to note: The name of the mutex is important:
  • If the mutex-name starts with "Global\", the mutex is visible in all terminal server sessions.
  • If the mutex-name starts with "Local\", the mutex is visible only in the terminal server session where it was created.
I thought I might want to detect app instances running in other TS-sessions, so I chose to use the "Global\" prefix, and use the TS session ID as part of the suffix.

I created a simple wrapper class for the mutex as follows:
public class AppMutex : IDisposable
{
    protected Mutex mutex;

    private String mutexName;

    private String QualifiedMutexName
    {
        get
        {
            return String.Format(@"Global\{0}", mutexName);
        }
    }

    public AppMutex(String _mutexName)
    {
        mutexName = _mutexName;
    }

    public bool AlreadyActive
    {
        get
        {
            bool mutexCreated;
            if (mutex != null)
                return true;

            mutex = new Mutex(true, QualifiedMutexName, out mutexCreated);
            return !mutexCreated;
        }
    }

    public void Dispose()
    {
        if (mutex != null)
        {
            mutex.ReleaseMutex();
        }
    }
}
Now i don't really like the fact the the AlreadyActive property actually creates the mutex too, but the idea is that this is called once at application startup, so it suffices for me.

I use the TS session ID as part of the mutex name (combined with the application name), as follows:
private static string GetTSAppId()
{
    string appId = String.Format("{0}_{1}", GetTSSessionID(), Process.GetCurrentProcess().ProcessName);
    return appId;
}

public static uint GetTSSessionID()
{
    try
    {
        Process _currentProcess = Process.GetCurrentProcess();
        uint processID = (uint)_currentProcess.Id;
        uint sessionID;
        bool result = ProcessIdToSessionId(processID, out sessionID);
        if (!result)
        {
            log.Error("Not able to retrieve Terminal-Services Session ID");
            return 0;
        }

        return sessionID;
    }
    catch (Exception ex)
    {
        log.Error("Unable to determine session ID", ex);
        return 0;
    }
}
, and construct the mutex in the static constructor of a Utility class:
private static AppMutex appMutex;

static Utility()
{
    if (IsRemoteSession())
    {
        string tsAppId = GetTSAppId();
        Console.WriteLine("Creating application mutex. ID={0}", tsAppId);
        appMutex = new AppMutex(tsAppId);
    }
}
Then we just check if the app is already running in the current TS session like so:
public static bool IsAppAlreadyRunning()
{
    return appMutex.AlreadyActive;
}

No comments:

Post a Comment