<2017 May>
SunMonTueWedThuFriSat
30123456
78910111213
14151617181920
21222324252627
28293031123
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
 

#  Monday, 04 June 2007

Microsoft released a UAC demo. It is just basic process elevation (read: save the time by not downloading it), which I described in more detail (with more reuseability) in UAC Elevation in Managed Code: Starting Elevated Processes.

Categories: .NET | Vista | UAC
Monday, 04 June 2007 10:34:52 (W. Europe Daylight Time, UTC+02:00)  #    Comments [0]

 



#  Wednesday, 21 February 2007

This is v2 of the Vista UAC development requirements document. From the TOC:

  • Why User Account Control?
  • How UAC Works
  • Will UAC Affect Your Application?
  • Designing Applications for Windows Vista
  • Deploying and Patching Applications for Standard Users
  • Troubleshooting Common Issues
  • References
Categories: Security | UAC | Vista
Wednesday, 21 February 2007 09:48:17 (W. Europe Standard Time, UTC+01:00)  #    Comments [0]

 



#  Friday, 16 February 2007

In my last blog entry UAC Elevation in Managed Code: A .NET COM Component Elevated I showed how to get up and running with an all-managed code solution for UAC and COM elevation. Today I want close out my series on UAC with some information on how to properly organize the project plus present a library you can reuse to get up and running quickly - without many of the manual and tedious steps from the previous proof of concept example.

Speaking of the previous sample: it is still the basis for this best practice, so the following directory layout will look familiar to you:

Before diving into code, I want to start out with the SampleSetup directory, which contains the executables. As you can guess, the starting point is Step1Register. It contains register.bat, which you have to execute:

Note that on machines without the .NET Framework SDK, there is no gacutil.exe. In that case, you have to drag & drop ManagedElevator.dll to c:\windows\assembly.

And in case you have been wondering from this screenshot, yes, the application now also plays nicely on Windows XP:

Of course, there is no consent UI popping up, nor is there a shield icon like there is on Windows Vista:

The magic for this cross-platform functionality is hidden in the UACHelper project - which brings us to the source section of this blog post:

All the necessary COM elevation magic is now moved to this neat little library - including the adapted UAC bits of VistaBridgeLibrary (no longer necessary). The names already give away the purpose of each class and where they are used:

  • COMRegistration Used by the elevated component to automatically register the necessary registry keys.
  • ShieldButton Used by the client to display a button with a shield icon (on Vista). For XP, no shield is rendered.
  • COMElevation Starts the requested component with admin privileges.
  • ElevatedProcess If you want to start a simple process elevated. Not used in this guidance.

The first customer of this library is the elevated component, so we start discussing this guy next:

At first glance, this is similar to the previous POC implementation. The main difference now is that I have broken down the functionality by feature area into namespaces:

  • The "main" namespace
  • The .Components namespace
  • The .Guids namespace
  • The .InterOp namespace

Let's look at these one by one.

The "main" namespace

Here, we have one class only:

class RegisterFunctions
{
  [ComRegisterFunction]
  public static void CustomRegister(Type t)
  {
    COMRegistration.RegisterForElevation(Assembly.GetExecutingAssembly().Location,
       SampleComponent.ClassToElevate,
       Global.AppId,
       100);

    // add additional "for elevation" components here by duplicating the above
  }

  [ComUnregisterFunction]
  public static void CustomUnregister(Type t)
  {
    COMRegistration.UnRegisterFromElevation(Assembly.GetExecutingAssembly().Location,
        Global.AppId);
  }
}

It is called when the assembly is regasm'ed, and it is here where you call into COMRegistration.RegisterForElevation to add all the necessary registry keys for elevation:

public static void RegisterForElevation(string assemblyLocation,
    string classToElevate,
    string appId,
    int localizedStringId)
{
 if (!UACHelperFunctions.IsUACEnabledOS()) return;

 // [HKEY_CLASSES_ROOT\CLSID\{71E050A7-AF7F-42dd-BE00-BF955DDD13D4}]
 // "AppID"="{75AB90B0-8B9C-45c9-AC55-C53A9D718E1A}"
 // "LocalizedString"="@E:\\Daten\\Firma\\Konferenzen und Talks\\..."
 RegistryKey classKey = Registry.ClassesRoot.OpenSubKey(@"CLSID\{" + classToElevate + "}", true);
 classKey.SetValue("AppId", "{" + appId + "}", RegistryValueKind.String);
 classKey.SetValue("LocalizedString", "@" + assemblyLocation + ",-" + localizedStringId.ToString(), RegistryValueKind.String);

 // [HKEY_CLASSES_ROOT\CLSID\{71E050A7-AF7F-42dd-BE00-BF955DDD13D4}\Elevation]
 // "Enabled"=dword:00000001
 RegistryKey elevationKey = classKey.CreateSubKey("Elevation");
 elevationKey.SetValue("Enabled", 1, RegistryValueKind.DWord);
 elevationKey.Close();

 classKey.Close();

 // [HKEY_CLASSES_ROOT\AppID\{75AB90B0-8B9C-45c9-AC55-C53A9D718E1A}]
 // @="ManagedElevator"
 // "DllSurrogate"=""
 RegistryKey hkcrappId = Registry.ClassesRoot.OpenSubKey("AppID", true);
 RegistryKey appIdKey = hkcrappId.CreateSubKey("{" + appId + "}");
 appIdKey.SetValue(null, Path.GetFileNameWithoutExtension(assemblyLocation));
 appIdKey.SetValue("DllSurrogate", "", RegistryValueKind.String);
 appIdKey.Close();

 // [HKEY_CLASSES_ROOT\AppID\ManagedElevator.dll]
 // "AppID"="{75AB90B0-8B9C-45c9-AC55-C53A9D718E1A}"
 RegistryKey asmKey = hkcrappId.CreateSubKey(Path.GetFileName(assemblyLocation));
 asmKey.SetValue("AppID", "{" + appId + "}", RegistryValueKind.String);
 asmKey.Close();

 hkcrappId.Close();
}

Please take note that when the component is registered on eg Windows XP, no registry entries are written. After all, they are not needed.

The .Components namespace

Not much of a change - it contains the administrative component(s).

The .Guids namespace

The guids have been moved to a separate namespace. The reason? That way you can reference the assembly in the client project and use the guids directly - no magic strings anywhere any more.

The .InterOp namespace

This is the most important change with regards to the POC project - defining the correct ComImport'ed interface is now the responsibility of the implementer of the elevated component. That way, anyone needing access to this component only needs to reference the assembly and they are good to go. It is a bad idea to have this interface part of the client codebase!

Speaking of the client... here is the button code for DemoForm.cs:

private void cmdLaunch_Click(object sender, EventArgs e)
{
 if (UACHelperFunctions.IsUACEnabledOS())
 {
   IHelloWorld ihw = COMElevation.Start<IHelloWorld>(
        SampleComponent.ClassToElevate, SampleComponent.IHelloWorld);
   ihw.SayHello();
   COMElevation.Release(ihw);
 }
 else
 {
   ManagedElevator.Components.ClassToElevate c = new ManagedElevator.Components.ClassToElevate();
   c.SayHello();
 }
}

What looks interesting at first is COMElevation.Start as well as Release:

public class COMElevation
{
 public static TIFace Start<TIFace>(string IID_Class, string IID_Interface)
 {
  return Start<TIFace>(new Guid(IID_Class), new Guid(IID_Interface));
 }

 public static TIFace Start<TIFace>(Guid IID_Class, Guid IID_Interface)
 {
  object o = UACManager.LaunchElevatedCOMObject(IID_Class, IID_Interface);
  return (TIFace)o;
 }

 public static void Release(object o)
 {
  Marshal.ReleaseComObject(o);
 }
}

Actually all it does is encapsulate the necessary calls to UACManager and Marshal. Why is there no if / else using IsUACEnabledOS here? Well, at first I thought I'd build such a switch, but then I thought again: why would I use COM InterOp if I don't have to? I already referenced the assembly for the component (for the guids and interop interface), so why not use managed all the way and save time? That's what I did in the cmdLaunch_Click event handler.

That's it for the code folks, now a little discussion at the end on why in the world would you even think about doing this in a cross-platform way, or why it is a stupid idea all along:

This approach is only sensible if your application runs as administrative user on XP, otherwise all the calls in the administrative component will fail. However, the cross-platform part is only there to make it a complete best practice, there is no "you must use it cross-platform" - if you build applications for Windows Vista with the eventual need to elevate a task, then UACHelper is definitely for you! (and forget about that it would even work on XP)

Oh, and I almost forgot - here is the complete download, source code included of course (my code is BSD licensed):

AutomaticRegistration.zip (91.92 KB)

Categories: Security | UAC | Use the source Luke | Vista
Friday, 16 February 2007 08:02:29 (W. Europe Standard Time, UTC+01:00)  #    Comments [1]

 



#  Monday, 05 February 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, 05 February 2007 22:41:46 (W. Europe Standard Time, UTC+01:00)  #    Comments [7]

 



#  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]

 



#  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]

 

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]

 



#  Thursday, 25 January 2007

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]

 



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