10

I have to restrict my .net 4 WPF application so that it can be run only once per machine. Note that I said per machine, not per session.
I implemented single instance applications using a simple mutex until now, but unfortunately such a mutex is per session.

Is there a way to create a machine wide mutex or is there any other solution to implement a single instance per machine application?

Daniel A. White
  • 187,200
  • 47
  • 362
  • 445
Marc
  • 9,012
  • 13
  • 57
  • 72
  • By *session* you mean *Windows Session*? – Darin Dimitrov Nov 19 '10 at 07:53
  • Yes, Windows session. So it must be allowed to run once per machine, not per user. – Marc Nov 19 '10 at 07:57
  • 1
    Actually an `Mutex` can help to implement single instance per [`Application Domain`](http://msdn.microsoft.com/en-us/library/ms173138%28VS.80%29.aspx), not per session. – Cheng Chen Nov 19 '10 at 08:06
  • The best question here is _why_? The most common reason is because the application uses some kind of unique resource. Often, the best answer is an answer that directly relates to that unique resource. For instance, when the unique resource is a file, the answer is to exclusively lock that file. – MSalters Nov 19 '10 at 08:40

7 Answers7

14

I would do this with a global Mutex object that must be kept for the life of your application.

MutexSecurity oMutexSecurity;

//Set the security object
oMutexSecurity = new MutexSecurity();
oMutexSecurity.AddAccessRule(new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null), MutexRights.FullControl, AccessControlType.Allow));

//Create the global mutex and set its security
moGlobalMutex = new Mutex(True, "Global\\{5076d41c-a40a-4f4d-9eed-bf274a5bedcb}", bFirstInstance);
moGlobalMutex.SetAccessControl(oMutexSecurity);

Where bFirstInstance returns if this is the first instance of your application running globally. If you omited the Global part of the mutex or replaced it with Local then the mutex would only be per session (this is proberbly how your current code is working).

I believe that I got this technique first from Jon Skeet.

The MSDN topic on the Mutex object explains about the two scopes for a Mutex object and highlights why this is important when using terminal services (see second to last note).

stevehipwell
  • 56,138
  • 6
  • 44
  • 61
  • Wow this indeed seems to be working! I wasn't aware about the Global/Local feature in the name of the Mutex. MSDN also doesn't mention anything about it. When I tested running the app from another account I now get an access denied error when the Mutex already exists. It is not found by the OpenExisting(..) method. If it is enough just catching this exception then I'll be very happy :-) Thanks a lot already! – Marc Nov 19 '10 at 09:01
  • @Marc - You need to set the mutex security when you create it so that there is no exception thrown by the second process. – stevehipwell Nov 19 '10 at 09:19
  • Fantastic, you just solved my problem :-) This solution seems to work fine in my tests so far. Thanks a lot! – Marc Nov 19 '10 at 10:13
  • Important: This solution does not work on non-English systems as the group "Users" or "Everyone" is localized. – Marc Jan 06 '11 at 11:54
  • 1
    Could you please edit this code and replace '"Users"' with 'new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null)'? This way it would work also on non-English installations, thanks. – Marc Jan 06 '11 at 12:24
3

I think what you need to do is use a system sempahore to track the instances of your application.

If you create a Semaphore object using a constructor that accepts a name, it is associated with an operating-system semaphore of that name.

Named system semaphores are visible throughout the operating system, and can be used to synchronize the activities of processes.

EDIT: Note that I am not aware if this approach works across multiple windows sessions on a machine. I think it should as its an OS level construct but I cant say for sure as i havent tested it that way.

EDIT 2: I did not know this but after reading Stevo2000's answer, i did some looking up as well and I think that the "Global\" prefixing to make the the object applicable to the global namespace would apply to semaphores as well and semaphore, if created this way, should work.

Jagmag
  • 10,283
  • 1
  • 34
  • 58
  • Sounds like a very interesting idea so I tried it. Unfortunately it's just the same as for the Mutex class, another user can safely create a semaphore with the same name. Note that I didn't use the constructor taking a SemaphoreSecurity object as parameter. Maybe something could be achieved with this? – Marc Nov 19 '10 at 08:28
  • Hmm..maybe i am not quite getting your requirement correctly but in your application - when it starts, first you check if a semaphore called "MyAppSemaphore" exists using OpenExisting("MyAppSemaphore"). If it exists, you close your application. If it doesnt, then u create a system semaphore using new Semaphore (1, 1, "MyAppSemaphore"). Is that not what you are looking for? – Jagmag Nov 19 '10 at 08:41
  • Yes exactly, but I have the following situation: User1 starts the app which creates "MyAppSemaphore". User1 won't be able to start another instance of the app. Now User2 logs in (Terminal server, fast user switching, etc) and starts the app which just happily creates another "MyAppSemaphore" again. So it's not a solution (I need single instance per machine as noted in the question). – Marc Nov 19 '10 at 08:47
  • @Marc - I see. So just to clarify your scenario. Is it correct to say that you need only one instance of your app running on a terminal server across multiple remote user sessions on that server? – Jagmag Nov 19 '10 at 09:28
  • Yes exactly. It should be one app instance per machine, also if this machine is a terminal server. (I can live with the fact that this won't be possible if load balancing with multiple terminal servers is used). – Marc Nov 19 '10 at 10:03
2

You could open a file with exclusive rights somewhere in %PROGRAMDATA% The second instance that starts will try to open the same file and fail if it's already open.

Jesper Larsen-Ledet
  • 6,625
  • 3
  • 30
  • 42
  • That sounds like a good idea. I thought about creating/deleting a lock file, but this would have issues with access rights. But simply opening/closing a file could do the trick. Thanks for the idea! – Marc Nov 19 '10 at 08:02
1

How about using the registry?

  1. You can create a registry entry under HKEY_LOCAL_MACHINE.
  2. Let the value be the flag if the application is started or not.
  3. Encrypt the key using some standard symmetric key encryption method so that no one else can tamper with the value.
  4. On application start-up check for the key and abort\continue accordingly.
  5. Do not forget to obfuscate your assembly, which does this encryption\decryption part, so that no one can hack the key in registry by looking at the code in reflector.
Unmesh Kondolikar
  • 9,256
  • 4
  • 38
  • 51
  • Thanks for the idea, but I don't like the registry, sorry. And wouldn't it have the same access rights problems like creating a file in program files when attempting to create an entry HKEY_LOCAL_MACHINE? – Marc Nov 19 '10 at 08:45
0

I did something similar once.

When staring up the application list, I checked all running processes for a process with identical name, and if it existed I would not allow to start the program.

This is not bulletproof of course, since if another application have the exact same process name, your application will never start, but if you use a non-generic name it will probably be more than good enough.

Øyvind Bråthen
  • 59,338
  • 27
  • 124
  • 151
  • The problem is that the user can just rename the assembly and he'll be fine. But indeed if there's no other solution then I'll use this technique. Maybe something could be done through a WCF channel or so... – Marc Nov 19 '10 at 07:59
  • Can a program run under a limited account see processes from other accounts? – Vilx- Nov 19 '10 at 08:01
  • Yes it can, I just tested it by creating a user which is just part of the "Users" group and this one can see the processes of the others. Just some of the properties will generate an access denied exception, but at least the process name can be used. – Marc Nov 19 '10 at 08:29
0

For the sake of completeness, I'd like to add the following which I just found now:
This web site has an interesting approach in sending Win32 messages to other processes. This would fix the problem of the user renaming the assembly to bypass the test and of other assemblies with the same name.
They're using the message to activate the main window of the other process, but it seems like the message could be a dummy message only used to see whether the other process is responding to it to know whether it is our process or not.

Note that I haven't tested it yet.

Marc
  • 9,012
  • 13
  • 57
  • 72
-1

See below for full example of how a single instace app is done in WPF 3.5

public class SingleInstanceApplicationWrapper :
Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase
{
public SingleInstanceApplicationWrapper()
{
// Enable single-instance mode.
this.IsSingleInstance = true;
}
// Create the WPF application class.
private WpfApp app;
protected override bool OnStartup(
Microsoft.VisualBasic.ApplicationServices.StartupEventArgs e)
{
app = new WpfApp();
app.Run();
return false;
}
// Direct multiple instances.
protected override void OnStartupNextInstance(
Microsoft.VisualBasic.ApplicationServices.StartupNextInstanceEventArgs e)
{
if (e.CommandLine.Count > 0)
{
app.ShowDocument(e.CommandLine[0]);
}
}
}

Second part:

public class WpfApp : System.Windows.Application
{
protected override void OnStartup(System.Windows.StartupEventArgs e)
{
base.OnStartup(e);
WpfApp.current = this;
// Load the main window.
DocumentList list = new DocumentList();
this.MainWindow = list;
list.Show();
// Load the document that was specified as an argument.
if (e.Args.Length > 0) ShowDocument(e.Args[0]);
}
public void ShowDocument(string filename)
{
try
{
Document doc = new Document();
doc.LoadFile(filename);
doc.Owner = this.MainWindow;
doc.Show();
// If the application is already loaded, it may not be visible.
// This attempts to give focus to the new window.
doc.Activate();
}
catch
{
MessageBox.Show("Could not load document.");
}
}
}

Third part:

 public class Startup
    {
    [STAThread]
    public static void Main(string[] args)
    {
    SingleInstanceApplicationWrapper wrapper =
    new SingleInstanceApplicationWrapper();
    wrapper.Run(args);
    }
    }

You may need to add soem references and add some using statements but it shoudl work.

You can also download a VS example complete solution by downloading the source code of the book from here.

Taken From "Pro WPF in C#3 2008 , Apress , Matthew MacDonald" , buy the book is gold. I did.

Liviu Mandras
  • 6,540
  • 2
  • 41
  • 65
  • As I added in the question, I need single instance PER MACHINE. The code you're posting is very well known and appreciated, but only to handle single instance PER SESSION/USER/LOGIN. I bought the "WPF4 Unleashed" book from Adam Nathan and I'm quite happy with it :-) – Marc Nov 19 '10 at 08:41