C# 在不显示 CMD 提示的情况下获取会话进程

C# Get Session Processes Without Showing CMD Prompt

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);
        }
    }

我显然在这里遗漏了一些非常重要的东西,因为我的 listProcs 方法只是一遍又一遍地 returns 相同的 ID。我需要阅读 C API 并了解 WTSEnumeratEProcesses 实际在做什么,以及如何查询这些进程。


Possible solution example code (top answer)

我正在为我的组织创建一个自助 IT 应用程序,用户可以在其中注销自己的会话并显示所有活动进程和 select 一个终止进程。

用户注销没有问题,但我在枚举进程时遇到问题。由于我使用登录名和密码来查询活动进程,因此每次发生这种情况时都会短暂显示 CMD window。我在文档中找不到任何解决方案,希望有人能指出正确的方向。

代码如下:

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;
        }
    }

}
}

非常感谢对此提出任何建议或反馈! 非常感谢

MSDN site 到 ProcessStartInfo.CreateNoWindow 属性 :

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.

没有提到解决方法或解决方案,而且我在任何地方都找不到。

当 运行 某些进程(CreateNoWindow 属性 在不使用用户名和密码时工作)时,我不得不求助于我的应用程序短暂显示 CMD windows。

远程桌面服务APIs当然可以做你想做的所有事情。但是我不确定是否允许非管理员用户在其他机器上操作他们自己的会话。

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

  • WTSOpenServer 获取特定服务器的句柄。
  • WTSEnumerateProcesses 获取进程列表。
  • WTSEnumerateSessions 获取会话列表。
  • 用于注销特定会话的 WTSLogoffSession。
  • WTSTerminateProcess 终止特定进程。

下面是一些使用 API 枚举会话的示例代码。这是使用常量 WTS_CURRENT_SESSION 打开当前服务器,但您可以使用 WTSOpenServer 与其他远程服务器通信。这是我从实时应用程序中破解出来的代码,因此它不会按原样编译。

如果您使用 C# 编程的时间足够长,您会遇到 C# 中不存在的 API,您必须调用 API 的 C 版本。我建议你看看 http://pinvoke.net 如果你需要帮助学习如何 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 所述,这可以使用 WTSEnumerateProcesses 来完成。

但是,如果有人想在不编组 C++ 方法的情况下列出远程进程(针对特定会话),您也可以使用 qprocess:

    qprocess /id:10 /server:servername

这会列出该会话中的所有进程 运行。

有关详细信息,请参阅 here