<November 2014>
SunMonTueWedThuFriSat
2627282930311
2345678
9101112131415
16171819202122
23242526272829
30123456

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
 

#  Monday, February 05, 2007

I admit it: UAC Elevation in Managed Code: "Talking" to an Elevated Process via WCF is a kludge. The reason why I dabbled with this approach at all is that I failed to implement COM elevation with managed code (not elevating a COM component, but the COM component itself). However, at long last, I succeeded in that respect too: I now present you the all-managed code solution to UAC elevation!

Once again I built myself a small demo frontend application:

As you can guess, the first button does plain vanilla COM InterOp without any UAC elevation. Thus its code is rather simple:

private void simpleCallButton_Click(object sender, EventArgs e)
{
  Type t = Type.GetTypeFromCLSID(new Guid("71E050A7-AF7F-42dd-BE00-BF955DDD13D4"));
  object o = Activator.CreateInstance(t);
  t.InvokeMember("SayHello", BindingFlags.InvokeMethod, null, o, null);
}

Why this reflection magic? Well, the COM component I am calling here is implemented in .NET - and both VS as well as tlbimp balk at reimporting the exported type library.

The COM component in question has been regasm'ed & gacutil'ed (ManagedElevator project in the download). Although the name implies that I am after elevation, it is pretty much a standard COM component written using C#:

public class TheGuids
{
  public const string IHelloWorld = "B8CD5C09-9ACD-49b0-BF6F-C7B0F29795F9";
  public const string ClassToElevate = "71E050A7-AF7F-42dd-BE00-BF955DDD13D4";
  public const string AppId = "75AB90B0-8B9C-45c9-AC55-C53A9D718E1A";
}

[Guid(TheGuids.IHelloWorld)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IHelloWorld
{
  [ComVisible(true)]
  void SayHello();
}

[Guid(TheGuids.ClassToElevate)]
[ClassInterface(ClassInterfaceType.None)]
public class ClassToElevate : IHelloWorld
{
 public ClassToElevate()
 {
 }

 [ComVisible(true)]
 public void SayHello()
 {
  MessageBox.Show("Hello World");
 }
}

So how do you go from "standard" "plain-vanilla" COM component to COM elevation? The part that stumped me for so long was the ClassInterface attribute - if you forget this guy, you'll end up with an InvalidCastException thrown by UACManager.LaunchElevatedCOMObject.

But that's not quite all to get up and running with COM elevation: in addition, you need to modify the default registration for this component - specifically, you need to configure the DllSurrogate. This is where the AppId GUID comes into play: it isn't used in code (kept there for documentation purposes only), but in registryadditions.reg. It binds the various registry keys. And speaking of this .reg file, please take note of the LocalizedString value: it contains the text for the UAC prompt (also check out UACPrompts.rc, resource.h, compilerc.bat as well as the properties of the ManagedElevator project where the compiled .res file is referenced).

Note Before importing the .reg file into the registry make sure to fix the file path contained in LocalizedString! And if you create your own elevated COM component DO NOT reuse any of my three GUIDs - use guidgen.exe to create your personal ones.

From there, UAC elevation is smooth sailing. The Reflection version of COM elevation looks very similar to non-elevated calls:

private void managedElevation_Click(object sender, EventArgs e)
{
  // CLSID
  Guid classId = new Guid("71E050A7-AF7F-42dd-BE00-BF955DDD13D4");

  // Interface ID
  Guid interfaceId = new Guid("B8CD5C09-9ACD-49b0-BF6F-C7B0F29795F9");

  object o = UACManager.LaunchElevatedCOMObject(classId, interfaceId);

  Type t = o.GetType();
  t.InvokeMember("SayHello", BindingFlags.InvokeMethod, null, o, null);

  Marshal.ReleaseComObject(o);
}

Of course this is not really a good solution (late binding). So instead I manually imported the IHelloWorld interface:

[
ComImport(),
Guid("B8CD5C09-9ACD-49b0-BF6F-C7B0F29795F9"),
InterfaceType(ComInterfaceType.InterfaceIsDual)
]
  interface IHelloWorld
  {
   [
   MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime),
   PreserveSig
   ]
    void SayHello();
  }

Which makes calls into the elevated COM object much easier and cleaner:

private void managedElevationInterface_Click(object sender, EventArgs e)
{
  Guid classId = new Guid("71E050A7-AF7F-42dd-BE00-BF955DDD13D4");
  Guid interfaceId = new Guid("B8CD5C09-9ACD-49b0-BF6F-C7B0F29795F9");

  object o = UACManager.LaunchElevatedCOMObject(classId, interfaceId);

  IHelloWorld ihw = (IHelloWorld)o;
  ihw.SayHello();

  Marshal.ReleaseComObject(o);
}

So why should you use the COM elevation solution instead of starting the process? Well, there are a couple of reasons:

  • You can package more than one component into a DLL and still have custom UAC prompts thanks to LocalizedString
  • Your users don't get "an unidentified program..." warnings. Thank you COM registration
  • If you ever need to talk more extensively with the elevated process then this approach can be adapted more easily

The source code

ConsumeMyElevatedCOM.zip (97.56 KB)

You will find a file aptly named notes.txt in the ManagedElevator project that describes all the necessary steps to get up and running.

I hope you find this sample useful and not have to spend as much time as I did. Cheers!

Categories: .NET | Security | UAC | Vista
Monday, February 05, 2007 10:41:46 PM (W. Europe Standard Time, UTC+01:00)  #    Comments [7]

 



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