Introduction
My original goal was to run a process from the command line that was only installed on a remote server. This process is an exe that does not run under .NET managed code.
I found The Code Project article “Push and Run .NET Code on Remote Machine” by Jim Wiese (a.k.a. Spunk) was the easiest way
to do this: Push and Run .NET.
All I had to do was ensure that the same domain account (that I was using to log in at the client) was added to the Administrators group under “Local Users and
Groups” on the remote server. No other setup was required on the remote server.
The minimum requirements are Windows XP Pro, Server 2003, or a newer version of Windows with Pro or better (.NET 2.0 or newer is also required).
The only thing I had to add was a .NET executable called “Process” to run existing unmanaged apps on the remote server. The whole reason for doing this is that I
needed to set up a batch file that was able to run some exe command line apps on the client, but one of these was only installed on a remote server. It
impacted the database tables that the client used, but you could not run the very useful command line utility on the client.
So, this article is about creating a .NET add-on utility for Jim Wiese’s RunRemote.exe.
As a side note, you will learn some techniques toward mastering the .NET Process
class.
Getting Started With Examples
Figure 1
Figure 1 shows an example of running RunRemote.exe on a network system named "CO007-PL-0025".
Process.exe is copied to CO007-PL-0025 and then executed using the service that was started remotely by RunRemote.exe.
Process.exe takes the arguments on the command line after it, where the first argument is the name of the exe to run, and the rest of the arguments are parameters that
apply to the exe. So in this case, Process runs "cmd.exe" with parameters:
/C – to run specified command and then terminate
dir – the cmd shell command to display a list of files
"C:\*.*" – [drive:][path][filename]
Any argument with spaces must have quotes, otherwise quotes are optional.
The normal dir output is shown and the last two lines are output by Process.exe to show the return value of the exe it started where, in the command line environment,
0 indicates OK, and errors are usually flagged with a return value of 1. And lastly, the "Completed:" status to make it easy to track what
happened if you are redirecting the output from a big batch file to a log file.
Figure 2 shows another example of running a Microsoft utility called "srvcheck.exe". This command is installed from a Windows Resource Kit
Tools bundle you can download off the Microsoft website. I only installed it on the remote server. When I installed it on my server, the installer automatically
added the path to the System Path environment variable.
SrvCheck.exe happens to take one required parameter: the computer name with the UNC "\\" preceding it. In my test environment, the only computer name that
works is the name of the local system that SrvCheck.exe is running on.
Figure 2
Note that cmd /C is not required here because srvcheck.exe is not part of the Windows cmd shell.
The output from srvcheck.exe is the list of shared folders with accounts and privileges on my home networking workgroup. (Quite
useful for examining those tricky shares between XP Home and Windows 7…)
The Proccess.exe Console Application
Now I will explain Process.exe in detail. Process.cs has very few lines of code, but it is also very powerful when used with RunRemote.exe.
I only reference the System.dll (version 2.0) and you need to use these four namespaces:
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
This is rest of the source listing:
namespace ProcessCmd
{
class Program
{
static void Main(string[] args)
{
if (args.Length < 1)
{
Console.WriteLine("Purpose: Use with RunRemote to shell execute any remote exe");
Console.WriteLine(@" Usaage: RunRemote <\\server> Process <remote.exe> [arg1 arg2 ...]");
System.Environment.ExitCode = 1;
return;
}
Process p = new Process();
p.StartInfo.FileName = args[0];
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.CreateNoWindow = false;
int nArgs = args.Length;
if (nArgs > 1) p.StartInfo.Arguments = args[1];
for (int i = 2; i < nArgs; ++i)
{
p.StartInfo.Arguments = String.Concat(p.StartInfo.Arguments," ", args[i]);
}
p.Start();
StreamReader so = p.StandardOutput;
while (!p.HasExited)
{
Thread.Sleep(100);
if (!so.EndOfStream)
{
Console.WriteLine(so.ReadLine());
}
}
Console.WriteLine(p.StandardOutput.ReadToEnd());
Console.Write("Return value = ");
Console.WriteLine(p.ExitCode);
Console.Write("\n");
if (!p.StandardError.EndOfStream)
{
Console.WriteLine("StdError: " + p.StandardError.ReadToEnd());
Console.Write("\n");
}
if (p.ExitCode == 0)
Console.WriteLine(String.Concat("Completed: ",
p.StartInfo.FileName, " ", p.StartInfo.Arguments));
Console.Write("\n");
System.Environment.ExitCode = p.ExitCode;
}
}
}
The first part checks that command line parameters meet requirements. If not, exit error code is set and it returns.
The next section sets up the System.Diagnostics.Process
class with the ProcessStartInfo
properties.
FileName
– Sets the application to start (found in arg[0]
). Must be an exe since UseShellExecute
is false
.UseShellExecute
– This is set to false
to enable redirect of standard output and error streams.RedirectStandardOutput
– Allows redirection of standard output through the Process
class StandardOutput
stream.RedirectStandardError
– Same for a standard error stream.CreateNoWindow
– false
is the default. Needed to allow Ctrl+c for killing the process.- Arguments – The remaining command line arguments are appended to this property.
Then the process pointed to by FileName
is started. The system Path environment variable will be used to find the exe if needed.
While the process is running, lines are read from standard output and written out using Console.WriteLine()
. Thread.Sleep(100)
prevents a "busy
waiting" loop state.
The reason Process handles the exe output this way is so the output will get sent back to RunRemote on the client system.
Once the started process exits, any data in standard error is output and the final result state messages are written.
About Push and Run .NET Code on Remote Machine
As you have probably figured out, this project relies heavily on Jim Wiese's project that can be found here: Push and Run .NET.
This is one of the few projects that I have not had to make any modifications to the source code in order for it to be useful for me.
If you want the source, you can get it from the "Push and Run…" article.
Software Firewalls
If your computers are not on a domain or you have a software firewall on each machine, it can be very difficult to get this to
work. You have to open up the DCOM port on TCP 135 among other things.
Here are links to a couple of articles about enabling your firewall settings in order to start the remote service:
While writing this article, I was able to get it to work between two Windows 7 (Ultimate/Pro) systems on a standard Workgroup networking
setup. You need to have a user with administrator rights on each system with the same username and password.
It was very easy to get it to work on servers using domain user accounts. Of course, you need access to an account that has local
administrator rights on the servers and both systems will use the same account by default.
I didn't test on a home network with XP Pro, but I think it will work. I don't think XP Home will work. The admin$ share was blocked when I tried it.
A Note About the Project
The reason I built the project with .NET 2.0 is to make sure it would work on most Windows Server 2003 systems that have not been
upgraded. Usually, in a production environment, one just can't upgrade a server and restart it. Upgrades have to be scheduled and tested to ensure other processes still work.
I work with a Windows Server 2003 system that has no .NET installed at all. In this case, the main task I want to run remotely has
to be run on another server. But fortunately, the command-line utility can redirect internally to the third server that has no .NET installed.