2

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.

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992

2 Answers2

2

As documented, in Inno Setup KB Detect 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 or lack of operating system support (versions of Windows NT prior to 4.0 Terminal Server Edition don't support the Global\ prefix).

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:

const string mutexId = "MyProg";
MutexAccessRule allowEveryoneRule =
    new MutexAccessRule(
        new SecurityIdentifier(WellKnownSidType.WorldSid, null),
        MutexRights.FullControl, AccessControlType.Allow);
MutexSecurity securitySettings = new MutexSecurity();
securitySettings.AddAccessRule(allowEveryoneRule);

Mutex globalMutex = null;

try
{
    bool createdNew;
    globalMutex =
        new Mutex(false, "Global\\" + mutexId, out createdNew, 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.

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
1

Great idea Martin. Here is my full solution for using mutex objects in WPF that an Inno Setup program will detect, even when other logged-in users are running the WPF app. BTW. I used Visual Studio.

  1. Assume that both the WPF app and project are called 'MyWPFApp'
  2. Open the project properties for MyWPFApp; on the 'Application' tab ensure that the startup object is 'MyWPFApp.App'.
  3. Change the Build Action of App.xaml from ApplicationDefinition to Page.
  4. Remove the StartupUri property from App.xaml, if used.
  5. If an Application.Startup event is used, remove any code that instantiates and displays the MainWindow.
  6. Add the following, or similar, code to App.xaml.cs as part of the App class.

    public partial class App : Application
    {
        private static readonly string _MutexID = "MyWPFApp"; // or whatever
    
        [STAThread]
        public static void Main()
        {
            var application = new App();
            application.InitializeComponent();
    
            MutexAccessRule allowEveryoneRule = new MutexAccessRule(
                       new SecurityIdentifier(WellKnownSidType.WorldSid, null),
                       MutexRights.FullControl,
                       AccessControlType.Allow);
    
            MutexSecurity securitySettings = new MutexSecurity();
            securitySettings.AddAccessRule(allowEveryone);
    
            Mutex globalMutex = null;
    
            try
            {
                bool createdNew;
                globalMutex = new Mutex(false, "Global\\" + _MutexID, out createdNew, securitySettings);
            }
            catch (UnauthorizedAccessException)
            {
                // ignore
            }
    
            Mutex localMutex = new Mutex(false, _MutexID);
    
            try
            {
                MainWindow mainWin = new MainWindow();
                application.Run(mainWin);
            }
            finally
            {
                if (globalMutex != null)
                {
                    globalMutex.Dispose();
                }
    
                localMutex.Dispose();
            }
        }
    }
    

The final step is to include the following line in the Inno Setup script:

[Setup]
AppMutex=MyWPFApp,Global\MyWPFApp

I tried to architect the code using C# using statements for both mutexes, but I got brain freeze.

Alternatively, one can also create a separate class with a Main method in it, and place the above code there. This requires steps 4 and 5 above, and in step 2 change the Startup object to the new class containing the Main method.

Thanks Martin.

Bob