So the Scenario looks like this:
What to do? The Client Processes must operate in the context of a user that is local to the client, and has access to the system level operations on the client machine. By default, the ASPNET user does not have these permissions.
If you are working with any IIS version 6 or later, the solution would be to use impersonation, and you would be correct. But in this time when enterprises like to hang on to their systems, and endure the slow torturous death of an operating system, we are working with IIS 5, where impersonation is not so easy to accomplish.
There are a couple of solutions to this connundrum, neither of which presented themselves at first glance (or at least they didn't to me)
- Use the Message Queue to communicate to a central processing module, placing messages on the queue with the web service, and removing them with the command module.
- Allow both User spaces to remain separate, by using the FileSystemWatcher object of the .Net framework.
After watching the remote invocation of the web service fail to gain access to some proesses that needed to be stopped as part of the functionality required, it became clear that permissions issue was the source of not only my troubles, but some that had come before me.
The previous solution used the Messaging Queue to communicate with the system. While it is a solution that deals with the issue at hand (communication is a limited permissions environment), the process was rather cumbersome.
The previous solution used the Messaging Queue to communicate with the system. While it is a solution that deals with the issue at hand (communication is a limited permissions environment), the process was rather cumbersome.
- Create and serialize a command object with several properties for transmission across the wire
- Receive the object on the remote machine , and place it on the message queue
- With the command module on the remote machine, remove the object from the queue, and de-serialize the command object to determine what process to run.
- Execute the process on the remote machine with the command module.
The code that accomplishes all of this is rather convoluted, so I'm not going to even attempt to post it. It took me two months to begin to understand what was happening when, and there are still some things I'm not clear on.
However, the solution that I chose is radically simpler, and still accomplishes the same thing. The command module running on the remote machine is still there, although there is a bit more functionality, and a lot less complexity.
The new process goes like this:
- From a web service on the server, make a call to a web service on the client with the name of the command that you want to run.
- On the remote machine: Build a web service that will receive the command (a string, mind you), and then run the necessary function to communicate to the command module with a file in a very specific place.
- Use the command module to perform the required functions, based on what the dropped file says to do.
The scenario itself is not a common one. Normally Web Services interact directly with a database of some sort, and then throw data back. Here we are talking about interaction with a remote host, and not trying to interfere with anything else.
With the web services on the remote machine, it was pretty simple (code is in C#):
string dropDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,@"Reclaim\");
string dropFile = string.Format(dropDir + "KillProcess.txt",a.Name);
if (!Directory.Exists(dropDir))
Directory.CreateDirectory(dropDir);
StringBuilder bldr=new StringBuilder();
if(File.Exists(dropFile))
bldr.AppendLine(File.ReadAllText(dropFile));
bldr.AppendLine(a.Name);
File.WriteAllText(dropFile, bldr.ToString());
And in the command application that is running under the required permission set, you have this in one method of the application
string dropDir = @"C:\inetpub\wwwroot\<Application Path>\Reclaim\";
if (!Directory.Exists(dropDir))
Directory.CreateDirectory(dropDir);
FileSystemWatcher reclaimer = new FileSystemWatcher(dropDir);
reclaimer.EnableRaisingEvents = true;
reclaimer.NotifyFilter = NotifyFilters.LastWrite;
reclaimer.Changed += new FileSystemEventHandler(reclaimer_Changed);
and in the event handler for the FileSystemWatcher
if (File.Exists(dropFile))
{
MethodCall();
string[] procNames = File.ReadAllLines(dropFile);
if (procNames.Length >= 3)
{
foreach (string str in procNames)
{
App a = AppList.Where(ap => ap.Name == str).FirstOrDefault();
if (a != null)
{
MethodCall(a);
System.Threading.Thread.Sleep(3000);
Ano(a);
}
}
File.Delete(@"C:\Inetpub\wwwroot\<Application Path>\Reclaim\KillProcess.txt");
}
}
And that's it! Simple call to a web service on the remote machine, and an application running on the remote machine and waiting for changes in the file to act on