0

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

Community
  • 1
  • 1
Bassie
  • 9,529
  • 8
  • 68
  • 159
  • https://github.com/slavagu/ConsoleAppLauncher is a nuGettable package redirecting console output (stdout, stderr) to a stream. If I remember correctly that conceals the window. – O. Jones Nov 16 '15 at 12:04
  • Hi Ollie, thanks for the tip! Will this work in my Windows Forms Application, or does this only work for Console Apps? Thanks again – Bassie Nov 16 '15 at 12:29
  • I've used it in console and winforms; also in a web app once (desperate measure!). It was a while ago; that's why I didn't write it up as an answer. The trick to concealing the cmd window is to redirect the console app's stdin, stdout, and stderr. – O. Jones Nov 16 '15 at 13:52
  • Thanks Ollie, I will try this out in my app. I tried redirected the std, err and in manually (using proc.RedirectStandardInput = true etc.) but still seeing the CMD window (redirecting input actually keeps the window there, expecting input! – Bassie Nov 16 '15 at 15:07

3 Answers3

1

The Remote Desktop Services APIs can certainly do all the things you want. However I'm not sure whether non-admin users are allowed to manipulate their own sessions on other machines.

https://msdn.microsoft.com/en-us/library/windows/desktop/aa383464%28v=vs.85%29.aspx

  • WTSOpenServer to get a handle to a particular server.
  • WTSEnumerateProcesses to get a list of processes.
  • WTSEnumerateSessions to get a list of sessions.
  • WTSLogoffSession to logoff a specific session.
  • WTSTerminateProcess to kill a specific process.

Here's some example code using the APIs to enumerate sessions. This is using the constant WTS_CURRENT_SESSION to open the current server, but you can use WTSOpenServer to talk to some other remote server. This is code I hacked out of a live app so it won't compile as-is.

If you program C# long enough you will come across APIs that just don't exist in C# and you have to pinvoke the C versions of the API. I suggest you have a look at http://pinvoke.net if you want help learning how to pinvoke C APIs.

    public const int WTS_CURRENT_SESSION = -1;

    [StructLayout(LayoutKind.Sequential)]
    public struct WTS_SESSION_INFO
    {
        public Int32 SessionID;

        public IntPtr pWinStationName;

        public WTS_CONNECTSTATE_CLASS State;
    }


    [DllImport("wtsapi32.dll")]
    public static extern bool WTSEnumerateSessions(
        IntPtr hServer,
        Int32 Reserved,
        Int32 Version,
        ref IntPtr ppSessionInfo,
        ref Int32 pCount);

    [DllImport("wtsapi32.dll")]
    public static extern void WTSFreeMemory(IntPtr pMemory);


        IntPtr pSessions = IntPtr.Zero;
        int count = 0;
        if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, ref pSessions, ref count))
        {
            unsafe
            {
                WTS_SESSION_INFO* pHead = (WTS_SESSION_INFO*)pSessions.ToPointer();
                for (int i = 0; i < count; ++i)
                {
                    WTS_SESSION_INFO* pCurrent = (pHead + i);
                    var session = new Session(pCurrent->SessionID, pCurrent->State);
                    _activeSessions[pCurrent->SessionID] = session;
                        session.Id, session.IsConnected, session.IsLoggedOn, session.User.UserName);
                }
            }

            WTSFreeMemory(pSessions);
        }
donovan
  • 1,442
  • 9
  • 18
  • Hi Donovan, thanks for sharing those with me. I have had a look at the link you posted and also searched around but can't find many examples which illustrate the use of this (the link only shows c++ usage). Do you know of anywhere that I can see examples of some of the different functions? I've added some code to the question which I found in another thread, but it doesn't seem like the nicest API to use if all the functions look like that! The main thing I would want to be able to do is get the current user's session ID and find out which server they are on! – Bassie Nov 27 '15 at 14:48
  • I've added some sample code showing enumeration of sessions. You'll have to learn how to use pinvoke to consume these C APIs as there is no equivalent API in C#. – donovan Nov 29 '15 at 22:34
  • Thanks for your help with this Donovan. I have been able to use your suggestions to enumerate and identify any session from any server by username, sessionID and session name. I am having some trouble enumerate the processes, but will keep trying! Thanks again – Bassie Dec 01 '15 at 10:48
  • It's probably permission related. I just had a look at the docs and the process enumeration is admin only (see the remarks on the MSDN page). Unfortunately there doesn't appear to be an option for the current non-admin user to enumerate their own processes. – donovan Dec 02 '15 at 00:36
  • I was able to achieve this using `WtsEnumerateProcesses`. However, to kill a process without showing the black CMD window I have to remote PowerShell into the server and from their execute a `kill process`. thank you for pointing me in the right direction! – Bassie Mar 23 '16 at 16:53
0

From MSDN site on ProcessStartInfo.CreateNoWindow Property :

Remarks

If the UseShellExecute property is true or the UserName and Password properties are not null, the CreateNoWindow property value is ignored and a new window is created.

There is no workaround or resolution mentioned, and I have been unable to find one anywhere.

I have had to resort to me application briefly displaying CMD windows when running certain processes (The CreateNoWindow property works when not using UserName and Password).

Bassie
  • 9,529
  • 8
  • 68
  • 159
0

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

Bassie
  • 9,529
  • 8
  • 68
  • 159