Whether you're a student, hobbyist, or serious developer this tutorial will leave you with a great understanding of how to implement User Account Control elevations in your application. If you have ever used an operating system from Vista and above you are probrably familiar with that shield icon on Windows dialogs and even on some of the daily applications you may use. The tutorial will show you how applications can be UAC aware.
What is COM Elevation?The simplest way to explain it would be executing code that runs in the context of an administrator. If you need to perform a few tasks that require administrative rights then use COM elevation. It's not neccesary to manifest your applications if your application doesn't need administrative rights "all the time".
The tutorial uses Visual Studio 2008 Professional and the .NET 3.5 framework.
You can find the complete project solution template at the end of this article. You may use it as a building block for adding COM elevations to your applications.
On the Visual Studio 2008 IDE [menu] choose File >
New > Project...Choose from the [Add New Project] dialog
Visual Basic > Windows > Windows Forms ApplicationName the project:
MyApplication Locate the solution explorer window and select
MyApplication project.
Right click and choose from the [menu]
Add > New Item...Choose from the [Add New Item] dialog
Common items > Code > ModuleName the module:
ElevationLocate the solution explorer window and select the
Solution 'My Application'Right click and choose from the [menu]
Add > New > Project...Choose from the [Add New Project] dialog
Visual Basic > Windows > Class LibraryName the project:
COMAdminUnder the
COMAdmin project class1.vb is created by default. Right click and choose
Delete.
Locate the solution explorer window and select
COMAdmin project.
Right click and choose from the [menu]
Add > New Item...Choose from the [Add New Item] dialog
Common items > Code > COM ClassName the COM Class:
UACAdminLocate the solution explorer window and select
COMAdmin project.Right click and choose from the [menu]
Add > New Item...Choose from the [Add New Item] dialog
Common items > Code > ModuleName the module:
UACRegisterLocate the solution explorer window and select
COMAdmin project.Right click and choose from the [menu]
Add > New Item...Choose from the [Add New Item] dialog
Common items > General > Installer ClassName the class:
UACAdminInstaller - From the COMAdmin project do the following:
Right click
UACAdminInstaller and choose
View CodeAdd the following source code to
UACAdminInstaller.vb1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29:
| Imports System.ComponentModel Imports System.Configuration.Install Imports System.Runtime.InteropServices
Public Class uacAdminInstaller Inherits Installer
Public Sub New() MyBase.New() InitializeComponent() End Sub
_ Public Overloads Overrides Sub Install(ByVal stateSaver As System.Collections.IDictionary) MyBase.Install(stateSaver) Dim RegServices As New RegistrationServices RegServices.RegisterAssembly(MyBase.GetType().Assembly, AssemblyRegistrationFlags.SetCodeBase) End Sub
_ Public Overloads Overrides Sub Uninstall(ByVal savedState As System.Collections.IDictionary) MyBase.Uninstall(savedState) Dim RegServices As New RegistrationServices RegServices.UnregisterAssembly(MyBase.GetType().Assembly) End Sub
End Class |
Right click
UACRegister and choose
View CodeAdd the following source code to
UACRegister.vb1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90:
| Imports System.Runtime.InteropServices Imports System.Reflection Imports Microsoft.Win32
Namespace UserAccountControlRegister
Module uacRegister
Public Sub UacRegisterElevations(ByVal ComClassId As String, ByVal ResourceStringId As Integer)
Dim ComAppId As String = GetExecAssemblyGuid() Dim ComLocation As String = GetExecAssemblyLocation() Dim ComPermission() As Byte = GetAccessPermissions()
Dim RegElevation1 As RegistryKey = Registry.ClassesRoot.OpenSubKey("CLSID\{" & ComClassId & "}", True) RegElevation1.SetValue(ElevationStrings.AppId, "{" & ComAppId & "}", RegistryValueKind.String) RegElevation1.SetValue(ElevationStrings.LocalizedString, "@" & ComLocation & ",-" & ResourceStringId.ToString, RegistryValueKind.String) RegElevation1.CreateSubKey(ElevationStrings.Elevation) RegElevation1.Close()
Dim RegElevation2 As RegistryKey = Registry.ClassesRoot.OpenSubKey("CLSID\{" & ComClassId & "}\Elevation\", True) RegElevation2.SetValue(ElevationStrings.Enabled, 1, RegistryValueKind.DWord) RegElevation2.Close()
Dim RegElevation3 As RegistryKey = Registry.ClassesRoot.OpenSubKey("AppID\", True) RegElevation3.CreateSubKey("{" & ComAppId & "}") RegElevation3.Close()
Dim RegElevation4 As RegistryKey = Registry.ClassesRoot.OpenSubKey("AppID\{" & ComAppId & "}", True) RegElevation4.SetValue(ElevationStrings.DllSurrogate, "", RegistryValueKind.String) RegElevation4.SetValue(ElevationStrings.AccessPermission, ComPermission, RegistryValueKind.Binary) RegElevation4.Close()
End Sub
Friend Function GetAccessPermissions() As Byte() Dim pSd As IntPtr Dim pSdLength As IntPtr Dim pSdBytes() As Byte = Nothing Dim lpszSDDL As String = "O:BAG:BAD:(A;;0x3;;;IU)(A;;0x3;;;SY)" If UnSafeNativeMethods.ConvertStringSecurityDescriptorToSecurityDescriptor(lpszSDDL, UnSafeNativeMethods.SDDL_REVISION_1, pSd, pSdLength) Then ReDim pSdBytes(pSdLength) Marshal.Copy(pSd, pSdBytes, 0, pSdLength) UnSafeNativeMethods.LocalFree(pSd) End If Return pSdBytes End Function
Friend Function GetExecAssemblyGuid() As String Dim id As [Assembly] Dim Attributes As Object id = [Assembly].GetExecutingAssembly Attributes = id.GetCustomAttributes(GetType(GuidAttribute), False) Return (DirectCast(Attributes(0), GuidAttribute).Value).ToString End Function
Friend Function GetExecAssemblyLocation() As String Return [Assembly].GetExecutingAssembly().Location.ToString End Function
End Module
End Namespace
Friend Module ElevationStrings
Friend LocalizedString As String = "LocalizedString" Friend DllSurrogate As String = "DllSurrogate" Friend AccessPermission As String = "AccessPermission" Friend Elevation As String = "Elevation" Friend Enabled As String = "Enabled" Friend AppId As String = "AppId"
End Module
Friend Module UnSafeNativeMethods Friend Const SDDL_REVISION_1 = 1 _ Friend Function ConvertStringSecurityDescriptorToSecurityDescriptor(<[In]()> ByVal StringSecurityDescriptor As String, <[In]()> ByVal StringSDRevision As UInt32, ByRef SecurityDescriptor As IntPtr, ByRef SecurityDescriptorSize As IntPtr) As Boolean End Function _ Friend Function LocalFree(<[In]()> ByVal hMem As IntPtr) As IntPtr End Function End Module |
Right click
UACAdmin and choose
View CodeAdd the following source code to
UACAdmin.vb1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58:
| Imports System.Runtime.InteropServices Imports COMAdmin.UserAccountControlRegister '// see [UACRegister.vb]
_ Public Class UACAdmin
#Region "COM GUIDs" ' These GUIDs provide the COM identity for this class ' and its COM interfaces. If you change them, existing ' clients will no longer be able to access the class. Public Const ClassId As String = "805509c5-bd55-4e70-a9b5-cf8438214ebd" Public Const InterfaceId As String = "62314a44-0740-4afc-9849-792d37371c91" Public Const EventsId As String = "a38756ce-6a35-4d5f-b8ac-c545dfb80266" #End Region
' A creatable COM class must have a Public Sub New() ' with no parameters, otherwise, the class will not be ' registered in the COM registry and cannot be created ' via CreateObject. Public Sub New() MyBase.New() End Sub
Public Sub Admin_CreateRootFile(ByVal szPath As String, ByVal szLineToWrite As String)
' TODO: ' Administrative task small example Dim fs As New IO.StreamWriter(szPath) fs.WriteLine(szLineToWrite) fs.Close()
End Sub
End Class
Public Class ComRegisterFunction
_ Friend Shared Sub RegisterFunction(ByVal t As Type) ' TODO: ' This registers the COM Class with elevation, only need to specify ' Class ID and Resource ID. The hard part is handled in UACRegister.vb uacRegister.UacRegisterElevations(UACAdmin.ClassId, 100)
MsgBox("RegisterFunction:[OK]" & vbCrLf & "Remove this debug message.[UACAdmin.vb]", MsgBoxStyle.SystemModal) End Sub
_ Friend Shared Sub UnregisterFunction(ByVal t As Type)
MsgBox("UnregisterFunction:[OK]" & vbCrLf & " Remove this debug message.[UACAdmin.vb]", MsgBoxStyle.SystemModal) End Sub
End Class |
Right click
COMAdmin project and choose from the [menu] >
Build- From the MyApplication project do the following:
Right click
MyApplication project and choose
PropertiesLocate the
References tab then click on the
Add.. button.
Locate
COMAdmin and add it as a reference to the MyApplication project.
Right click
Elevation and choose
View CodeAdd the following source code to
Elevation.vb1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96:
| Imports System Imports System.Threading Imports System.Security.Principal Imports System.Security.Permissions Imports System.Runtime.InteropServices
Namespace UserAccountControl
Module Elevation
Public Function CreateElevatedComObject(ByVal comclsid As Guid, ByVal comiid As Guid) As Object ' Executes a COM method with administrative rights. Dim comClsidDirective As String = comclsid.ToString("B") ' use format directive {GUID} Dim bo3 As New UnsafeNativeMethods.BIND_OPTS3 Dim comObj As Object = Nothing Dim szmoniker As String = "Elevation:Administrator!new:" & comClsidDirective bo3.cbStruct = Marshal.SizeOf(bo3) bo3.hwnd = IntPtr.Zero bo3.dwClassContext = UnsafeNativeMethods.CLSCTX_LOCAL_SERVER Try comObj = CoGetObject(szmoniker, bo3, comiid) Catch comObj = Nothing End Try
Return comObj End Function
Public Function CreateElevatedProcess(ByVal FileName As String, ByVal param As String) As Process ' Launches a processes using RunAs verb. Dim psi As New ProcessStartInfo()
psi.UseShellExecute = True psi.WorkingDirectory = Environment.CurrentDirectory psi.FileName = FileName psi.Verb = "runas" psi.Arguments = param psi.ErrorDialog = True
Return Process.Start(psi) End Function
Public Sub Button_SetElevationRequiredState(ByVal this As IntPtr, ByVal bShow As Boolean) ' Adds shield icons to buttons/links or command links UnsafeNativeMethods.SendMessage(this, BCM_SETSHIELD, IntPtr.Zero, New IntPtr(If(bShow, 1, 0))) End Sub
Public Function IsAdminRole() As Boolean Dim myPrincipal As WindowsPrincipal = CType(Thread.CurrentPrincipal, WindowsPrincipal) Dim sid As New SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, Nothing)
Return myPrincipal.IsInRole(sid) End Function
Public Function IsElevatedOs() As Boolean Dim v As OperatingSystem v = Environment.OSVersion Return If(v.Version.Major >= 6, 1, 0) End Function
End Module
End Namespace
Friend Module UnsafeNativeMethods
Friend Const CLSCTX_LOCAL_SERVER As UInteger = &H4 Friend Const BCM_SETSHIELD As UInteger = &H160C
' Windows Vista and above structure. _ Friend Structure BIND_OPTS3 Dim cbStruct As UInteger Dim grfFlags As UInteger Dim grfMode As UInteger Dim dwTickCountDeadline As UInteger Dim dwTrackFlags As UInteger Dim dwClassContext As UInteger Dim locale As UInteger Dim pServerInfo As Object Dim hwnd As IntPtr End Structure
_ Friend Function CoGetObject(ByVal pszName As String, <[In]()> ByRef pBindOptions As BIND_OPTS3, <[In](), MarshalAs(UnmanagedType.LPStruct)> ByVal riid As Guid) As Object End Function
_ Friend Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As UInt32, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr End Function
End Module |
Double click
Form1, from the toolbox add a new Button to the form
.(Button1) by default.
Add the following source code to
Form11: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55:
| Imports MyApplication.UserAccountControl Imports COMAdmin
' Visual Studio 2008 .NET 3.5 ' TODO: You can start building your application from here ' with user account control elevations ready to be ' used with your new application. ' NOTE: Don't forget to add a reference to [COMAdmin.dll] in ' this application project. ' NOTE: You must have full edition to take advanatage of the ' setup installer project. The Express editions don't ' have visual studio installer templates.
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' TODO: Example that adds a shield icon to the button ' informing the user the action requires elevated ' user rights. Button1.FlatStyle = FlatStyle.System Elevation.Button_SetElevationRequiredState(Button1.Handle, True)
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Dim clsid As Guid = New Guid(COMAdmin.UACAdmin.ClassId) Dim iid As Guid = New Guid(COMAdmin.UACAdmin.InterfaceId)
If Elevation.IsElevatedOs Then
Dim comRef As UACAdmin._UACAdmin = Nothing ' Interface comRef = Elevation.CreateElevatedComObject(clsid, iid) If IsNothing(comRef) <> True Then ' admin::task comRef.Admin_CreateRootFile("c:\uac.txt", "working")
End If
Else
If Elevation.IsAdminRole Then ' admin::task Dim comRef As New UACAdmin comRef.Admin_CreateRootFile("c:\uac.txt", "working") End If
End If
End Sub
End Class |
When you have finished choose from the Visual Stuido IDE [menu]
Build > Build Solution.
- 2
Creating and Embending Win32 resource into VB.NET
There is a few things you need to do before elevation can take place. The first is that the DLL needs to have a Win32 Resource string embended inside the DLL. This resource string is what will be displayed on the UAC prompt if it is enabled.
Locate your projects directory under windows.
example: C:\Documents and Settings\User\My Documents\Visual Studio 2008\Projects\MyApplicationCreate a new folder named:
Win32ResourceCreate a new text file named:
resource.hAdd the following:
1: 2:
| #define IDS_ELEVATION 100 |
Create a new text file named:
ElevationPrompt.rcAdd the following:
1: 2: 3: 4: 5: 6: 7:
| #include "resource.h"
STRINGTABLE BEGIN IDS_ELEVATION "My Application" END |
Create a new file named:
ElevationPrompt.batAdd the following:
1: 2:
| rc.exe ElevationPrompt.rc |
Copy
RC.EXE to the same location. You may also need to copy its dependancy
RCDLL.DLLRun
ElevationPrompt.bat a new file should be created named
ElevationPrompt.resCopy the file
ElevationPrompt.res file into the
COMAdmin project folder located in the root of your project solution.
Under the same location right click
COMAdmin.vbproj and choose
Open With > NotepadAdd the following line to the
member:
Save the changes to ComAdmin.vbproj . The VB.NET IDE should detect the change when presented with the dialog just click 'Reload'
When you have finished choose from the Visual Studio IDE [menu] Build > Rebuild Solution.
The steps described for adding the line to the project file are used for VB.NET projects. The VB.NET IDE doesn't have a direct option for adding true Win32 resources to projects. You must follow the steps described to embend the string resource into the DLL for VB.NET.
Note: C# projects can directly add resource files under the Properties > Application tab section.
- 3
Creating a Setup Installation package that automatically installs and registers the COM class
Locate the solution explorer window and select Solution 'My Application'.
Right click and choose from the [menu] Add > New > Project...
Choose from the [Add New Project] dialog Other Project Types > Setup and Deployment > Setup Project
Name the setup project: MyApplicationDeployment
- From the MyApplicationDeployment project do the following:
Locate the solution explorer window and select MyApplicationDeployment project.
Right click and choose from the [menu] Add > File...
Add the COMAdmin.dll file from the dialog.
Example: C:\Documents and Settings\User\My Documents\Visual Studio 2008\Projects\MyApplication\MyApplication\bin\Debug\COMAdmin.dll
Right click and choose from the [menu] Add > File...
Add the MyApplication.exe file from the dialog.
Example: C:\Documents and Settings\User\My Documents\Visual Studio 2008\Projects\MyApplication\MyApplication\bin\Debug\MyApplication.exe
Right click and choose from the [menu] View > Custom Actions...
Right click the Install folder and choose Add Custom Action...
When the dialog appears double click the Applications Folder option.
Select the COMAdmin.dll and choose OK.
Repeat the following steps above for the Uninstall folder.
Right click the UnInstall folder and choose Add Custom Action...
When the dialog appears double click the Applications Folder option.
Select the COMAdmin.dll and choose OK.
Right click the MyApplicationDeployment project.
Choose from the [menu] Build.
Visual Studio should have created a setup (MSI) package.
You can download the solution project template here. Everything that has been discussed in the tutorial can be located in the project.
No comments:
Post a Comment