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
  • 167,268
  • 50
  • 405
  • 846
  • Seems like it should work fine since you are prefixing with \global as per https://msdn.microsoft.com/en-us/library/system.threading.mutex.aspx. Is the code above from your setup assembly or from your application assembly? – Botonomous Nov 07 '16 at 19:36
  • You shouldn't need to add code for AppMutex to work, I'd suspect code in the application. [Here is](http://www.jrsoftware.org/iskb.php?mutexsessions) a kb entry about it. [Here](http://stackoverflow.com/q/229565/243614) is a question about the application side. – Sertac Akyuz Nov 07 '16 at 19:44

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.

Community
  • 1
  • 1
Martin Prikryl
  • 167,268
  • 50
  • 405
  • 846
  • Martin, I apologize for misusing the "Answer" feature. I did it out of ignorance. So, this morning I quickly coded up your suggestion in my application code. And in the Inno Setup script I removed the mutex code from the "InitializeSetup" function, and modified my AppMutex setting in the [Setup] section. I installed my application and ran it. I then ran the installer again but it did not detect the running application. It did detect that there was a program (my app) that was using files that it needed in order to continue. I can't see what I'm missing? Thanks again... –  Nov 08 '16 at 16:57
  • Is your whole program running where, the `// Run your program here` comment is? If I create a simple console application and put `Console.ReadLine` on that place, run the application in another session, the installer detects that perfectly. – Martin Prikryl Nov 08 '16 at 17:34
  • Yes. It's a WPF app, where you've indicated, I have `code` MainWindow mainWin = new MainWindow(); mainWin.Show();`code` Man, I'm stumped. –  Nov 08 '16 at 17:43
  • I do not do WPF, but from a documentation, it's clear that the [`Window.Show`](https://msdn.microsoft.com/en-us/library/system.windows.window.show.aspx) is not blocking: *"Opens a window and returns without waiting for the newly opened window to close."*. You have to guarantee that the `Mutex.Dispose` is called only after you close the application. – Martin Prikryl Nov 08 '16 at 17:49
  • You probably need to put `Application.Run` there. – Martin Prikryl Nov 08 '16 at 18:04
  • Simple, and in his case, possibly better, solution is to simply delete the `globalMutex.Dispose();` and `localMutex.Dispose();` calls and let the system release the mutexes, when the (WPF) application exits. – Martin Prikryl Nov 08 '16 at 20:17
  • Consider posting a new answer with your WPF-specific solution. Your edit to deleted post is visible only to you and high-reputation users. – Martin Prikryl Nov 09 '16 at 09:06
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