Since the release of Windows Vista and UAC implementation. Administrative tasks that could be done on previous operating systems are ignored on Windows Vista. The bottom line is that when you run your process it's given a standard user token. You could be an administrator but when you launch a process it's given a standard user access token (by default). This means that if you need your application to do a few administrative tasks you must elevate the process access token.

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-8E7BD6B64607}

      This is the CLSID value of your COM class.

  • {59C1DB76-DAB3-4421-84FA-6AFE53ED745F}

      This is the interface identifier of your COM class.

  • {96d004e7-58c9-40f2-bd65-c2774327246b}

      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.

 
VB Resource Editor
188811
 


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.
 
 
CLSID Location
188357
 

 
 
Elevation Location
188361
 



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 ConvertStringSecurityDescriptorToSecurityDescriptorW API.
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.
 
 
AppID Location
188367
 


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.
 
BM_SETSHIELD
188530
 


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/en-us/library/ms679687(VS.85).aspx



==================================================
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? --->>
==================================================