How to run a CmdLet from a Process class? - c#

I want to run a CmdLet (in particular Write-Output) from a Process class, but I am getting a The system cannot find the file specified. error. Which makes sense, as Write-Output is not a file:
using (Process process = new Process()) {
process.StartInfo.FileName = "Write-Output";
process.StartInfo.Arguments = "hello";
process.Start();
await process.WaitForExitAsync();
}
But, how could I use this correctly? I want to grab hello from standardoutput later and return it.

The Process class runs executables native to the OS. A commandlet is not an executable to the OS, but a concept of PowerShell ("inside" PowerShell). You have to invoke PowerShell, then make it run the commandlet you need.
But first you need to decide (you didn't say in the question) if you want to use Windows PowerShell or PowerShell (core). The Windows PowerShell is installed in "%windir%\System32\WindowsPowerShell\v1.0\powershell.exe". PowerShell (Core) is installed in various locations. On my system I have it (installed) in C:\Program Files\PowerShell\7-preview\pwsh.exe. YMMV, for more details on the different PowerShell Versions check this.
The command line won't differ, so I use pwsh.exe as an example.
process.StartInfo.FileName = "<path>\pwsh.exe";
process.StartInfo.Arguments = "-Command \"Write-Output 'Hello'\""
Redirecting/reading the output of PowerShell invoked like this does not differ from doing this for any other command. There are already lots of Q&A about this on SO (for example this one).
One more thing. For any nontrivial use, I would think about running the commandlet inside your own process. You can host powershell in your own application. Certainly more advanced, but might be worth it, if you need to run specific PowerShell commands a lot.

Related

How to get a new process to know the location of dotnet in Linux?

Our test and development team has been working to implement and test a new version of our software using .NET 6 that we will be releasing early next year. For test's part, we have had to upgrade all of our C# automated testing to be cross-platform compatible. One set of tests requires starting a new process and executing a console application we have as a supporting library. Below is the following code I am using:
#if (WINDOWS)
string strCmdText = "/C \" SAT.FCCHandling -l" + password + " -i" + ip + " -xDeleteFCC\"";
#endif
#if (LINUX)
string strCmdText = "-c \" .//SAT.FCCHandling -l" + password + " -i" + ip + " -xDeleteFCC\"";
#endif[![enter image description here](https://i.stack.imgur.com/6SYCA.png)](https://i.stack.imgur.com/6SYCA.png)
using (var proc = Process.Start(new ProcessStartInfo
{
#if (WINDOWS)
FileName = "cmd.exe",
#endif
#if (LINUX)
FileName = "/bin/bash",
#endif
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true,
Arguments = strCmdText,
WindowStyle = ProcessWindowStyle.Hidden
}))
{
proc.WaitForExit();
if (proc.ExitCode != 0)
{ Assert.Fail("There was an issue Inserting an invalid FCC."); }
}
I have been using VSCode in Linux to debug problems that arise. When the code above is executed in Linux (specifically in Ubuntu 20.04), in the developer console it's indicating that the location for .NET is not found:
We also use TShark for packet capture in some testing. The methodology of code is used in launching TShark as a new process. I bring this up not because TShark is written in C# with .NET 6, but merely to point out that this approach for launching an application does work.
The problem, as I perceive it, is this child process being launched has no knowledge of environment variables. If I launch the application directly from the terminal in the binary directory, the application runs just fine.
I have done some digging and research online before posing this question. I haven't found anything that is exactly similar to my problem. I have tried a couple avenues based upon what I have read online, but these don't seem to be the right solution:
Assigning all variables to the StartInfo of the new process.
Assigning specifically EnvironmentVariables["TEST_ENV"] = "From parent"; to the Process.StartInfo. I saw something online that seemed to indicate that passing environment variables down to the child process was a necessity as the variables are instances.
Using the StartInfo.Verb = “runas” to elevate the privileges of the process.
I know that the solution is probably an obvious one that I'm just not seeing. I am not a Linux expert by any stretch of the imagination and have been having to learn as I go through this release. Any guidance is much appreciated and I'm happy to provide further information if necessary. Thank you.
I spent some time during the holidays researching, debugging, and testing. I was able to find a solution to my problem. Several things:
In order to know exactly what was happening to my process on launch, I attached two threads to the standard output and standard error of the process I start.
This lead me to discover that the file location was unknown to bash when calling it.
With this resolved, I discovered that I needed to execute 'chmod +x ' before executing the console application.
It's worthy of note that by trying to debug my problem from a .NET 6 console app (launching another console app) was injecting a different kind of problem into my existing problem. Using my existing NUnit testing that calls the console app was what lead me to the discovery noted above.
Lesson learned: If trying to call a .exe in Linux from your .NET 6 application by starting a process, be sure to make the file executable before you try calling it with bash or another shell.

C# Windows Application vs Console Application Causing Differences to Process.Start()

I have a C# GUI wrapper that is using Process.Start() to call various third party python scripts (these scripts cannot be changed). The relevant code:
Process process = new Process();
process.StartInfo.FileName = FileParserConstants.PYTHON_PATH; // Path to python exe
process.StartInfo.Arguments = FileParserConstants.SCRIPT_PATH + sInput; // Path to scripts with input script + arguments
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.RedirectStandardOutput = true;
process.Start();
process.WaitForExit();
return process.StandardOutput.ReadToEnd().Trim();
This seems relatively simple, and it works when my C# project type is set to "Windows Application." However, upon switching to a "Console Application," though there is no error, the python scripts fail to function as intended.
Namely, there is a python script that accepts arguments and calls an exe to change settings given the arguments. When the python script is called from the project set as Console Application, a "OSError: [WinError 6] The handle is invalid" occurs within the exe called from within the python (this error is handled within the python scripts and logged). Again, when the script is called from the project set as Windows Application, it functions correctly.
For reasons unreleated to the issue, I am being forced to have this project be a Console Application, and am having trouble finding the documentation of the differences between the two project types and what may be causing Process to behave differently.

Get already running windows process' console output in a command prompt? Direct StreamReader to command prompt

I am trying to determine if there is a way to get the console output, in a command prompt, of an already running process in the windows environment via C#. I have seen an answer for linux based systems through the shell and also a way to retrieve a Process object. Though neither offer a solution to get the output of the process.
I my code I currently find a process (MongodDB daemon) this way
Process[] procs = Process.GetProcessesByName("mongod");
if (procs.Length > 0)
{
MongoProcess = procs[0];
Console.Out.WriteLine("Found mongod.exe, process id: " + MongoProcess.Id);
}
I have also found the Process.StandardOutput property which supplies "A StreamReader that can be used to read the standard output stream of the application.". Is there a way to direct this StreamReader input to a command prompt output?
I also know that I can start a process and display the command prompt (process output), but this is only when the process is started "by me".
Process.Start(new ProcessStartInfo("notepad.exe") { CreateNoWindow = false })
I also know that I could simply read from the StreamReader and display the output in my own way, but I would really prefer to just display a command prompt.
Windows does not provide any mechanism to retroactively give another process a standard output handle if it was created without one. (Besides, in most cases an application will check the standard output handle only during startup.)
If you know (or can successfully guess) how a specific application generates output, it may in principle be possible to start capturing that output by injecting your own code into the process. For example, if you know that the application uses C++ and is dynamically linked to the VS2015 runtime library, you might inject code that calls freopen as shown here. (In this scenario, you must know which runtime library the application uses because you have specify it when looking up the address for freopen.)
However, code injection is not trivial, and creates a risk of inadvertently causing the process to malfunction. Also, any output that has already been generated will almost certainly have been discarded and be irrevocably lost.

Capture ALL (stdout, stderr AND CON) output of cmd executing plink with C# (std out+err ok, CON not working)

I want to open SSH connections from c# via opening a process and running plink. All output should be collected and depending on the results the program will fire actions to the ssh.
My big problem is, that i am using a couple if different scripts and i need (automated) user interaction. Therefore i have to capture ALL output data (standard output, standard error AND CONSOLE).
Looking at the following test batch should make the case more clear:
1: #ECHO OFF
2: ECHO StdOut
3: ECHO StdErr 1>&2
4: ECHO Cons>CON
The code is like:
Process process;
Process process;
process = new Process();
process.StartInfo.FileName = #"cmd.exe";
process.StartInfo.Arguments = "/c test.bat";
process.StartInfo.UseShellExecute = false;
process.StartInfo.ErrorDialog = false;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardInput = true;
process.Start();
process.OutputDataReceived += new DataReceivedEventHandler(process_OutputDataReceived);
process.ErrorDataReceived += new DataReceivedEventHandler(process_OutputDataReceived);
process.BeginOutputReadLine();
process.BeginErrorReadLine();
StreamWriter inputWriter = process.StandardInput;
[...]
I am able to capture lines 2+3, but not 4 (used by some programs).
I have also tried powershell (or directly plink) instead of cmd.exe as starting point, but same result.
Is there any way in c# to capture the console out as well or do you know any third party command line being able to redirect CON out to stdout or something like this?
I'm not sure that is even possible - well at least not using some plain simple API.
Please note that I have not found (although I tried) any information on the web that directly confirms that, but here is how I come to this conclusion.
The console in Windows is its own subsystem, managed by csrss.exe (and starting with Windows Vista also conhost.exe, but I digress). It has it's own set APIs (AttachConsole, WriteConsole, etc.) and you can only have one "console" per process.
CMD.EXE on the other hand is just another console mode application, that just happens to use the console and being launched it a console window. You can observe this effect, by launching a different console mode application and watch the process tree in e.g. Process Explorer: there is no CMD.EXE parent process (but rather it is Explorer or whatever you used to start it - including of course CMD.EXE).
Thus far I was trying to show the difference between "the console" and CMD.EXE, batch files, or console mode applications in general.
So when in CMD.EXE you use > CON you are actually causing the same effect as doing a write to CONOUT$ in native applications (or your typical write to /dev/console on a UNIX-like OS). There doesn't seem to be a direct equivalent for managed code, as Console.Out and Console.Error equal stdout and stderr in native applications (or 1 and 2 as file descriptors in CMD.EXE).
Having all that said, when you start a process you're only enabled to redirect it's standard output and standard error streams, but not (per se) the messages it writes to the console (or CONOUT$ handle). I guess that would be the same as trying to redirect output that a process writes to some file, which is also not (generically) possible.
You could possible achieve this using some hooking or injecting something inside the child process to grab the console output.
Being not able to easily do this, is also (one of) the reason(s) why writing a complete terminal (i.e. console window, not CMD.EXE!) replacement for Windows is not easily possible and requires some hacks or workarounds.
AFAIK know what you want (redirecting CON) is only possible by hooking/injecting which is rather complex and basically not necessary to do SSH.
For SSH you can use any number of C# libraries out there (free and commercial):
http://www.chilkatsoft.com/ssh-sftp-component.asp (commercial)
http://www.tamirgal.com/blog/page/SharpSSH.aspx (free)
http://sshnet.codeplex.com/ (free)
http://www.rebex.net/ssh-pack/default.aspx (commercial)
http://www.eldos.com/sbb/desc-ssh.php (commercial)
The main advantage of using a library is that you have much greater control over the SSH session than you could ever achieve via some redirected console AND it is much easier to implement...
UPDATE - as per comments:
To get all output from the remotely running program you need to use a library which comes with an "interactive/terminal" class for SSH - for example the one at http://www.rebex.net/ssh-pack/default.aspx comes with such a class and works really fine (not affilliated, just a happy customer), it can be used as a code-only component or as a visual control (whatever suits your needs).
Here are some CodeProject projects which do this (via native code), which AFAICT better handle redirecting all console output:
Universal Console Redirector
Redirecting an arbitrary Console's Input/Output
Also, the command freopen allows a program to redirect its streams. However, if a program does an explicit WriteConsole() I'm not sure if it is possible to redirect that.

C# Application Calling Powershell Script Issues

I have a C# Winforms application which is calling a simple powershell script using the following method:
Process process = new Process();
process.StartInfo.FileName = #"powershell.exe";
process.StartInfo.Arguments = String.Format("-noexit \"C:\\Develop\\{1}\"", scriptName);
process.Start();
The powershell script simply reads a registry key and outputs the subkeys.
$items = get-childitem -literalPath hklm:\software
foreach($item in $items)
{
Write-Host $item
}
The problem I have is that when I run the script from the C# application I get one set of results, but when I run the script standalone (from the powershell command line) I get a different set of results entirely.
The results from running from the c# app are:
HKEY_LOCAL_MACHINE\software\Adobe
HKEY_LOCAL_MACHINE\software\Business Objects
HKEY_LOCAL_MACHINE\software\Helios
HKEY_LOCAL_MACHINE\software\InstallShield
HKEY_LOCAL_MACHINE\software\Macrovision
HKEY_LOCAL_MACHINE\software\Microsoft
HKEY_LOCAL_MACHINE\software\MozillaPlugins
HKEY_LOCAL_MACHINE\software\ODBC
HKEY_LOCAL_MACHINE\software\Classes
HKEY_LOCAL_MACHINE\software\Clients
HKEY_LOCAL_MACHINE\software\Policies
HKEY_LOCAL_MACHINE\software\RegisteredApplications
PS C:\Develop\RnD\SiriusPatcher\Sirius.Patcher.UI\bin\Debug>
When run from the powershell command line I get:
PS M:\> C:\Develop\RegistryAccess.ps1
HKEY_LOCAL_MACHINE\software\ATI Technologies
HKEY_LOCAL_MACHINE\software\Classes
HKEY_LOCAL_MACHINE\software\Clients
HKEY_LOCAL_MACHINE\software\Equiniti
HKEY_LOCAL_MACHINE\software\Microsoft
HKEY_LOCAL_MACHINE\software\ODBC
HKEY_LOCAL_MACHINE\software\Policies
HKEY_LOCAL_MACHINE\software\RegisteredApplications
HKEY_LOCAL_MACHINE\software\Wow6432Node
PS M:\>
The second set of results match what I have in the registry, but the first set of results (which came from the c# app) don't.
Any help or pointers would be greatly apreciated :)
Ben
This is actually not a particularly good way to embed PowerShell within a C# api. There are APIs for that.
You can find an example of them on MSDN, but in your case the could would look something like
PowerShell.Create().AddScript("get-childitem -literalPath hklm:\software").Invoke()
You can also check out this blog post, which will show you how to dot source inside of the API and how to use this API to get at the other data streams in PowerShell.
Hope this helps
Are you running a 64bit version of Windows by chance? It could be a difference in how the two "hives" are being shown. Try forcing your C# app to compile to x86/x64 instead of "Any" in the Project properties. See if that makes any difference.
Also, your command line syntax is a little strange, see the following thread for better details, but you may want to adjust your syntax:
String cmd = "-Command "& { . \"" + scriptName + "\" }";
Process process = new Process();
process.StartInfo.FileName = #"powershell.exe";
process.StartInfo.Arguments = cmd;
process.Start();
Calling a specific PowerShell function from the command line
I did look at alternative methods for calling Powershell and came across that API.
Am I right in thinking though that they rely on a Microsoft SDK??
I'm not really a fan of dependencies on external SDKs. I work in a rather large company and ensuring that the SDK is installed on all of the developers machines would be a nightmare.
If I'm wrong in my thinking, I am open to a better way of calling Powershell. I didn't particularly like calling the script as a separate process and would like the ability to have values returned from the script.
Ben

Categories