I'm trying to pull a WinForms application through the Windows App Certification Kit and gets a fail on this test:
<TEST INDEX="17" NAME="Multi user session test" DESCRIPTION="Multi User checks Application invocation in multiple sessions." EXECUTIONTIME="00h:00m:24s.22ms">
<RESULT><![CDATA[FAIL]]></RESULT>
<MESSAGES />
I guess that this is because I only allow one instance of the application to run, like this:
using ( var p = System.Diagnostics.Process.GetCurrentProcess() )
if ( System.Diagnostics.Process.GetProcessesByName( p.ProcessName ).Length > 1 )
{
MessageBox.Show(
"An instance of xxx is already running!",
Title,
MessageBoxButtons.OK,
MessageBoxIcon.Exclamation );
return;
}
It is a tray-application activated by a hot-key combination, registered with this function:
[DllImport( "user32", EntryPoint = "RegisterHotKey", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true )]
public static extern int RegisterHotkey( IntPtr hWnd, int id, int fsModifiers, int vk );
So I guess I have two questions:
1) How can I correctly prevent multiple sessions being run within the same user session but allow several instances across multiple user sessions?
2) Will I be able to register the same hot-key in different user sessions? Or must I somehow unregister and re-register the hot-key when user sessions are switched?
TIA
You can achieve the same effect using a Mutex. See MSDN for details, but the short version is that any mutex created with a name starting with "Local\" will be per-session. Enter a mutex named "Local\MyAppName" and only one instance of your app can run per-session.
Hotkeys are registered per-session and it will not be a problem to register the same hotkey in multiple sessions.
Example Use (from Run single instance of an application using Mutex):
bool ownsMutex = false;
// NOTE: Local is the default namespace, so the name could be shortened to myApp
Mutex myMutex = new Mutex(false, #"Local\myApp");
try
{
ownsMutex = myMutex.WaitOne(0)
}
catch (AbandonedMutexException)
{
ownsMutex = true;
}
if (!ownsMutex)
{
MessageBox.Show("myApp is already running!", "Multiple Instances");
return;
}
else
{
try
{
Application.Run(new Form1());
}
finally
{
myMutex.ReleaseMutex();
}
}
Related
This code has been running fine for years inside a utility program. We recently updated the program to enforce UAC but we find this code only works when NOT running as administrator; the code inside the while loop is never executed when run as admin but the same code returns a list of moniker names when running unelevated.
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
namespace ROTExplorer
{
class Program
{
[DllImport("ole32.dll")]
static extern int GetRunningObjectTable(uint reserved, out IRunningObjectTable rot);
[DllImport("Ole32.Dll")]
static extern int CreateBindCtx(int reserved, out IBindCtx bindCtx);
static void Main(string[] args)
{
FindEntryInROT();
Console.WriteLine("Press any key to continue.");
Console.ReadKey();
}
private static string FindEntryInROT()
{
IRunningObjectTable rot = null;
IBindCtx bindCtx = null;
IEnumMoniker enumMoniker = null;
IMoniker[] monikers = new IMoniker[1];
string displayName = null;
try
{
GetRunningObjectTable(0, out rot);
CreateBindCtx(0, out bindCtx);
rot.EnumRunning(out enumMoniker);
IntPtr fetched = IntPtr.Zero;
while (enumMoniker.Next(1, monikers, fetched) == 0)
{
string tempName;
monikers[0].GetDisplayName(bindCtx, null, out tempName);
Marshal.ReleaseComObject(monikers[0]);
monikers[0] = null;
try
{
Console.WriteLine(tempName);
}
catch
{
Console.WriteLine("Bad string");
}
}
}
catch (Exception ex)
{
Console.WriteLine("Failure while examining ROT: " + ex.Message);
}
finally
{
ReleaseCOMObject(monikers[0]);
ReleaseCOMObject(enumMoniker);
ReleaseCOMObject(bindCtx);
ReleaseCOMObject(rot);
}
Console.WriteLine(displayName);
return displayName;
}
private static void ReleaseCOMObject(object comObject)
{
if (comObject != null)
{
Marshal.ReleaseComObject(comObject);
comObject = null;
}
}
}
I've tried this on 2 machines. Can someone else please try this and confirm that this code only returns the moniker list when NOT running as administrator.
Does anyone have any thoughts about why the IEnumMoniker returns no monikers when running in an elevated process but returns a list when not run as admin?
I opened a ticket with Microsoft. It was escalated and I finally got an answer: it's working as designed. Here's the relevant conversation:
Microsoft Support:
The SCM/RPCSS service is where the Running Object Table lives. When the table is enumerated, the service does several checks. One of these checks is specifically to match the elevation level of the client token with the elevation level of the token's entry. If it doesn't match, then the entry will not be returned.
The behavior you are seeing is by design.
Me:
Can you send me a link to where this is documented? Having 'elevated' privileges should give the user access to more objects, not less. What you're describing seems to be akin to simply logging in as a different user.
Microsoft Support:
It’s not documented directly.
In some ways, your last statement is correct. An admin user has two security tokens, one for normal operation, and one for elevated. They are never used at the same time. https://learn.microsoft.com/en-us/windows/security/identity-protection/user-account-control/how-user-account-control-works
When an administrator logs on, two separate access tokens are created for the user: a standard user access token and an administrator access token. The standard user access token contains the same user-specific information as the administrator access token, but the administrative Windows privileges and SIDs are removed.
I believe the reasoning behind all of this is security related, but I can’t explain that very well.
Me:
The administrator token has access to the standard user's files; why are the user's COM objects treated differently from the user's file objects?
Microsoft Support:
Because that’s the design decision the product group made. I don’t have further information on exactly why they made it like this.
Me:
It really sounds like a bug to me, or a faulty design. Is there a way to put this on the product group's radar?
Microsoft Support:
Unfortunately, there’s no leverage for getting any change in this area.
I am developing a tray icon based application in C++ CLI. I am using Mutex to ensure single instance of my application running at a time. But each time a new instance starts, the current instance's window should go active.
I am sending a message to the window using PostMessage(Pinvoke). But after 3 or 4 successive run, my application crashes.
Any ideas why that happen. please help!!
The code I have written in the main() function is,
Mutex ^mutex = gcnew Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
if (mutex->WaitOne(TimeSpan::Zero, true))
{
// New Instance. Proceed......................
}
else// An instance is already running. Activate it and return
{
// send our Win32 message to make the currently running instance
// jump on top of all the other windows
try
{
HWND hWindow = FindWindow( nullptr, "MyWindow" );
if(hWindow)
PostMessage(hWindow, WM_ACTIVATE_APP, nullptr,nullptr);
}
catch(Exception^ Ex)
{
}
return -1;
}
Thanks & Regards,
Rohini
Try this instead of PostMessage():
ShowWindowAsync(hWindow, 1); // SW_SHOWNORMAL
SetForegroundWindow(hWindow);
So I have a program that takes in the user credentials via Windows form, Now through the MessageBox I am currently displaying the user input, what I am trying to do is pass it into my console application so that if the user enters in the correct credentials it then continues in the console application, how do I go about doing this?
you may need to add a while loop to look for a txt file in your console application. In your windows forms application, you can write a success or failure message into a txt file. (Add encryption for security) The moment you write down the information your console app should read it and continue from there.
Algorithm:
1.console application start
2. console app while loop until txt file detected
3. forms app show input screen
4. user enter credential
5. write success or failure into txt file
6. read txt file
7. continue based on credential result
8. Remove txt file
Since the form is also in the console app project (I assume it from your wordings) you can do the following
class Program
{
public static object abc;
static void Main(string[] args)
{
//do something here if required
Form1 frm = new Form1();
if (frm.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
//login success do what ever on success
Console.WriteLine("Login success");
Console.WriteLine(abc.ToString());
}
else
{
Console.WriteLine("Login failure");
Console.WriteLine(abc.ToString());
//login failure
}
Console.ReadLine();
}
}
and the login button click event in your login form class
private void Login_Click(object sender, EventArgs e)
{
if(true)
{
Program.abc = "any success object here";
//on successful login
this.DialogResult= System.Windows.Forms.DialogResult.OK;
}
else
{
Program.abc = "any failure object here";
this.DialogResult = System.Windows.Forms.DialogResult.Cancel;
}
}
Thanks,
Esen
you could host a wcf service within your form app. then make the console app a client. creating a service oriented system is surprisingly easy with wcf. See here for a tutorial. you will learn a lot if you follow this approach
Use IPC (Inter process communication) with Named Pipes.Its easy to implement between two processes check out http://msdn.microsoft.com/en-us/library/bb546085.aspx
Essentially you'll probably have to use some native Windows API functions (Alloc/FreeConsole) and use a WinForms controller.
Semi-pseudocode:
[DllImport("kernel32.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool AllocConsole();
[DllImport("kernel32.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool FreeConsole();
//--- form code
if (Do_validation() && AllocConsole())
{
this.Hide();
this.ShowInTaskbar = false;
Enter_Console_Code();
FreeConsole();
System.Threading.Thread.Sleep(50); //FreeConsole sometimes doesn't finish closing straight away which means your form flickers to the front and then minimizes.
this.ShowInTaskbar = true;
this.Show();
}
//---
private void Enter_Console_Code()
{
string line = string.Empty;
while ((line = Console.ReadLine()) != "q")
Console.WriteLine(line); //pointless code ftw!
}
Essentially what this code does is do your "GUI" validation step, and then if that's successful it attempts to allocate a console for the application. Once the console is allocated it enters "Console Mode" by completely hiding the GUI and displaying only the new console (closing the console closes the application by the way). Your "Console Mode" code is executed, then the console is closed and the GUI comes back.
I am creating app domain in my winform application using this code in program.cs file
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
///
[STAThread]
static void Main(string[] args)
{
string appGuid = ((GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), false).GetValue(0)).Value.ToString();
// unique id for global mutex - Global prefix means it is global to the machine
string mutexId = string.Format("Global\\{{{0}}}", appGuid);
using (var mutex = new Mutex(false, mutexId))
{
var allowEveryoneRule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow);
var securitySettings = new MutexSecurity();
securitySettings.AddAccessRule(allowEveryoneRule);
mutex.SetAccessControl(securitySettings);
if (mutex.WaitOne(TimeSpan.Zero, true) || (args.Length > 0 && string.Compare(args[0], "secondary", true) == 0))
{
ErrorHandler errorHandler = new ErrorHandler();
DffEnvironment.Default.AppErrorHandler = errorHandler;
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(errorHandler.Application_ErrorHandler);
MainForm mainForm = new MainForm();
DffEnvironment.Default.MainForm = mainForm;
if (args.Length > 0)
{
MessageBox.Show(" CurrentDomain" + AppDomain.CurrentDomain.FriendlyName);
}
Application.Run(mainForm);
}
else
{
// send our Win32 message to make the currently running instance
// Add new app domain
NativeMethods.PostMessage(
(IntPtr)NativeMethods.HWND_BROADCAST,
NativeMethods.WM_SHOWME,
IntPtr.Zero,
IntPtr.Zero);
}
}
}
}
and in my MainForm (Form) I override the code WndProc method and wrote this
static int procNumber=0;
protected override void WndProc(ref System.Windows.Forms.Message m)
{
if (m.Msg == NativeMethods.WM_SHOWME)
{
try
{
procNumber++;
AppDomain appDomain = AppDomain.CreateDomain("MyAppDomainApplication" + procNumber.ToString(), null, setupInfo);
string[] arguments = { "secondary" };
appDomain.ExecuteAssembly("MyAppDomainApplication.exe", null, arguments);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
base.WndProc(ref m);
}
It is working fine, it creates another app domain in the same process already running when I try to open my application.
My first question is can I create app domain of the same process in already running from other user, for example
John is working on this app and having two app domains and one process. And Steve logged in on the same machine and tried to open this application and application should not create process and it should add new app domain in already running process by John.
I detected the process running in another user by prefixing the name of the mutex with "Global\". as mentioned here
and second question is here when I edit the following code in program.cs
NativeMethods.PostMessage(
(IntPtr)NativeMethods.HWND_BROADCAST,
NativeMethods.WM_SHOWME,
IntPtr.Zero,
IntPtr.Zero);
to
try
{
procNumber++;
AppDomain appDomain = AppDomain.CreateDomain("MyAppDomainApplication" + procNumber.ToString(), null, setupInfo);
string[] arguments = { "secondary" };
appDomain.ExecuteAssembly("MyAppDomainApplication.exe", null, arguments);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
It creates another process why it is not working in Program.cs file, why should I have to send message to my Form and do the same thing in WndProc method
I'll answer to your second question.
Application domains are isolated environments inside process. Here you have two processes and they both have their own application domains. You must send a message from one process to another if you want to command the other process to create a new application domain. That is why you have to send the message.
I also suspect that the code does not work in the way you have intended. The ExecuteAssembly() is run in the same thread as the main user interface. If the executed assembly begins a new message loop, your call stack will grow after every WM_SHOWME message and you will eventually get stack overflow exception.
In that case your call stack will look more or less like this:
at Application.Run()
at Main()
at AppDomain.ExecuteAssembly()
...
at Application.Run()
at Main()
at AppDomain.ExecuteAssembly()
at Application.Run()
at Main()
I have a .NET application that I only allow to run a single process at a time of, however that app is used on Citrix boxes from time to time, and as such, can be run by multiple users on the same machine.
I want to check and make sure that the application is only running once per user session, because right now if user A is running the app, then user B gets the "App already in use" message, and should not.
This is what I have now that checks for the running process:
Process[] p = Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName);
if (p.Length > 1)
{
#if !DEBUG
allowedToOpen &= false;
errorMessage +=
string.Format("{0} is already running.{1}", Constants.AssemblyTitle, Environment.NewLine);
#endif
}
EDIT: Improved the answer according to this cw question ...
You can use a mutex for checking wether the app already runs:
using( var mutex = new Mutex( false, AppGuid ) )
{
try
{
try
{
if( !mutex.WaitOne( 0, false ) )
{
MessageBox.Show( "Another instance is already running." );
return;
}
}
catch( AbandonedMutexException )
{
// Log the fact the mutex was abandoned in another process,
// it will still get aquired
}
Application.Run(new Form1());
}
finally
{
mutex.ReleaseMutex();
}
}
Important is the AppGuid - you could make it depend on the user.
Maybe you like to read this article: the misunderstood mutex
As tanascius already say, you can use the Mutex.
On a server that is running Terminal Services, a named system mutex can have two levels of visibility. If its name begins with the prefix "Global\", the mutex is visible in all terminal server sessions. If its name begins with the prefix "Local\", the mutex is visible only in the terminal server session where it was created.
Source: msdn, Mutex Class
Just stating the obvious - although Mutex is usually considered better solution, you can still solve the single-instance-per-session issue without Mutex - just test the SessionId as well.
private static bool ApplicationIsAlreadyRunning()
{
var currentProcess = Process.GetCurrentProcess();
var processes = Process.GetProcessesByName(currentProcess.ProcessName);
// test if there's another process running in current session.
var intTotalRunningInCurrentSession = processes.Count(prc => prc.SessionId == currentProcess.SessionId);
return intTotalRunningInCurrentSession > 1;
}
Source (no Linq)
If Form1 launches non-background threads, and that Form1 exits, you've got a problem: the mutex is released but the process is still there. Something along the lines below is better IMHO:
static class Program {
private static Mutex mutex;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main() {
bool createdNew = true;
mutex = new Mutex(true, #"Global\Test", out createdNew);
if (createdNew) {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
else {
MessageBox.Show(
"Application is already running",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error
);
}
}
}
The mutex won't be released as long as the primary application domain is still up. And that will be around as long as the application is running.