Single-instance Windows Forms Application Launcher

using System; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Reflection; using System.Security.AccessControl; using System.Security.Principal; using System.Text; using System.Threading; using System.Windows.Automation; using System.Windows.Forms; namespace YourSingleInstanceApplication { /// <summary> /// Contains functions used to launch a single-instance-only Windows Forms application. /// References to UIAutomationClient and UIAutomationTypes are needed for the RestoreExistingToFront() method. /// </summary> static class InstanceGuard { private static Mutex _mutex; /// <summary> /// Runs the application if there are no other instances currently running. /// If another instance is found, the user is notified and it is brought to the front of the screen. /// </summary> /// <param name="allowOtherUsers">Determines whether other users on the same PC can have their own instance.</param> /// <param name="timeout">Determines how long to wait for a mutex in milliseconds.</param> /// <typeparam name="TForm">The type of Form class to create as the main application form.</typeparam> public static void RunOnlyInstance<TForm>(bool allowOtherUsers = false, int timeout = 200) where TForm : Form, new() { string product = Application.ProductName; try { if (!CreateInstance(allowOtherUsers, timeout)) { MessageBox.Show( $"There is already another instance of {product} running.", product, MessageBoxButtons.OK, MessageBoxIcon.Warning ); RestoreExistingToFront(true); Environment.Exit(-1); } } catch (Exception ex) { MessageBox.Show( $"An error occured while trying to launch {product}:\n\n{ex.Message}", product, MessageBoxButtons.OK, MessageBoxIcon.Error ); Environment.Exit(-1); } LaunchInstance<TForm>(); } /// <summary> /// Attempts to aquire a mutex used to prevent other instances from being executed. /// </summary> /// <param name="allowOtherUsers">Determines whether other users on the same PC can have their own instance.</param> /// <param name="timeout">Determines how long to wait for a mutex in milliseconds.</param> /// <returns>Returns true if there are no other instances, otherwise returns false.</returns> public static bool CreateInstance(bool allowOtherUsers = false, int timeout = 200) { string guid = ( ((GuidAttribute)Assembly .GetExecutingAssembly() .GetCustomAttributes(typeof(GuidAttribute), false) .GetValue(0)) .Value ); var mutexName = string.Format(CultureInfo.InvariantCulture, "{0}{{{1}}}", (allowOtherUsers ? "" : "Global\\"), guid); var securitySettings = new MutexSecurity(); securitySettings.AddAccessRule( new MutexAccessRule( new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow ) ); _mutex = new Mutex(false, mutexName, out bool unused, securitySettings); try { return _mutex.WaitOne(timeout, false); } catch (AbandonedMutexException) { return true; } } /// <summary> /// Runs the applications main form and disposes of the mutex on exit/exception. /// </summary> public static void LaunchInstance<TForm>() where TForm : Form, new() { try { Application.Run(new TForm()); } finally { _mutex?.Close(); _mutex?.Dispose(); } } /// <summary> /// Attempts to locate the window of an existing instance and bring it to the front before exiting the current instance. /// </summary> public static bool RestoreExistingToFront(bool maximized = true) { int currentId = Process.GetCurrentProcess().Id; string product, currentProduct = Application.ProductName; var guiProcesses = Process.GetProcesses().Where(p => p.MainWindowHandle != IntPtr.Zero).ToArray(); foreach (var process in guiProcesses) { if (process.Id == currentId) continue; if (Environment.OSVersion.Version.Major >= 6) { string assemblyPath = GetExecutablePathNatively(process.Id); if (string.IsNullOrWhiteSpace(assemblyPath) || !File.Exists(assemblyPath)) continue; product = FileVersionInfo.GetVersionInfo(assemblyPath).ProductName; } else { product = process.MainModule.FileVersionInfo.ProductName; } if (product == currentProduct) { AutomationElement element = AutomationElement.FromHandle(process.MainWindowHandle); WindowPattern WPattern = element.GetCurrentPattern(WindowPattern.Pattern) as WindowPattern; WPattern.SetWindowVisualState(maximized ? WindowVisualState.Maximized : WindowVisualState.Normal); return true; } } return false; } /// <summary> /// Used to get the executable path of a process on Windows Vista or above without getting 'Access is denied' error. /// </summary> internal static string GetExecutablePathNatively(int processId) { var buffer = new StringBuilder(1024); IntPtr handle = OpenProcess(ProcessAccessFlags.ProcessQueryLimitedInformation, false, processId); if (handle != IntPtr.Zero) { try { int size = buffer.Capacity; if (QueryFullProcessImageName(handle, 0, buffer, out size)) return buffer.ToString(); } finally { CloseHandle(handle); } } int errorCode = Marshal.GetLastWin32Error(); if (errorCode != 0) throw new Win32Exception(errorCode); else return null; } internal enum ProcessAccessFlags : uint { All = 0x001F0FFF, Terminate = 0x00000001, CreateThread = 0x00000002, VMOperation = 0x00000008, VMRead = 0x00000010, VMWrite = 0x00000020, DupHandle = 0x00000040, SetInformation = 0x00000200, QueryInformation = 0x00000400, Synchronize = 0x00100000, ProcessQueryInformation = 0x0400, ProcessQueryLimitedInformation = 0x1000, } [DllImport("kernel32.dll")] internal static extern bool QueryFullProcessImageName(IntPtr hProcess, int dwFlags, StringBuilder lpExeName, out int size); [DllImport("kernel32.dll")] internal static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, bool bInheritHandle, int dwProcessId); [DllImport("kernel32.dll", SetLastError = true)] internal static extern bool CloseHandle(IntPtr hHandle); } }
Static class with methods used to greatly simplify restricting an application to a single instance.
Replace "Application.Run(new TForm())" with "InstanceGuard.RunOnlyInstance<MainForm>()"
to launch your main form or display instance-related errors and restore the existing instance
to the front of the screen.

Be the first to comment

You can use [html][/html], [css][/css], [php][/php] and more to embed the code. Urls are automatically hyperlinked. Line breaks and paragraphs are automatically generated.