Search code examples
c#sessioncitrixprocessstartinfo

C# Get Session Processes Without Showing CMD Prompt


Current attempt at WTSEnumerateProcesses:

    [DllImport("wtsapi32.dll", SetLastError = true)]
    static extern Int32 WTSEnumerateProcesses(
         IntPtr serverHandle, // Handle to a terminal server. 
         Int32 reserved,     // must be 0
         Int32 version,      // must be 1
         ref IntPtr ppProcessInfo, // pointer to array of WTS_PROCESS_INFO
         ref Int32 pCount     // pointer to number of processes 
        );  

    public struct WTS_PROCESS_INFO
    {
        public int SessionID;
        public int ProcessID;
        // This is spointer to a string...
        public IntPtr ProcessName;
        public IntPtr userSid;
    }

    public static void ListProcs(String ServerName)
    {
        IntPtr serverHandle = IntPtr.Zero;
        List<string> resultList = new List<string>();
        serverHandle = OpenServer(ServerName);

        IntPtr ProcessInfoPtr = IntPtr.Zero;
        Int32 processCount = 0;
        Int32 retVal = WTSEnumerateProcesses(serverHandle, 0, 1, ref ProcessInfoPtr, ref processCount);
        Int32 dataSize = Marshal.SizeOf(typeof(WTS_PROCESS_INFO));
        Int32 currentProcess = (int)ProcessInfoPtr;
        uint bytes = 0;

        if (retVal != 0)
        {
            WTS_PROCESS_INFO pi = (WTS_PROCESS_INFO)Marshal.PtrToStructure((System.IntPtr)currentProcess, typeof(WTS_PROCESS_INFO));
            currentProcess += dataSize;

            for (int i = 0; i < processCount; i++)
            {
                MessageBox.Show(pi.ProcessID.ToString());
            }

            WTSFreeMemory(ProcessInfoPtr);
        }
    }

I am obviously missing something pretty crucial here, as my listProcs method just returns the same ID over and over again. I need to read up on C APIs and work out what WTSEnumeratEProcesses is actually doing, and how I can query these processes.


Possible solution example code (top answer)

I am creating a self-help IT app for my organisation, where users have the ability to log off their own session as well as display all active processes and select one to terminate.

Users have no problem logging off, but I am having an issue when enumerating processes. Due to the fact that I am using a log in name and password to query active processes, the CMD window is displayed briefly every time this occurs. I can't find any solution to this in the documentation, and was hoping someone could point me in the right direction.

The code is below:

using System.Drawing;
using System;
using System.ComponentModel;
using System.Security;
using System.Diagnostics;
using System.DirectoryServices;
using System.Collections.Generic;
using System.Windows.Forms;

namespace ITHelp
{
    class funcs
{
    ///////////////////////////////////////////////////// GET SERVERS
    public static List<string> get_Servers()
    {
        // Get servers using AD directory searcher
        List<string> serverList = new List<string>();
        DirectoryEntry rootDSE = new DirectoryEntry("LDAP://RootDSE");
        string domainContext = rootDSE.Properties["defaultNamingContext"].Value as string;
        DirectoryEntry searchRoot = new DirectoryEntry("LDAP://OU=XA76-2012,OU=Servers,OU=XenApp,dc=MYDOMAINNAME1,dc=co,dc=uk");

        using (DirectorySearcher searcher = new DirectorySearcher(
           searchRoot,
           "(&(objectClass=computer)(!(cn=*MASTER*)))",
           new string[] { "cn" },
           SearchScope.Subtree))
        {
            foreach (SearchResult result in searcher.FindAll())
            {
                foreach (string server in result.Properties["cn"])
                {
                    serverList.Add(server);
                }
            }
        }

        return serverList;
    }

    ///////////////////////////////////////////////////// GET SESSION
    public static string[] get_Session(List<string> servers, string name)
    {
        string[] sessionDetails = new string[3];

        // Iterate through serverList to find the correct connection - then add this to the sessionDetails array
        string current = "";
        for (int i = 0; i < servers.Count; i++)
        {
            ProcessStartInfo startInfo = new ProcessStartInfo("cmd", "/c QUERY SESSION " + name + " /SERVER:" + servers[i] + ".MYDOMAINNAME1.co.uk ")
            {
                WindowStyle = ProcessWindowStyle.Hidden,
                UseShellExecute = false,
                RedirectStandardOutput = true,
                CreateNoWindow = true
            };

            Process getsess = Process.Start(startInfo);
            getsess.OutputDataReceived += (x, y) => current += y.Data;
            getsess.BeginOutputReadLine();
            getsess.WaitForExit();

            if (current.Length != 0)
            {
                // Session ID
                // Better to use this as an identifer than session name, as this is always available
                sessionDetails[0] = current.Substring(119, 4);
                // Server Name
                sessionDetails[1] = servers[i] + ".MYDOMAINNAME1.co.uk";

                // Session Name (ica-)
                // This is only available if the session is not disconnected
                //sessionDetails[2] = current.Substring(76, 11);
                // Removed this as it is not used - BUT COULD BE HELPFUL FOR CHECKING SESSION EXISTENCE/DETAILS
                break;
            }
        }
        return sessionDetails;
    }

    ///////////////////////////////////////////////////// GET PROCESSES
    public static Dictionary<string, string> getProc(string server, string sessID)
    {
        var ss = new SecureString();
        ss.AppendChar('M');
        ss.AppendChar('y');
        ss.AppendChar('p');
        ss.AppendChar('a');
        ss.AppendChar('s');
        ss.AppendChar('s');
        ss.AppendChar('w');
        ss.AppendChar('o');
        ss.AppendChar('r');
        ss.AppendChar('d');
        ss.MakeReadOnly();

        ProcessStartInfo startInfo = new ProcessStartInfo("cmd", "/C tasklist /S " + server + " /FI \"SESSION eq " + sessID + "\" /FO CSV /NH")
        {
            WindowStyle = ProcessWindowStyle.Minimized,
            UseShellExecute = false,
            RedirectStandardOutput = true,
            CreateNoWindow = true,

            WorkingDirectory = @"C:\windows\system32",
            Verb = "runas",
            Domain = "MYDOMAINNAME1",
            UserName = "XATest",
            Password = ss
        };

        List<string> procList = new List<string>();

        Process proc = Process.Start(startInfo);
        proc.OutputDataReceived += (x, y) => procList.Add(y.Data);
        proc.BeginOutputReadLine();
        proc.WaitForExit();

        // Create a new ditionary ...
        Dictionary<string, string> procDict = new Dictionary<string, string>();

        for (int i = 0; i < procList.Count - 1; i++)
        {
            if (procDict.ContainsKey(procList[i].Split(',')[0].Trim('"')))
            {
                // Do nothing 
            }

            else
            {
                procDict.Add(procList[i].Split(',')[0].Trim('"'), procList[i].Split(',')[1].Trim('"'));
            }
        }

        return procDict;
    }

    ///////////////////////////////////////////////////// RESET SESSION
    public static void reset_Session(string sessID, string servName, string name)
    {
        // Ensure the sesion exists
        if (sessID != null)
        {
            // Log session off
            logoff_Session(sessID, servName);

            // While isLoggedIn returns true, wait 1 second (checks 50 times)
            for (int i = 0; i < 50; i++)
            {
                if (isLoggedIn(name, servName) == true)
                {
                    System.Threading.Thread.Sleep(1000);
                }
                else
                {
                    break;
                }
            }

            // Wait here to prevent starting a session while still logged in
            System.Threading.Thread.Sleep(3000);
        }

        // Finally, start the session (Outlook)
        start_Session(name);
    }

    ///////////////////////////////////////////////////// LOGOFF SESSION
    public static void logoff_Session(string sessID, string servName)
    {
        Process logoff = new Process();
        ProcessStartInfo startInfo = new ProcessStartInfo();
        startInfo.WindowStyle = ProcessWindowStyle.Hidden;
        startInfo.FileName = "cmd.exe";
        startInfo.Arguments = "/C LOGOFF " + sessID + " /SERVER:" + servName;
        logoff.StartInfo = startInfo;
        logoff.Start();
    }

    ///////////////////////////////////////////////////// START SESSION
    public static void start_Session(string name)
    {
        // Start Outlook
        Process.Start("C:\\Users\\" + name + "\\AppData\\Roaming\\Citrix\\SelfService\\Test_Outlook2013.exe");
    }

    ///////////////////////////////////////////////////// IS LOGGED IN
    private static bool isLoggedIn(string name, string server)
    {
        string current = " ";

        ProcessStartInfo startInfo = new ProcessStartInfo("cmd", "/c QUERY SESSION " + name + " /SERVER:" + server)
        {
            WindowStyle = ProcessWindowStyle.Hidden,
            UseShellExecute = false,
            RedirectStandardOutput = true,
            CreateNoWindow = true
        };

        Process logcheck = Process.Start(startInfo);
        logcheck.OutputDataReceived += (x, y) => current += y.Data;
        logcheck.BeginOutputReadLine();
        logcheck.WaitForExit();

        if (current.Contains(userName()))
        {
            return true;
        }
        else
        {
            return false;
        }

    }

    ///////////////////////////////////////////////////// USERNAME
    public static string userName()
    {
        // Get userName
        string userName = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
        userName = userName.Remove(0, 8);
        return userName;
    }

    ///////////////////////////////////////////////////// KILL PROCESS
    public static void killProc(string server, string procid)
    {
        var ss = new SecureString();
        ss.AppendChar('M');
        ss.AppendChar('y');
        ss.AppendChar('p');
        ss.AppendChar('a');
        ss.AppendChar('s');
        ss.AppendChar('s');
        ss.AppendChar('w');
        ss.AppendChar('o');
        ss.AppendChar('r');
        ss.AppendChar('d');

        ss.MakeReadOnly();

        ProcessStartInfo startInfo = new ProcessStartInfo("cmd", "/C taskkill /S " + server + " /PID " + procid + " /F")
        {
            WorkingDirectory = @"C:\windows\system32",
            Verb = "runas",
            Domain = "MYDOMAINNAME1",
            UserName = "XATest",
            Password = ss,
            WindowStyle = ProcessWindowStyle.Minimized,
            UseShellExecute = false,
            CreateNoWindow = true
        };

        Process proc = Process.Start(startInfo);
        proc.WaitForExit();
    }

    ///////////////////////////////////////////////////// KILL BUSYLIGHT
    public static void killBL()
    {
        foreach (KeyValuePair<string, string> entry in Program.proclist)
        {
            if (entry.Key == "Busylight.exe")
            {
                killProc(Program.servName, entry.Value);
                System.Threading.Thread.Sleep(3000);

                Process.Start("C:\\Users\\" + Program.name + "\\AppData\\Roaming\\Citrix\\SelfService\\Test_Busylight.exe");
                return;
            }
            // Start BUSYLIGHT - the above method should close the application instantly
        }
    }

    ///////////////////////////////////////////////////// KILL LYNC
    public static void killLync()
    {
        foreach (KeyValuePair<string, string> entry in Program.proclist)
        {
            if (entry.Key == "lync.exe")
            {
                killProc(Program.servName, entry.Value);
                Process.Start("C:\\Users\\" + Program.name + "\\AppData\\Roaming\\Citrix\\SelfService\\Test_SkypeforBusiness.exe");
                System.Threading.Thread.Sleep(3000); /////////////////////////////////////////////////////////
                return;
            }
        }
    }

    ///////////////////////////////////////////////////// CHECK RUNNING
    public static bool checkRunning(string procName)
    {
        var ss = new SecureString();
        ss.AppendChar('M');
        ss.AppendChar('y');
        ss.AppendChar('p');
        ss.AppendChar('a');
        ss.AppendChar('s');
        ss.AppendChar('s');
        ss.AppendChar('w');
        ss.AppendChar('o');
        ss.AppendChar('r');
        ss.AppendChar('d');

        ss.MakeReadOnly();

        ProcessStartInfo startInfo = new ProcessStartInfo();
        startInfo.FileName = "cmd.exe";
        startInfo.Arguments = "/C tasklist /S " + Program.servName + " /FI \"SESSION eq " + Program.sessID + "\" /FO CSV /NH";

        startInfo.WorkingDirectory = @"C:\windows\system32";
        startInfo.Verb = "runas";
        startInfo.Domain = "MYDOMAINNAME1";
        startInfo.UserName = "XATest";
        startInfo.Password = ss;

        startInfo.WindowStyle = ProcessWindowStyle.Hidden;
        startInfo.UseShellExecute = false;
        startInfo.RedirectStandardOutput = true;
        startInfo.CreateNoWindow = true;

        string strCheck = " ";

        Process proc = Process.Start(startInfo);
        proc.OutputDataReceived += (x, y) => strCheck += y.Data;

        proc.BeginOutputReadLine();
        proc.WaitForExit();

        if (strCheck.Contains(procName))
        {
            return true;
        }
        else
        {
            return false;
        }
    }

}
}

Any suggestions or feedback on this much appreciated! Many thanks


Solution

  • Found another solution to this.

    As Donovan mentioned, this can be done using WTSEnumerateProcesses.

    However, if someone wanted to list remote processes (for a specific session) without marshalling c++ methods, you could also use qprocess:

        qprocess /id:10 /server:servername
    

    This lists all processes running on that session.

    For more details see here