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)

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