Search code examples
c#inno-setupmutex

Tell if any logged on user is running the application about to be installed/uninstalled


My program uses Inno Setup to install/uninstall it. In my application code I create a Global mutex using the CreateMutex Windows API function. Then in my Inno Setup program I have the following code:

AppMutex=Global\MyProgramMutex.2A23834B-2919-4007-8C0A-3C7EDCA7186E

function InitializeSetup(): Boolean;
begin
  Result := True;

  if (CreateMutex(0, False, '{#SetupSetting('AppId')}') <> 0) and (DLLGetLastError = ERROR_ALREADY_EXISTS) then
  begin
    Result := False;
    MsgBox('Another instance of the Setup program is already running. Please close it and try again', mbCriticalError, MB_OK);
  end;

  if CheckForMutexes('{#SetupSetting('AppMutex')}') then
  begin
    Result := False;
    MsgBox('{#SetupSetting('AppName')} ' + 'appears to be running. Please close all instances of the program before continuing.', mbCriticalError, MB_OK); 
  end;
end; 

This works great, as expected, for the user running the Inno Setup program. The question/problem I have is: If I "Switch User" and start the application as a different user, and then switch back to the original user, the Setup program does not detect that the application is running under a different user.

I'm not knowledgeable all round enough to know, if the Setup program can detect the running application.


Solution

  • As documented, in Inno Setup FAQ Detecting instances running in any user session with AppMutex:

    To detect mutexes created in other sessions, your application must create two mutexes: one with a Global\ prefix and the other without.

    Mutexes with the Global\ prefix are accessible from any user session. A like-named mutex must also be created in the session namespace (i.e. without the Global\ prefix) in case the creation of the Global mutex failed due to security restrictions.

    Additionally, a special security descriptor must be passed in each of the CreateMutex() calls to ensure the mutex is accessible by different users.

    To make a mutex accessible by all users in C#, see:
    What is a good pattern for using a Global Mutex in C#?

    In sum, the code in your C# application should be like (you need System.Threading.AccessControl NuGet package):

    const string mutexId = "MyProg";
    var everyone = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
    var allowEveryoneRule =
        new MutexAccessRule(
            everyone, MutexRights.FullControl, AccessControlType.Allow);
    var securitySettings = new MutexSecurity();
    securitySettings.AddAccessRule(allowEveryoneRule);
    
    Mutex globalMutex = null;
    
    try
    {
        const string globalMutexId = $@"Global\{mutexId}";
        globalMutex =
            MutexAcl.Create(false, globalMutexId, out bool _, securitySettings);
    }
    catch (UnauthorizedAccessException)
    {
        // Ignore
    }
    
    Mutex localMutex = new Mutex(false, mutexId);
    
    try
    {
        // Run your program here
    }
    finally
    { 
        // These have to be called only after the application (its windows) closes.
        // You can also remove these calls and let the system release the mutexes.
        if (globalMutex != null)
        {
            globalMutex.Dispose();
        }
        localMutex.Dispose();
    }
    

    On Inno Setup side, all you need is to list both mutexes in the AppMutex directive:

    [Setup]
    AppMutex=MyProg,Global\MyProg
    

    You do not need your CreateMutex and CheckForMutexes calls in the InitializeSetup funcion.