There are several possible ways to work around the Vista UAC obstacles, some better than others:
- Using ShellExecute() and re-launching your application using RunAs verb.
Problems:
a. You close your entire process and re-launch it with administrator token.
b. You launch a separate process that has administrator tasks and ask for administrator token. Requires a separate executable and much harder to manage.
- Creating a manifest that invokes running as Administrator when a user double clicks the executable.
Problems:
a. The process asks the user at runtime for administrator access. This is annoying.
b. The process runs with administrator token for the entire lifetime.
c. While this is the most commonly used it's also the most careless.
- Creating a COM component with your administrative tasks.
Advantages:
a. The application can launch without prompting the user immediately for administrator access.
b. The application can inform the user the task will need to be elevated to run successfully.
c. The process doesn't run with full administrator token during the lifetime.
d. This is the best way to manage administrator tasks providing the best user experience.
In this article I will show how you can include your administrative tasks inside a COM component consumed in your Visual Basic application. The underlying method is based on a Microsoft article that is included in the Reference section at the end of this article.
NOTE: In order to elevate UAC priviledges, you have to do this under a different 'name' than your application. This pseudonym usage is the reason you see "moniker" being used.
NOTE: The values that will be used for this article only represent the COM object I created for use as examples in this article. They will be completely different for each COM object you compile.
- {F69522A2-E694-49ED-BB0D-
8E7BD6B646 07}
This is the CLSID value of your COM class.
- {59C1DB76-DAB3-4421-84FA-
6AFE53ED74 5F}
This is the interface identifier of your COM class.
- {96d004e7-58c9-40f2-bd65-
c277432724 6b}
This is a unique GUID that you create, you can use CoCreateGuid().
- 1
- Registry Requirements
1: 2: 3: 4: |
HKEY_LOCAL_MACHINE\Software\Classes\CLSID\
{CLSID value of your COM class}
LocalizedString = @c:\share\yourdllname.dll,-101 |
What you should know:
Data Type: REG_SZ
The LocalizedString entry is a friendly name that points to the
location of your component on the system.
The value (-101) referes to a string resource ID you create
in your COM class. The ID is equal to 101. You can choose any
valid resource number to use but it must match that of your ID
in the COM class. This entry is important because windows will
read this value from your class and use it to display the message
on the UAC dialog.
Tip:
Creating a string resource will be different for each programming language.
It's recommended that you read how to add a resource string to your COM
class for your specific language.
1: 2: 3: 4: |
HKEY_LOCAL_MACHINE\Software\Classes\CLSID\
{CLSID value of your COM class}
AppID = {96d004e7-58c9-40f2-bd65-c2774327246b} |
What you should know:
Data Type: REG_SZ
The AppID value is a unique GUID value that you create.
This value is also important, windows maps this value to another
location in the registry to obtain additional information. We'll discuss
this location later in the article.
Tip:
You can use a tool that generates GUID values.
You can use the GUID in your headers.
You can use CoCreateGuid() API.
1: 2: 3: 4: 5: |
HKEY_LOCAL_MACHINE\Software\Classes\CLSID\
{CLSID value of your COM class}
Elevation
Enabled = 1 |
What you should know:
The Elevation member is a new key you create under your CLSID key.
The Enabled is that of REG_DWORD and should be located in the
elevation key you created.
The following images show the registry layout required.
In addtion to the above mentioned registry locations.
You must also create an additional key.
1: 2: 3: |
HKEY_LOCAL_MACHINE\Software\Classes\AppID\
{GUID that you create} |
What you should know:
You must create a key value in this location
that is the value of your unique GUID. This is also the
same value you added as the AppID in previous steps.
1: 2: 3: 4: |
HKEY_LOCAL_MACHINE\Software\Classes\AppID\
{96d004e7-58c9-40f2-bd65-c2774327246b}
AccessPermission = |
What you should know:
Data Type: REG_BINARY
The AccessPermission value is a SID value that you obtain
from using ConvertStringSecurityDescr
This API returns a buffer with the data that should be written to this entry.
Tip:
See the source code examples for more information about this value.
1: 2: 3: 4: |
HKEY_LOCAL_MACHINE\Software\Classes\AppID\
{96d004e7-58c9-40f2-bd65-c2774327246b}
DllSurrogate = "" |
What you should know:
Data Type: REG_SZ
The DllSurrogate the class is a DLL that is to be activated in a
surrogate process, and the surrogate process to be used.
This value should be left blank or NULL.
The following images show the registry layout required.
That completes the registry entries that are required.
- 2
- Visual Basic Example
The following source code shows how you can obtain the AccessPermission value.
SetAccessPermissions Method
1: 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: |
Option Explicit
Private Const SDDL_REVISION_1 = 1
Private Const HKEY_LOCAL_MACHINE = &H80000002
Private Const KEY_ALL_ACCESS = &HF003F
Private Const REG_BINARY = 3
Private Const REG_SZ = 1
Private Declare Function ConvertStringSecurityDescriptorToSecurityDescriptorW Lib "advapi32" (ByVal StringSecurityDescriptor As Long, ByVal StringSDRevision As Long, ByRef SecurityDescriptor As Long, ByRef SecurityDescriptorSize As Long) As Long
Private Declare Function GetSecurityDescriptorLength Lib "advapi32" (ByVal pSecurityDescriptor As Long) As Long
Private Declare Function RegSetValueExW Lib "advapi32" (ByVal hKey As Long, ByVal lpValueName As Long, ByVal Reserved As Long, ByVal dwType As Long, ByVal lpData As Long, ByVal cbData As Long) As Long
Private Declare Function RegCreateKeyExW Lib "advapi32" (ByVal hKey As Long, ByVal lpSubKey As Long, ByVal Reserved As Long, ByVal lpClass As Long, ByVal dwOptions As Long, ByVal samDesired As Long, ByVal lpSecurityAttributes As Long, ByRef phkResult As Long, ByRef lpdwDisposition As Long) As Long
Private Declare Function RegCloseKey Lib "advapi32" (ByVal hKey As Long) As Long
Private Declare Function LocalFree Lib "kernel32" (ByVal hMem As Long) As Long
Public Sub SetAccessPermissions(ByVal hKey As Long)
'// This method is the same as both C++ functions examples.
'// GetAccessPermissionsForLUAServer
'// SetAccessPermissions
Dim lpszSDDL As String '// administrator/interactive group SID
Dim pSdLength As Long '// length of SID
Dim dwLen As Long '// same as pSdLength
Dim pSD As Long '// pointer to SID
'// administrator/interactive group SID
lpszSDDL = "O:BAG:BAD:(A;;0x3;;;IU)(A;;0x3;;;SY)"
If ConvertStringSecurityDescriptorToSecurityDescriptorW(StrPtr(lpszSDDL), SDDL_REVISION_1, pSD, pSdLength) Then
'// Microsoft examples use (GetSecurityDescriptorLength),
'// un-certain why they don't use pSdLength. I will keep
'// as per example from microsoft.
dwLen = GetSecurityDescriptorLength(pSD)
'// write the REG_BINARY (AccessPermission) value.
If RegSetValueExW(hKey, StrPtr("AccessPermission"), 0, REG_BINARY, pSD, dwLen) = 0 Then
'// success
Debug.Print "AccessPermission write success!"
End If
'// write the REG_SZ DllSurrogate value.
If RegSetValueExW(hKey, StrPtr("DllSurrogate"), 0, REG_SZ, 0, 0) = 0 Then
'// success
Debug.Print "DllSurrogate write success!"
End If
'// free the memory
LocalFree pSD
End If
End Sub |
Usage:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: |
Dim szKey As String '// location
Dim hKey As Long '// hKey
Dim dwDisp As Long '// value indicates if key exists already.
'// your unique GUID
szKey = "SOFTWARE\Classes\AppID\{96d004e7-58c9-40f2-bd65-c2774327246b}"
'// create a new KEY of your unique GUID key in the AppID key.
If RegCreateKeyExW(HKEY_LOCAL_MACHINE, StrPtr(szKey), 0, 0, 0, KEY_ALL_ACCESS, 0, hKey, dwDisp) = 0 Then
'// writes the AccessPermission value.
Call SetAccessPermissions(hKey)
'// close the handle.
RegCloseKey hKey |
The following example shows what you add to your application to execute
the administrative tasks from your COM class.
What you should know:
The rclsid parameter is your COM CLSID.
The riid parameter is your interface IID CLSID.
Tip:
You can get all this information by using the OleView tool
located in your Vistual Studio tools folder.
CoCreateInstanceAsAdmin
1: 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: |
Option Explicit
Private Const CLSCTX_LOCAL_SERVER = &H4&
Private Type GUID
data1 As Long
data2 As Integer
data3 As Integer
data4(7) As Byte
End Type
Private Type BIND_OPTS3 ' Vista +
cbStruct As Long
grfFlags As Long
grfMode As Long
dwTickCountDeadline As Long
dwTrackFlags As Long
dwClassContext As Long
locale As Long
pServerInfo As Long
hWnd As Long
End Type
Private Declare Function IIDFromString Lib "ole32" (ByVal lpszIID As Long, ByRef iid As Any) As Long
Private Declare Function CoGetObject Lib "ole32" (ByVal pszName As Long, ByRef pBindOptions As BIND_OPTS3, ByRef riid As GUID, ByRef ppv As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (dest As Any, Source As Any, ByVal bytes As Long)
Public Function ObjFromPtr(ByVal pObj As Long) As Object
'// object pointer to object
Dim obj As Object
CopyMemory obj, pObj, 4
Set ObjFromPtr = obj
CopyMemory obj, 0&, 4
End Function
Public Function CoCreateInstanceAsAdmin(ByVal hWnd As Long, _
ByVal rclsid As String, ByVal riid As String, ByRef pvv As Long) As Long
Dim bo As BIND_OPTS3 '// struct introduced in vista only.
Dim iid As GUID '// riid string to guid
Dim szMonikerName As String '// moniker string name
'// The moniker string.
szMonikerName = "Elevation:Administrator!new:" & rclsid
'// riid string to guid
IIDFromString StrPtr(riid), iid
'// BIND_OPTS3 information.
bo.cbStruct = LenB(bo)
bo.hWnd = hWnd
bo.dwClassContext = CLSCTX_LOCAL_SERVER
'// HRESULT
CoCreateInstanceAsAdmin = CoGetObject(StrPtr(szMonikerName), bo, iid, pvv)
End Function |
Usage:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: |
Dim MyObjPtr As Long
Dim ObjEgl As EGL '// COM reference.
'clsid 'iid 'out ptr
If CoCreateInstanceAsAdmin(0, _
"{F69522A2-E694-49ED-BB0D-8E7BD6B64607}", _
"{59C1DB76-DAB3-4421-84FA-6AFE53ED745F}", _
MyObjPtr) = 0 Then '//S_OK
Set ObjEgl = ObjFromPtr(MyObjPtr)
'// run admin task
ObjEgl.Test
Set ObjEgl = Nothing
Else
Debug.Print "Failed!"
End If |
- 3
- Providing a SHIELD icon
You should display to the user that the task requires additional privileges. The shield icon is used just for this purpose.
BM_SETSHIELD
Note: This message is defined on Windows Vista + and you must be linked to common controls version 6.0
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: |
Option Explicit
Private Const BM_SETSHIELD = &H160C
Private Declare Function SendMessageA Lib "user32" (ByVal hwnd As Long, _
ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Private Sub Form_Load()
SendMessageA Command1.hwnd, BM_SETSHIELD, 0, 1
End Sub |
The image displays the result of adding the icon with BM_SETSHIELD.
When the user clicks the button with the shield icon from the example image above.
They will be prompted with UAC dialog. If the user accepts and chooses yes your COM
method will be executed with administative access. Leaving the process at the intended
security level.
If you take notice to this dialog. Where it says Program Name this was the resource string you added to your COM object.
Reference:
http://msdn.microsoft.com/
==========================
If you found this article helpful you can support it by clicking 'Yes'
Located just to your right of this message. Was this article helpful? --->>
==========================


