Introduction
When I Google how to run a program on a remote machine, inevitably someone says to use either PsExec or WMI. The only problem is.... well, there are a number of problems:
- PsExec is often flagged by anti-virus software as dangerous
- Sys Admins do not want a tool like PsExec on servers
- The license is often in the remote computer registry or linked to the computer name so even with UNC or a mapped drive, you end up with the demo version... or it doesn't work at all
- In a web application, the web server needs the app installed locally or a mapped drive. Also not popular with Sys Admins and Webmasters
Not that PsExec and WMI are bad solutions; just sometimes, because of policy, security, licensing, or cost, you need to execute the remote program using C# and only C#.
The solution is to create a WCF service that calls the Process class, runs the remote program, and then the service returns any additional information you need.
Background
This article has two parts - the service and using the service. We use an example of a software installed on a remote computer. This example installs the web service as a Windows Service even though it could easily - and probably more easily - be set up as a web service in IIS. Even though it runs as a Windows Service, it is available as a URL endpoint.
In the example, we run StatTransfer
from a service. StatTransfer
is a software used to convert statistical data like R, SAS, SPSS, and Stata to other statistical data types, or to ODBC sources like SQL Server. Therein lies the real use case - no libraries exist and there is no way that you can parse this type of data, unless you write the equivalent of the commercial software.
The service is written with .NET 4.5. The web application is .NET 4.0. I am using Visual Studio 2012 in the example.
Using the Code
The Service
There is no magic here. Most of the code comes directly from this MSDN article. But it does help to have a working example. The following will walk you through it step-by-step:
- First, create a new C# Console Application (not a WCF Application) called
MyDataServices
. - In Program.cs, you will need to add namespaces for
ServiceModel
, ServiceProcess
, Diagnostics
, IO
, and Configuration
(see download). You basically remove everything (class Program
and static void Main(string[] args
). Then you set up the interface, service, methods, and the path to your executable and any arguments. This is not all that straight-forward so the following is a bit of a code dump to show you how it is done:
IMPORTANT: The service will run on the same computer where the local executable is installed.
[ServiceContract(Namespace = "http://MyDataServices.foo.com")]
public interface IDataService
{
[OperationContract]
bool ProcessStatTransfer(MemoryStream inputCommands, string inputName);
}
public class DataService : IDataService
{
public bool ProcessStatTransfer(MemoryStream inputCommands, string inputName)
{
try
{
string m_stattransfer_loc = "C:\\Program Files\\StatTransfer\\StatTransfer12-64\\st.exe"
string m_stattransfer_file = "C:\\MyFolder\\" + inputName;
using (FileStream m_fsfile = new FileStream(m_stattransfer_file, FileMode.Create, FileAccess.Write))
{
inputCommands.WriteTo(m_fsfile);
}
ProcessStartInfo processInfo = new ProcessStartInfo("\"" + m_stattransfer_loc + "\"");
processInfo.Arguments = "\"" + m_stattransfer_file + "\"";
processInfo.UseShellExecute = false;
processInfo.ErrorDialog = false;
processInfo.CreateNoWindow = true;
Process batchProcess = new Process();
batchProcess.StartInfo = processInfo;
batchProcess.Start();
return true;
}
catch
{
return false;
}
}
}
The service is almost ready to be called remotely, but in this example we are setting it up as a Windows Service, so you also want to add the OnStart, OnStop and code for the ProjectInstaller
class which allows the service to be installed by the Installutil.exe tool . Again, see the download for the full example. See this MSDN link for how to install the service using Installutil.exe.
So after all this, you should have a WCF service, running as a Windows Service, that you can access with a URL that looks something like this:
http:
Using the Service
The first thing to do is create a reference to the service endpoint. Go to References; Add Service Reference and put in the URL above. Note that you may need to open port 8080 if you follow this example.
Once you have the reference, add a "using
" statement...
using MyDataServiceReference;
...then add a "client." Then, you are ready to call the remote program via the WCF service. The whole thing looks like this (service call in bold):
MyDataServiceReference.DataServiceClient m_client = new DataServiceClient();
using (MemoryStream m_ms_stcmd = new MemoryStream())
{
StreamWriter m_sw = new StreamWriter(m_ms_stcmd);
m_sw.Write("DBW table " + m_filename_noex);
m_sw.WriteLine();
m_sw.Write("copy \"" + m_fullpath + "\" odbc");
m_sw.WriteLine();
m_sw.Write("quit");
m_sw.Flush();
m_ms_stcmd.Position = 0;
bool m_svcresult = m_client.ProcessStatTransfer(m_ms_stcmd, m_filename_noex + ".stcmd");
}
That is pretty much it. This example is simplified in terms of error handling, ConfigurationManager
entries, security, etc. The main point is not to connect directly to the remote computer, but to create a service wrapper that runs on the remote computer and then you call the service.
History
-
1st April, 2014: Initial version