5

I need to check whether a program (xyz.exe) is running, but only for the current user. Whatever method is used cannot require elevated rights, and has to run fast (so WMI is out).

Process.GetProcessesByName("xyz") returns results for "xyz" for all logged in users... but I only care about the current user.

Ideas?

ltwally
  • 239
  • 3
  • 7
  • 2
    http://stackoverflow.com/questions/300449/how-do-you-get-the-username-of-the-owner-of-a-process – DLeh Feb 11 '15 at 18:15
  • possible duplicate of [Get all process of current active session](http://stackoverflow.com/questions/7848774/get-all-process-of-current-active-session) – Nikolay Kostov Feb 11 '15 at 18:16
  • If it's a duplicate, I have yet to see a single post that actually answers is. Please provide evidence that it is a duplicate. Keep in mind that I explicitly said "no WMI". – ltwally Feb 11 '15 at 19:45
  • See the link in the second comment above, which does not use WMI. The first link does, which is why it was not the one I linked as a duplicate. – Ken White Feb 11 '15 at 20:05
  • @KenWhite: that is where I got the idea to use `Process.GetProcessesByName`. But it does not provide any means of filtering the user list. Which is what I was looking for. I may be a dunce, but this thread is not a duplicate, given that I was looking for something beyond what that link entails. – ltwally Feb 11 '15 at 20:08
  • The part related to `sameAsthisSession` matches the current session. The current session is the one for the current user, so all session IDs that match the current session should be the ones running for the current user. (If you've based your code on another post here, it's usually a good idea to link to it in your question for reference.) – Ken White Feb 11 '15 at 20:12
  • @KenWhite: I provided a snippet of the the same code that was in that link, and mentioned that it was insufficient. Apologies if my question does not meet your standards. It's still not a duplicate. – ltwally Feb 11 '15 at 20:15
  • You didn't post *a snippet of code*; you posted a single partial function call (`Process.GetProcessesByName("xyz")`) with no additional information. The question I referred to does not use that function, and adds several more lines of code that does additional work. Have you even *looked* at the link I mentioned? It's the one in the *second* comment - the one by Nikolay Kostov. – Ken White Feb 11 '15 at 20:35

4 Answers4

10

Use the current process SessionId to filter the list of processes:

    public static bool IsProcessRunningSameSession(string processName)
    {
        var currentSessionID = Process.GetCurrentProcess().SessionId;
        return Process.GetProcessesByName(processName).Where(p => p.SessionId == currentSessionID).Any();
    }
Geoff
  • 8,551
  • 1
  • 43
  • 50
0

I found the answer here: http://dotbay.blogspot.com/2009/06/finding-owner-of-process-in-c.html

I'll copy/paste it in case that blog ever goes by-by.

////
        // 'WindowsIdentity' Extension Method Demo: 
        //   Enumerate all running processes with their associated Windows Identity
        ////

        foreach (var p in Process.GetProcesses())
        {
            string processName;

            try
            {
                processName = p.WindowsIdentity().Name;

            }
            catch (Exception ex)
            {

                processName = ex.Message; // Probably "Access is denied"
            }

            Console.WriteLine(p.ProcessName + " (" + processName + ")");
        }

Here's the corresponding class:

//-----------------------------------------------------------------------
// <copyright file="ProcessExtensions.cs" company="DockOfTheBay">
//     http://www.dotbay.be
// </copyright>
// <summary>Defines the ProcessExtensions class.</summary>
//-----------------------------------------------------------------------

namespace DockOfTheBay
{
    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Security.Principal;

    /// <summary>
    /// Extension Methods for the System.Diagnostics.Process Class.
    /// </summary>
    public static class ProcessExtensions
    {
        /// <summary>
        /// Required to query an access token.
        /// </summary>
        private static uint TOKEN_QUERY = 0x0008;

        /// <summary>
        /// Returns the WindowsIdentity associated to a Process
        /// </summary>
        /// <param name="process">The Windows Process.</param>
        /// <returns>The WindowsIdentity of the Process.</returns>
        /// <remarks>Be prepared for 'Access Denied' Exceptions</remarks>
        public static WindowsIdentity WindowsIdentity(this Process process)
        {
            IntPtr ph = IntPtr.Zero;
            WindowsIdentity wi = null;
            try
            {
                OpenProcessToken(process.Handle, TOKEN_QUERY, out ph);
                wi = new WindowsIdentity(ph);
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                if (ph != IntPtr.Zero)
                {
                    CloseHandle(ph);
                }
            }

            return wi;
        }

        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern bool OpenProcessToken(IntPtr processHandle, uint desiredAccess, out IntPtr tokenHandle);

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool CloseHandle(IntPtr hObject);
    }
}
ltwally
  • 239
  • 3
  • 7
  • To this, I simply did this: `if(processName.Contains(Environment.UserName)){...}` – ltwally Feb 11 '15 at 19:49
  • A slightly better solution I found that handles active directory slightly better is instead of `Environment.UserName`, `WindowsIdentity.GetCurrent().Name` – Todd A. Stedel Dec 06 '19 at 20:56
0

Here's the complete program. It's a CommandLine C# application. It's ugly and has no comments. But it works. You feed it the name of an EXE (including the path), it checks to see if it's already running, and if not, starts it.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;


namespace SingleRun
{
    class Program
    {
        static void Main(string[] args)
        {
            string path = "";
            var prog = "";
            if (args.Length == 0) {
                MessageBox.Show("Please include a program to start.\n\nExample: \nSingleRun.exe \"C:\\Program Files\\Windows NT\\Accessories\\wordpad.exe\"", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                System.Environment.Exit(1);
            }else{
                path = args[0];
                if (!File.Exists(path)) {
                    MessageBox.Show("\"" + path + "\" does not exist.\nPlease check the location.\nAnything with spaces in it needs to be inside double-quotes.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    System.Environment.Exit(1);
                }else{
                    var splits = path.Split('\\');
                    prog = splits[splits.Length - 1];
                    foreach (var p in Process.GetProcessesByName(prog.Replace(".exe",""))) {
                        string processOwner;
                        try {
                            processOwner = p.WindowsIdentity().Name;
                        }
                        catch (Exception ex) {
                            processOwner = ex.Message; // Probably "Access is denied"
                        }
                        if (processOwner.Contains(Environment.UserName)) {
                            MessageBox.Show("Program already running with PID " + p.Id, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                            System.Environment.Exit(1);
                        }
                    }
                    Process newProcess = Process.Start(path);
                    Console.WriteLine("Launching " + prog + " with PID: " + newProcess.Id);
                }
            }
        }
    }

    /// <summary>
    /// Extension Methods for the System.Diagnostics.Process Class.
    /// </summary>
    public static class ProcessExtensions {
        /// <summary>
        /// Required to query an access token.
        /// </summary>
        private static uint TOKEN_QUERY = 0x0008;

        /// <summary>
        /// Returns the WindowsIdentity associated to a Process
        /// </summary>
        /// <param name="process">The Windows Process.</param>
        /// <returns>The WindowsIdentity of the Process.</returns>
        /// <remarks>Be prepared for 'Access Denied' Exceptions</remarks>
        public static WindowsIdentity WindowsIdentity(this Process process) {
            IntPtr ph = IntPtr.Zero;
            WindowsIdentity wi = null;
            try {
                OpenProcessToken(process.Handle, TOKEN_QUERY, out ph);
                wi = new WindowsIdentity(ph);
            }
            catch (Exception) {
                throw;
            }
            finally {
                if (ph != IntPtr.Zero) {
                    CloseHandle(ph);
                }
            }

            return wi;
        }

        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern bool OpenProcessToken(IntPtr processHandle, uint desiredAccess, out IntPtr tokenHandle);

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool CloseHandle(IntPtr hObject);
    }
}
ltwally
  • 239
  • 3
  • 7
0

Works fine code above. But if you want only know if current user see the opened app: If the process is not form current user, you have already an exception if you try to get the handle. So, you can do it much more simple with this extention:

    public static bool ProcessAccessibleForCurrentUser(this Process process)
    {
        try
        {
            var ptr = process.Handle;
            return true;
        }
        catch
        {
            return false;
        }
    }
S. Frei
  • 11
  • 1
  • If the current user has administrator privileges, then they can get the handle without generating an exception, so this simplification is insufficient to solve the problem. – nwsmith Sep 04 '20 at 19:54