Elevated Credentials for VB6 - vb6

I need to get elevated credentials (to start a service) in a VB6 application, but only if the user needs to restart the service (I.e. I don't want to get elevated credentials whenever the application is started, only when the user selects restart). How can I do this in VB6?

Fairly easy, but the preferred way involves a new elevated process. This example uses itself run with a switch to know to perform the Service Start instead of normal operations:
VERSION 5.00
Begin VB.Form Form1
BorderStyle = 1 'Fixed Single
Caption = "Form1"
ClientHeight = 3060
ClientLeft = 45
ClientTop = 345
ClientWidth = 4560
LinkTopic = "Form1"
MaxButton = 0 'False
MinButton = 0 'False
ScaleHeight = 3060
ScaleWidth = 4560
StartUpPosition = 3 'Windows Default
Begin VB.CommandButton Command1
Caption = "Start Service"
Height = 495
Left = 1448
TabIndex = 0
Top = 1283
Width = 1665
End
End
Attribute VB_Name = "Form1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Option Explicit
Private Const BCM_SETSHIELD As Long = &H160C&
Private Declare Function SendMessage Lib "user32" _
Alias "SendMessageA" ( _
ByVal hWnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
lParam As Any) As Long
Private Declare Function ShellExecute Lib "shell32.dll" _
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 Long) As Long
Private Sub Command1_Click()
ShellExecute hWnd, "runas", App.EXEName & ".exe", "-start", CurDir$(), vbNormalFocus
End Sub
Private Sub Form_Load()
If UCase$(Trim$(Command$())) = "-START" Then
Caption = "Starting Service"
Command1.Visible = False
'Service starting functionality goes here.
Else
Caption = "Service Starter"
'For Shield to work you must have a Common Controls v. 6
'manifest and call InitCommonControls before loading
'this form (i.e. preferably from Sub Main).
SendMessage Command1.hWnd, BCM_SETSHIELD, 0&, 1&
Command1.Visible = True
End If
End Sub

One solution is to use the COM elevation moniker http://msdn.microsoft.com/en-us/library/ms679687(VS.85).aspx.
This link should be useful if your target is VB6 http://www.vbforums.com/showthread.php?t=459643.

You'll need to call into the WinAPI - CoImpersonateClient, or LogonUser.
Just remember to lower your privileges afterwards, and be DARN careful what you do when elevated (e.g. don't do ANYthing with user input).
Another option, which I believe is preferable (if available), is to use a COM+ configured object. You can have the COM+ subsystem manage credentials, and just limit access to call the object as necessary. This has the benefit of creating and ACTUAL trust boundary between low-privileged code and high-privileged code.

Related

Microsoft Access script cannot connect to device camera

I have been working on an Access Database where the user can click a button and the code will access the "webcam" present on the computer and then proceed to take a picture. The code works fine on regular laptops but I tried running it on a Laptop/Tablet hybrid (Latitude 5290 2-in-1 Laptop) and for whatever reason, the code does not access the camera at all.
I downloaded a third party app called Dorgem and tried to access the camera but got an error saying "Failed to connect to device." To me, it sounds like a permission issue but I made sure that camera permission is enabled in settings (https://www.tenforums.com/tutorials/71414-allow-deny-os-apps-access-camera-windows-10-a.html). I strongly believe that it is still a permission issue but I cannot find a way around it. I would really appreciate if I can get some input on how to solve this issue.
Here is the code I have been using in access.
Option Compare Database
Option Explicit
Public ImageLocation As String
Public AttachmentIndicator As String
Const WS_CHILD As Long = &H40000000
Const WS_VISIBLE As Long = &H10000000
Const WM_USER As Long = &H400
Const WM_CAP_START As Long = WM_USER
Const WM_CAP_DRIVER_CONNECT As Long = WM_CAP_START + 10
Const WM_CAP_DRIVER_DISCONNECT As Long = WM_CAP_START + 11
Const WM_CAP_SET_PREVIEW As Long = WM_CAP_START + 50
Const WM_CAP_SET_PREVIEWRATE As Long = WM_CAP_START + 52
Const WM_CAP_DLG_VIDEOFORMAT As Long = WM_CAP_START + 41
Const WM_CAP_FILE_SAVEDIB As Long = WM_CAP_START + 25
Private Declare PtrSafe Function capCreateCaptureWindow _
Lib "avicap32.dll" Alias "capCreateCaptureWindowA" _
(ByVal lpszWindowName As String, ByVal dwStyle As Long _
, ByVal X As Long, ByVal Y As Long, ByVal nWidth As Long _
, ByVal nHeight As Long, ByVal hwndParent As LongPtr _
, ByVal nID As Long) As Long
Private Declare PtrSafe Function SendMessage Lib "user32" _
Alias "SendMessageA" (ByVal hWnd As LongPtr, ByVal wMsg As Long _
, ByVal wParam As Long, ByRef lParam As Any) As Long
Dim hCap As LongPtr
Private Sub TakePictureButton_Click()
Dim sFileName As String
Call SendMessage(hCap, WM_CAP_SET_PREVIEW, CLng(False), 0&)
sFileName = "C:\Users\212764307\Documents\" & Forms!IRForm.IRNO & ".jpg"
ImageLocation = sFileName
Call SendMessage(hCap, WM_CAP_FILE_SAVEDIB, 0&, ByVal CStr(sFileName))
DoFinally:
Call SendMessage(hCap, WM_CAP_SET_PREVIEW, CLng(True), 0&)
End Sub
Private Sub Cmd3_Click()
Dim Temp As Long
Temp = SendMessage(hCap, WM_CAP_DRIVER_DISCONNECT, 0&, 0&)
DoCmd.Close
End Sub
Private Sub StartCameraButton_Click()
hCap = capCreateCaptureWindow("Take a Camera Shot", WS_CHILD Or WS_VISIBLE, 0, 0, PicWebCam.Width, PicWebCam.Height, PicWebCam.Form.hWnd, 0)
If hCap <> 0 Then
Call SendMessage(hCap, WM_CAP_DRIVER_CONNECT, 0, 0)
Call SendMessage(hCap, WM_CAP_SET_PREVIEWRATE, 66, 0&)
Call SendMessage(hCap, WM_CAP_SET_PREVIEW, CLng(True), 0&)
End If
End Sub
Private Sub Cmd2_Click()
Dim Temp As Long
Temp = SendMessage(hCap, WM_CAP_DLG_VIDEOFORMAT, 0&, 0&)
End Sub
Private Sub Form_Load()
StartCameraButton.Caption = "Start Camera"
cmd2.Caption = "&Format Cam"`enter code here`
cmd3.Caption = "&Close Cam"
TakePictureButton.Caption = "&Take Picture"
End Sub'
Use newest version of avicap32.dll (copy to windows\system32 and windows\sysWOW64 directories,also check camera drivers, there is always problem on them with avicap.

VB6 mouse hook to capture a control a user clicks

I have a keyboard hook that listens for [shift] + [F12] key button press to activate an edit mode in my program. By edit mode, I mean that any form that is inactive in the program window is disabled and focus is set to the active window. Furthermore, I alter the GUI to reflect that the user is running edit mode.
The purpose is of this all is to customize specific form controls that a user clicks on (e.g If they click on a label or combobox, a user would be able to edit the data that populates this information from a database). What I am really searching for is the ability to access the control name of the control that a user clicks on in the active form, DYNAMICALLY (without setting events on each form). Therefore, once a user clicks on a control such as a label, combo box, listview or listbox (on the active form), I would like to capture the control name clicked and pass that to another form that will handle the editing of this control.
You don't need to go to the trouble of using the API for what you want to do. All of the controls you mention expose a Click event. (If the control you want to use doesn't have a Click event, it almost certainly has a MouseDown event which will work just as well.) Just write a sub that takes the control as an argument and passes what info you want to the other form. Then in each of the controls (you can use control arrays for controls of the same type), call this sub. Something like this:
Sub DoTheWork(cCtl As Control)
Form2.CallSomeMethod(cCtl) 'Passes a reference to the entire control
Form2.CallSomeOtherMethod(cCtl.Name) 'Just passes the name
End Sub
Sub Command1_Click()
DoTheWork Command1
End Sub
Sub Label1_Click(Index As Integer) 'control array
DoTheWork Label1(Index)
End Sub
Now, if you really want to get involved in using SetWindowsHookEx and all that, here's a bit of annotated code that you can use to figure it out. This code allows you to change fonts on the MsgBox function, by substituting itself for any MsgBox call. (FYI, Microsoft implemented "CBT hooking" to support computer-based training back in the day, hence the term.)
'This code allows font changes and various other format customizations of the standard VB6 MsgBox dialog box. It
'uses CBT hooking to intercept an VB6-internal window call. In this case, it intercepts a MsgBox call, then gets
'a handle to the MsgBox window as well as its various child windows (the label containing the message text, any
'buttons, and an icon if it exists). It then resizes the window to accommodate the message text and other windows,
'and repositions the icon and any command buttons. Finally, it positions the msgbox window in the center of the
'screen.
'General Note: notes are above the line of code to which they apply.
Option Explicit
' Window size and position constants
Private Const ICON_WIDTH As Integer = 32
Private Const BTN_WIDTH As Integer = 75
Private Const BTN_HEIGHT As Integer = 23
Private Const BTN_SPACER As Integer = 6 ' Space between 2 buttons
Private Const STW_OFFSET As Integer = 12 ' Standard window offset, minimum distance one window can be from
' the edge of its container
' SendMessage constants that we will use
Private Const WM_SETFONT = &H30
Private Const WM_GETTEXT = &HD
' Necessary constants for CBT hooking
Private Const HCBT_CREATEWND = 3
Private Const HCBT_ACTIVATE = 5
Private Const WH_CBT = 5
' Working variables that require module-wide scope
Private hHook As Long
Private myFont As IFont
Private cPrompt As String
Private hwndStatic As Long
Private ButtonHandles() As Long
Private xPixels As Long
Private yPixels As Long
Private isIcon As Boolean
' The API Type declarations we need
Private Type SIZE
cx As Long
cy As Long
End Type
Private Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Private Declare Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As Long, ByVal hmod As Long, ByVal dwThreadId As Long) As Long
Private Declare Function UnhookWindowsHookEx Lib "user32" (ByVal hHook As Long) As Long
'GETTEXT needs a String argument for lParam, SETFONT needs an Any argument there, hence 2 declarations for SendMessageA
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Private Declare Function SendMessageS Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As String) As Long
Private Declare Function SetWindowPos Lib "user32" (ByVal hwnd As Long, ByVal hWndInsertAfter As Long, ByVal x As Long, ByVal y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long
Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Private Declare Function EnumChildWindows Lib "user32" (ByVal hWndParent As Long, ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long
Private Declare Function GetClientRect Lib "user32" (ByVal hwnd As Long, lpRect As RECT) As Long
' Wrapper for the normal MsgBox function
Public Function myMsgBox(Prompt As String, Buttons As VbMsgBoxStyle, ByVal fSize As Integer, ByVal fBold As Boolean, ByVal fItalic As Boolean, ByVal fULine As Boolean, fFaceName As String, Optional Title As String, Optional HelpFile As String, Optional Context As Long, Optional x As Long, Optional y As Long) As Long
'x and y arguments are optional and are in twips. If not specified, msgbox will use default window sizes
'and positions, which work fine if you are using default font sizes. If you aren't they may not.
cPrompt = Prompt
Set myFont = New StdFont
With myFont ' We can make whatever adjustments we like here to the font
.SIZE = fSize
.Bold = fBold
.Italic = fItalic
.Underline = fULine
.Name = fFaceName
End With
'Convert x and y arguments to pixels from twips. (Twips are the same size no matter what the screen resolution; pixels aren't.)
If Not IsMissing(x) Then
xPixels = Int(x / Screen.TwipsPerPixelX)
End If
If Not IsMissing(y) Then
yPixels = Int(y / Screen.TwipsPerPixelY)
End If
'Set up the hook to catch windows messages, call CBTProc when there is one
hHook = SetWindowsHookEx(WH_CBT, AddressOf CBTProc, App.hInstance, 0)
'This will call CBTProc, passing the handle of the MsgBox window to the wParam argument.
myMsgBox = MsgBox(Prompt, Buttons, Title, HelpFile, Context)
End Function
Private Function CBTProc(ByVal lMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Dim i As Integer
Dim statX As Integer 'X dimension of static (text) window
Dim statY As Integer 'Y dimension of same
Dim cLeft As Integer 'Current Left value for current button, used to position buttons along x axis
Dim rc As RECT 'Used with GetClientRect
If lMsg = HCBT_ACTIVATE Then
'Immediately unhook (we have the event we're looking for, and don't want to handle any more CBT events)
UnhookWindowsHookEx hHook
'Call EnumChildWindowProc once for each window that is contained in the MsgBox (each button and frame is a child window)
EnumChildWindows wParam, AddressOf EnumChildWindowProc, 0
'Reinitialize the static buttoncount variable, see the notes in the proc
EnumChildWindowProc 0, 1
'Should always be true, but this prevents an abend if for some reason we fail to get the text window
If hwndStatic Then
'If the x parameter has been supplied to the main wrapper, then xPixels <> 0
If xPixels <> 0 Then
With Screen
'Center the MsgBox window in the screen
SetWindowPos wParam, 0, (.Width / .TwipsPerPixelX - xPixels) / 2, _
(.Height / .TwipsPerPixelY - yPixels) / 2, xPixels, yPixels, 0
End With
'Analogous to the ScaleWidth and ScaleHeight properties. Client rectangle's dimensions are
'returned to the rc type and exclude the dimensions of the title bar and the borders.
GetClientRect wParam, rc
'Calculate x and y values for text window. If there's an icon, we need to reduce the size of the
'text window by the width of the icon plus a standard offset value.
statX = rc.Right - rc.Left - STW_OFFSET * 2 - ((isIcon And 1) * (ICON_WIDTH + STW_OFFSET))
statY = rc.Bottom - rc.Top - BTN_HEIGHT - STW_OFFSET * 2
'We need to position the text window along the x axis such that it's a standard offset from the left
'border of the msgbox, plus the width of the icon and another standard offset if the icon exists.
SetWindowPos hwndStatic, 0, STW_OFFSET + (isIcon And 1) * (ICON_WIDTH + STW_OFFSET), STW_OFFSET, statX, statY, 0
isIcon = 0
'Loop through the button handles, calculating the left border position each time.
For i = 0 To UBound(ButtonHandles)
'Current left border is half the container window's width, less the width of half the total
'number of buttons, plus the offset of the current button in the array.
cLeft = Int(xPixels / 2 + BTN_WIDTH * (i - (UBound(ButtonHandles) + 1) / 2))
'Modify the above to add button spacer widths.
cLeft = cLeft + BTN_SPACER * (i - (UBound(ButtonHandles) - 1) + (UBound(ButtonHandles) Mod 2) / 2)
'The Y value is 1 standard offset more than the height of the text window.
SetWindowPos ButtonHandles(i), 0, cLeft, statY + STW_OFFSET, BTN_WIDTH, BTN_HEIGHT, 0
Next
End If
SendMessage hwndStatic, WM_SETFONT, myFont.hFont, True
End If
End If
CBTProc = 0 ' allow operation to continue
End Function
Private Function EnumChildWindowProc(ByVal hChild As Long, ByVal lParam As Long) As Long
Static ButtonCount As Integer
Dim sLen As Integer
Dim wClass As String
Dim wText As String
Dim rc As RECT
If lParam Then
ButtonCount = 0 'See the direct call of this proc in CBTProc: resets the ButtonCount variable to 0
Exit Function
End If
wClass = String(64, 0)
'look up the type of the current window
sLen = GetClassName(hChild, wClass, 63)
wClass = Left(wClass, sLen)
'We have either one or two static windows: optionally the icon (the first window if it's there) and the
'text window (analogous to a label control).
If wClass = "Static" Then
'If we already have the text window's handle, we don't need to do this anymore.
If Not hwndStatic Then
'Find out if the current window's text value is the same as the text passed in to the cPrompt
'argument in the main wrapper function. If it is, it's the text window and we store the handle
'value in hwndStatic. If it isn't, then it's an icon and we set the isIcon flag.
wText = String(Len(cPrompt) + 1, 0)
sLen = SendMessageS(hChild, WM_GETTEXT, 255, wText)
wText = Left(wText, sLen)
If wText = cPrompt Then
hwndStatic = hChild
Else
isIcon = True
End If
End If
ElseIf wClass = "Button" Then
'Store the button's handle in the ButtonHandles array
ReDim Preserve ButtonHandles(ButtonCount)
ButtonHandles(ButtonCount) = hChild
ButtonCount = ButtonCount + 1
End If
EnumChildWindowProc = 1 ' Continue enumeration
End Function

How can I stop Excel workbook flicker on automation open?

I'm using GetObject with a workbook path to either create a new or grab an existing Excel instance. If it's grabbing an existing user-created instance, the application window is visible; if the workbook path in question is closed, it will open and hide, but not before it flickers on the screen. Application.ScreenUpdating does not help with this.
I don't think I can use the Win32Api call LockWindowUpdate, because I don't know whether I'm getting or creating before the file is open. Is there some other VBA-friendly way (i.e. WinAPI) to freeze the screen long enough to get the object?
EDIT: Just to clarify, because the first answer suggests using the Application object... These are the steps to reproduce this behavior.
1. Open Excel--make sure you're only running one instance--save and close the default workbook. Excel window now visible but "empty"
2. Open Powerpoint or Word, insert a module, add the following code
Public Sub Open_SomeWorkbook()
Dim MyObj As Object
Set MyObj = GetObject("C:\temp\MyFlickerbook.xlsx")
'uncomment the next line to see the workbook again'
'MyObj.Parent.Windows(MyObj.Name).Visible = True'
'here's how you work with the application object... after the fact'
Debug.Print MyObj.Parent.Version
End Sub
Note the flicker as Excel opens the file in the existing instance, and then hides it... because it's automation
Note also, however, that there is no application object to work with, until the flickering is done. This is why I'm looking for some larger API method to "freeze" the screen.
Try,
Application.VBE.MainWindow.Visible = False
If that doesn't work try
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal ClassName As String, ByVal WindowName As String) As Long
Private Declare Function LockWindowUpdate Lib "user32" _
(ByVal hWndLock As Long) As Long
Sub EliminateScreenFlicker()
Dim VBEHwnd As Long
On Error GoTo ErrH:
Application.VBE.MainWindow.Visible = False
VBEHwnd = FindWindow("wndclass_desked_gsk", _
Application.VBE.MainWindow.Caption)
If VBEHwnd Then
LockWindowUpdate VBEHwnd
End If
'''''''''''''''''''''''''
' your code here
'''''''''''''''''''''''''
Application.VBE.MainWindow.Visible = False
ErrH:
LockWindowUpdate 0&
End Sub
Both found here Eliminating Screen Flicker During VBProject Code
Ok you didn't mention multiple instances... [1. Open Excel--make sure you're only running one instance] :)
How about something like this.....
Public Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Public Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" _
(ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Public Declare PtrSafe Function ShowWindow Lib "user32" (ByVal lHwnd As Long, _
ByVal lCmdShow As Long) As Boolean
Public Declare PtrSafe Function LockWindowUpdate Lib "user32" (ByVal hwndLock As Long) As Long
Sub GetWindowHandle()
Const SW_HIDE As Long = 0
Const SW_SHOW As Long = 5
Const SW_MINIMIZE As Long = 2
Const SW_MAXIMIZE As Long = 3
'Const C_WINDOW_CLASS = "XLMAIN"
Const C_WINDOW_CLASS = vbNullString
Const C_FILE_NAME = "Microsoft Excel - Flickerbook.xlsx"
'Const C_FILE_NAME = vbNullString
Dim xlHwnd As Long
xlHwnd = FindWindow(lpClassName:=C_WINDOW_CLASS, _
lpWindowName:=C_FILE_NAME)
'Debug.Print xlHwnd
if xlHwnd = 0 then
Dim MyObj As Object
Dim objExcel As Excel.Application
Set objExcel = GetObject(, "Excel.Application")
objExcel.ScreenUpdating = False
Set MyObj = GetObject("C:\temp\MyFlickerbook.xlsx")
'uncomment the next line to see the workbook again'
'MyObj.Parent.Windows(MyObj.Name).Visible = True
'here's how you work with the application object... after the fact'
Debug.Print MyObj.Parent.Version
MyObj.Close
objExcel.ScreenUpdating = True
else
'Either HIDE/SHOW or MINIMIZE/MAXIMISE
ShowWindow xlHwnd, SW_HIDE
Set MyObj = GetObject("C:\temp\MyFlickerbook.xlsx")
'manage MyObj
ShowWindow xlHwnd, SW_SHOW
'Or LockWindowUpdate then Unlock
LockWindowUpdate xlHwnd
Set MyObj = GetObject("C:\temp\MyFlickerbook.xlsx")
'manage MyObj
LockWindowUpdate 0
end if
' 'Get Window Name
' Dim strWindowTitle As String
' strWindowTitle = Space(260) ' We must allocate a buffer for the GetWindowText function
' Call GetWindowText(xlHwnd, strWindowTitle, 260)
' debug.print (strWindowTitle)
End Sub
I ended up basically ditching GetObject, because it wasn't granular enough, and wrote my own flickerless opener, with some inspiration from osknows and great code samples from here and here. Thought I would share it in case others found it useful. First the complete module
'looping through, parent and child (see also callbacks for lpEnumFunc)
Private Declare Function EnumWindows Lib "user32.dll" (ByVal lpEnumFunc As Long, _
ByVal lParam As Long) As Long
Private Declare Function EnumChildWindows Lib "user32.dll" (ByVal hWndParent As Long, _
ByVal lpEnumFunc As Long, _
ByVal lParam As Long) As Long
'title of window
Private Declare Function GetWindowTextLength Lib "user32.dll" Alias "GetWindowTextLengthA" (ByVal hWnd As Long) As Long
Private Declare Function GetWindowText Lib "user32.dll" Alias "GetWindowTextA" (ByVal hWnd As Long, _
ByVal lpString As String, _
ByVal cch As Long) As Long
'class of window object
Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hWnd As Long, _
ByVal lpClassName As String, _
ByVal nMaxCount As Long) As Long
'control window display
Private Declare Function ShowWindow Lib "user32" (ByVal lHwnd As Long, _
ByVal lCmdShow As Long) As Boolean
Private Declare Function BringWindowToTop Lib "user32" (ByVal hWnd As Long) As Long
Private Declare Function SetFocus Lib "user32" (ByVal hWnd As Long) As Long
Public Enum swcShowWindowCmd
swcHide = 0
swcNormal = 1
swcMinimized = 2 'but activated
swcMaximized = 3
swcNormalNoActivate = 4
swcShow = 5
swcMinimize = 6 'activates next
swcMinimizeNoActivate = 7
swcShowNoActive = 8
swcRestore = 9
swcShowDefault = 10
swcForceMinimized = 11
End Enum
'get application object using accessibility
Private Declare Function AccessibleObjectFromWindow Lib "oleacc" (ByVal hWnd As Long, _
ByVal dwId As Long, _
ByRef riid As GUID, _
ByRef ppvObject As Object) _
As Long
Private Declare Function IIDFromString Lib "ole32" (ByVal lpsz As Long, _
ByRef lpiid As GUID) As Long
'Const defined in winuser.h
Private Const OBJID_NATIVEOM As Long = &HFFFFFFF0
'IDispath pointer to native object model
Private Const Guid_Excel As String = "{00020400-0000-0000-C000-000000000046}"
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(7) As Byte
End Type
'class names to search by (Excel, in this example, is XLMAIN)
Private mstrAppClass As String
'title (a.k.a. pathless filename) to search for
Private mstrFindTitle As String
'resulting handle outputs - "default" app instance and child with object
Private mlngFirstHwnd As Long
Private mlngChildHwnd As Long
'------
'replacement GetObject
'------
Public Function GetExcelWbk(pstrFullName As String, _
Optional pbleShow As Boolean = False, _
Optional pbleWasOpenOutput As Boolean) As Object
Dim XLApp As Object
Dim xlWbk As Object
Dim strWbkNameOnly As String
Set XLApp = GetExcelAppForWbkPath(pstrFullName, pbleWasOpenOutput)
'other stuff can be done here if the app needs to be prepared for the load
If pbleWasOpenOutput = False Then
'load it, without flicker, if you plan to show it
If pbleShow = False Then
XLApp.ScreenUpdating = False
End If
Set xlWbk = XLApp.Workbooks.Open(pstrFullName)
Else
'get it by its (pathless, if saved) name
strWbkNameOnly = PathOrFileNm("FileNm", pstrFullName)
Set xlWbk = XLApp.Workbooks(strWbkNameOnly)
End If
Set GetExcelWbk = xlWbk
Set xlWbk = Nothing
Set XLApp = Nothing
End Function
Private Function GetExcelAppForWbkPath(pstrFullName As String, _
pbleWbkWasOpenOutput As Boolean, _
Optional pbleLoadAddIns As Boolean = True) As Object
Dim XLApp As Object
Dim bleAppRunning As Boolean
Dim lngHwnd As Long
'get a handle, and determine whether it's for a workbook or an app instance
lngHwnd = WbkOrFirstAppHandle(pstrFullName, pbleWbkWasOpenOutput)
'if a handle came back, at least one instance of Excel is running
'(this isnt' particularly useful; just check XLApp.Visible when you're done getting/opening;
'if it's a hidden instance, it wasn't running)
bleAppRunning = (lngHwnd > 0)
'get an app instance.
Set XLApp = GetAppForHwnd(lngHwnd, pbleWbkWasOpenOutput, pbleLoadAddIns)
Set GetExcelAppForWbkPath = XLApp
Set XLApp = Nothing
Exit Function
End Function
Private Function WbkOrFirstAppHandle(pstrFullName As String, _
pbleIsChildWindowOutput As Boolean) As Long
Dim retval As Long
'defaults
mstrAppClass = "XLMAIN"
mstrFindTitle = PathOrFileNm("FileNm", pstrFullName)
mlngFirstHwnd = 0
mlngChildHwnd = 0
'find
retval = EnumWindows(AddressOf EnumWindowsProc, 0)
If mlngChildHwnd > 0 Then
pbleIsChildWindowOutput = True
WbkOrFirstAppHandle = mlngChildHwnd
Else
WbkOrFirstAppHandle = mlngFirstHwnd
End If
'clear
mstrAppClass = ""
mstrFindTitle = ""
mlngFirstHwnd = 0
mlngChildHwnd = 0
End Function
Private Function GetAppForHwnd(plngHWnd As Long, _
pbleIsChild As Boolean, _
pbleLoadAddIns As Boolean) As Object
On Error GoTo HandleError
Dim XLApp As Object
Dim AI As Object
If plngHWnd > 0 Then
If pbleIsChild = True Then
'get the parent instance using accessibility
Set XLApp = GetExcelAppForHwnd(plngHWnd)
Else
'get the "default" instance
Set XLApp = GetObject(, "Excel.Application")
End If
Else
'no Excel running
Set XLApp = CreateObject("Excel.Application")
If pbleLoadAddIns = True Then
'explicitly reload add-ins (automation doesn't)
For Each AI In XLApp.AddIns
If AI.Installed Then
AI.Installed = False
AI.Installed = True
End If
Next AI
End If
End If
Set GetAppForHwnd = XLApp
Set AI = Nothing
Set XLApp = Nothing
Exit Function
End Function
'------
'API wrappers and utilities
'------
Public Function uWindowClass(ByVal hWnd As Long) As String
Dim strBuffer As String
Dim retval As Long
strBuffer = Space(256)
retval = GetClassName(hWnd, strBuffer, 255)
uWindowClass = Left(strBuffer, retval)
End Function
Public Function uWindowTitle(ByVal hWnd As Long) As String
Dim lngLen As Long
Dim strBuffer As String
Dim retval As Long
lngLen = GetWindowTextLength(hWnd) + 1
If lngLen > 1 Then
'title found - pad buffer
strBuffer = Space(lngLen)
'...get titlebar text
retval = GetWindowText(hWnd, strBuffer, lngLen)
uWindowTitle = Left(strBuffer, lngLen - 1)
End If
End Function
Public Sub uShowWindow(ByVal hWnd As Long, _
Optional pShowType As swcShowWindowCmd = swcRestore)
Dim retval As Long
retval = ShowWindow(hWnd, pShowType)
Select Case pShowType
Case swcMaximized, swcNormal, swcRestore, swcShow
BringWindowToTop hWnd
SetFocus hWnd
End Select
End Sub
Private Function EnumWindowsProc(ByVal hWnd As Long, ByVal lParam As Long) As Long
Dim strThisClass As String
Dim strThisTitle As String
Dim retval As Long
Dim bleMatch As Boolean
'mlngWinCounter = mlngWinCounter + 1
'type of window is all you need for parent
strThisClass = uWindowClass(hWnd)
bleMatch = (strThisClass = mstrAppClass)
If bleMatch = True Then
strThisTitle = uWindowTitle(hWnd)
'Debug.Print "Window #"; mlngWinCounter; " : ";
'Debug.Print strThisTitle; "(" & strThisClass & ") " & hWnd
If mlngFirstHwnd = 0 Then mlngFirstHwnd = hWnd
'mlngChildWinCounter 0
retval = EnumChildWindows(hWnd, AddressOf EnumChildProc, 0)
If mlngChildHwnd > 0 Then
'If mbleFindAll = False And mlngChildHwnd > 0 Then
'stop EnumWindows by setting result to 0
EnumWindowsProc = 0
Else
EnumWindowsProc = 1
End If
Else
EnumWindowsProc = 1
End If
End Function
Private Function EnumChildProc(ByVal hWnd As Long, ByVal lParam As Long) As Long
Dim strThisClass As String
Dim strThisTitle As String
Dim retval As Long
Dim bleMatch As Boolean
strThisClass = uWindowClass(hWnd)
strThisTitle = uWindowTitle(hWnd)
If Len(mstrFindTitle) > 0 Then
bleMatch = (strThisTitle = mstrFindTitle)
Else
bleMatch = True
End If
If bleMatch = True Then
mlngChildHwnd = hWnd
EnumChildProc = 0
Else
EnumChildProc = 1
End If
End Function
Public Function GetExcelAppForHwnd(pChildHwnd As Long) As Object
Dim o As Object
Dim g As GUID
Dim retval As Long
'for child objects only, e.g. must use a loaded workbook to get its parent Excel.Application
'make a valid GUID type
retval = IIDFromString(StrPtr(Guid_Excel), g)
'get
retval = AccessibleObjectFromWindow(pChildHwnd, OBJID_NATIVEOM, g, o)
If retval >= 0 Then
Set GetExcelAppForHwnd = o.Application
End If
End Function
Public Function PathOrFileNm(pstrPathOrFileNm As String, _
pstrFileNmWithPath As String)
On Error GoTo HandleError
Dim i As Integer
Dim j As Integer
Dim strChar As String
If Len(pstrFileNmWithPath) > 0 Then
i = InStrRev(pstrFileNmWithPath, "\")
If i = 0 Then
i = InStrRev(pstrFileNmWithPath, "/")
End If
If i > 0 Then
Select Case pstrPathOrFileNm
Case "Path"
PathOrFileNm = Left(pstrFileNmWithPath, i - 1)
Case "FileNm"
PathOrFileNm = Mid(pstrFileNmWithPath, i + 1)
End Select
ElseIf pstrPathOrFileNm = "FileNm" Then
PathOrFileNm = pstrFileNmWithPath
End If
End If
End Function
And then some sample/test code.
Public Sub Test_GetExcelWbk()
Dim MyXLApp As Object
Dim MyXLWbk As Object
Dim bleXLWasRunning As Boolean
Dim bleWasOpen As Boolean
Const TESTPATH As String = "C:\temp\MyFlickerbook.xlsx"
Const SHOWONLOAD As Boolean = False
Set MyXLWbk = GetExcelWbk(TESTPATH, SHOWONLOAD, bleWasOpen)
If Not (MyXLWbk Is Nothing) Then
Set MyXLApp = MyXLWbk.Parent
bleXLWasRunning = MyXLApp.Visible
If SHOWONLOAD = False Then
If MsgBox("Show " & TESTPATH & "?", vbOKCancel) = vbOK Then
MyXLApp.Visible = True
MyXLApp.Windows(MyXLWbk.Name).Visible = True
End If
End If
If bleWasOpen = False Then
If MsgBox("Close " & TESTPATH & "?", vbOKCancel) = vbOK Then
MyXLWbk.Close SaveChanges:=False
If bleXLWasRunning = False Then
MyXLApp.Quit
End If
End If
End If
End If
Set MyXLWbk = Nothing
Set MyXLApp = Nothing
End Sub
Hope someone else finds this useful.

How do I integrate UAC into my VB6 program?

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.
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
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.
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.
Move all of the code that requires elevation into external processes.
Send your buttons the BCM_SETSHIELD message to add the shield icon.
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;
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>
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"
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
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.
Compile your project.
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).
Try running your program on Vista or later. If UAC is indeed enabled, it should come up with the prompt right away.

How to enumerate available COM ports on a computer?

Other than looping from 1 to 32 and trying open each of them, is there a reliable way to get COM ports on the system?
I believe under modern windows environments you can find them in the registry under the following key HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM. I'm not sure of the correct way to specify registry keys. However I have only ever tested this on Windows XP.
Check out this article from Randy Birch's site: CreateFile: Determine Available COM Ports
There's also the approach of using an MSCOMM control: ConfigurePort: Determine Available COM Ports with the MSCOMM Control
The code's a bit too long for me to post here but the links have everything you need.
It's 1 to 255. Fastest you can do it is using QueryDosDevice like this
Option Explicit
'--- for CreateFile
Private Const GENERIC_READ As Long = &H80000000
Private Const GENERIC_WRITE As Long = &H40000000
Private Const OPEN_EXISTING As Long = 3
Private Const INVALID_HANDLE_VALUE As Long = -1
'--- error codes
Private Const ERROR_ACCESS_DENIED As Long = 5&
Private Const ERROR_GEN_FAILURE As Long = 31&
Private Const ERROR_SHARING_VIOLATION As Long = 32&
Private Const ERROR_SEM_TIMEOUT As Long = 121&
Private Declare Function QueryDosDevice Lib "kernel32" Alias "QueryDosDeviceA" (ByVal lpDeviceName As Long, ByVal lpTargetPath As String, ByVal ucchMax As Long) As Long
Private Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" (ByVal lpFileName As String, ByVal dwDesiredAccess As Long, ByVal dwShareMode As Long, ByVal lpSecurityAttributes As Long, ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Function PrintError(sFunc As String)
Debug.Print sFunc; ": "; Error
End Function
Public Function IsNT() As Boolean
IsNT = True
End Function
Public Function EnumSerialPorts() As Variant
Const FUNC_NAME As String = "EnumSerialPorts"
Dim sBuffer As String
Dim lIdx As Long
Dim hFile As Long
Dim vRet As Variant
Dim lCount As Long
On Error GoTo EH
ReDim vRet(0 To 255) As Variant
If IsNT Then
sBuffer = String$(100000, 1)
Call QueryDosDevice(0, sBuffer, Len(sBuffer))
sBuffer = Chr$(0) & sBuffer
For lIdx = 1 To 255
If InStr(1, sBuffer, Chr$(0) & "COM" & lIdx & Chr$(0), vbTextCompare) > 0 Then
vRet(lCount) = "COM" & lIdx
lCount = lCount + 1
End If
Next
Else
For lIdx = 1 To 255
hFile = CreateFile("COM" & lIdx, GENERIC_READ Or GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0)
If hFile = INVALID_HANDLE_VALUE Then
Select Case Err.LastDllError
Case ERROR_ACCESS_DENIED, ERROR_GEN_FAILURE, ERROR_SHARING_VIOLATION, ERROR_SEM_TIMEOUT
hFile = 0
End Select
Else
Call CloseHandle(hFile)
hFile = 0
End If
If hFile = 0 Then
vRet(lCount) = "COM" & lIdx
lCount = lCount + 1
End If
Next
End If
If lCount = 0 Then
EnumSerialPorts = Split(vbNullString)
Else
ReDim Preserve vRet(0 To lCount - 1) As Variant
EnumSerialPorts = vRet
End If
Exit Function
EH:
PrintError FUNC_NAME
Resume Next
End Function
The snippet falls back to CreateFile on 9x. IsNT function is stubbed for brevity.
Using VB6 or VBScript to enumerate available COM ports can be as simple as using VB.NET, and this can be done by enumerating values from registry path HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM. It's better than calling QueryDosDevice() and doing string comparison to filter out devices which's name is leading by COM since you will get something like CompositeBattery (or other stuff which have full upper case name leading by COM) that isn't a COM port. Another benefit of doing this is that the registry values also containing USB to COM devices, which could not be detected by using the codes such as WMIService.ExecQuery("Select * from Win32_SerialPort"). If you try to plug the USB to COM devices in or out of the computer, you can see the registry values also appear or disappear immediately, since it's keeping updated.
Option Explicit
Sub ListComPorts()
List1.Clear
Dim Registry As Object, Names As Variant, Types As Variant
Set Registry = GetObject("winmgmts:\\.\root\default:StdRegProv")
If Registry.EnumValues(&H80000002, "HARDWARE\DEVICEMAP\SERIALCOMM", Names, Types) <> 0 Then Exit Sub
Dim I As Long
If IsArray(Names) Then
For I = 0 To UBound(Names)
Dim PortName As Variant
Registry.GetStringValue &H80000002, "HARDWARE\DEVICEMAP\SERIALCOMM", Names(I), PortName
List1.AddItem PortName & " - " & Names(I)
Next
End If
End Sub
Private Sub Form_Load()
ListComPorts
End Sub
The code above is using StdRegProv class to enumerate the values of a registry key. I've tested the code in XP, Windows 7, Windows 10, and it works without any complainant. The items which were added to the Listbox looks like below:
COM1 - \Device\Serial0
COM3 - \Device\ProlificSerial0
The downside of this code is that it could not detect which port is already opened by other programs since every port could only be opened once. The way to detect a COM port is opened by another program or not can be done by calling the API CreateFile. Here is an example.

Resources