Click here to Skip to main content
15,889,034 members
Articles / Programming Languages / C#
Article

Add a UAC shield to a button when elevation is required for admin tasks

Rate me:
Please Sign up or sign in to vote.
4.55/5 (35 votes)
22 Apr 20072 min read 192.1K   2.5K   77   33
Add a UAC shield to a button when elevation is required for admin tasks using the API, and elevate the process if required.
Screenshot - VistaSecurity.png

Introduction

Any application that does not always need administrator privileges should not run with them by default. However, when a user wants to perform a task that requires elevation, you need to show them that this is required by displaying the Vista shield icon. When this is clicked, your application will then need to restart with administrator privileges. Interested? Then read on...

Making the VistaSecurity Class

First we need to create a VistaSecurity class. Inside it we need the SendMessage API.

C#
[DllImport("user32")]
public static extern UInt32 SendMessage
    (IntPtr hWnd, UInt32 msg, UInt32 wParam, UInt32 lParam);

internal const int BCM_FIRST = 0x1600; //Normal button
internal const int BCM_SETSHIELD = (BCM_FIRST + 0x000C); //Elevated button

First, before we add a shield, we need to know if the process is elevated or not which is easily done. To add a shield to a button, make sure that the button uses FlatStyle.System and then sends the appropriate message using the API. The important API function parameters are the handle of the button and the BCM_SETSHIELD message.

C#
static internal bool IsAdmin()
{
    WindowsIdentity id = WindowsIdentity.GetCurrent();
    WindowsPrincipal p = new WindowsPrincipal(id);
    return p.IsInRole(WindowsBuiltInRole.Administrator);
}

static internal void AddShieldToButton(Button b)
{
    b.FlatStyle = FlatStyle.System;
    SendMessage(b.Handle, BCM_SETSHIELD, 0, 0xFFFFFFFF);
}

We then need a way to elevate the process if required. We just restart the process with the "runas" Verb. The current unelevated process is then exited unless the System.ComponentModel.Win32Exception is thrown, as this indicates that the user has clicked Cancel on the UAC prompt.

C#
internal static void RestartElevated()
{
    ProcessStartInfo startInfo = new ProcessStartInfo();
    startInfo.UseShellExecute = true;
    startInfo.WorkingDirectory = Environment.CurrentDirectory;
    startInfo.FileName = Application.ExecutablePath;
    startInfo.Verb = "runas";
    try
    {
        Process p = Process.Start(startInfo);
    }
    catch(System.ComponentModel.Win32Exception ex)
    {
        return;
    }

    Application.Exit();
}

That's it for the VistaSecurity class.

Using the code

On the form, you need the following code in your constructor after InitializeComponent which will add the shield.

C#
if (!VistaSecurity.IsAdmin())
{
    this.Text += " (Standard)"; //Unnecessary
    VistaSecurity.AddShieldToButton(buttonGetElevation); //Important
}
else
    this.Text += " (Elevated)";

When the button is clicked we need to check if you have permission to perform the required action or elevate.

C#
if (VistaSecurity.IsAdmin())
{
    DoAdminTask();
}
else
{
    VistaSecurity.RestartElevated();
}

Well, that's how to display a shield and get elevation!

Points of Interest

For the admin task in this demo, I added a file VISTA.TXT to All Users' start menu at \ProgramData\Microsoft\Windows\Start Menu\. To get rid of the file, run the demo again (admin credentials may be required!). The Try Admin Task button just tries to create the file without asking for elevation.

History

  • 22 Apr 07 - Article written
  • 24 Apr 07 - Checking of Administrator account works for non-English versions of Windows by using WindowsBuiltInRole.Administrator instead of a non-localized string. In DoAdminTask(), MessageBoxes show what has been done, more error catching added, and an Environment.SpecialFolder is used to get the path.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior)
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralPerfect Pin
almamy67-Apr-20 8:30
almamy67-Apr-20 8:30 
GeneralMy vote of 5 Pin
almamy67-Apr-20 8:09
almamy67-Apr-20 8:09 
QuestionCant get the UAC icon to a button Pin
hanonymouss1-Mar-12 4:11
hanonymouss1-Mar-12 4:11 
QuestionLicense for this Component Pin
streetsjingo30-Nov-11 10:45
streetsjingo30-Nov-11 10:45 
QuestionAwesome! + How wrong can you be? Pin
Vercas3-Oct-10 4:10
Vercas3-Oct-10 4:10 
GeneralCode Pin
Brandon Holland12-Jul-09 3:21
Brandon Holland12-Jul-09 3:21 
GeneralRestart application Pin
jammmie99924-May-09 22:55
professionaljammmie99924-May-09 22:55 
GeneralRe: Restart application Pin
McoreD30-Aug-09 15:34
McoreD30-Aug-09 15:34 
AnswerRe: Restart application Pin
Indivara28-Jan-10 14:14
professionalIndivara28-Jan-10 14:14 
Questionhow to remove it ? Pin
vishal108221-May-09 16:46
vishal108221-May-09 16:46 
AnswerRe: how to remove it ? Pin
jammmie99924-May-09 22:54
professionaljammmie99924-May-09 22:54 
AnswerRe: how to remove it ? Pin
Sky Sanders27-Dec-09 7:23
Sky Sanders27-Dec-09 7:23 
GeneralIsAdmin Function - an easier way Pin
dmex7-Jan-09 7:51
dmex7-Jan-09 7:51 
GeneralRe: IsAdmin Function - an easier way Pin
vtchris-peterson7-Jan-10 8:11
vtchris-peterson7-Jan-10 8:11 
GeneralVb.net Pin
Jorge Rocha29-Dec-08 15:05
Jorge Rocha29-Dec-08 15:05 
GeneralRe: Vb.net Pin
JumpyCODE13-Mar-09 20:55
JumpyCODE13-Mar-09 20:55 
GeneralRe: Vb.net Pin
zerosoft_uk26-Jul-11 12:14
zerosoft_uk26-Jul-11 12:14 
GeneralExcellant... and i rolled it all into a handy control :D Pin
FocusedWolf25-May-08 19:32
FocusedWolf25-May-08 19:32 
GeneralRe: Excellant... and i rolled it all into a handy control :D Pin
FocusedWolf25-May-08 19:35
FocusedWolf25-May-08 19:35 
GeneralRe: Excellant... and i rolled it all into a handy control :D Pin
FocusedWolf26-May-08 10:11
FocusedWolf26-May-08 10:11 
GeneralRe: Excellant... and i rolled it all into a handy control :D Pin
FocusedWolf30-May-08 9:40
FocusedWolf30-May-08 9:40 
I found many problems with it while refactoring... so that's all fixed.

http://wolfsfiles.googlepages.com/StudioShieldButton.cs[^]

if link is dead, here's a pasted copy:

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing.Design;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Principal;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using System.IO;

namespace StudioControls.Controls
{
    /// <summary>
    /// Makes a button that shows a shield if its application is run without admin privileges.
    /// 
    /// This control can be used to restart the current application with admin, or start another with admin.
    /// 
    /// Place code in a subscribed Click event that you only want executed by administrators.
    /// The reason is, you can use the button to both obtain admin power via UAC, and when the program restarts
    /// it'll loose the shield icon (but theirs a option to still show it), and then whatever code you wanted
    /// to be executed in the click event --- for admin only --- can now be executed in the priviliged app.
    /// </summary>
    /// <example>
    /// Some things to watch for:
    /// 
    /// 0.  AlwaysShowShield property for your enjoyment.
    /// 
    /// 1.  Not all UAC privilege escalation requires a program restart, for example in "programs and features", the "turn windows features on or off" 
    ///     feature opens a new application without closing the one which spawned it.
    /// 
    ///     Use the following properties to specify loading a new process:
    ///
    ///         EscalationCustomProcessPath = string path to a .exe, etc
    ///         EscalationGoal = EscalationGoal.StartAnotherApplication;
    ///     
    /// 2.  if the user gets admin priviliges, and this controls EscalationGoal = EscalationGoal.RestartThisApplication, 
    ///     the program will restart... so your program might require saving and restoring of program state.
    ///     You can listen for this in general by hooking onto this event "Application.ApplicationExit += ..."
    ///     However, their is no way to differentiate between UAC starting the application shutdown/restart, and a normal shutdown, 
    ///     so i included additional events:
    ///
    ///         EscalationStarting
    ///         EscalationCancelled
    ///         EscalationSuccessful
    ///         
    ///     Note: if EscalationGoal = EscalationGoal.StartAnotherApplication then this program will not exit.
    ///     
    /// 3.  When your program restarts, make sure you preserve your programs StartPosition, else it might look sloppy if it suddenly moves from where it
    ///     appears in the greyed out UAC background... but then again... the taskmanager doesn't care about its StartPosition when you press 
    ///     "Show processes from all users" and it restarts lol. For me i just use StartPosition = CenterScreen for the affected forms.
    /// </example>
    /// <remarks>
    /// Reference: 
    ///     http://www.codeproject.com/KB/vista-security/UAC_Shield_for_Elevation.aspx, 
    ///     http://buildingsecurecode.blogspot.com/2007/06/how-to-display-uac-shield-icon-inside.html
    /// </remarks>
    public class StudioShieldButton : Button
    {
        #region Fields

        public delegate void EscalationHandler(EscalationGoal escalationGoal, object sender, EventArgs e);

        #endregion

        #region Properties

        #region FlatStyle Appearance Property

        /// <summary>
        /// Determines the appearance of the control when a user moves the mouse the mouse over the control and clicks.
        /// </summary>
        [
        Category("Appearance"),
        Description("Determines the appearance of the control when a user moves the mouse the mouse over the control and clicks."),
        DefaultValue(typeof(FlatStyle), "System"),
        ReadOnly(true),
        Browsable(false)
        ]
        public new FlatStyle FlatStyle
        {
            get { return base.FlatStyle; }
        }

        #endregion

        #region AlwaysShowShield Appearance Property

        private bool alwaysShowShield = false;
        /// <summary>
        /// Gets or sets if the shield icon should always be visible.
        /// </summary>
        [
        Category("Appearance"),
        Description("Gets or sets if the shield icon should always be visible."),
        DefaultValue(typeof(bool), "false"),
        ]
        public bool AlwaysShowShield
        {
            get { return alwaysShowShield; }
            set
            {
                alwaysShowShield = value;

                if (!UACUtilities.HasAdminPrivileges() || alwaysShowShield) // then show the shield on the button
                {
                    // Show the shield
                    SendMessage(new HandleRef(this, this.Handle), BCM_SETSHIELD, new IntPtr(0), new IntPtr(1)); // the (1) for true
                }

                else
                {
                    // Hide the shield
                    SendMessage(new HandleRef(this, this.Handle), BCM_SETSHIELD, new IntPtr(0), new IntPtr(0)); // the (0) for false
                }

            }
        }

        #region Serialization
        /// <summary>
        /// Should serialize AlwaysShowShield property.
        /// </summary>
        /// <returns>True if should serialize, else false.</returns>
        private bool ShouldSerializeAlwaysShowShield()
        {
            //Only serialize nondefault values
            return (alwaysShowShield != false);
        }
        /// <summary>
        /// Resets AlwaysShowShield property.
        /// </summary>
        private void ResetAlwaysShowShield()
        {
            alwaysShowShield = false;

            if (UACUtilities.HasAdminPrivileges()) // then show the shield on the button
            {
                // Show the shield
                SendMessage(new HandleRef(this, this.Handle), BCM_SETSHIELD, new IntPtr(0), new IntPtr(1)); // the (1) for true
            }

            else
            {
                // Hide the shield
                SendMessage(new HandleRef(this, this.Handle), BCM_SETSHIELD, new IntPtr(0), new IntPtr(0)); // the (0) for false
            }
        }
        #endregion

        #endregion

        #region EscalationGoal Behavior Property

        private EscalationGoal escalationGoal = EscalationGoal.StartAnotherApplication;
        /// <summary>
        /// Gets or sets the EscalationGoal for this UAC "shield" button.
        /// </summary>
        [
        Category("Behavior"),
        Description("Gets or sets the EscalationGoal for this UAC \"shield\" button."),
        DefaultValue(typeof(EscalationGoal), "StartAnotherApplication")
        ]
        public EscalationGoal EscalationGoal
        {
            get { return escalationGoal; }
            set { escalationGoal = value; }
        }

        #region Serialization
        /// <summary>
        /// Should serialize EscalationGoal property.
        /// </summary>
        /// <returns>True if should serialize, else false.</returns>
        private bool ShouldSerializeEscalationGoal()
        {
            //Only serialize nondefault values
            return (escalationGoal != EscalationGoal.StartAnotherApplication);
        }
        /// <summary>
        /// Resets EscalationGoal property.
        /// </summary>
        private void ResetEscalationGoal()
        {
            escalationGoal = EscalationGoal.StartAnotherApplication;
        }
        #endregion

        #endregion

        #region EscalationCustomProcessPath Behavior Property

        private string escalationCustomProcessPath = String.Empty;
        /// <summary>
        /// Gets or sets the path to the application that this program starts when hit. 
        /// Leave it \"\" to load the current Application.ExecutablePath.
        /// </summary>
        [
        Category("Behavior"),
        Description("Gets or sets the path to the application that this program starts when hit. Leave it \"\" to load the current Application.ExecutablePath."),
        DefaultValue(typeof(string), "\"\""),
        Editor(typeof(FilteredFileNameEditor /*FileNameEditor*/), typeof(UITypeEditor))
        ]
        public string EscalationCustomProcessPath
        {
            get { return escalationCustomProcessPath; }
            set { escalationCustomProcessPath = value; }
        }

        #region Serialization
        /// <summary>
        /// Should serialize EscalationCustomProcessPath property.
        /// </summary>
        /// <returns>True if should serialize, else false.</returns>
        private bool ShouldSerializeEscalationCustomProcessPath()
        {
            //Only serialize nondefault values
            return (escalationCustomProcessPath != String.Empty);
        }
        /// <summary>
        /// Resets EscalationCustomProcessPath property.
        /// </summary>
        private void ResetEscalationCustomProcessPath()
        {
            escalationCustomProcessPath = String.Empty;
        }
        #endregion

        #endregion

        #endregion

        #region DllImports

        private const uint BCM_SETSHIELD = 0x0000160C;

        [DllImport("user32.dll")]
        private static extern IntPtr SendMessage(HandleRef hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

        #endregion

        #region Constructor

        public StudioShieldButton()
        {
            base.FlatStyle = FlatStyle.System; // This must be FlatStyle.System for the shield to work
            // I also locked the FlatStyle property:
            //   DefaultValue(typeof(FlatStyle), "System"), 
            //   ReadOnly(true),
            //   Browsable(false) 
            // so hopefully vs.net won't try to serialize it.

            AlwaysShowShield = alwaysShowShield; //i'm forcing the code in the Set{} of "AlwaysShowShield" to run.
        }

        #endregion

        #region Members

        private string EscalationProcessPath()
        {
            string path;

            if (escalationGoal == EscalationGoal.StartAnotherApplication)
            {
                if (String.IsNullOrEmpty(escalationCustomProcessPath))
                    throw new Exception("escalationGoal == EscalationGoal.StartAnotherApplication but escalationCustomProcessPath is null or empty.");

                path = escalationCustomProcessPath;
            }

            else
                path = Application.ExecutablePath;

            return path;
        }        

        private void OnEscalationStarting(EscalationGoal escalationGoal, object sender, EventArgs e)
        {
            if (EscalationStarting != null)
            {
                EscalationStarting(escalationGoal, sender, e);
            }
        }

        private void OnEscalationCancelled(EscalationGoal escalationGoal, object sender, EventArgs e)
        {
            if (EscalationCancelled != null)
            {
                EscalationCancelled(escalationGoal, sender, e);
            }
        }

        private void OnEscalationSuccessful(EscalationGoal escalationGoal, object sender, EventArgs e)
        {
            if (EscalationSuccessful != null)
            {
                EscalationSuccessful(escalationGoal, sender, e);
            }
        }

        #endregion

        #region Overrided Members

        protected override void OnClick(EventArgs e)
        {
            if (UACUtilities.HasAdminPrivileges())
                base.OnClick(e);  // this can only be called if we're an admin, we are assuming it contains admin-only stuff

            else
            {
                UACUtilities.AttemptPrivilegeEscalation(
                    EscalationProcessPath(),
                    delegate()
                    {
                        OnEscalationStarting(escalationGoal, this, new EventArgs());
                    },
                    delegate()
                    {
                        OnEscalationCancelled(escalationGoal, this, new EventArgs());
                    },
                    delegate()
                    {
                        OnEscalationSuccessful(escalationGoal, this, new EventArgs());

                        if (escalationGoal == EscalationGoal.RestartThisApplication)
                            Application.Exit();
                    });
            }
        }

        #endregion

        #region Events

        #region EscalationStarting Action

        [Category("Action")]
        [Description("A UAC privilege escalation is going to start next.")]
        public event EscalationHandler EscalationStarting;

        #endregion

        #region EscalationCancelled Action

        [Category("Action")]
        [Description("The user cancelled the UAC privilege escalation prompt.")]
        public event EscalationHandler EscalationCancelled;

        #endregion

        #region EscalationSuccessful Action

        [Category("Action")]
        [Description("The user was successful in getting admin privileges.")]
        public event EscalationHandler EscalationSuccessful;

        #endregion

        #endregion
    }

    public class UACUtilities
    {
        #region Fields

        public delegate void EscalationEvent();

        #endregion

        #region Members

        public static bool HasAdminPrivileges()
        {
            WindowsIdentity windowsIdentity = WindowsIdentity.GetCurrent();
            WindowsPrincipal windowsPrincipal = new WindowsPrincipal(windowsIdentity);
            return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator);
        }

        public static ProcessStartInfo EscalationProcessStartInfo(string path)
        {
            if (String.IsNullOrEmpty(path))
                throw new ArgumentNullException("path");

            if (!File.Exists(path))
                throw new FileNotFoundException("path");

            ProcessStartInfo startInfo = new ProcessStartInfo();
            startInfo.UseShellExecute = true;
            startInfo.FileName = path;
            startInfo.Verb = "runas"; // will bring up the UAC run-as menu when this ProcessStartInfo is used

            return startInfo;
        }

        public static void AttemptPrivilegeEscalation(string path)
        {
            if (String.IsNullOrEmpty(path))
                throw new ArgumentNullException("path");

            AttemptPrivilegeEscalation(path, null, null, null);
        }

        public static void AttemptPrivilegeEscalation(string path, EscalationEvent starting, EscalationEvent cancelled, EscalationEvent successful)
        {
            if (String.IsNullOrEmpty(path))
                throw new ArgumentNullException("path");

            if (!File.Exists(path))
                throw new FileNotFoundException("path");

            if (HasAdminPrivileges())
                throw new SecurityException("Already have administrator privileges.");

            ProcessStartInfo startInfo = EscalationProcessStartInfo(path);

            if(starting != null)
                starting();

            ////todo: slight issue with this... when the program is in admin status... 
            ////if my EscalationCustomProcessPath is set to some random program that goes online right away
            ////i get norton saying this application program (not the other program) is trying to connect to the internet...

            try
            {
                Process.Start(startInfo);
            }

            catch (System.ComponentModel.Win32Exception) //occurs when the user has clicked Cancel on the UAC prompt.
            {
                if(cancelled != null)
                    cancelled();
                
                return; // By returning, we are ignoring the user tried to get UAC priviliges but then hit cancel at the "Run-As" prompt.
            }

            if(successful != null)
                successful();
        }

        #endregion
    }

    public enum EscalationGoal
    {
        RestartThisApplication,
        StartAnotherApplication
    }

    //http://72.14.205.104/search?q=cache:2HrHWneYMSAJ:forums.microsoft.com/MSDN/ShowPost.aspx%3FPostID%3D66703%26SiteID%3D1+UITypeEditor+directory+path+Editor+property&hl=en&ct=clnk&cd=2&gl=us
    internal class FilteredFileNameEditor : UITypeEditor
    {
        private OpenFileDialog ofd = new OpenFileDialog();

        public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
        {
            return UITypeEditorEditStyle.Modal;
        }

        public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
        {
            ofd.FileName = value.ToString();
            ofd.Filter = "Programs|*.exe|All Files|*.*";
            //ofd.Filter = "Programs|*.exe;*.pif,*.com;*.bat;*.cmd|All Files|*.*"; 
            //ofd.Filter = "Text File|*.txt|All Files|*.*";
            if (ofd.ShowDialog() == DialogResult.OK)
            {
                return ofd.FileName;
            }
            return base.EditValue(context, provider, value);
        }
    }
}

GeneralRe: Excellant... and i rolled it all into a handy control :D Pin
f r i s c h19-Aug-08 1:54
f r i s c h19-Aug-08 1:54 
RantRe: Excellant... and i rolled it all into a handy control :D Pin
98z2825-Feb-10 8:48
98z2825-Feb-10 8:48 
GeneralWont detect properly except for first user in Vista Pin
leifre11-Apr-08 8:08
leifre11-Apr-08 8:08 
Question.NET 3? Pin
JimShabadoo2-Nov-07 8:34
JimShabadoo2-Nov-07 8:34 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.