How do I integrate UAC into my VB6 program?

10,859

Solution 1

Here's a VB6 example of ShellExecuteEx that will allow you to optionally execute any process with admin permissions. You can drop this into a module or class.

Option Explicit

Private Const SEE_MASK_DEFAULT = &H0

Public Enum EShellShowConstants
        essSW_HIDE = 0
        essSW_SHOWNORMAL = 1
        essSW_SHOWMINIMIZED = 2
        essSW_MAXIMIZE = 3
        essSW_SHOWMAXIMIZED = 3
        essSW_SHOWNOACTIVATE = 4
        essSW_SHOW = 5
        essSW_MINIMIZE = 6
        essSW_SHOWMINNOACTIVE = 7
        essSW_SHOWNA = 8
        essSW_RESTORE = 9
        essSW_SHOWDEFAULT = 10
End Enum

Private Type SHELLEXECUTEINFO
        cbSize        As Long
        fMask         As Long
        hwnd          As Long
        lpVerb        As String
        lpFile        As String
        lpParameters  As String
        lpDirectory   As String
        nShow         As Long
        hInstApp      As Long
        lpIDList      As Long     'Optional
        lpClass       As String   'Optional
        hkeyClass     As Long     'Optional
        dwHotKey      As Long     'Optional
        hIcon         As Long     'Optional
        hProcess      As Long     'Optional
End Type

Private Declare Function ShellExecuteEx Lib "shell32.dll" Alias "ShellExecuteExA" (lpSEI As SHELLEXECUTEINFO) As Long

Public Function ExecuteProcess(ByVal FilePath As String, ByVal hWndOwner As Long, ShellShowType As EShellShowConstants, Optional EXEParameters As String = "", Optional LaunchElevated As Boolean = False) As Boolean
    Dim SEI As SHELLEXECUTEINFO

    On Error GoTo Err

    'Fill the SEI structure
    With SEI
        .cbSize = Len(SEI)                  ' Bytes of the structure
        .fMask = SEE_MASK_DEFAULT           ' Check MSDN for more info on Mask
        .lpFile = FilePath                  ' Program Path
        .nShow = ShellShowType              ' How the program will be displayed
        .lpDirectory = PathGetFolder(FilePath)
        .lpParameters = EXEParameters       ' Each parameter must be separated by space. If the lpFile member specifies a document file, lpParameters should be NULL.
        .hwnd = hWndOwner                   ' Owner window handle

        ' Determine launch type (would recommend checking for Vista or greater here also)
        If LaunchElevated = True Then ' And m_OpSys.IsVistaOrGreater = True
            .lpVerb = "runas"
        Else
            .lpVerb = "Open"
        End If
    End With

     ExecuteProcess = ShellExecuteEx(SEI)   ' Execute the program, return success or failure

    Exit Function
Err:
    ' TODO: Log Error
    ExecuteProcess = False
End Function

Private Function PathGetFolder(psPath As String) As String
    On Error Resume Next
    Dim lPos As Long
    lPos = InStrRev(psPath, "\")
    PathGetFolder = Left$(psPath, lPos - 1)
End Function

Solution 2

Code examples can really run on, but here is a trivial one showing the "second instance of me" approach.

The program has a startup static module with a few public functions including an "elevated operation" handler, and a Form with just one CommandButton on it:

Module1.bas

Option Explicit

Private Const BCM_SETSHIELD As Long = &H160C&

Private Declare Sub InitCommonControls Lib "comctl32" ()

Private Declare Function IsUserAnAdmin Lib "shell32" () As Long

Private Declare Function SendMessage Lib "user32" _
    Alias "SendMessageA" ( _
    ByVal hWnd As Long, _
    ByVal wMsg As Long, _
    ByVal wParam As Long, _
    ByRef lParam As Any) As Long

Private Declare Function ShellExecute Lib "shell32" _
    Alias "ShellExecuteA" ( _
    ByVal hWnd As Long, _
    ByVal lpOperation As String, _
    ByVal lpFile As String, _
    ByVal lpParameters As String, _
    ByVal lpDirectory As String, _
    ByVal nShowCmd As VbAppWinStyle) As Long

Private mblnIsElevated As Boolean

Public Function IsElevated() As Boolean
    IsElevated = mblnIsElevated
End Function

Public Sub OperationRequiringElevation(ByRef Params As Variant)
    MsgBox "Insert logic here for: " & vbNewLine _
         & Join(Params, vbNewLine)
End Sub

Public Sub RequestOperation( _
    ByVal hWnd As Long, _
    ByVal Focus As VbAppWinStyle, _
    ByRef Params As Variant)

    ShellExecute hWnd, "runas", App.EXEName & ".exe", _
                 Join(Params, " "), CurDir$(), Focus
End Sub

Public Sub SetShield(ByVal hWnd As Long)
    SendMessage hWnd, BCM_SETSHIELD, 0&, 1&
End Sub

Private Sub Main()
    If Len(Command$()) > 0 Then
        'Assume we've been run elevated to execute an operation
        'specified as a set of space-delimited strings.
        OperationRequiringElevation Split(Command$(), " ")
    Else
        mblnIsElevated = IsUserAnAdmin()
        InitCommonControls
        Form1.Show
    End If
End Sub

Form1.frm

Option Explicit

Private Sub Command1_Click()
    Dim Params As Variant

    Params = Array("ReplaceFile", "abc", "123")
    If IsElevated() Then
        OperationRequiringElevation Params
    Else
        RequestOperation hWnd, vbHide, Params
    End If
End Sub

Private Sub Form_Load()
    If Not IsElevated() Then
        SetShield Command1.hWnd
    End If
End Sub

The application has a simple "asInvoker" manifest selecting the Common Controls 6.0 assembly.

Solution 3

First, take the code that runs when someone clicks the button, and put it in a separate exe. Change your button-click code to launch the exe using ShellExecute. Second, build external manifests for each new exe and have it specify requireAdministrator. Third, send your buttons the BCM_SETSHIELD message (you will probably have to look up the numerical value of the message ID) to make the shield appear on them.

Solution 4


  1. Move all of the code that requires elevation into external processes.
  2. Send your buttons the BCM_SETSHIELD message to add the shield icon.
  3. Embed manifests into those processes, telling Windows that they require elevation. See below.



In order to force Vista and higher to run a VB6 exe as administrator in UAC, you must embed a manifest xml as a resource inside of it. Steps follow;

  1. Create the manifest file. Name it "YourProgram.exe.manifest" it should contain the following. The important line is the "requestedExecutionLevel". Change the attributes in to match your exe.

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>>
    <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
      <assemblyIdentity
        version="1.0.0.0"
        processorArchitecture="X86"
        name="YourProgram"
        type="win32"
      >
      <description>application description</description>
      <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
        <security
          <requestedPrivileges>
            <requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
          </requestedPrivileges>
        </security>
      </trustInfo>
    </assembly>
    
  2. Create a file named "YourProgram.exe.manifest.rc". It should contain the following.

    #define CREATEPROCESS_MANIFEST_RESOURCE_ID 1
    #define RT_MANIFEST 24 CREATEPROCESS_MANIFEST_RESOURCE_ID
    RT_MANIFEST "YourProgram.exe.manifest"

  3. Compile your resource using rc.exe. It is located by default in C:\Program Files\Microsoft Visual Studio\COMMON\MSDev98\Bin. This will create a file called YourProgram.exe.manifest.RES. The syntax is;

    rc /r YourProgram.exe.manifest.rc

  4. Add the .RES file to your project. Do this using the Resource Editor Add-In in VB6. The icon on the toolbar looks like green blocks. If you do not have the icon, make sure it is enabled in the addin manager. If it is not in the addin manager, you need to regsvr32 on C:\Program Files\Microsoft Visual Studio\VB98\Wizards\Resedit.dll. Once you've got the resource editor open, click open and select your .RES file.

  5. Compile your project.

  6. To double check that the manifest was embedded properly, you can use a tool called InspectExe. In explorer, go to the properties of the exe, and if the manifest was embedded you should have a manifest tab (.Net assemblies will also have this manifest tab).

  7. Try running your program on Vista or later. If UAC is indeed enabled, it should come up with the prompt right away.

Share:
10,859
WindozeNT
Author by

WindozeNT

I am the founder of WindozeNT Software.

Updated on June 13, 2022

Comments

  • WindozeNT
    WindozeNT almost 2 years

    I need some code that will add the admin rights icon to command buttons and display the prompt when such buttons are clicked. How can I do this in VB6? Some actions require admin rights because they replace files and stuff where Windows Vista/7 don't allow the program normal access to the files.

  • Joe Jordan
    Joe Jordan over 13 years
    I would just suggest, rather than creating a separate exe, launching a 2nd instance of the app with a command line argument indicating you just want to perform this specific task, then exit.
  • Kate Gregory
    Kate Gregory over 13 years
    Problem with the second instance of yourself is the manifest. You want the second instance to be elevated and the first one not to be. Same app means same manifest, so that won't work.
  • Joe Jordan
    Joe Jordan over 13 years
    Kate, you can use ShellExecuteEx and pass it a SHELLEXECUTEINFO struct that has .lpFile as the app path and .lpVerb = "runas". That will launch the existing app with admin privileges.
  • WindozeNT
    WindozeNT over 13 years
    How would I do this though? Example(s)?
  • WindozeNT
    WindozeNT over 13 years
    How do I add the shield icon to the button?
  • WindozeNT
    WindozeNT over 13 years
    Nevermind I found BCM_SETSHEILD for VB6. How do I get the prompt for performing an action instead for starting the program. Like where it says "Windows needs your permission to continue" instead of "An unidentified program wants to access your computer"? And what's the syntax to run?
  • WindozeNT
    WindozeNT over 13 years
    Error logging example: Open <filename> For Output As FreeFile
  • Joe Jordan
    Joe Jordan over 13 years
    To get the less scary prompt, you'll need to sign your executable with a code signing certificate. KSoftware is a well-known and reliable reseller for Comodo code signing certificates: secure.ksoftware.net/code_signing.html
  • WindozeNT
    WindozeNT about 13 years
    I'm using Visual Basic 6 Portable, so it's just IDE only.
  • WindozeNT
    WindozeNT about 13 years
    @JoeJordan Do you have the singtool utility? I need it, but just the EXE only instead of the full SDK thing.
  • Joe Jordan
    Joe Jordan about 13 years
    Try the web installer, it should let you select only the components you need: bit.ly/hBBhZr
  • Bob77
    Bob77 over 12 years
    There is no "Portable Edition" - you're using stolen software.
  • Bob77
    Bob77 over 12 years
    You do not need these RC.EXE gyrations to embed an application manifest. Just create your manifest file as ANSI text (close enough to UTF-8 normally), make sure it is padded to an even multiple of 4 bytes, then add it as a Custom Resource using the IDE Resource Editor and set its Type to #24 and Id to #1 and Save.
  • N_A
    N_A about 12 years
    @WindozeNT If working with VB6 Portable is part of your question then it should be in the question. If VB6 Portable is part of your question, then it's not a valid question (encourages use of illegal software). Either accept one of the current (quite excellent) answers, or close the question.
  • WindozeNT
    WindozeNT almost 12 years
    It seems BCM_SETSHIELD is broken in Win7 because even though the prompt displays, the shield icon doesn't show up on the button.