<2007 February>
SunMonTueWedThuFriSat
28293031123
45678910
11121314151617
18192021222324
25262728123
45678910

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, 04 February 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, 04 February 2007 22:23:45 (W. Europe Standard Time, UTC+01:00)  #    Comments [3]

 

Categories: Cool Download | Vista
Sunday, 04 February 2007 11:43:49 (W. Europe Standard Time, UTC+01:00)  #    Comments [0]

 



#  Tuesday, 30 January 2007

The previous installment UAC Elevation in Managed Code: Starting Elevated Processes dealt with starting executables with the "real" administrative token. In this blog post, we deal with starting a COM component with elevated privileges. For in-depth background information, please consult Kenny Kerr's absolutely excellent post on Windows Vista for Developers – Part 4 – User Account Control.

To start with, we need a COM component. Instead of writing an ATL C++ COM component from scratch, I took the MyElevateCom sample from CoCreateInstanceAsAdmin or CreateElevatedComObject sample from the Vista Compatibility Team Blog. Note that for building it, check out my post Visual Studio on Vista: Not so Fast!

Assuming that you built and successfully registered the COM component (it is built to the instuctions from Kenny's post), you can go about and write the managed caller. First, we need a reference to the component:

Then comes the tricky part - actually instantiating the COM component. When you take a look at the C++ example, you see that quite some "moniker magic" is involved that cannot be replicated by simply newing up the component. So how to mimic this behavior in managed code? The Microsoft® Windows® Software Development Kit for Windows Vista™ and .NET Framework 3.0 Runtime Components comes to the rescue: inside, you find C:\Program Files\Microsoft SDKs\Windows\v6.0\Samples\CrossTechnologySamples.zip, which contains the VistaBridge sample.

From that, I took the VistaBridgeLibary, and modified the static UACManager.LaunchElevatedCOMObject method a bit:

[return: MarshalAs(UnmanagedType.Interface)]
public static object LaunchElevatedCOMObject(Guid Clsid, Guid InterfaceID)
{
  string CLSID = Clsid.ToString("B");
  string monikerName = "Elevation:Administrator!new:" + CLSID;

  NativeMethods.BIND_OPTS3 bo = new NativeMethods.BIND_OPTS3();
  bo.cbStruct = (uint)Marshal.SizeOf(bo);
  bo.hwnd = IntPtr.Zero;
  bo.dwClassContext = (int)NativeMethods.CLSCTX.CLSCTX_LOCAL_SERVER;

  object retVal = UnsafeNativeMethods.CoGetObject(monikerName, ref bo, InterfaceID);

  return (retVal);
}

Modifications: the method is now public instead of internal, and CLSCTX changed to local server (otherwise it wouldn't work).

Next, we need a UI:

This button is the CommandLinkWinForms control from VistaBridgeLibary, with the ShieldIcon property set to true.

Let's hook up the event code:

private void tryItButton_Click(object sender, EventArgs e)
{
 Guid IID_ITheElevated =
  new Guid(0x5EFC3EFB, 0xC7D3, 0x4D00, 0xB7, 0x2E, 0x2F, 0x86, 0x4A, 0x1E, 0xAD, 0x06);

 Guid CLSID_TheElevated =
  new Guid(0x253E7696, 0xA524, 0x4E49, 0x9E, 0x50, 0xBF, 0xCC, 0x29, 0x91, 0x31, 0x23);

 object o = UACManager.LaunchElevatedCOMObject(CLSID_TheElevated, IID_ITheElevated);

 ITheElevated iface = (ITheElevated)o;

 // Call the method on the interface just like in the C++ example
 iface.ShowMe();

 // Release the object
 Marshal.ReleaseComObject(o);
}

The interface ID as well as class ID guids come directly from the C++ project (it is always a good idea to "speak" more than one language), but you could obtain those from the type library or registry as well if you don't have the source code of the component handy.

Object creation is handled via LaunchElevatedCOMObject, and the resultant object is cast to the interface from the imported type library. Noteable (and important) is the last line: because the object wasn't created by the runtime, we have to take care of its destruction (the created interface doesn't have a Release() method, so we use Marshal.ReleaseComObject).

That's it - your managed code is now instantiating an elevated COM object that has full reign over the system.

ElevateCOMComponentSample.zip (117.07 KB)

Categories: .NET | Security | UAC | Use the source Luke | Vista
Tuesday, 30 January 2007 10:14:50 (W. Europe Standard Time, UTC+01:00)  #    Comments [0]

 

I would agree that this is indeed a sensible error message when I were about to add a COM reference to an inproc (DLL) server. However, this is an out of process server, an EXE. This guy (callee) does load in a separate process space from the caller, with a separate instance of the .NET runtime. Someone care to enlighten me why this restriction is in place?

By the way, tlbimp behaves the same way (just to make sure...):

TlbImp : error TI0000 : System.Runtime.InteropServices.COMException - Type library 'OutOfProcServer' was exported from a CLR assembly and cannot be re-imported as a CLR assembly.

Categories: .NET | Visual Studio
Tuesday, 30 January 2007 09:45:30 (W. Europe Standard Time, UTC+01:00)  #    Comments [0]

 

When you are working with Windows Vista, you know that even the administrative users are stripped ("filtered") of their privileges for normal operations, and that when you have to perform tasks requiring administrative privileges, you are presented with an UAC elevation prompt. The idea of this blog post series is to provide you with working samples on how to work with elevation from inside managed applications (you might also want to read Windows Vista Application Development Requirements for User Account Control Compatibility).

I want to side-step the really easy part - providing a manifest to start the entire application elevated (a good idea if the application makes no sense at all unless it has administrative rights, like regedit.exe). You can find information on those topics in Adding a UAC Manifest to Managed Code and Vista: User Account Control.

Now back to the topic of this post: App A needs to start App B with administrative rights (because App B e.g. needs to write to HKLM or Program Files). Therefore, we somehow must run App B as an administrative user (or with the non-filtered token of the current user). So how do we go about it?

First, some eye candy. You definitely already saw those nice shield icons before:

Those shield icons are stock on Windows Vista and indicate to the user that the action that hides behind the button requires elevation. I didn't create a button control myself - instead, I reused one that is readily available on the Web: Add a UAC Shield to your Winforms buttons in C#.

All I had to do myself was to start the Process ("App B"):

private void startProcess_Click(object sender, EventArgs e)
{
  ProcessStartInfo psi = new ProcessStartInfo();
  psi.FileName = theProcess;
  psi.Verb = "runas";
  Process.Start(psi);
}

The ticket (so to speak) for the elevation prompt is setting the Verb to "runas" in the ProcessStartInfo instance - this will pop up the elevation prompt if necessary when Process.Start is called.

This simplistic approach has a problem though - once App B is started, users can switch back to App A, because it App B isn't "modal" for App A. To solve this problem, I incorporated the approach from Daniel Moth outlined in his post Launch elevated and modal too:

private void launchModal_Click(object sender, EventArgs e)
{
  ProcessStartInfo psi = new ProcessStartInfo();
  psi.FileName = theProcess;
  psi.Verb = "runas";

  psi.ErrorDialog = true;
  psi.ErrorDialogParentHandle = this.Handle;

  try
  {
    Process p = Process.Start(psi);
    p.WaitForExit();
  }
  catch (Exception ex)
  {
    MessageBox.Show(ex.ToString());
  }
}

And that's it - App B is now modal. Once App B quits, control is relinquished to App A (which still doesn't run with administrative rights).

ElevateProcessSample.zip (21.1 KB)

Categories: Security | UAC | Use the source Luke | Vista
Tuesday, 30 January 2007 08:14:31 (W. Europe Standard Time, UTC+01:00)  #    Comments [1]

 



#  Monday, 29 January 2007

If you want to do this (Register Output, C++)

and don't want to get this error message

then start Visual Studio with Run as Administrator. I have SP1 and no Vista supplements installed, so maybe there will be (or already is) an elevation prompt for registering output.

Categories: Vista | Visual Studio
Monday, 29 January 2007 17:46:24 (W. Europe Standard Time, UTC+01:00)  #    Comments [0]

 

From the "What could possibly go wrong?" department: starting a WPF application (verified offenders are MsbuildG and VistaBridge from the Windows Vista SDK) crashes Vista. Or the graphics driver to be more precise. The result is nonetheless a perfectly reproducible BSOD on my IBM X31 laptop. The funny part? This ATI Mobility driver (6.14.10.6546) came from the Windows Vista Update Service and is MS HW Compat signed!

Categories: .NET | Tri 0 | Vista
Monday, 29 January 2007 10:20:11 (W. Europe Standard Time, UTC+01:00)  #    Comments [2]

 



#  Thursday, 25 January 2007

Got a developer question on how Windows Vista security affects your application? Then the MSDN Forum Security for Applications in Windows Vista is the right place to go.

Categories: Community | Security | Vista
Thursday, 25 January 2007 10:38:07 (W. Europe Standard Time, UTC+01:00)  #    Comments [0]

 

I don't recommend turning off UAC (User Account Control) on Windows Vista, but there might be valid reasons to shut it off once in a while for testing purposes (like in a VM). That is where TweakUAC comes in handy:

Categories: Security | UAC | Vista
Thursday, 25 January 2007 10:32:21 (W. Europe Standard Time, UTC+01:00)  #    Comments [0]

 



#  Wednesday, 24 January 2007

The Shift key can be very useful on Windows Vista. For example, type in the name of the application in the Search box and press Ctrl+Shift+Enter:

This will start the application as Administrator. Next is Windows Explorer, where Shift + right-clicking on a file will yield a different context menu:

The new feature is "Copy as Path" - allowing you to copy the full file path to the clipboard. I need that quite often, so this is a welcome addition indeed.

Speaking of Shift +  right-clicking: when you do that without a file selected on the right-hand pane in Explorer, you get another option, namely "Open Command Windows Here":

That is most useful!

Categories: Vista
Wednesday, 24 January 2007 10:23:10 (W. Europe Standard Time, UTC+01:00)  #    Comments [3]

 



© Copyright 2017 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.