I have the following code that I'm using to open cmd and run the SQL Server setup.exe with a configuration file.
ProcessStartInfo pStartInfo = new ProcessStartInfo();
pStartInfo.FileName = "cmd.exe";
pStartInfo.Arguments = "/c /q setup.exe /c /q /configurationFile=../configurationFile.ini";
pStartInfo.WorkingDirectory = installDir + #"\SQL Server Unpacked";
pStartInfo.CreateNoWindow = true;
pStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
pStartInfo.UseShellExecute = false;
pStartInfo.RedirectStandardInput = true;
pStartInfo.RedirectStandardOutput = true;
pStartInfo.RedirectStandardError = true;
Process p = new Process();
p.StartInfo = pStartInfo;
lb_SQLServerReadout.Text = "Please wait while the setup runs";
p.Start();
This is working alright. It'll attempt to install with the details of the configuration file, and if the configuration file contains invalid details then the setup.exe will give an error and not install.
I want to be able to get that error code and write it to a label on my form, so the user has some way of knowing that an error occurred and why it occurred. I considered using Process.StandardError.ReadToEnd, but it will always return null. I believe this is because the Process itself is cmd, not setup.exe (where the error actually occurs).
I've read that it's possible to get the last error code in cmd using echo %errorlabel%, and that does work when I run it manually, so I tried implementing it in my code:
p.StandardInput.WriteLine("echo %errorlevel%");
string s = p.StandardOutput.ReadLine();
p.WaitForExit();
I was hoping that s would be set to the output of the echo %errorlevel% input, but it is just set to null.
How can I get the error code of SQL Server setup, through my cmd Process?
Just as a side note, I've considered running setup.exe as the Process, but it requires elevation, which means enabling UseShellExecute, which in turn means disabling RedirectStandardError, which would prevent me getting the error code anyway.
That is all not necessary, the exit code of the "executable" invoked with cmd.exe /c will be the exit code of cmd.exe itself.
You can try this on the command line as well:
c:\> cmd.exe /c app.exe make-it-fail
c:\> echo %errorlevel%
The second line will print the exit code of "app.exe make-it-fail".
So, just read the value of the Process.ExitCode property, after Process.WaitForExit().
What particular values setup.exe (of SQL Server) sets in case it fails, I don't know. Should be something different than 0 though (by convention).
Related
I have 4 independent servers (not in domain):
IIS, SQL1, SQL2, SQL3
I want to copy a database backup from SQL1 to SQL2 or SQL3 (depending on parameters) by button click on webpage hosted on IIS
I wrote a button click method for that, which is calling batch file located in inetpub folder on IIS
Batch is using pstools to run robocopy on SQL1 which should copy required file to destination server (SQL2 or SQL3)
This solution works if I execute batch directly on IIS (cmd as Administrator) or when I debug it on my local machine, but it doesn't if it is called from the running site.
It even doesn't spend any time between the following lines:
batchProcess.Start();
batchProcess.WaitForExit();
Here is my copy method:
private bool ProcessCopy(string file, string destinationIp)
{
SecureString password = ConvertToSecureString("myPassword");
try
{
string batchPath = Server.MapPath(".") + "\\CopyFile.bat";
string cmd = #"c:\Windows\System32\cmd.exe";
ProcessStartInfo processInfo = new ProcessStartInfo
{
FileName = cmd,
UseShellExecute = false
};
Process batchProcess = new Process {StartInfo = processInfo};
batchProcess.StartInfo.Arguments = $"/C {batchPath} {file} {destinationIp}";
batchProcess.StartInfo.Domain = "";
batchProcess.StartInfo.UserName = "Administrator";
batchProcess.StartInfo.Password = password;
batchProcess.StartInfo.RedirectStandardOutput = true;
batchProcess.StartInfo.RedirectStandardError = true;
batchProcess.StartInfo.CreateNoWindow = true;
batchProcess.Start();
batchProcess.WaitForExit();
string response = batchProcess.StandardOutput.ReadToEnd();
response += batchProcess.StandardError.ReadToEnd();
statusStringAppend($"response: {response}");
return true;
}
catch (Exception ex)
{
statusStringAppend($"Failed: {ex.Message}. {ex.StackTrace}");
}
return false;
}
Batch body is:
#echo off
c:\qa\tools\pstools\psexec64.exe -accepteula -u Administrator -p myPassword \\SourceIP robocopy \\SourceIP\qa\db_backup\ \\%2\qa\db_backup\ %1 /is
My questions are:
1. Why the file was not copied?
2. Is there any better way to get it copied?
CODE UPDATED ACCORDING TO SUGGESTIONS BELOW
My guess is that you never executed pstools as the user that your IIS service is running as before, so the EULA dialog is blocking your execution.
If you remember, you always got a window and needed to press the accept button when running any sysinternals tool like pstools the first time.
I guess this should work for you:
c:\qa\tools\pstools\psexec64.exe -accepteula -u Administrator -p myPassword \\SourceIP robocopy \\SourceIP\qa\db_backup\ \\%2\qa\db_backup\ %1 /is
[EDIT]
You would most likely have hit this problem later on, anyway it did not work for you, so i have to list what else could be wrong with your code:
starting a .bat file needs cmd.exe as mother process, you cannot just start a .bat file as process directly. Instead you can for example use another method than ProcessStartInfo that spawns the system default script interpreter automatically: Executing Batch File in C#
the process for executing batch files is "cmd.exe", first parameter "/C", second parameter the batch file you are executing
when executing typical commandline tools, you might consider reading the SDTOUT (standard output) of the process you are executing, like this: Capturing console output from a .NET application (C#)
When launching an application directly, the application is launched, but when launched through cmd - it's not.
For example:
Works:
Process.Start("firefox");
Doesn't work:
Process.Start(
new ProcessStartInfo
{
FileName = "cmd",
Arguments = "/k firefox"
});
I've tried setting UseShellExecute to true, but to no avail. I still get:
'firefox' is not recognized as an internal or external command,
operable program or batch file.
So, yes, I can specify the complete path. But is there a way to avoid that? Or in other words - what's the difference between the two that makes the second fail?
Haven't tested it but I guess you are probably looking for the start command:
Process.Start(
new ProcessStartInfo
{
FileName = "cmd",
Arguments = "/k start firefox"
});
As a tip, simply run "firefox" in a command prompt -> you'd get the same error message.
I wan to add some DNS records in c# program via a bat file so I have written these lines in bat file:
set servername=%1
set siteaddress=%2
"C:\Windows\System32\dnscmd.exe" %servername% /zoneadd %siteaddress% /primary /file %siteaddress%.dns
and I have written these lines in C#:
Process p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.WorkingDirectory = Application.StartupPath;
p.StartInfo.FileName = General.DnsBatPath;
p.StartInfo.Arguments = string.Format("{0} {1}", General.DnsServerName, txtSiteAddress.Text);
p.Start();
p.WaitForExit();
I get this error "dnscmd.exe is not recognized as internal or external command..." but when I run bat file manually (outside of C#) all things are OK.
I changed my C# code to check what happened
Process.Start(#"C:\Windows\System32\dnscmd.exe");
I still get "not recognized ..." error.but I can see dnscmd.exe in "C:\Windows\System32".
I changed my C# code again to check another thing:
Process.Start(#"C:\Windows\System32\cmd.exe");
and after that CMD windows will be opened???
any idea?
In answer to your second question, you can always check the environmental variable PROCESSOR_ARCHITECTURE to see if it contains the number 64.
set servername=%1
set siteaddress=%2
if "%PROCESSOR_ARCHITECTURE%" equ "%PROCESSOR_ARCHITECTURE:64=%" (
REM 32bit
"C:\Windows\System32\dnscmd.exe" %servername% /zoneadd %siteaddress% /primary /file %siteaddress%.dns
) else (
REM 64bit
"%windir%\Sysnative\dnscmd.exe" %servername% /zoneadd %siteaddress% /primary /file %siteaddress%.dns
)
Possibly a more reliable method is to get it from the registry:
set servername=%1
set siteaddress=%2
for /f "tokens=3" %%x in ('reg Query HKLM\Hardware\Description\System\CentralProcessor\0 /v Identifier') do (
set arch=%%x
)
if %!arch:~-2!%==64 (
set dnsPath=%windir%\Sysnative
) else (
SET dnsPath=C:\Windows\System32
)
"%dnsPath%\dnscmd.exe" %servername% /zoneadd %siteaddress% /primary /file %siteaddress%.dns
In my case I created a console application to run a batch file with some java command but was getting 'java' not recognized as an internal command error.
Took me few hours but the solution is straight forward. My server had JVM running on 64 bit so I changed the platform target to 64 bit.
x86 is 32 bit and x64 is 64 bit. Project properties are below:
Try this code:
ProcessInfo psi= new ProcessStartInfo("cmd.exe", string.Format("/c {0} {1} {2}", General.DnsBatPath, General.DnsServerName, txtSiteAddress.Text);
psi.UseShellExecute = false;
process = Process.Start(psi);
process.WaitForExit();
The /c parameter says that the given command should run and then cmd.exe should exit. For details, run cmd /? in windows console.
You can also try what happens when you set ShellExecute to true. Then the process should start the same way like a file is double-clicked in explorer. The disadvantage of shell execution is that if the user changed the .BAT file default application to (for example ) notepad, the file will not be executed but opened in notepad.
If you also want to redirect the console output, look here: Executing Batch File in C#
I want to run git commands from c#. below is the coded I had written and it does execute the git command but I am not able to capture the return value. When I manually run it from command line this is the output I get.
When I run from the program the only thing I get is
Cloning into 'testrep'...
Rest of the info is not capture, but the command is executed successfully.
class Program
{
static void Main(string[] args)
{
ProcessStartInfo startInfo = new ProcessStartInfo("git.exe");
startInfo.UseShellExecute = false;
startInfo.WorkingDirectory = #"D:\testrep";
startInfo.RedirectStandardInput = true;
startInfo.RedirectStandardOutput = true;
startInfo.Arguments = "clone http://tk1:tk1#localhost/testrep.git";
Process process = new Process();
process.StartInfo = startInfo;
process.Start();
List<string> output = new List<string>();
string lineVal = process.StandardOutput.ReadLine();
while (lineVal != null)
{
output.Add(lineVal);
lineVal = process.StandardOutput.ReadLine();
}
int val = output.Count();
process.WaitForExit();
}
}
From the manual page for git clone:
--progress
Progress status is reported on the standard error stream by default when it is attached to
a terminal, unless -q is specified. This flag forces progress status even if the standard
error stream is not directed to a terminal.
The last three lines in the output when running git clone interactively are sent to standard error, not standard output. They won't show up there when you run the command from your program, however, since it's not an interactive terminal. You could force them to appear, but the output isn't going to be anything usable for a program to parse (lots of \rs to update the progress values).
You are better off not parsing the string output at all, but looking at the integer return value of git clone. If it's nonzero, you had an error (and there will probably be something in standard error that you can show to your user).
Have you tried libgit2sharp? The documentation is not complete, but it is pretty easy to use and there's a nuget package for it. You can always look at the test code to see about usage as well. A simple clone would be like this:
string URL = "http://tk1:tk1#localhost/testrep.git";
string PATH = #"D:\testrep";
Repository.Clone(URL, PATH);
Fetching changes is easy as well:
using (Repository r = new Repository(PATH))
{
Remote remote = r.Network.Remotes["origin"];
r.Network.Fetch(remote, new FetchOptions());
}
Once you call process.WaitForExit() and the process has terminated, you can simply use process.ExitCode which will get you the value that you want.
Your code Looks OK.
this is git problem.
git clone git://git.savannah.gnu.org/wget.git 2> stderr.txt 1> stdout.txt
stderr.txt is empty
stdout.txt:
Cloning into 'wget'...
It looks like git not uses standard console.write() like output you can see it when it writes percentage it's all in one line not like:
10%
25%
60%
100%
process.StandardError.ReadToEnd() + "\n" + process.StandardOutput.ReadToEnd();
I'm trying to run a fortran executable with Process.Start and it is not working.
Process proc = new Process();
string args = "<C:\\file.in> C:\\file.out";
proc.StartInfo = new ProcessStartInfo(AppName, args);
proc.Start();
if I paste those arguments into a command window the application runs as expected. proc.Start() does not run as expected.
Any ideas how I can view what Start is actually passing as arguments? My gut feeling is that this is a quotes issue.
The executable launches and hangs, so I'm confident the AppName is getting passed in correctly, it looks like an argument problem.
I tried setting the WorkingDirectory to that of the input and output files as suggested in this question: process.start() arguments but that did not work.
Redirection with the < and > command line operators is a feature that's implemented by the command line processor. Which is cmd.exe. Use its /c argument to execute just a single command:
string args = "/c " + AppName + " < C:\\file.in > C:\\file.out";
proc.StartInfo = new ProcessStartInfo("cmd.exe", args);
proc.Start();
Your args string is exactly what is being passed as arguments to the executable. You can double check it reading your Process ProcessStartInfo.Arguments Property.
Something similar happened to me once, i.e., calling the executable from the command line worked and from code didn't, and it turned out that when called from the command line the executable was running on my PC's [C:] drive, and when called from code it was running on my PC's [E:] drive, which was full!
To check which directory your application is using to run the executable use the Directory.GetCurrentDirectory Method.