Search code examples
c#performancecmdtasklist

Process.WaitForExit() Much Slower After Refactor When Calling Taskkill


I am in the process of rewriting an application I made some time ago. One of the functions available to users is to enumerate all processes which are currently running over the active Citrix session and display them (Similar Windows Task Manager). The issue is when querying tasklist on the user's machine, and the length of time taken to output the results of this command.

My new version of the code takes a much more Object-Oriented approach by using non-static classes to represent Sessions and Procs (Processes).

The original code looked like this, and it worked fairly well in terms of length of time taken to actually run the query and retrieve the output results:

OLD CODE:

    public static Dictionary<string, string> GetProcs(string server, string sessID)
    {
        SecureString ss = CreatePW();
        ProcessStartInfo startInfo = new ProcessStartInfo("cmd", "/C tasklist /S " + server + " /FI \"SESSION eq " + sessID + "\" /FO CSV /NH")
        {
            WindowStyle = ProcessWindowStyle.Hidden,
            UseShellExecute = false,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            CreateNoWindow = true,

            WorkingDirectory = @"C:\windows\system32",
            Verb = "runas",
            Domain = "BARDOM1",
            UserName = "zzkillcitrix",
            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;
    }

This entire application as very messy and so I've rewritten most of it, but my only concern is that the new method for retrieving the current list of processes is a lot slower (probably around 4 - 5 times slower than the old version).


NEW CODE:

In the ProcessHelper class

    public static List<Proc> GetProcList(Session session)
    {
        // Get the current tasks
        List<string> processQueryResult = TaskList(session);
        List<Proc> procList = new List<Proc>();

        foreach (var processDetails in processQueryResult)
        {
            // Only create the Proc if the process is in the 'valid' array ...
            // Get the procname
            string procName = processDetails.Split(',')[0].Trim('"').ToUpper();

            // Make sure it's position is not -1 ...
            int pos = Array.IndexOf(MyGlobals.ProcArray, procName);
            if (pos > -1)
            {
                int procId = Int32.Parse(processDetails.Split(',')[1].Trim('"'));

                Proc p = new Proc(procName, procId, session.ServerName, session.ID);
                procList.Add(p);

                SupportMI.Trace = "--adding" + p.Name + "--";
            }
        }

        return procList;
    }

    private static List<string> TaskList(Session session)
    {
        string cmdIn = "tasklist /S " + session.ServerName + " /FI \"SESSION eq " + session.ID + "\" /FO CSV /NH";
        List<string> cmdOut = Cmd.StdOutAdminList(cmdIn);

        return cmdOut;
    }

In the Cmd class

    public static List<string> StdOutAdminList(string args)
    {
        List<string> cmdOut = new List<string>();

        SecureString ss = pw();
        ProcessStartInfo startInfo = new ProcessStartInfo("cmd", "/C " + args)
        {
            WindowStyle = ProcessWindowStyle.Hidden,
            UseShellExecute = false,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            CreateNoWindow = true,

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

        cmdOut = ExecuteListCommand(startInfo);
        return cmdOut;
    }

    private static List<string> ExecuteListCommand(ProcessStartInfo startInfo)
    {
        List<string> procList = new List<string>();

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

        return procList;
    }

Possible Reasons

In the new version of the Program I also introduced several new objects (For example a Session class and a Proc class to store information about separate processes). Is it possible that adding these extra classes slows down the Process.WaitForExit() method?

After some debugging, it seems that the point at which the program is slowing down relative to the old code is when Process.WaitForExit() is called - Does anything affect this method call apart from the ProcessStartInfo details? if not, then I am very confused as I set the ProcessStarInfos to the same settings but still the new code has a delay.

Another thought I had was that perhaps the addition of more objects, meaning more parameters being passed around, is slowing down the whole application, which is somehow manifesting itself in the way described above.

Any insight into why this may be happening is much appreciated. Please let me know if I can provide any more details or code, or run any tests.

I also considered calling "tasklist" directly from Process rather than "cmd", but this had no affect, so I have ruled this out as a possibility.


Solution

  • This was due to the query not including the domain name after the server name.

    I ran several tests using C# Stopwatch class, and it seems that running this query:

    TASKLIST /S XA7-17

    Is a lot slower than running

    TASKLIST /S XA7-17.domain.co.uk

    After including the domain name at the end of the server, my queries are just as fast a in the old application.