Is sleep is required before Process.StandardOutput.Read() method? - c#

I'm trying create a sub-process in C# program (cmd for example) and performing read/write with process IO streams. I'm using StandardOutput.Read() method to read process output.
When I put a Thread.Sleep() method before Read(), it gives complete output but if I remove it, it displays only single line of output.
Here is the code:
string sProcess = "cmd.exe";
ProcessStartInfo psiInfo = new ProcessStartInfo();
psiInfo.FileName = sProcess;
psiInfo.CreateNoWindow = true;
psiInfo.UseShellExecute = false;
psiInfo.RedirectStandardOutput = true;
psiInfo.RedirectStandardError = true;
psiInfo.RedirectStandardInput = true;
Process pChild = new Process();
pChild.StartInfo = psiInfo;
if (pChild.Start())
{
int ch;
do
{
Thread.Sleep(50);
ch = pChild.StandardOutput.Peek();
if (ch > 0)
Console.Write((char)pChild.StandardOutput.Read());
} while (ch > 0);
Console.WriteLine("exit");
pChild.StandardInput.WriteLine("exit");
}
Output with Sleep enabled:
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation. All rights reserved.
D:\ProcessDemo_001\bin\Release>exit
Output with Sleep disabled:
Microsoft Windows [Version 6.1.7601]exit
I want to know why this happens?

I want to know why this happens?
Your loop is running faster than the output is being produced. As soon as it gets through the output, it ends, so it never sees the second line.

It may happen that your Peek command is executed before pChild has output text. When that happens, ch will be 0 and the while loop quits.

Related

How can I programmatically turn off or on 'Windows Features'

When I try to update Windows features; When I update UseShellExecute to "true"; "The Process object must have the UseShellExecute property set to false in order to redirect IO streams." I get an error. When I set it to False; Unable to update. How can I do it ? Do you have any other suggestions?
static void InstallIISSetupFeature()
{
var featureNames = new List<string>() {
"IIS-WebServerRole",
"IIS-WebServer",
"IIS-CommonHttpFeatures",
"IIS-HttpErrors",
"IIS-HttpRedirect",
"IIS-ApplicationDevelopment",
"IIS-Security",
"IIS-RequestFiltering",
"IIS-NetFxExtensibility",
"IIS-NetFxExtensibility45",
"IIS-HealthAndDiagnostics",
"IIS-HttpLogging",
"IIS-LoggingLibraries",
"IIS-RequestMonitor",
"IIS-HttpTracing",
"IIS-URLAuthorization",
"IIS-IPSecurity",
"IIS-Performance",
"IIS-HttpCompressionDynamic",
"IIS-WebServerManagementTools",
"IIS-ManagementScriptingTools",
"IIS-IIS6ManagementCompatibility",
"IIS-Metabase",
"IIS-HostableWebCore","IIS-StaticContent",
"IIS-DefaultDocument",
"IIS-DirectoryBrowsing",
"IIS-WebDAV",
"IIS-WebSockets",
"IIS-ApplicationInit",
"IIS-ASPNET",
"IIS-ASPNET45",
"IIS-ASP",
"IIS-CGI",
"IIS-ISAPIExtensions",
"IIS-ISAPIFilter",
"IIS-ServerSideIncludes",
"IIS-CustomLogging",
"IIS-BasicAuthentication",
"IIS-HttpCompressionStatic",
"IIS-ManagementConsole",
"IIS-ManagementService",
"IIS-WMICompatibility",
"IIS-LegacyScripts",
"IIS-LegacySnapIn",
"IIS-FTPServer",
"IIS-FTPSvc",
"IIS-FTPExtensibility",
"IIS-CertProvider",
"IIS-WindowsAuthentication",
"IIS-DigestAuthentication",
"IIS-ClientCertificateMappingAuthentication",
"IIS-IISCertificateMappingAuthentication",
"IIS-ODBCLogging",
"NetFx4-AdvSrvs",
"NetFx4Extended-ASPNET45",
"NetFx3",
"WAS-WindowsActivationService",
"WCF-HTTP-Activation",
"WCF-HTTP-Activation45",
"WCF-MSMQ-Activation45",
"WCF-NonHTTP-Activation",
"WCF-Pipe-Activation45",
"WCF-TCP-Activation45",
"WCF-TCP-PortSharing45",
"WCF-Services45",
};
ManagementObjectSearcher obj = new ManagementObjectSearcher("select * from Win32_OperatingSystem");
foreach (ManagementObject wmi in obj.Get())
{
string Name = wmi.GetPropertyValue("Caption").ToString();
Name = Regex.Replace(Name.ToString(), "[^A-Za-z0-9 ]", "");
if (Name.Contains("Server 2008 R2") || Name.Contains("Windows 7"))
{
featureNames.Add("IIS-ASPNET");
featureNames.Add("IIS-NetFxExtensibility");
featureNames.Add("WCF-HTTP-Activation");
featureNames.Add("WCF-MSMQ-Activation");
featureNames.Add("WCF-Pipe-Activation");
featureNames.Add("WCF-TCP-Activation");
featureNames.Add("WCF-TCP-Activation");
}
string Version = (string)wmi["Version"];
string Architecture = (string)wmi["OSArchitecture"];
}
foreach (var featureName in featureNames)
{
Run(string.Format("dism/online/Enable-Feature:{0}", featureName));
}
}
static void Run(string arguments)
{
try
{
string systemPath = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32");
var dism = new Process();
dism.StartInfo.WorkingDirectory = systemPath;
dism.StartInfo.Arguments = arguments;
dism.StartInfo.FileName = "dism.exe";
dism.StartInfo.Verb = "runas";
dism.StartInfo.UseShellExecute = true;
dism.StartInfo.RedirectStandardOutput = true;
dism.Start();
var result = dism.StandardOutput.ReadToEnd();
dism.WaitForExit();
}
catch (Exception ex)
{
}
}`
I tried to update the feature with dism.exe and cmd.exe, when it gave an authorization error, I used the Verb property
`
Since the use of .Verb = "RunAs" requires .UseShellExecute = true, and since the latter cannot be combined with RedirectStandardOutput = true, you cannot directly capture the elevated process' output in memory.
It seems that the system itself, by security-minded design, prevents a non-elevated process from directly capturing an elevated process' output.
The workaround is to launch the target executable (dism.exe, in your case) indirectly, via a shell, and then use the latter's redirection feature (>) to capture the target executable's output (invariably) in a file, as shown below.
string systemPath = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32");
// Create a temp. file to capture the elevated process' output in.
string tempOutFile = Path.GetTempFileName();
var dism = new Process();
dism.StartInfo.WorkingDirectory = systemPath;
// Use cmd.exe as the executable, and pass it a command line via /c
dism.StartInfo.FileName = "cmd.exe" ;
// Use a ">" redirection to capture the elevated process' output.
// Use "2> ..." to also capture *stderr* output.
// Append "2>&1" to capture *both* stdout and stderr in the file targeted with ">"
dism.StartInfo.Arguments =
String.Format(
"/c {0} {1} > \"{2}\"",
"dism.exe", arguments, tempOutFile
);
dism.StartInfo.Verb = "RunAs";
dism.StartInfo.UseShellExecute = true;
dism.Start();
dism.WaitForExit();
// Read the temp. file in which the output was captured...
var result = File.ReadAllText(tempOutFile);
// ... and delete it.
File.Delete(tempOutFile);
First, you can use WindowsPrincipal::IsInRole() to check if you're running elevated.
See Microsoft Learn for details.
Second, this may be one of those cases where using native PS is easier than the cmdlet approach (admittedly, still not great).
If the script is supposed to run on clients as well as server operating systems: use Get-WmiObject or Get-CimInstance to get a reference to what you're running on. ActiveDirectory also has that information (in operatingSystem attribute).
For servers use Get-WindowsFeature in ServerManager module.
For clients use Get-WindowsOptionalFeature with switch -Online in DISM module which, if you indeed need to support OSes older than 6.3.xxxx, can be copied over from a machine that has it and added to $Env:Path before C:\Windows and C:\Windows\System32.
For either platform just pass the list of features to configure.
If in a (binary) cmdlet you have to call external tools then the advantage of them is mostly gone. It may be possible to access Windows CBS using a managed API to avoid this but even then the script based approach gets more results faster, especially since you can just just put together a quick wrapper around dism.exe .

Updating a file fails before Process execution

I am starting a VBScript via C# application. Before starting, I programmatically change some content of the VBScript: Exchanging a placeholder keyword with a computer hostname.
The VBScript executes but uses the placeholder keyword instead of the computer hostname. However, when stopping the code right infront of Process.Start() I can see that the VBScript was successfully modified and saved, containing the computer hostname.
I also tried with adding a Thread.Sleep before execution, but it did not help.
It worked before, I updated some unrelated content on the program and now it fails to work for most of the time. When I use the debugger, sometimes the VBScript is loaded with the correct content. But the final program fails almost all the time.
Any suggesstions what this could be?
Here is the code of the C# process execution:
if (boEditStartupFileFirst)
{
string[] stFileContent = File.ReadAllLines(stFullPath);
for (int i = 0; i < stFileContent.Length; i++)
{
try
{
stFileContent[i] = stFileContent[i].Replace("NAME", stHostname);
}
catch { }
}
File.Delete(stFullPath);
StreamWriter swWriter = new StreamWriter(stFullPath, true);
for (int i = 0; i < stFileContent.Length; i++)
{
swWriter.WriteLine(stFileContent[i]);
}
swWriter.Close();
}
Process p = new Process();
p.StartInfo.FileName = stFullPath;
p.StartInfo.Arguments = stArgs;
p.Start();
Here is the content of the VBScript:
If Not WScript.Arguments.Named.Exists("elevate") Then
CreateObject("Shell.Application").ShellExecute WScript.FullName _
, WScript.ScriptFullName & " /elevate", "", "runas", 1
WScript.Quit
End If
Set WshShell = WScript.CreateObject("WScript.Shell")
WshShell.Run "regedit"
WScript.Sleep 500
WshShell.SendKeys "%DM"
WScript.Sleep 200
WshShell.SendKeys "NAME"
WScript.Sleep 200
WshShell.SendKeys "{ENTER}"
What the VBScript does: Open RegEdit and connect to remote computer.

Why can't I get the output of a C++ program using Process in C#?

I've got a C++ program which uses wprintf_s function to print the results into the command line. But when I uses Process in C# to read the output of the program, I cannot get any words of it. However when I added a fflush(stdout) after the wprintf_s statement, I can finally read the standard output in my C# program.
The code I use to start the progress is:
var proc = new Process {
StartInfo = new ProcessStartInfo {
FileName = "FILENAME",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
proc.Start();
StringCollection values = new StringCollection();
proc.OutputDataReceived += (s, args) => {
lock (values) {
values.Add(args.Data);
}
};
proc.BeginOutputReadLine();
proc.WaitForExit();
Can anybody tell me why a fflush(stdout) would work?
The output is being buffered in the C++ process and will remain so until the buffer is full or it is flushed, e.g. by calling fflush(), by closing the stream, or other OS dependant reasons.
fflush() just causes any data in the output buffer to be written to the stream.
If you don't want to explicitly call fflush(), you can consider setting unbuffered mode on the output stream by calling setbuf() with a NULL pointer for the second argument:
#include <stdio.h>
#include <unistd.h>
int main()
{
setbuf(stdout, (char *)NULL);
while (1)
{
fputs("hi there\n", stdout);
sleep(1);
}
}
Now the output will appear immediately.
Note that if stdout is a terminal, setbuf(f, NULL) is unnecessary as this is the default behaviour for terminal devices. If stdout is a pipe, then setbuf(f, NULL) will make it unbuffered.

Redirect standard output isn't flushed during process execution

I have a console application and I want to process the std out in a c# application.
Basically I already managed to do this with this code:
Process ProcessObj = new Process();
ProcessObj.StartInfo.WorkingDirectory = WorkingPath;
ProcessObj.StartInfo.FileName = ApplicationPath;
ProcessObj.StartInfo.Arguments = ApplicationArguments;
ProcessObj.StartInfo.UseShellExecute = false;
ProcessObj.StartInfo.CreateNoWindow = true;
ProcessObj.StartInfo.RedirectStandardOutput = true;
// Start the process
ProcessObj.Start();
// loop through until the job is done
bool stopper = false;
while (!stopper)
{
stopper = ProcessObj.WaitForExit(100);
string line = null;
// handle normal outputs (loop through the lines)
while (true)
{
line = ProcessObj.StandardOutput.ReadLine();
if (line == null)
break;
Logger.Trace("Out: \"" + line + "\"");
}
}
When the process runs only a few seconds it looks like the whole thing is working without any problem. When I change the configuration of the console application to calculate more, it comes that the process is running for hours. In this time my C# application gets no response from the console app. Since the console app is hidden it looks like the app stucked but that's not true. It is already running in the background and it seems that all std outputs are only piped through to my c# app when the console app was finished the execution.
So the problem is, I don't see the std out lines live in my c# app. It will be refreshed after hours when the console app has finished.
Is there any way to flush this std out redirection?
Anybody knows why this isn't working like I want?
PS: When I execute the console app as standalone in a normal cmd window the outputs are shown live without any problem.
Please help.
Try and read the output while the application is running? And save it to a buffer? Then process the output when the application exits.
pseudo stuff
string buffer = string.Empty;
while(!process.HasExited)
{
string line = process.Stream.ReadLine();
if (line != null)
buffer += Enviorment.Newline + line
}
// Do stuff without put here
Console.Write(buffer);

Execute a command line utility in ASP.NET

I need some advice regarding the use of a command line utility from a C#/ASP.NET web application.
I found a 3rd party utility for converting files to CSV format. The utility works perfectly and it can be used from the command line.
I have been looking on the web for examples on how to execute the command line utility and found this example.
The problem is this is not very good. When I try to us the example code with my utility, I get a prompt asking me to install the utility on the client machine. This is not what I want. I do not want the user to see what is going on in the background.
Is it possible to execute the command server side and processing the file from there?
Any help would be greatly appreciated.
I've done something like this several times in the past, and here's what's worked for me:
Create an IHttpHandler implementation (easiest to do as an .ashx file) to handle a convert. Within the handler, use System.Diagnostics.Process and ProcessStartInfo to run your command line utility. You should be able to redirect the standard output to the output stream of your HTTP response. Here's some code:
public class ConvertHandler : IHttpHandler
{
#region IHttpHandler Members
bool IHttpHandler.IsReusable
{
get { return false; }
}
void IHttpHandler.ProcessRequest(HttpContext context)
{
var jobID = Guid.NewGuid();
// retrieve the posted csv file
var csvFile = context.Request.Files["csv"];
// save the file to disk so the CMD line util can access it
var filePath = Path.Combine("csv", String.Format("{0:n}.csv", jobID));
csvFile.SaveAs(filePath);
var psi = new ProcessStartInfo("mycsvutil.exe", String.Format("-file {0}", filePath))
{
WorkingDirectory = Environment.CurrentDirectory,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
using (var process = new Process { StartInfo = psi })
{
// delegate for writing the process output to the response output
Action<Object, DataReceivedEventArgs> dataReceived = ((sender, e) =>
{
if (e.Data != null) // sometimes a random event is received with null data, not sure why - I prefer to leave it out
{
context.Response.Write(e.Data);
context.Response.Write(Environment.NewLine);
context.Response.Flush();
}
});
process.OutputDataReceived += new DataReceivedEventHandler(dataReceived);
process.ErrorDataReceived += new DataReceivedEventHandler(dataReceived);
// use text/plain so line breaks and any other whitespace formatting is preserved
context.Response.ContentType = "text/plain";
// start the process and start reading the standard and error outputs
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
// wait for the process to exit
process.WaitForExit();
// an exit code other than 0 generally means an error
if (process.ExitCode != 0)
{
context.Response.StatusCode = 500;
}
}
}
#endregion
}
The command is running server side. Any code is running on the server. The code in the example that you give works. You just need to make sure that the utility is set up properly on the server and that you have permissions to the directory/file.

Categories