<September 2014>
SunMonTueWedThuFriSat
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

On this page...

Search

Links

Member of...


ASP Insiders

MVP Visual Developer ASP/ASP.NET

Enter CodeZone

Blog Categories

Microsoft

Blogroll

Deutsche Resourcen

Management

Sign In
 

#  Sunday, February 04, 2007

In the blog post UAC Elevation in Managed Code: Starting Elevated Processes I talked about how to start an elevated process. However, just starting a process might not cut the mustard, for example if you need to hand over data to the elevated process. You could achieve this by passing, let's say, some data as command line arguments to ProcessInfo before starting the elevated process. But that seriously limits communication.

So how can you perform communication with an elevated process? My first idea was to use .NET Remoting. Once I thought through the multi-instance scenario, I quickly realized that this meant the server had to be running in the non-elevated application, because only it could properly choose a port. And because I am not a fan of Remoting anyways, I decided to give WCF (Windows Communication Foundation, a pillar of .NET 3.0) a try.

It looked like smooth sailing at first, but then I realized that with WCF too I needed to implement the service inside the non-elevated application. This time, however, the reason was "How do I know when the elevated application has initialized before I can actually start communicating with it?". Back to the drawing board.

The final solution now looks like this: the non-elevated application starts a service. The operations contract specifies a callback, which, once the elevated application has signalled its readiness, can be used by the non-elevated application to "talk" with the elevated application. I didn't intend to go duplex, but hey, if there's no other way I am willing to take plunge. Speaking of tricks of the trade: I am using imperative binding to a named pipe. Reason? Well, WS bindings won't work (see here and here), and the TCP channel would pop up a firewall warning. That's why.

Let's look at the applications - first the non-elevated one:

This time I forfeited eye candy (the shield button). Same (missing eye candy) goes for the elevated application as it is a console application only:

Solution-wise, this simple two-application scenario is split into four projects:

So where do we start? With the easy part inside ElevationContract:

[ServiceContract(Namespace = "http://Christoph.Wille.Samples",
CallbackContract = typeof(IElevatedProcess))]
public interface IWaitForElevatedProcess
{
  [OperationContract(IsOneWay = false)]
  void ElevatedProcessStarted();
}

[ServiceContract(Namespace = "http://Christoph.Wille.Samples")]
public interface IElevatedProcess
{
  [OperationContract(IsOneWay = false)]
  void SayHello(string message);
}

The interface IWaitForElevatedProcess is implemented in StandardUserApp. It is the service endpoint that is initialized before the elevated process is started - and once the elevated application is up and running, it calls into ElevatedProcessStarted. And we are in business for using the IElevatedProcess callback that is implemented in the ElevatedProcess console application.

So how is the service endpoint intialized - let's take a look inside:

private const string theProcess = @"..\..\..\ElevatedProcess\bin\Debug\ElevatedProcess.exe";

private void tryitButton_Click(object sender, EventArgs e)
{
  string channelIdentifier = MiscHelpers.CreateRandomString(64);
  MyUACServiceHost.StartService(channelIdentifier);

  // starting it modal doesn't work (obviously - unless we have more threads, of course)
  ElevatedProcess.Start(theProcess, channelIdentifier);
}

Interesting tidbit #1 is CreateRandomString: it creates a random string to use for the address. Why? Well, if multiple instances of our application are running and trying to elevate a process, we are in trouble. Which brings me to StartService:

internal static void StartService(string pipeEndPoint)
{
  NetNamedPipeBinding binding = new NetNamedPipeBinding();
  binding.Name = "uacbinding";
  binding.Security.Mode = NetNamedPipeSecurityMode.Transport;

  Uri baseAddress = new Uri("net.pipe://localhost/uac/" + pipeEndPoint);

  myServiceHost = new ServiceHost(typeof(SampleService), baseAddress);
  myServiceHost.AddServiceEndpoint(typeof(IWaitForElevatedProcess), binding, baseAddress);
  myServiceHost.Open();
}

As I said before, I am doing it imperatively (no configuration in app.config necessary). That's all there is to getting the service up and running.

Now let's switch to the console application's Main method:

static void Main(string[] args)
{
  if (args.Length != 1)
  {
    Console.WriteLine("One argument expected - the channel identifier");
    return;
  }

  NetNamedPipeBinding binding = new NetNamedPipeBinding();
  binding.Name = "uacbinding";
  binding.Security.Mode = NetNamedPipeSecurityMode.Transport;

  String url = "net.pipe://localhost/uac/" + args[0];
  EndpointAddress address = new EndpointAddress(url);

  WaitForElevatedProcess client = new WaitForElevatedProcess(
      new InstanceContext(new SampleCallback()),
      binding,
      address);

  client.ElevatedProcessStarted();

  Console.WriteLine("The elevated process is now ready");
  Console.ReadLine();

  client.Close();
}

Similar to normal client WCF code, however, with the duplex twist hidden inside WaitForElevatedProcess:

public class WaitForElevatedProcess : DuplexClientBase<IWaitForElevatedProcess>, IWaitForElevatedProcess
{
  public WaitForElevatedProcess(System.ServiceModel.InstanceContext callbackInstance,
 
    System.ServiceModel.Channels.Binding binding,
    System.ServiceModel.EndpointAddress remoteAddress)
       : base(callbackInstance, binding, remoteAddress)
  {
  }

  public void ElevatedProcessStarted()
  {
    base.Channel.ElevatedProcessStarted();
  }
}

Once the channel is connected, this elevated process calls back into the service piece which lives in the non-elevated application, namely SampleService:

[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant,
      InstanceContextMode = InstanceContextMode.PerSession)]
public class SampleService : IWaitForElevatedProcess
{
  public void ElevatedProcessStarted()
  {
    OperationContext.Current.GetCallbackChannel<IElevatedProcess>().SayHello("Chris");
  }
}

This method is the workhorse where I can talk to the elevated process - if only my callback interface had more as well as more serious methods ;-)

Speaking of talking, I owe you the code for the callee in the console application:

[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)]
public class SampleCallback : IElevatedProcess
{
  public void SayHello(string message)
  {
    Console.WriteLine("Hello world " + message);
  }
}

That's it - to recap: first, we initialize the WCF service. Then elevate a process. This process, once initialized, calls into our service and leaves a callback. And then we are in business talking to the elevated process (setting data, being notified when the elevated application quits and why, ...).

Sample warnings before you download: MyUACServiceHost definitely should be instance instead of static. And, more restricting - starting the elevated process modal won't allow communication unless you start the service on a separate thread. For simplicity reasons I didn't do this for the POC.

ElevateProcessTalkWCF.zip (27 KB)

Before concluding I wanted to add a few words: my ideal implementation for UAC would be COM elevation. That way, one can put more than one component into a single DLL, and still get a meaningful UAC prompt thanks to the LocalizedString registry key - which is per component, and not per executable (which is the case for this solution if you add multiple actions). If you need differing prompts for each administrative action, there is only one course of action you can take with processes: create multiple executables. Not very pretty, but I failed with writing an elevatable (not a word, I am sure) managed (C#) COM component.

Categories: .NET | 3.0 | Security | UAC | Vista | WCF
Sunday, February 04, 2007 10:23:45 PM (W. Europe Standard Time, UTC+01:00)  #    Comments [3]

 



#  Sunday, November 12, 2006

This Q&A item is part of the current MSDN magazine's Security Brief's column by Keith Brown. I am pretty sure that this problem will rear its head sooner or later on every developers machine, that's why I am 'pinning' the link in my blog for my own reference too.

Categories: .NET | IIS | Security | 3.0 | WCF
Sunday, November 12, 2006 4:41:26 PM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]

 



© Copyright 2014 Christoph Wille

newtelligence dasBlog 2.3.9074.18820
Subscribe to this weblog's RSS feed with SharpReader, Radio Userland, NewsGator or any other aggregator listening on port 5335 by clicking this button.   RSS 2.0|Atom 1.0  Send mail to the author(s)

 
Don't contact us via this (fleischfalle@alphasierrapapa.com) email address.