Retrieving shell script exit code (return value) - c#

Trying to validate a Logstash config file. When running the following line from a Windows command line:
C:> C:\Logstash\bin\logstash -t -f C:\Logstash\config\my.config
I can then check the result using
echo %errorlevel%
which returns 1 in case of a syntax error. Now I want to do this programatically in C#, so:
using System.Diagnostics;
var logstashProcess = Process.Start(#"C:\Logstash\bin\logstash", #"-t -f C:\Logstash\config\my.config");
logstashProcess.WaitForExit();
return logstashProcess.ExitCode == 0;
The problem is that it always returns true (exit code is zero) - even when the config file is totally messed up.
My guess: since C:\Logstash\bin\logstash is a shell script, the zero I get is the shell itself running successfully - not the Logstash process (which is executed from within that script using jruby). Any idea on how to get the real return value? Will a batch file work? (I prefer not to add an extra script to the party at this point)

Related

Unable to pipe infinite output of console app using PowerShell

During development of a console app I noticed that I'm unable to pipe its output into itself in PowerShell.
I created a small repro (source below) that works like this:
PS> .\but-why.exe print # continuously prints a random number every 500 ms
1746112985
1700785785
331650882
...
PS> .\but-why.exe read # echoes stdin
foo # this was typed
read 'foo'
bar # this too
read 'bar'
PS> "foo","bar" | .\but-why.exe read
read 'foo'
read 'bar'
But when I try to feed the output of print into read nothing happens:
PS> .\but-why.exe print | .\but-why.exe read
Same when I redirect all outputs to the success stream:
PS> .\but-why.exe print *>&1 | .\but-why.exe read
However, when I use CMD everything works as expected:
CMD> but-why.exe print | but-why.exe read
read '317394436'
read '1828759797'
read '767777814'
...
Through debugging I found that the second instance .\but-why.exe read never seems to be started.
Maybe it's my rather old PS version?
PS> $host.Version
Major Minor Build Revision
----- ----- ----- --------
5 1 19041 610
Source of the console app (net5.0):
using System;
using System.Threading;
switch (args[0]) {
case "print": Print(); break;
case "read": Read(); break;
}
void Print() {
var rng = new Random();
while (true) {
Console.WriteLine(rng.Next());
Thread.Sleep(500);
}
}
void Read() {
string? text;
while ((text = Console.ReadLine()) != null) {
Console.WriteLine($"read '{text}'");
}
}
You're seeing a design limitation in Windows PowerShell that has since been fixed in the cross-platform PowerShell [Core] 7+ edition:
When Windows PowerShell pipes data to an external program (which is then invariably text), it unexpectedly does not exhibit the usual streaming behavior.
That is, instead of passing the originating command's lines (stringified objects) on as they're being produced, Windows PowerShell tries to collect them all in memory first, before piping them to the external program.
In your case, because the first program never stops producing output, the Windows PowerShell engine never stops waiting for all output to be collected and therefore effectively hangs (until it eventually runs out of memory) - the target program is never even launched, because that only happens after collecting the output has finished.
Workarounds:
If feasible, switch to PowerShell [Core] 7+, where this limitation has been removed.
In Windows PowerShell, invoke your pipeline via cmd.exe, which does exhibit the expected streaming behavior, as you've observed.
# Workaround via cmd.exe
cmd /c '.\but-why.exe print | .\but-why.exe read'

Changing the current working directory of cmd (from a child process)

So I am trying to write a cd -like program that can be executed using cmd and after it exits the working directory of the calling cmd process should be changed.
Now before this post is flagged as a duplicate: I am aware of this and this question that were asked for pretty much this exact problem but using Linux instead of Windows as well as being pretty broad and unspecific, and I am aware that similar limitations apply to Windows as well (changing the working directory of my process will not change the parent’s working directory).
There is actually is a working solution to this for linux. However it is using gdb for this, and I would like to achieve this task using only built-in Windows utilities (WinAPI, dotNET, etc.).
What I have tried so far
I did manage to use Cheat Engine and the OpenProcess() / WriteProcessMemory() WinAPI funtions to successfully override cmd's working directory. However this solution feels sloppy and doesn't work well (or at least requires more work to be put into.)
My question
Is there a different (maybe simpler?) way on Windows to achieve this? Like a way to invoke/inject code to the cmd process to execute cd whatever\directory\I\want directly without overriding its memory? I have seen the CreateRemoteThread() functions however I didn't manage to find a way to put them to use.
FYI: I am mainly using C# but C/C++ solutions should help too as long as they are based on the native Microsoft libraries.
This post describes a Windows implementation of a function that launches a child process, creates pipes to stdin and stdout from which a command is sent, and a response is returned. Finally, once all response is captured the child process is terminated. If this sounds familiar it is similar in concept to Linux's popen() function with the exception that this implementation was specifically created to capture the response into a buffer of any command that returns one. (Also included is a variant for use when no-response is expected or needed.)
The full source can be adapted for use within a standalone executable, or as an API. (.dll) Either way, the resulting functions accept and process any command using standard Windows CMD syntax. The function cmd_rsp(...) returns the Windows response via stdout into a self-sizing buffer.
The exported prototypes are:
int __declspec(dllexport) cmd_rsp(const char *command, char **chunk, unsigned int size);
int __declspec(dllexport) cmd_no_rsp(const char *command);
A simple use case when capturing a response:
#include "cmd_rsp.h"
int main(void)
{
char *buf = {0};
buf = calloc(100, 1);//initialize to some initial size
if(!buf)return 0;
cmd_rsp("dir /s", &buf, 100);//buffer will grow to accommodate response as needed.
printf("%s", buf);
free(buf);
return 0;
}
A simple use case when response is not needed:
#include "cmd_rsp.h"
int main(void)
{
cmd_no_rsp("cd C:\\dir1\\dir2");
return 0;
}
A detailed description of purpose and usage is described in the link provided above. To illustrate, here are a few sample command inputs, each in this case change the working directory, then execute a command from that directory:
A command to change to sqlite directory, then execute a query:
cd c:\\tempExtract\\sqlite\\Tools\\sqlite-tools-win32-x86-3250300 && sqlite3.exe .\\extract.db \"select * from event, eventdata where eventType=38 and eventdata .eventid=event.eventid\
A command to change to teraterm directory, then execute a script:
"c:\\Program Files (x86)\\teraterm\" && ttpmacro c:\\DevPhys\\LPCR_2\\play\\Play.ttl
A command to change directory then execute a command to send multiple digital acquisition channel settings.
cd C:\\Dir1\\Dir2\\Dir3\\support\\Exes\\WriteDigChannel && .\\WriteDigChannel.exe P1_CH0 1 && .\\WriteDigChannel.exe P1_C H0 0 && .\\WriteDigChannel.exe P1_CH0 1
A recursive directory search from a specified location:
cd C:\\dir1\\dir2 && dir /s /b
I got it working. As was suggested SendInput finally did the trick.
I used a combination of WinAPI calls to GetForegroundWindow() / SetForegroundWindow() and the Windows Forms System.Windows.Forms.SendKeys.SendWait() Method to achieve what I wanted:
Upon calling my cd-wrapper program (sd.exe) and providing my custom target directory (~/ home) it generates the corresponding command along with the "Enter-Pressed-Event" to be sent to it's parent cmd process.
Here's the complete C# code:
if (args.Length != 1)
{
Console.WriteLine(Directory.GetCurrentDirectory());
return;
}
string targetDirectory = args[0];
string command = string.Empty;
if (targetDirectory.Equals("~"))
{
command = #"pushd C:\Users\fred\Desktop";
}
else if (!Directory.Exists(targetDirectory))
{
Console.WriteLine("I/O Error: No such file or directory.");
return;
}
else
{
command = #"cd " + targetDirectory;
}
Target target = Target.Create(Process.GetCurrentProcess().GetParentProcess());
target.SendKeys(command + "{ENTER}", true);
Note that I kind of started to write a complete Framework for this and similar problems alongside this project that contains all my different approaches to this question and the low level WinAPI calls as well as the Extension methods to get the parent process :D
As it would be a bit overkill to paste all of it's code in this answer, here's the GitHub. If I can find the time I'll go ahead and optimize the code, but for now this'll do. Hope this helps anyone encountering a similar problem :)
Edit:
An even "cleaner" way is to use dll injection to directly make cmd switch it's working directory. While it is a lot harder to get working it has the advantage of not littering the cmd command history as compared to the approach described above. In addition to that cmd seems to be aware of any changes to it's current working directory, so it automatically updates the prompt text. Once I have a fully working example, that allows to dynamically specify the target directory I will post it here :)

C#/.Net launched process with exit code -2146234327

I have a C# 4 application which launches another one to execute some Python code. The Python code can be executed with no problems (checked on PythonWin).
In my app I see that the exit code is -2146234327. I've been Googling and couldn't figure out what does it mean.
Any ideas?
Thanks
-2146234327 is HRESULT code, typically looked for in hex. See Interpreting HRESULTS returned from .NET/CLR: 0x8013XXXX:
HOST_E_EXITPROCESS_TIMEOUT 0x80131029 Process exited due to
Timeout escalation.
I’ve written a workaround based on the fact that this is a warning rather than an error.
I have included the following in the DOS Batch file that runs Analyse and Index Rebuild processes for ArcGIS
set myRC=%ERRORLEVEL%
echo %myRC%
set Constant=-2146234327
echo %Constant%
if %myRC% EQU %Constant% set ERRORLEVEL=0
echo %ERRORLEVEL%
Line 1 sets a variable to the value returned from the call command e.g. call D:\Python27\ArcGIS10.2\python.exe D:\Analyze\Analyze.py >> D:\Analyze\Log\output.txt
Line 2 echoes the value returned
Line 3 sets a constant variable
Line 4 echoes this out
Line 5 compares the value returned from the CALL command against the constant and sets the ERRORLEVEL variable to zero if they match
Line 6 echoes out the return Code.
Found this information elsewhere that may shed a little more light on the issue:
https://github.com/ucd-cws/arcpy_metadata/issues/13

Remote Process Execution using Wmi win32_Process - Getting Stdout of Process

Hi i am able to execute a remote process using Wmi and was able to get return Value and Process Id of the Process. Is there any way to get the Output of the Process which was started by Wmi. Eg. If i start an exe which prints something in console will i be able to get those values using this Api. Any help is appreciated.
You must redirect the output to a file, then read the file across the network.
Use the CMD.EXE /S /C option to do this.
Example command line to run Program.exe:
CMD.EXE /S /C " "c:\path\to\program.exe" "argument1" "argument2" > "c:\path\to\stdout.txt" 2> "c:\path\to\stderr.txt" "
Then connect to server like this \\servername\c$\path\to\stdout.txt to read the stdout results.
Note: Pay careful attention to the extra quotes around the command to run. These are necessary to ensure the command line is interpreted correctly.
You can use psexec to execute the program.exe and get the stdout by pipe
p = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while p.poll() is None:
line = p.stdout.readline()
line = line.strip().decode('gbk')
if line:
print('Subprogram output: [{}]'.format(line))
if p.returncode == 0:
print('Subprogram success')
return True
else:
print('Subprogram failed')
return False

Run a c# Console App with .application file type from a .BAT

I've created a console app (using Visual Studio 2010) which can read command arguments.
When I debug, I parse some test parameters which are set in Project-> [project name] Properties... -> Debug -> Command line arguments:
It reads:
"parametername1|parametervalue1" "parametername2|parametervalue2" "parametername3|parametervalue3"
I used the following code to read the parameters:
for (Int16 argumentsCount = 0; argumentsCount < args.Length; argumentsCount++)
{
String[] parameterItem = args[argumentsCount].Split('|');
String parameterName = parameterItem[0].ToString();
String parameterValue = parameterItem[1].ToString();
/*code continues*/
}
When I run in debug mode the app it works just fine and all parameters are read.
I then published the app to a server and ensured it was installed with the correct permissions (for the purposes of demonstration lets say it's on C:\MyApp and the Complied code resides in MyApp.application
I then created a batch script that executes the app. The *.BAT contains the following command:
"C:\MyApp\MyApp.application" "parametername1|parametervalue1" "parametername2|parametervalue2" "parametername3|parametervalue3"
This kind of works as the application executes when I run the batch... However... none of my parameters are being received by my app. I know this because I recompiled and published with some code to read how many parameters are being received with:
Console.Write("Arguments " + args.Length.ToString());
and that shows Arguments: 0
Can someone please tell me how to write my batch script to run the app and parse my parameters/command line arguments.
ETA: Nevermind. Your problem is .application instead of a .exe. Look in your file associations what happens with .application compared to .exe:
> assoc .application
.application=Application.Manifest
> ftype Application.Manifest
Application.Manifest=rundll32.exe dfshim.dll,ShOpenVerbApplication %1
> assoc .exe
.exe=exefile
> ftype exefile
exefile="%1" %*
You see the difference in what is passed there? Namely that normal executables get command-line arguments (the %*). So I guess you should use an executable instead of an executable manifest or whatever .application actually is (I've never seen it in the wild, honestly).
With a fairly minimal test program
class Args {
static void Main(string[] args) {
for (int i = 0; i < args.Length; i++) {
System.Console.WriteLine("[{0}]=<{1}>", i, args[i]);
}
}
}
it works fine for me. The following batch file:
#"args.exe" "parametername1|parametervalue1" "parametername2|parametervalue2" "parametername3|parametervalue3"
yields the following output:
[0]=<parametername1|parametervalue1>
[1]=<parametername2|parametervalue2>
[2]=<parametername3|parametervalue3>
So I guess there is something wrong in the code you didn't show us. Maybe you're not actually using the command-line arguments in your C# application but instead reference a different string[] there?
The pipe character | has a special meaning in batch files. I would suggest using a different character to make things easier. Otherwise you have to use a Escape Character to use the pipe character. It would probably look like this:
"C:\MyApp\MyApp.application" "parametername1^|parametervalue1" "parametername2^|parametervalue2" "parametername3^|parametervalue3"
note the caret ^ before the pipe |.

Categories