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

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.

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.

Related

Getting the error of a Powershell Script within c# and outputting it with conditional statement?

I have a console application and a method that executes a PowerShell script within the console application. So I'm trying to grab an error text that it outputs in the application and do something with it.
Example/What I'm trying to do:
If Error.contains("Object")
{
// do something here
}
Here is my current method
public void ExecutePowershellScript()
{
var file = #"C:\Path\filename.ps1";
var start = new ProcessStartInfo()
{
FileName = "powershell.exe",
Arguments = $"-NoProfile -ExecutionPolicy unrestricted -file \"{file}\"",
UseShellExecute = false
};
Process.Start(start);
}
Process.start: how to get the output?
When you create your Process object set StartInfo appropriately:
var proc = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "program.exe",
Arguments = "command line arguments to your executable",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
then start the process and read from it:
proc.Start();
while (!proc.StandardOutput.EndOfStream)
{
string line = proc.StandardOutput.ReadLine();
// do something with line
}
You can use int.Parse() or int.TryParse() to convert the strings to numeric values. You may have to do some string manipulation first if there are invalid numeric characters in the strings you read.
You can set RedirectStandardError = true and access any errors from process.StandardError
public static void ExecutePowershellScript()
{
var file = #"C:\Path\filename.ps1";
var start = new ProcessStartInfo()
{
FileName = "powershell.exe",
Arguments = $"-NoProfile -ExecutionPolicy unrestricted -file \"{file}\"",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true
};
using Process process = Process.Start(start);
string output = process.StandardOutput.ReadToEnd();
string errors = process.StandardError.ReadToEnd();
}
Okay, scratch the above suggestion.
After being corrected by mklement0,
This is a perfectly reasonable attempt, but, unfortunately, it can lead to hangs (while waiting for one's stream end, the other, when exceeding the buffer size, may cause process execution to block). If you need to capture both streams, you must collect the output from one of them via events. – mklement0
I changed the solution to use the ErrorDataReceived event
public static async Task ExecutePowershellScript()
{
var file = #"C:\Path\filename.ps1";
var start = new ProcessStartInfo
{
FileName = "powershell.exe",
Arguments = $"-NoProfile -ExecutionPolicy unrestricted -file \"{file}\"",
UseShellExecute = false,
// redirect standard error stream to process.StandardError
RedirectStandardError = true
};
using var process = new Process
{
StartInfo = start
};
// Subscribe to ErrorDataReceived event
process.ErrorDataReceived += (sender, e) =>
{
// code to process the error lines in e.Data
};
process.Start();
// Necessary to start redirecting errors to StandardError
process.BeginErrorReadLine();
// Wait for process to exit
await process.WaitForExitAsync();
}
start.Start();
while (!start.StandardOutput.EndOfStream)
{
string line = start.StandardOutput.ReadLine();
}

How to execute a shell command across platforms in .NET Core?

In my .NET Core Console app, I receive multiple commands in form of an array of string, and would like to execute them as console command (and showing their output in my own app if possible but not hard requirement).
At first, I tried to parse each command to separate their name and arguments and put them in ProcessStartInfo. However, some command does not work (even simple commands like echo "Hello").
Now I switched to call Powershell instead like this:
static IEnumerable<ProcessStartInfo> ParseCommands(string[] args)
{
return args
.Skip(1)
.Select(q => new ProcessStartInfo()
{
FileName = "powershell",
Arguments = q,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
}).ToList();
}
static void RunCommand(ProcessStartInfo processInfo)
{
Console.WriteLine($"{processInfo.Arguments}");
var process = new Process()
{
StartInfo = processInfo,
};
process.Start();
while (!process.StandardOutput.EndOfStream)
{
Console.WriteLine(process.StandardOutput.ReadLine());
}
process.WaitForExit();
}
The problem is I don't think this one can run on Linux or MacOS. Is there any "standard" way to tell my app to "run this as if it's a console command"?
This is my current code by using the Platform to determine the console command, feel free to tell me if there is a better way:
static IEnumerable<ProcessStartInfo> ParseCommands(string[] args)
{
var argsPrepend = "";
var shellName = "/bin/bash";
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
shellName = "cmd";
argsPrepend = "/c ";
}
return args
.Skip(1)
.Select(q => new ProcessStartInfo()
{
FileName = shellName,
Arguments = argsPrepend + q,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
}).ToList();
}
static void RunCommand(ProcessStartInfo processInfo)
{
Console.WriteLine($"{processInfo.Arguments.Substring(processInfo.FileName == "cmd" ? 3 : 0)}");
var process = new Process()
{
StartInfo = processInfo,
};
process.Start();
while (!process.StandardOutput.EndOfStream)
{
Console.WriteLine(process.StandardOutput.ReadLine());
}
process.WaitForExit();
}

How to identify the hardware details of a Linux/Mac machine using .Net Core

How to identify the hardware details of a Linux/Mac machine using.Net Core.
For windows machines, we can use System.Management and WMI Query.
So is there any similar way to identify the hardware details (like RAM ,Processor,Monitor ,CAM etc) of Linux and Mac machines.
For windows, I'm using:
ManagementObjectSearcher searcher =
new ManagementObjectSearcher("select * from Win32_Processor");
This is a piece of code to write bash linux commends in .net core:
using System;
using System.Diagnostics;
public static class ShellHelper
{
public static string Bash(this string cmd)
{
var escapedArgs = cmd.Replace("\"", "\\\"");
var process = new Process()
{
StartInfo = new ProcessStartInfo
{
FileName = "/bin/bash",
Arguments = $"-c \"{escapedArgs}\"",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true,
}
};
process.Start();
string result = process.StandardOutput.ReadToEnd();
process.WaitForExit();
return result;
}
}
This is an extension method, you use it like this:
var output = "ps aux".Bash();
As for the commends, refer the Get Linux System and Hardware Details on the Command Line article on VITUX to help you out writing the commends, it lists most of the commends to collect system information on Linux.
For MAC:
System.Management.ManagementClass mc = default(System.Management.ManagementClass);
ManagementObject mo = default(ManagementObject);
mc = new ManagementClass("Win32_NetworkAdapterConfiguration");
ManagementObjectCollection moc = mc.GetInstances();
foreach (var mo in moc) {
if (mo.Item("IPEnabled") == true) {
Adapter.Items.Add("MAC " + mo.Item("MacAddress").ToString());
}
}
I have done a workaround to get hardware info as per Platform. For windows I have used old way of system Management classes, for Linux i have used different Bash commands to Get Processor Id, Model,model version,machine id.
Following are some linux commands i am using
1. "LinuxModel": "cat /sys/class/dmi/id/product_name"
2. "LinuxModelVersion": "cat /sys/class/dmi/id/product_version"
3. "LinuxProcessorId": "dmidecode -t processor | grep -E ID | sed 's/.*: //' | head -n 1"
4. "LinuxFirmwareVersion": "cat /sys/class/dmi/id/bios_version",
5. "LinuxMachineId": "cat /var/lib/dbus/machine-id"
Waiting for some support in the .net core framework soon
My gihub post address is https://github.com/dotnet/corefx/issues/22660
I have also used similar extension method with a bit optimized code for bash command
public static string Bash(this string cmd)
{
string result = String.Empty;
try
{
var escapedArgs = cmd.Replace("\"", "\\\"");
using (Process process = new Process())
{
process.StartInfo = new ProcessStartInfo
{
FileName = "/bin/bash",
Arguments = $"-c \"{escapedArgs}\"",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true,
};
process.Start();
result = process.StandardOutput.ReadToEnd();
process.WaitForExit(1500);
process.Kill();
};
}
catch (Exception ex)
{
//Logger.ErrorFormat(ex.Message, ex);
}
return result;
}

C# Process.Start() not starting despite file existing and setting working directory

Question update
I'm trying to use a C# script in Unity to call a python program. After following these two links:
How do I run a Python script from C#?
Process.Start() not starting the .exe file (works when run manually)
I could not get my C# script to call the python program. I know this isn't a problem with my python script because I can run the program in terminal without a problem.
public class JsonStream : MonoBehaviour
{
private string GetStream()
{
ProcessStartInfo start = new ProcessStartInfo();
//none of the paths work, the uncommented variables return true when using File.Exists
//while the commented variables return false
start.FileName = "/Library/Frameworks/Python.framework/Versions/3.6/bin/python3";
//start.FileName = "\"/Library/Frameworks/Python.framework/Versions/3.6/bin/python3\"";
start.Arguments = Directory.GetCurrentDirectory() + "/Assets/googlesheetspyprojectbatch/stream.py";
//start.Arguments = "\"" + Directory.GetCurrentDirectory() + "/Assets/googlesheetspyprojectbatch/stream.py" + "\"";
start.WorkingDirectory = Directory.GetCurrentDirectory() + "/Assets/googlesheetspyprojectbatch";
//start.WorkingDirectory = "\"" + Directory.GetCurrentDirectory() + "/Assets/googlesheetspyprojectbatch" + "\"";
start.UseShellExecute = false;
start.RedirectStandardOutput = true;
start.ErrorDialog = true;
start.RedirectStandardError = true;
using (Process process = Process.Start(start))
{
using (StreamReader reader = process.StandardOutput)
{
string result = reader.ReadToEnd();
return result;
}
}
}
public void DoEverythingStream()
{
UnityEngine.Debug.Log("Doing Stream");
string json_text = GetStream();
string[] games = json_text.Trim().Split('\n');
foreach (string game in games)
{
UnityEngine.Debug.Log(game);
}
}
}
Running the program raises no errors but also doesn't output anything. Does anyone have any ideas what may be wrong with my program? Thanks!
Old question, but I was struggling to figure out why C# (Unity) wasn't running my Python file even though a manual command prompt could.
By putting my python script's path in triple double quotes ("""), C# was able to run the script.
Example:
string fileName = #"""PATH\TO\YOUR\SCRIPT.PY""";
string pythonPath = #"PATH\TO\PYTHON.EXE";
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo = new System.Diagnostics.ProcessStartInfo(pythonPath, fileName)
{
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};
p.Start();

How to use "<" (input redirection) with ProcessStartInfo in C#?

I have the following:
C:\temp\dowork.exe < input.txt
processing.......
complete
C:\
I try this:
processArguments = " < input.txt";
pathToExe = "C:\\temp\dowork.exe";
startInfo = new ProcessStartInfo
{
FileName = pathToExe,
UseShellExecute = false,
WorkingDirectory = FilepathHelper.GetFolderFromFullPath(pathToExe),
Arguments = processArguments
};
try
{
using (_proc = Process.Start(startInfo))
_proc.WaitForExit();
}
catch (Exception e)
{
Console.WriteLine(e);
}
and my dowork.exe crashes after Start() is called.
Any suggestions?
Post Question Update.
Thank you everyone for your input. I solved the problem using amit_g's answer. Extended thanks to Phil for showing likely the best way (although I didn't test it out, I can see why it is better). Below is my complete solution. Feel free to copy and modify for your own issue.
1) create a console application project, add this class
internal class DoWork
{
private static void Main(string[] args)
{
var fs = new FileStream("C:\\temp\\output.txt", FileMode.Create, FileAccess.ReadWrite, FileShare.None);
var toOutput = "Any items listed below this were passed in as args." + Environment.NewLine;
foreach (var s in args)
toOutput += s + Environment.NewLine;
Console.WriteLine("I do work. Please type any letter then the enter key.");
var letter = Console.ReadLine();
Console.WriteLine("Thank you.");
Thread.Sleep(500);
toOutput += "Anything below this line should be a single letter." + Environment.NewLine;
toOutput += letter + Environment.NewLine;
var sw = new StreamWriter(fs);
sw.Write(toOutput);
sw.Close();
fs.Close();
}
}
2) Create 1 file: C:\temp\input.txt
3) edit input.txt, type a single letter 'w' and save (that's right the file contains a single letter).
4) Create a new class library project. Add a reference to nunit (i'm using version 2.2).
5) Create a testfixture class, it should look like the following. Note: this test fixture is handling external resources, as such you cannot run the entire fixture, instead, run each test one-at-a-time. You can fix this by making sure all file streams are closed, but i didn't care to write this, feel free to extend it yourself.
using System.Diagnostics;
using System.IO;
using NUnit.Framework;
namespace Sandbox.ConsoleApplication
{
[TestFixture]
public class DoWorkTestFixture
{
// NOTE: following url explains how ms-dos performs redirection from the command line:
// http://www.febooti.com/products/command-line-email/batch-files/ms-dos-command-redirection.html
private string _workFolder = "C:\\Temp\\";
private string _inputFile = "input.txt";
private string _outputFile = "output.txt";
private string _exe = "dowork.exe";
[TearDown]
public void TearDown()
{
File.Delete(_workFolder + _outputFile);
}
[Test]
public void DoWorkWithoutRedirection()
{
var startInfo = new ProcessStartInfo
{
FileName = _workFolder + _exe,
UseShellExecute = false,
WorkingDirectory = _workFolder
};
var process = Process.Start(startInfo);
process.WaitForExit();
Assert.IsTrue(File.Exists(_workFolder + _outputFile));
}
[Test]
public void DoWorkWithoutRedirectionWithArgument()
{
var startInfo = new ProcessStartInfo
{
FileName = _workFolder + _exe,
UseShellExecute = false,
WorkingDirectory = _workFolder,
Arguments = _inputFile
};
var process = Process.Start(startInfo);
process.WaitForExit();
var outputStrings = File.ReadAllLines(_workFolder + _outputFile);
Assert.IsTrue(File.Exists(_workFolder + _outputFile));
Assert.AreEqual(_inputFile, outputStrings[1]);
}
[Test]
public void DoWorkWithRedirection()
{
var startInfo = new ProcessStartInfo
{
FileName = _workFolder + _exe,
UseShellExecute = false,
WorkingDirectory = _workFolder,
RedirectStandardInput = true
};
var myProcess = Process.Start(startInfo);
var myStreamWriter = myProcess.StandardInput;
var inputText = File.ReadAllText(_workFolder + _inputFile);
myStreamWriter.Write(inputText);
// this is usually needed, not for this easy test though:
// myProcess.WaitForExit();
var outputStrings = File.ReadAllLines(_workFolder + _outputFile);
Assert.IsTrue(File.Exists(_workFolder + _outputFile));
// input.txt contains a single letter: 'w', it will appear on line 3 of output.txt
if(outputStrings.Length >= 3) Assert.AreEqual("w", outputStrings[2]);
}
[Test]
public void DoWorkWithRedirectionAndArgument()
{
var startInfo = new ProcessStartInfo
{
FileName = _workFolder + _exe,
UseShellExecute = false,
WorkingDirectory = _workFolder,
RedirectStandardInput = true
};
var myProcess = Process.Start(startInfo);
var myStreamWriter = myProcess.StandardInput;
var inputText = File.ReadAllText(_workFolder + _inputFile);
myStreamWriter.Write(inputText);
myStreamWriter.Close();
// this is usually needed, not for this easy test though:
// myProcess.WaitForExit();
var outputStrings = File.ReadAllLines(_workFolder + _outputFile);
Assert.IsTrue(File.Exists(_workFolder + _outputFile));
// input.txt contains a single letter: 'w', it will appear on line 3 of output.txt
Assert.IsTrue(outputStrings.Length >= 3);
Assert.AreEqual("w", outputStrings[2]);
}
}
}
You have to use the STDIN redirection. Like this...
inputFilePath = "C:\\temp\input.txt";
pathToExe = "C:\\temp\dowork.exe";
startInfo = new ProcessStartInfo
{
FileName = pathToExe,
UseShellExecute = false,
WorkingDirectory = FilepathHelper.GetFolderFromFullPath(pathToExe),
RedirectStandardInput = true
};
try
{
using (_proc = Process.Start(startInfo))
{
StreamWriter myStreamWriter = myProcess.StandardInput;
// Use only if the file is very small. Use stream copy (see Phil's comment).
String inputText = File.ReadAllText(inputFilePath);
myStreamWriter.Write(inputText);
}
_proc.WaitForExit();
}
catch (Exception e)
{
Console.WriteLine(e);
}
You are going to want to do something like,
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = pathToExe,
UseShellExecute = false,
WorkingDirectory = FilepathHelper.GetFolderFromFullPath(pathToExe)
};
Process process = Process.Start(startInfo);
FileStream reader = File.OpenRead("input.txt");
reader.CopyTo(process.StandardInput.BaseStream);
This can be done, at least in a somewhat "hacky" fashion by passing arguments directly to cmd.exe. However, as I recommend emulating the "<" manually as in the other answers, this is here as a note only.
(foo.txt is contains two lines, "b" and "a", so that they will be reversed when correctly sorted)
var x = new ProcessStartInfo {
FileName = "cmd",
Arguments = "/k sort < foo.txt",
UseShellExecute = false,
};
Process.Start(x);
You can replace /k with /c to prevent cmd.exe from remaining open. (See cmd /? for the options).
Happy coding.
First, you don't need a space preceeding your args. The method does it for you. This might mess you up to begin with. So it would be:
processArguments = "< input.txt";
But if that doesn't work you can try::
process = "cmd.exe";
processArguments = "/c dowork.exe < input.txt";

Categories

Resources