How can I capture the console output of dpinst when running silently? - c#

I've got a process (written in C#) that runs with administrative rights and invokes dpinst.exe to perform an automated driver installation. It writes its own custom dpinst.xml file to specify things like suppressing the EULA page, suppressing the wizard, running a "quiet install," and searching in all subdirectories.
When I invoke this manually from the command line (see example below), it seems to work fine. Because of the command line switches I'm using it prints a bunch of INFO level log messages in the console.
C:\Path\To\Drivers> dpinst.exe /C /L 0x409
I want to log what gets printed in the console, so my C# code looks something like this:
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = #"C:\Path\To\Drivers\dpinst.exe",
Arguments = "/C /L 0x409",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true,
Verb = "runas"
}
};
string output;
process.Start();
using (var reader = process.StandardOutput)
{
output = reader.ReadToEnd();
reader.Close();
}
However, when I run that code, the value of output is always blank. So, for my next experiment, I tried using the command line directly to pipe the output to a file, like this:
C:\Path\To\Drivers> dpinst.exe /C /L 0x409 > test.log 2>&1
That created the test.log file, but it was also blank. Interestingly enough I could still see all of the console output that dpinst.exe generates in that same console window; for some reason it didn't get redirected to the file that I specified. So the symptom is the same regardless of how I invoke the dpinst executable; it doesn't want to redirect output. I'm not sure what the underlying reason for that is, nor how to solve it. How can I capture the console output?
EDIT: If anyone wants a copy of dpinst.exe to run locally and test out themselves, I've provided one at this link, bundled with the dpinst.xml file I'm using. Remember that you need to invoke it with the /C command line switch in order to generate any command line output. Alternatively, if you're paranoid and don't want to download an executable from some random Stack Overflow question, you can get the dpinst.exe utility as part of the Windows Driver Kit from Microsoft. It's a free download, but you have to extract the utility (which is only 500 KB) from the full WDK ISO (which is ~700 MB). Up to you. :)

Your code runs perfectly fine with standard tools (like "ping") for example. So maybe for some reason dpinst writes to standard error instead? You can set RedirectStandardError = true and read StandardError to check if that is the case.
UPDATED in case anyone else will hit this problem:
It seems dpinst does not write to standard output but logs messages to console in some other way. Only way to achieve your goal which comes to my mind is: remember size of "%SystemRoot%\DPINST.LOG" file, run your command, wait for exit, then read everything between remembered position and end of file. This way you will get your logs.

I my answer will help other. When you run Dpinst.exe and you add the swicth /C to dump the log to console, it also creates a log file in this directory "C:\Windows\DPINST.LOG"
You can locate the log file there..

Related

Run an External Program or Batch File From a C# Xamarin Program

I want to have my C# (Xamarin) program run an EXE or batch (BAT) file. The user will be running my program, and will click on one of several buttons, some of which open Web pages and others of which run external programs. These files will be on the same computer as the one running the main program and don't need greater permissions. The overall program will be in Windows, UWP.
I already have code to pull info from the database saying "the button the user clicked references a program and it's (eg) C:\Tools\MyTool.exe". (Real path more like (C:\Users\Me\source\repos\ProductNameV2\ProductName\ProductName.UWP\Assets\EXE\whatever.exe".) I used a "demo.bat" file containing nothing but echo and pause statements, or references to a built-in Windows program like Notepad or Calc that an ordinary command prompt can recognize without an explicit path (ie. that's part of the recognized system Path). Yes, the real path to the dummy file does exist; I checked. I've also explicitly added files demo.bat and dummy.txt to my C# project.
Here's roughly what I've tried so far to actually run a batch file, or an EXE, or just to try opening a text file. Nothing works.
1)
bool check = await Launcher.CanOpenAsync(#"file:///C:\Tools\demo.bat"); // Returns false.
bool check = await Launcher.CanOpenAsync(#"file:///C:\Tools\dummy.txt"); // Returns true.
await Launcher.OpenAsync(#"file:///C:\Tools\demo.bat") // Seems to do nothing; silently fails.
await Launcher.OpenAsync(#"file:///C:\Tools\dummy.txt") // Same.
2)
Process batchProcess = new Process();
batchProcess.StartInfo.FileName = #"file:///C:\Tools\demo.bat"; // Same result with notepad.exe
batchProcess.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
batchProcess.Start();
batchProcess.WaitForExit();
// Result: "Access is denied" error during Start().
3)
var otherProcessInfo = new ProcessStartInfo(#"file:///C:\Tools\demo.bat")
var otherProcess = Process.Start(otherProcessInfo);
otherProcess.WaitForExit();
otherProcess.Close();
// Result: "The system cannot find the file specified" despite it being the same path as in previous examples.
// Also tried literally using the path C:\Tools\demo.bat, without adding that to the C# project.
// One thing that slightly works is to use:
var otherProcessInfo = new ProcessStartInfo("cmd.exe", "/c echo Hello world!");
// This version opens a window and instantly closes it again. With "/c pause" instead, it opens, saying "press any key to continue".
// Chaining multiple commands with newline or semicolon characters doesn't work as a form of batch file.
So: the only tiny success I've had here is to run cmd.exe, to run a one-line command. I suppose that depending on what the batch file must do, there's some possibility of receiving a string, breaking it into lines, then running cmd.exe using method 3 to call them one at a time. Which is ugly at best.
Is there some better way to do this -- to run a batch file or an EXE from within my program?
EDIT: Yes, I did in fact look at documentation before asking. Why did I use URIs? Because of multiple errors telling me that the simple path strings ("C:\this\that") I was using were in an "Invalid URI format". Using Process.Start("notepad.exe") silently fails, doing nothing. Using a method involving System.Diagnostics.Process (found at How to run external program via a C# program? and yes I saw that before) fails with an error of "Access denied" when using my batch file reference, or silently failing (no window opens) using plain old notepad.exe. I avoided setting Process options that say hide the window.
So to rephrase: Is there a way to make my program run some EXE somewhere on the computer, or to run a batch file that has more than one command in it? What is that way?
Using the data you collected, I was able to run a batch file by doing the following:
var strPathToExeOrBat = System.IO.Path.Combine("C:\\Tools", "demo.bat");
var otherProcessInfo = new ProcessStartInfo("cmd.exe", $"/c call \"{strPathToExeOrBat\"");
var otherProcess = Process.Start(otherProcessInfo);
otherProcess.WaitForExit();
otherProcess.Close();
I also think it would be helpful to review the capabilities of the cmd.exe application.
I found this post to be helpful:
https://stackoverflow.com/questions/515309/what-does-cmd-c-mean#:~:text=%2FC%20Carries%20out%20the%20command%20specified%20by%20the%20string%20and,switches%20by%20typing%20cmd%20%2F%3F%20.
In particular the /k option will leave the window open, if you don't want it to close after running a script.
Thank you very much for your question! It really helped me find the answer to this! (at least for my situation of a .NET MAUI windows app, but MAUI is built off of Xamarin.Forms, so you shouldn't have a problem doing the same thing)
EDIT: Updated to use file path from question and string interpolation with System.IO.Path.Combine for slightly greater cross platform capability

Empty standard output when run an exe file using C#

My question may seem as a straight forward one, because it was asked many times before.Anyway I think my case is completely different and I can't find any intuition about it. I have an exe file compiled from a code written in assembly language and I want to run this exe using another code and capture its output. I did this using C# and here is the code for it:
static string runCommand(string command, string args)
{
if (File.Exists(command))
{
Process p = new Process();
p.StartInfo.FileName = command;
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.Arguments = args;
p.Start();
p.WaitForExit();
string output = p.StandardOutput.ReadToEnd();
return output;
}
return "";
}
The code for the target exe file is pretty simple (I'm using Irvine library):
INCLUDE Irvine32.inc
.data
.code
main proc
call dumpregs
exit
main ENDP
end main
Where command is the exe file path, args is the arguments passed to the exe file (be noted that no arguments required by this exe)
The output is always equal to "" !. When I run the exe using command prompt, the console output is definitely not empty.Here is what I get:
I also tried to capture the console output using python but the returned string is awlays empty.
I have done this several times but the exe file was written in C# instead of assembly language, but I think it shouldn't be any difference exist.
EDIT
Solutions attempted so far and suggested by hatchet:
Capturing console output from a .NET application (C#)
How to spawn a process and capture its STDOUT in .NET? [duplicate]
Process.start: how to get the output?
None of them worked for me
If you need any further information let me know in the comments
Your C# code is correct. The standard way of capturing the output of a console process is to redirect the standard output by setting the process object's StartInfo.RedirectStandardOutput property to true, as described here. This is exactly what you have done.
The problem, as correctly diagnosed by rkhb, is that you're using the Irvine32 library in the auxiliary process, and its implementation of dumpregs calls the Win32 WriteConsole function, which cannot be redirected. If you attempt to redirect the standard output (e.g., to a pipe or file), then WriteConsole will fail with ERROR_INVALID_HANDLE, as documented on MSDN:
ReadConsole and WriteConsole can only be used with console handles; ReadFile and WriteFile can be used with other handles (such as files or pipes). ReadConsole and WriteConsole fail if used with a standard handle that has been redirected and is no longer a console handle.
Like rkhb's comment, this except from the documentation also hints at the solution. To support redirection, you need to change the implementation of dumpregs to call WriteFile instead of WriteConsole. This is a more general function that can write to any type of handle, including the standard console output (a la WriteConsole) or any other type of object that you might redirect the output to. Once you've made this change, you will, of course, need to rebuild the Irvine32 library.
The only significant limitation of WriteFile is that it doesn't support Unicode output like WriteConsole does, but that isn't a problem in your case. The output of dumpregs is all ANSI.

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.

Different command prompt window behaviors

I've seen a very similar question asked before, here, however the answer thread fell apart. Basically, I installed Python (and checked the option to add to the Path variable), confirmed that it is indeed in the path variable (through the Environment Variables window like you would normally).
When opening a cmd window manually, I can type python -V and get the version back, and anything else really, and everything works fine, python is indeed exposed through the command prompt (when opened manually).
However, when I attempt to run a command through cmd.exe with a C# app I have, I get
'python' is not recognized as an internal or external command,
operable program or batch file.
The block of C# code I have
var proc = new Process();
var startInfo = new ProcessStartInfo
{
UseShellExecute = true,
FileName = "cmd.exe",
Arguments = "/K " + command
};
proc.StartInfo = startInfo;
proc.Start();
This has worked fine in the past. However I had my work machine upgraded to Windows 10 and have been struggling to get this working.
The command text hasn't changed since the application was working before my windows upgrade, and if I take it's text and run it through a manually opened command prompt it executes fine with no issues. So I'm hesitant to believe it's an issue with the command itself.
UPDATE: If I run echo %PATH% in a regular prompt I see python, if I run it in the command prompt my application opens, I do not. I tried using set PATH, but that didn't help. Why is it that the PATH variable is different between a command prompt I open manually and one my application opens?
I thought it might have something to do with User and System variables having their own path, but Python is in both of them when checked via System Properties, so I'm at a loss.

Can't Use tskill from C#

I have a C# program. It is literally one line:
System.Diagnostics.Process.Start(#"C:\ProgramData\task manager\killtask.vbs");
There is a VBS file there, which generates a batch file that allows you to type a command and it will be executed and closed. It then sends the following keys: "tskill /a notepad {ENTER}". I know that's probably the worst practice you've ever seen, but bear with me.
When the VBS file is run by hand, it successfully closes notepad. When it is run through C# using the above line, it prints "tskill is not recognized" etc. before it closes.
Why is it that I can't use tskill through batch via VBS via C#, but I can use it just through batch via VBS? Remember, both clicking on it and running my C# code successfully ATTEMPT to kill notepad, but only clicking on it by hand closes notepad successfully.
You probably need to set the working directory. Without it your program will be executed from the directory of the process that starts your script and this directory is not the correct one.
Juse try with
var processStartInfo = new ProcessStartInfo();
processStartInfo.WorkingDirectory = #"C:\ProgramData\task manager");
processStartInfo.FileName = "killtask.vbs";
Process proc = Process.Start(processStartInfo);

Categories