I would like to write this to the registry:
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout]
"Scancode Map"=hex:
00,00,00,00,00,00,00,00,
04,00,00,00,3A,00,00,00,
00,00,3A,00,00,00,45,00,
00,00,00,00,00,00,00,00
"Scancode Map" is of type Binary.
I am using the following aged module to write to the registry, and I have never used the REG_BINARY option yet.
I would therefore like to ask how I could write these hex values to the registry using my aged module (which I got from the internet many years ago).
The following fails with Type Mismatch error:
ReturnValue = RegSetValueEx(hKey, SubKey, 0, KeyType, CByte(SubKeyValue), 4)
This is what I tried to pass:
WriteRegKey REG_BINARY, HKEY_LOCAL_MACHINE, "SYSTEM\CurrentControlSet\Control\Keyboard Layout", "Scancode Map", "00,00,00,00,00,00,00,00,04,00,00,00,3A,00,00,00,00,00,3A,00,00,00,45,00,00,00,00,00,00,00,00,00"
When I try to write it as a string...
ReturnValue = RegSetValueEx(hKey, SubKey, 0, KeyType, ByVal SubKeyValue, Len(SubKeyValue))
... then it writes garbage to the registry.
Thank you!
'=========================================================================================
' modRegistry
' registry functions and routines
'=========================================================================================
' Adapted and Modified By: Marc Cramer
' Published Date: 04/18/2001
' Copyright Datr: Marc Cramer ?04/18/2001
' WebSite: www.mkccomputers.com
'=========================================================================================
' Based On: API description and examples from Windows API Guide
' WebSite: Windows API Guide # www.vbapi.com
' Based On: API description and examples from The AllAPI Network
' WebSite: The AllAPI Network # www.allapi.net
'=========================================================================================
Option Explicit
'=========================================================================================
' Enums/Constants used for Registry Access
'=========================================================================================
Public Enum KeyRoot
[HKEY_CLASSES_ROOT] = &H80000000 'stores OLE class information and file associations
[HKEY_CURRENT_CONFIG] = &H80000005 'stores computer configuration information
[HKEY_CURRENT_USER] = &H80000001 'stores program information for the current user.
[HKEY_LOCAL_MACHINE] = &H80000002 'stores program information for all users
[HKEY_USERS] = &H80000003 'has all the information for any user (not just the one provided by HKEY_CURRENT_USER)
End Enum
Public Enum KeyType
[REG_BINARY] = 3 'A non-text sequence of bytes
[REG_DWORD] = 4 'A 32-bit integer...visual basic data type of Long
[REG_SZ] = 1 'A string terminated by a null character
End Enum
Private Const KEY_ALL_ACCESS = &HF003F 'Permission for all types of access.
Private Const KEY_ENUMERATE_SUB_KEYS = &H8 'Permission to enumerate subkeys.
Private Const KEY_READ = &H20019 'Permission for general read access.
Private Const KEY_WRITE = &H20006 'Permission for general write access.
Private Const KEY_QUERY_VALUE = &H1 'Permission to query subkey data.
' used for import/export registry key
Private Const REG_FORCE_RESTORE As Long = 8& 'Permission to overwrite a registry key
Private Const TOKEN_QUERY As Long = &H8&
Private Const TOKEN_ADJUST_PRIVILEGES As Long = &H20&
Private Const SE_PRIVILEGE_ENABLED As Long = &H2
Private Const SE_RESTORE_NAME = "SeRestorePrivilege" 'Important for what we're trying to accomplish
Private Const SE_BACKUP_NAME = "SeBackupPrivilege"
'=========================================================================================
' Type used for Registry
'=========================================================================================
' used for writing registry keys
Private Type SECURITY_ATTRIBUTES
nLength As Long
lpSecurityDescriptor As Long
bInheritHandle As Boolean
End Type
' used for enumerating registrykeys
Private Type FILETIME
dwLowDateTime As Long
dwHighDateTime As Long
End Type
' used for import/export registry key
Private Type LUID
lowpart As Long
highpart As Long
End Type
Private Type LUID_AND_ATTRIBUTES
pLuid As LUID
Attributes As Long
End Type
Private Type TOKEN_PRIVILEGES
PrivilegeCount As Long
Privileges As LUID_AND_ATTRIBUTES
End Type
'=========================================================================================
' API Function Declarations used for Registry
'=========================================================================================
Private Declare Function RegSetValueEx Lib "advapi32.dll" Alias "RegSetValueExA" (ByVal hKey As Long, ByVal lpValueName As String, ByVal Reserved As Long, ByVal dwType As Long, lpData As Any, ByVal cbData As Long) As Long
Private Declare Function RegQueryValueEx Lib "advapi32.dll" Alias "RegQueryValueExA" (ByVal hKey As Long, ByVal lpValueName As String, ByVal lpReserved As Long, lpType As Long, lpData As Any, lpcbData As Long) As Long
Private Declare Function RegOpenKeyEx Lib "advapi32.dll" Alias "RegOpenKeyExA" (ByVal hKey As Long, ByVal lpSubKey As String, ByVal ulOptions As Long, ByVal samDesired As Long, phkResult As Long) As Long
Private Declare Function RegDeleteValue Lib "advapi32.dll" Alias "RegDeleteValueA" (ByVal hKey As Long, ByVal lpValueName As String) As Long
Private Declare Function RegDeleteKey Lib "advapi32.dll" Alias "RegDeleteKeyA" (ByVal hKey As Long, ByVal lpSubKey As String) As Long
Private Declare Function RegCreateKeyEx Lib "advapi32.dll" Alias "RegCreateKeyExA" (ByVal hKey As Long, ByVal lpSubKey As String, ByVal Reserved As Long, ByVal lpClass As String, ByVal dwOptions As Long, ByVal samDesired As Long, lpSecurityAttributes As SECURITY_ATTRIBUTES, phkResult As Long, lpdwDisposition As Long) As Long
Private Declare Function RegCloseKey Lib "advapi32.dll" (ByVal hKey As Long) As Long
Private Declare Function RegSaveKey Lib "advapi32.dll" Alias "RegSaveKeyA" (ByVal hKey As Long, ByVal lpFile As String, lpSecurityAttributes As Any) As Long
Private Declare Function RegRestoreKey Lib "advapi32.dll" Alias "RegRestoreKeyA" (ByVal hKey As Long, ByVal lpFile As String, ByVal dwFlags As Long) As Long
Private Declare Function RegEnumKeyEx Lib "advapi32.dll" Alias "RegEnumKeyExA" (ByVal hKey As Long, ByVal dwIndex As Long, ByVal lpName As String, lpcbName As Long, lpReserved As Long, ByVal lpClass As String, lpcbClass As Long, lpftLastWriteTime As FILETIME) As Long
Private Declare Function RegEnumValue Lib "advapi32.dll" Alias "RegEnumValueA" (ByVal hKey As Long, ByVal dwIndex As Long, ByVal lpValueName As String, lpcbValueName As Long, ByVal lpReserved As Long, lpType As Long, lpData As Byte, lpcbData As Long) As Long
' used for export/import registry keys
Private Declare Function AdjustTokenPrivileges Lib "advapi32.dll" (ByVal TokenHandle As Long, ByVal DisableAllPriv As Long, NewState As TOKEN_PRIVILEGES, ByVal BufferLength As Long, PreviousState As TOKEN_PRIVILEGES, ReturnLength As Long) As Long 'Used to adjust your program's security privileges, can't restore without it!
Private Declare Function LookupPrivilegeValue Lib "advapi32.dll" Alias "LookupPrivilegeValueA" (ByVal lpSystemName As Any, ByVal lpName As String, lpLuid As LUID) As Long 'Returns a valid LUID which is important when making security changes in NT.
Private Declare Function OpenProcessToken Lib "advapi32.dll" (ByVal ProcessHandle As Long, ByVal DesiredAccess As Long, TokenHandle As Long) As Long
Private Declare Function GetCurrentProcess Lib "kernel32" () As Long
'=========================================================================================
' Routines used to Access Registry
'=========================================================================================
Public Function ExportRegKey(KeyRoot As KeyRoot, KeyPath As String, FileName As String) As Boolean
' routine to export registry keys
On Error Resume Next
Dim hKey As Long
Dim ReturnValue As Long
' check to see if allowed to do this
If EnablePrivilege(SE_BACKUP_NAME) = False Then
ExportRegKey = False
Exit Function
End If
' open the registry key
ReturnValue = RegOpenKeyEx(KeyRoot, KeyPath, 0&, KEY_ALL_ACCESS, hKey)
If ReturnValue <> 0 Then
' error encountered
ExportRegKey = False
ReturnValue = RegCloseKey(hKey)
Exit Function
End If
' check for a copy of the export and delete old one if applicable
If Dir(FileName) <> "" Then Kill FileName
' export the registry key
ReturnValue = RegSaveKey(hKey, FileName, ByVal 0&)
If ReturnValue = 0 Then
' no error encountered
ExportRegKey = True
Else
' error encountered
ExportRegKey = False
End If
' close the registry key
ReturnValue = RegCloseKey(hKey)
End Function 'ExportRegKey(KeyRoot As KeyRoot, KeyPath As String, FileName As String) As Boolean
'=========================================================================================
Public Function ImportRegKey(KeyRoot As KeyRoot, KeyPath As String, FileName As String) As Boolean
' routine to import registry keys
' will overwrite current settings, but will not create keys
On Error Resume Next
Dim hKey As Long
Dim ReturnValue As Long
' check to see if allowed to do this
If EnablePrivilege(SE_RESTORE_NAME) = False Then
ImportRegKey = False
Exit Function
End If
' open the registry key
ReturnValue = RegOpenKeyEx(KeyRoot, KeyPath, 0&, KEY_ALL_ACCESS, hKey)
If ReturnValue <> 0 Then
' error encountered
ImportRegKey = False
ReturnValue = RegCloseKey(hKey)
Exit Function
End If
' import the registry key
ReturnValue = RegRestoreKey(hKey, FileName, REG_FORCE_RESTORE)
If ReturnValue = 0 Then
' no error encountered
ImportRegKey = True
Else
' error encountered
ImportRegKey = False
End If
' close the registry key
ReturnValue = RegCloseKey(hKey)
End Function 'ImportRegKey(KeyRoot As KeyRoot, KeyPath As String, FileName As String) As Boolean
'=========================================================================================
Public Function ReadRegKey(KeyRoot As KeyRoot, KeyPath As String, SubKey As String, Optional NoKeyFoundValue As String = "") As String
' routine to read entry from registry
On Error Resume Next
Dim hKey As Long ' receives a handle to the opened registry key
Dim ReturnValue As Long ' return value
' open the registry key
ReturnValue = RegOpenKeyEx(KeyRoot, KeyPath, 0, KEY_READ, hKey)
If ReturnValue <> 0 Then
' key doesn't exist so return default value
ReadRegKey = NoKeyFoundValue
ReturnValue = RegCloseKey(hKey)
Exit Function
End If
' get the keys value
ReadRegKey = GetSubKeyValue(hKey, SubKey)
' close the registry key
ReturnValue = RegCloseKey(hKey)
End Function 'ReadRegKey(KeyRoot As KeyRoot, KeyPath As String, SubKey As String, Optional NoKeyFoundValue As String = "") As String
'=========================================================================================
Public Function WriteRegKey(KeyType As KeyType, KeyRoot As KeyRoot, KeyPath As String, SubKey As String, SubKeyValue As String) As Boolean
' routine to write entry to registry
On Error Resume Next
Dim hKey As Long ' receives handle to the newly created or opened registry key
Dim SecurityAttribute As SECURITY_ATTRIBUTES ' security settings of the key
Dim NewKey As Long ' receives 1 if new key was created or 2 if an existing key was opened
Dim ReturnValue As Long ' return value
' Set the name of the new key and the default security settings
SecurityAttribute.nLength = Len(SecurityAttribute) ' size of the structure
SecurityAttribute.lpSecurityDescriptor = 0 ' default security level
SecurityAttribute.bInheritHandle = True ' the default value for this setting
' create or open the registry key
ReturnValue = RegCreateKeyEx(KeyRoot, KeyPath, 0, "", 0, KEY_WRITE, SecurityAttribute, hKey, NewKey)
If ReturnValue <> 0 Then
' error encountered
WriteRegKey = False
ReturnValue = RegCloseKey(hKey)
Exit Function
End If
' determine type of key and write it to the registry
Select Case KeyType
Case REG_SZ
ReturnValue = RegSetValueEx(hKey, SubKey, 0, KeyType, ByVal SubKeyValue, Len(SubKeyValue))
Case REG_DWORD
ReturnValue = RegSetValueEx(hKey, SubKey, 0, KeyType, CLng(SubKeyValue), 4)
Case REG_BINARY
ReturnValue = RegSetValueEx(hKey, SubKey, 0, KeyType, CByte(SubKeyValue), 4)
End Select
If ReturnValue = 0 Then
' no error encountered
WriteRegKey = True
Else
' error encountered
WriteRegKey = False
End If
' close the registry key
ReturnValue = RegCloseKey(hKey)
End Function 'WriteRegKey(KeyType As KeyType, KeyRoot As KeyRoot, KeyPath As String, SubKey As String, SubKeyValue As String) As Boolean
'=========================================================================================
Public Function EnumerateRegKeys(KeyRoot As KeyRoot, KeyPath As String) As String
' routine to enumerate all subkeys under a registry key
On Error Resume Next
Dim hKey As Long ' receives a handle to the opened registry key
Dim ReturnValue As Long ' return value
Dim Counter As Long
Dim MyBuffer As String
Dim MyBufferSize As Long
Dim ClassNameBuffer As String
Dim ClassNameBufferSize As Long
Dim LastWrite As FILETIME
' open the registry key
ReturnValue = RegOpenKeyEx(KeyRoot, KeyPath, 0, KEY_ENUMERATE_SUB_KEYS, hKey)
If ReturnValue <> 0 Then
' key doesn't exist so return default value
EnumerateRegKeys = ""
ReturnValue = RegCloseKey(hKey)
Exit Function
End If
Counter = 0
' loop until no more registry keys
Do Until ReturnValue <> 0
MyBuffer = Space(255)
ClassNameBuffer = Space(255)
MyBufferSize = 255
ClassNameBufferSize = 255
ReturnValue = RegEnumKeyEx(hKey, Counter, MyBuffer, MyBufferSize, ByVal 0, ClassNameBuffer, ClassNameBufferSize, LastWrite)
If ReturnValue = 0 Then
MyBuffer = Left$(MyBuffer, MyBufferSize)
ClassNameBuffer = Left$(ClassNameBuffer, ClassNameBufferSize)
EnumerateRegKeys = EnumerateRegKeys & MyBuffer & ","
End If
Counter = Counter + 1
Loop
' trim off the last delimiter
If EnumerateRegKeys <> "" Then EnumerateRegKeys = Left$(EnumerateRegKeys, Len(EnumerateRegKeys) - 1)
' close the registry key
ReturnValue = RegCloseKey(hKey)
End Function 'EnumerateRegKeys(KeyRoot As KeyRoot, KeyPath As String) As String
'=========================================================================================
Public Function EnumerateRegKeyValues(KeyRoot As KeyRoot, KeyPath As String) As String
' routine to enumerate all the values under a key in the registry
On Error Resume Next
Dim hKey As Long ' receives a handle to the opened registry key
Dim ReturnValue As Long ' return value
Dim Counter As Long
Dim MyBuffer As String
Dim MyBufferSize As Long
Dim KeyType As KeyType
' open the registry key to enumerate the values of.
ReturnValue = RegOpenKeyEx(KeyRoot, KeyPath, 0, KEY_QUERY_VALUE, hKey)
' check to see if an error occured.
If ReturnValue <> 0 Then
EnumerateRegKeyValues = ""
ReturnValue = RegCloseKey(hKey)
Exit Function
End If
Counter = 0
' loop until no more registry keys value
Do Until ReturnValue <> 0
MyBuffer = Space(255)
MyBufferSize = 255
ReturnValue = RegEnumValue(hKey, Counter, MyBuffer, MyBufferSize, 0, KeyType, ByVal 0&, ByVal 0&) 'ByteData(0), ByteDataSize)
If ReturnValue = 0 Then
MyBuffer = Left$(MyBuffer, MyBufferSize)
EnumerateRegKeyValues = EnumerateRegKeyValues & MyBuffer & "*"
EnumerateRegKeyValues = EnumerateRegKeyValues & GetSubKeyValue(hKey, MyBuffer) & ","
End If
Counter = Counter + 1
Loop
' trim off the last delimiter
If EnumerateRegKeyValues <> "" Then EnumerateRegKeyValues = Left$(EnumerateRegKeyValues, Len(EnumerateRegKeyValues) - 1)
' close the registry key
ReturnValue = RegCloseKey(hKey)
End Function 'EnumerateRegKeyValues(KeyRoot As KeyRoot, KeyPath As String) As String
'=========================================================================================
Public Function DeleteRegKey(KeyRoot As KeyRoot, KeyPath As String, SubKey As String) As Boolean
' routine to delete a registry key
' under Win NT/2000 all subkeys must be deleted first
' under Win 9x all subkeys are deleted
On Error Resume Next
Dim ReturnValue As Long ' return value
' Attempt to delete the desired registry key.
ReturnValue = RegDeleteKey(KeyRoot, KeyPath & "\" & SubKey)
If ReturnValue = 0 Then
' no error encountered
DeleteRegKey = True
Else
' error encountered
DeleteRegKey = False
End If
End Function 'DeleteRegKey(KeyRoot As KeyRoot, KeyPath As String, SubKey As String) As Boolean
'=========================================================================================
Public Function DeleteRegKeyValue(KeyRoot As KeyRoot, KeyPath As String, Optional SubKey As String = "") As Boolean
' routine to delete a value from a key (but not the key) in the registry
On Error Resume Next
Dim hKey As Long ' handle to the open registry key
Dim ReturnValue As Long ' return value
' First, open up the registry key which holds the value to delete.
ReturnValue = RegOpenKeyEx(KeyRoot, KeyPath, 0, KEY_ALL_ACCESS, hKey)
If ReturnValue <> 0 Then
' error encountered
DeleteRegKeyValue = False
ReturnValue = RegCloseKey(hKey)
Exit Function
End If
' check to see if we are deleting a subkey or primary key
If SubKey = "" Then SubKey = KeyPath
' successfully opened registry key so delete the desired value from the key.
ReturnValue = RegDeleteValue(hKey, SubKey)
If ReturnValue = 0 Then
' no error encountered
DeleteRegKeyValue = True
Else
' error encountered
DeleteRegKeyValue = False
End If
' close the registry key
ReturnValue = RegCloseKey(hKey)
End Function 'DeleteRegKeyValue(KeyRoot As KeyRoot, KeyPath As String, Optional SubKey As String = "") As Boolean
'=========================================================================================
Private Function GetSubKeyValue(ByVal hKey As Long, ByVal SubKey As String) As String
' routine to get the registry key value and convert to a string
On Error Resume Next
Dim ReturnValue As Long
Dim KeyType As KeyType
Dim MyBuffer As String
Dim MyBufferSize As Long
'get registry key information
ReturnValue = RegQueryValueEx(hKey, SubKey, 0, KeyType, ByVal 0, MyBufferSize)
If ReturnValue = 0 Then ' no error encountered
' determine what the KeyType is
Select Case KeyType
Case REG_SZ
' create a buffer
MyBuffer = String(MyBufferSize, Chr$(0))
' retrieve the key's content
ReturnValue = RegQueryValueEx(hKey, SubKey, 0, 0, ByVal MyBuffer, MyBufferSize)
If ReturnValue = 0 Then
' remove the unnecessary chr$(0)'s
GetSubKeyValue = Left$(MyBuffer, InStr(1, MyBuffer, Chr$(0)) - 1)
End If
Case Else 'REG_DWORD or REG_BINARY
Dim MyNewBuffer As Long
' retrieve the key's value
ReturnValue = RegQueryValueEx(hKey, SubKey, 0, 0, MyNewBuffer, MyBufferSize)
If ReturnValue = 0 Then ' no error encountered
GetSubKeyValue = MyNewBuffer
End If
End Select
End If
End Function 'GetSubKeyValue(ByVal hKey As Long, ByVal SubKey As String) As String
'=========================================================================================
Private Function EnablePrivilege(seName As String) As Boolean
' routine to enable inport/export of registry settings
On Error Resume Next
Dim p_lngRtn As Long
Dim p_lngToken As Long
Dim p_lngBufferLen As Long
Dim p_typLUID As LUID
Dim p_typTokenPriv As TOKEN_PRIVILEGES
Dim p_typPrevTokenPriv As TOKEN_PRIVILEGES
' open the current process token
p_lngRtn = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES Or TOKEN_QUERY, p_lngToken)
If p_lngRtn = 0 Then
' error encountered
EnablePrivilege = False
Exit Function
End If
If Err.LastDllError <> 0 Then
' error encountered
EnablePrivilege = False
Exit Function
End If
' look up the privileges LUID
p_lngRtn = LookupPrivilegeValue(0&, seName, p_typLUID)
If p_lngRtn = 0 Then
' error encountered
EnablePrivilege = False
Exit Function
End If
' adjust the program's security privilege.
p_typTokenPriv.PrivilegeCount = 1
p_typTokenPriv.Privileges.Attributes = SE_PRIVILEGE_ENABLED
p_typTokenPriv.Privileges.pLuid = p_typLUID
' try to adjust privileges and return success or failure
EnablePrivilege = (AdjustTokenPrivileges(p_lngToken, False, p_typTokenPriv, Len(p_typPrevTokenPriv), p_typPrevTokenPriv, p_lngBufferLen) <> 0)
End Function 'EnablePrivilege(seName As String) As Boolean
'=========================================================================================
I have tried the StrPtr approach, and here is what it does for me:
SubKey = "00,00,00,00,00,00,00,00,04,00,00,00,3A,00,00,00,00,00,3A,00,00,00,45,00,00,00,00,00,00,00,00,00"
ReturnValue = RegSetValueEx(hKey, SubKey, 0, REG_BINARY, StrPtr(SubKeyValue), Len(SubKeyValue))
You need to put all those hex values in a byte array and pass the first element of the array (byteArray(0)) as a parameter to the RegSetValueEx function. The last parameter is the length of the array: ubound(byteArray)-lbound(byteArray)+1
Using your existing code you can also write it as:
ReturnValue = RegSetValueEx(hKey, SubKey, 0, KeyType, StrPtr(SubKeyValue), Len(SubKeyValue))
That is assuming the "SubKeyValue" string actually contains those hex bytes that you want written.
I trying to terminate a SYSTEM process, but my attempts to kill it fail.
The first error occurs when I call AdjustTokenPrivileges.
GetLastError returns 1300 which means ERROR_NOT_ALL_ASSIGNED.
What do I need to change make it work?
I want to complete this task with as little previleges as possible. I want to avoid triggering UAC.
Also, my application does not require admin rights, and I would like to keep that.
Thank you!
Here is my entire code:
Option Explicit
Private Type Luid
UsedPart As Long
IgnoredForNowHigh32BitPart As Long
End Type
Const TH32CS_SNAPHEAPLIST = &H1
Const TH32CS_SNAPPROCESS = &H2
Const TH32CS_SNAPTHREAD = &H4
Const TH32CS_SNAPMODULE = &H8
Private Const TH32CS_SNAPALL = (TH32CS_SNAPHEAPLIST Or TH32CS_SNAPPROCESS Or TH32CS_SNAPTHREAD Or TH32CS_SNAPMODULE)
Const TH32CS_INHERIT = &H80000000
Const MAX_PATH As Integer = 260
Private Const PROCESS_QUERY_LIMITED_INFORMATION = &H1000
Private Const PROCESS_QUERY_INFORMATION = &H400
Private Const STATUS_PENDING = &H103&
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Type PROCESSENTRY32
dwSize As Long
cntUsage As Long
th32ProcessID As Long
th32DefaultHeapID As Long
th32BookID As Long
cntThreads As Long
th32ParentProcessID As Long
pcPriClassBase As Long
dwFlags As Long
szExeFile As String * 260
End Type
Private Declare Function CreateToolhelp32Snapshot Lib "kernel32" (ByVal dwFlags As Long, ByVal th32ProcessID As Long) As Long
Private Declare Function Process32First Lib "kernel32" (ByVal hSnapshot As Long, lppe As PROCESSENTRY32) As Long
Private Declare Function Process32Next Lib "kernel32" (ByVal hSnapshot As Long, lppe As PROCESSENTRY32) As Long
Private Declare Function OpenProcessToken Lib "advapi32.dll" (ByVal ProcessHandle As Long, ByVal DesiredAccess As Long, TokenHandle As Long) As Long
Private Declare Function GetCurrentProcess Lib "kernel32" () As Long
Private Declare Function AdjustTokenPrivileges Lib "advapi32.dll" (ByVal TokenHandle As Long, ByVal DisableAllPrivileges As Long, NewState As TOKEN_PRIVILEGES, ByVal BufferLength As Long, PreviousState As TOKEN_PRIVILEGES, ReturnLength As Long) As Long
Private Declare Function LookupPrivilegeValue Lib "advapi32.dll" Alias "LookupPrivilegeValueA" (ByVal lpSystemName As String, ByVal lpName As String, lpLuid As Luid) As Long
Private Const ANYSIZE_ARRAY = 1
Private Const TOKEN_ADJUST_PRIVILEGES = &H20
Private Const TOKEN_QUERY = &H8
Private Const SE_PRIVILEGE_ENABLED = &H2
Private Const PROCESS_TERMINATE = &H1
Private Declare Function TerminateProcess Lib "kernel32" (ByVal ApphProcess As Long, ByVal uExitCode As Long) As Long
Private Declare Function OpenProcess Lib "Kernel32.dll" (ByVal dwDesiredAccessas As Long, ByVal bInheritHandle As Long, ByVal dwProcId As Long) As Long
Private Declare Function EnumProcesses Lib "psapi.dll" (ByRef lpidProcess As Long, ByVal cb As Long, ByRef cbNeeded As Long) As Long
Private Declare Function EnumProcessModules Lib "psapi.dll" (ByVal hProcess As Long, ByRef lphModule As Long, ByVal cb As Long, ByRef cbNeeded As Long) As Long
Private Const SYNCHRONIZE = &H100000
Private Const PROCESS_ALL_ACCESS = &H1F0FFF
Private Type LUID_AND_ATTRIBUTES
pLuid As Luid
Attributes As Long
End Type
Private Type TOKEN_PRIVILEGES
PrivilegeCount As Long
Privileges(ANYSIZE_ARRAY) As LUID_AND_ATTRIBUTES
End Type
Private Const SE_DEBUG_NAME = "SeDebugPrivilege"
Private Sub cmd1_Click()
TerminateProcessByName "AnyDesk.exe"
End Sub
Private Sub TerminateProcessByName(ByVal u As String)
Dim sName As String
Dim PList As String
Dim ret As Long
Dim hSnap As Long
Dim proc As PROCESSENTRY32
hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0)
If hSnap = 0 Then
Exit Sub
End If
proc.dwSize = LenB(proc)
Dim f As Long
f = Process32First(hSnap, proc)
Do
sName = proc.szExeFile
sName = Left(sName, InStr(sName, Chr(0)) - 1)
If sName = u Then
KillProcess proc.th32ProcessID
End If
f = Process32Next(hSnap, proc)
Loop While f = 1
ret = CloseHandle(hSnap)
End Sub
Private Function SetPrivilege(hToken As Long, Privilege As String, bSetFlag As Boolean) As Boolean
Dim TP As TOKEN_PRIVILEGES ' Used in getting the current
' token privileges
Dim TPPrevious As TOKEN_PRIVILEGES ' Used in setting the new
' token privileges
Dim Luid As Luid ' Stores the Local Unique
' Identifier - refer to MSDN
Dim cbPrevious As Long ' Previous size of the
' TOKEN_PRIVILEGES structure
Dim lResult As Long ' Result of various API calls
' Grab the size of the TOKEN_PRIVILEGES structure,
' used in making the API calls.
cbPrevious = Len(TP)
' Grab the LUID for the request privilege.
lResult = LookupPrivilegeValue("", Privilege, Luid)
' If LoopupPrivilegeValue fails, the return result will be zero.
' Test to make sure that the call succeeded.
If (lResult = 0) Then
Debug.Assert False
SetPrivilege = False
End If
' Set up basic information for a call.
' You want to retrieve the current privileges
' of the token under concern before you can modify them.
TP.PrivilegeCount = 1
TP.Privileges(0).pLuid = Luid
TP.Privileges(0).Attributes = 0
SetPrivilege = lResult
' You need to acquire the current privileges first
lResult = AdjustTokenPrivileges(hToken, -1, TP, Len(TP), _
TPPrevious, cbPrevious)
' If AdjustTokenPrivileges fails, the return result is zero,
' test for success.
If (lResult = 0) Then
SetPrivilege = False
End If
lResult = Err.LastDllError
If lResult <> 0 Then
Debug.Assert False
SetPrivilege = False
Exit Function
End If
' Now you can set the token privilege information
' to what the user is requesting.
TPPrevious.PrivilegeCount = 1
TPPrevious.Privileges(0).pLuid = Luid
' either enable or disable the privilege,
' depending on what the user wants.
Select Case bSetFlag
Case True: TPPrevious.Privileges(0).Attributes = TPPrevious.Privileges(0).Attributes Or (SE_PRIVILEGE_ENABLED)
Case False: TPPrevious.Privileges(0).Attributes = TPPrevious.Privileges(0).Attributes Xor (SE_PRIVILEGE_ENABLED And TPPrevious.Privileges(0).Attributes)
End Select
' Call adjust the token privilege information.
lResult = AdjustTokenPrivileges(hToken, -1, TPPrevious, cbPrevious, TP, cbPrevious)
' Determine your final result of this function.
If (lResult = 0) Then
' You were not able to set the privilege on this token.
SetPrivilege = False
Else
lResult = Err.LastDllError
If lResult <> 0 Then
Debug.Assert False
SetPrivilege = False
Exit Function
End If
' You managed to modify the token privilege
SetPrivilege = True
End If
End Function
Private Sub KillProcess(ByVal uIDOfProcessToKill As Long)
Dim hProcessID As Long ' Handle to your sample
' process you are going to
' terminate.
Dim hProcess As Long ' Handle to your current process
Dim hToken As Long ' Handle to your process token.
Dim lPrivilege As Long ' Privilege to enable/disable
Dim iPrivilegeflag As Boolean ' Flag whether to enable/disable
' the privilege of concern.
Dim lResult As Long ' Result call of various APIs.
' set the incoming PID to our internal variable
hProcessID = uIDOfProcessToKill
' get our current process handle
hProcess = GetCurrentProcess
' open the tokens for this our own app
lResult = OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES Or _
TOKEN_QUERY, hToken)
If (lResult = 0) Then
Debug.Assert False
MsgBox "Error: Unable To Open Process Token: " & Err.LastDllError
CloseHandle (hToken)
Exit Sub
End If
' Now that you have the token for this process, you want to set
' the SE_DEBUG_NAME privilege.
lResult = SetPrivilege(hToken, SE_DEBUG_NAME, True)
' Make sure you could set the privilege on this token.
If (lResult = False) Then
MsgBox "Error : Could Not Set SeDebug Privilege on Token Handle"
Debug.Assert False
CloseHandle (hToken)
Exit Sub
End If
' Now that you have changed the privileges on the token,
' have some fun. You can now get a process handle to the
' process ID that you passed into this program, and
' demand whatever access you want on it!
hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, hProcessID)
' Make sure you opened the process so you can do stuff with it
If (hProcess = 0) Then
Debug.Assert False
MsgBox "Error : Unable To Open Process : " & Err.LastDllError 'error 5
CloseHandle (hToken)
Exit Sub
End If
' Now turn the SE_DEBUG_PRIV back off,
lResult = SetPrivilege(hToken, SE_DEBUG_NAME, False)
' Make sure you succeeded in reversing the privilege!
If (lResult = False) Then
Debug.Assert False
MsgBox "Error : Unable To Disable SeDebug Privilege On Token Handle"
CloseHandle (hProcess)
CloseHandle (hToken)
Exit Sub
End If
' Now you want to kill the application, which you can do since
' your process handle to the application includes full access to
' romp and roam - you got the process handle when you had the
' SE_DEBUG_NAME privilege enabled!
lResult = TerminateProcess(hProcess, 0)
' Let's see the result, and go from there.
If (lResult = 0) Then
lResult = Err.LastDllError '6=ERROR_INVALID_HANDLE
Debug.Assert False
CloseHandle (hProcess)
CloseHandle (hToken)
Exit Sub
End If
' Close our handles and get out of here.
CloseHandle (hProcess)
CloseHandle (hToken)
End Sub
I have VB6 Application which depends upon another EXE file which is invoked via CreateProcess from lib kernel32 and pipe connection to fetch the output.
How can I invoke it asynchronously in my main form without freezing the UI?
Currently, Form1 Freezes when the external application takes a longer time to respond.
Attribute VB_Name = "CmdOutput"
Option Explicit
''''''''''''''''''''''''''''''''''''''''
' Joacim Andersson, Brixoft Software
' http://www.brixoft.net
''''''''''''''''''''''''''''''''''''''''
' STARTUPINFO flags
Private Const STARTF_USESHOWWINDOW = &H1
Private Const STARTF_USESTDHANDLES = &H100
' ShowWindow flags
Private Const SW_HIDE = 0
' DuplicateHandle flags
Private Const DUPLICATE_CLOSE_SOURCE = &H1
Private Const DUPLICATE_SAME_ACCESS = &H2
' Error codes
Private Const ERROR_BROKEN_PIPE = 109
Private Type SECURITY_ATTRIBUTES
nLength As Long
lpSecurityDescriptor As Long
bInheritHandle As Long
End Type
Private Type STARTUPINFO
cb As Long
lpReserved As String
lpDesktop As String
lpTitle As String
dwX As Long
dwY As Long
dwXSize As Long
dwYSize As Long
dwXCountChars As Long
dwYCountChars As Long
dwFillAttribute As Long
dwFlags As Long
wShowWindow As Integer
cbReserved2 As Integer
lpReserved2 As Long
hStdInput As Long
hStdOutput As Long
hStdError As Long
End Type
Private Type PROCESS_INFORMATION
hProcess As Long
hThread As Long
dwProcessId As Long
dwThreadId As Long
End Type
Private Declare Function CreatePipe _
Lib "kernel32" ( _
phReadPipe As Long, _
phWritePipe As Long, _
lpPipeAttributes As Any, _
ByVal nSize As Long) As Long
Private Declare Function ReadFile _
Lib "kernel32" ( _
ByVal hFile As Long, _
lpBuffer As Any, _
ByVal nNumberOfBytesToRead As Long, _
lpNumberOfBytesRead As Long, _
lpOverlapped As Any) As Long
Private Declare Function CreateProcess _
Lib "kernel32" Alias "CreateProcessA" ( _
ByVal lpApplicationName As String, _
ByVal lpCommandLine As String, _
lpProcessAttributes As Any, _
lpThreadAttributes As Any, _
ByVal bInheritHandles As Long, _
ByVal dwCreationFlags As Long, _
lpEnvironment As Any, _
ByVal lpCurrentDriectory As String, _
lpStartupInfo As STARTUPINFO, _
lpProcessInformation As PROCESS_INFORMATION) As Long
Private Declare Function GetCurrentProcess _
Lib "kernel32" () As Long
Private Declare Function DuplicateHandle _
Lib "kernel32" ( _
ByVal hSourceProcessHandle As Long, _
ByVal hSourceHandle As Long, _
ByVal hTargetProcessHandle As Long, _
lpTargetHandle As Long, _
ByVal dwDesiredAccess As Long, _
ByVal bInheritHandle As Long, _
ByVal dwOptions As Long) As Long
Private Declare Function CloseHandle _
Lib "kernel32" ( _
ByVal hObject As Long) As Long
Private Declare Function OemToCharBuff _
Lib "user32" Alias "OemToCharBuffA" ( _
lpszSrc As Any, _
ByVal lpszDst As String, _
ByVal cchDstLength As Long) As Long
' Function GetCommandOutput
'
' sCommandLine: [in] Command line to launch
' blnStdOut [in,opt] True (defualt) to capture output to STDOUT
' blnStdErr [in,opt] True to capture output to STDERR. False is default.
' blnOEMConvert: [in,opt] True (default) to convert DOS characters to Windows, False to skip conversion
'
' Returns: String with STDOUT and/or STDERR output
'
Public Function GetCommandOutput( _
sCommandLine As String, _
Optional blnStdOut As Boolean = True, _
Optional blnStdErr As Boolean = False, _
Optional blnOEMConvert As Boolean = True _
) As String
Dim hPipeRead As Long, hPipeWrite1 As Long, hPipeWrite2 As Long
Dim hCurProcess As Long
Dim sa As SECURITY_ATTRIBUTES
Dim si As STARTUPINFO
Dim pi As PROCESS_INFORMATION
Dim baOutput() As Byte
Dim sNewOutput As String
Dim lBytesRead As Long
Dim fTwoHandles As Boolean
Dim lRet As Long
Const BUFSIZE = 1024 ' pipe buffer size
' At least one of them should be True, otherwise there's no point in calling the function
If (Not blnStdOut) And (Not blnStdErr) Then
Err.Raise 5 ' Invalid Procedure call or Argument
End If
' If both are true, we need two write handles. If not, one is enough.
fTwoHandles = blnStdOut And blnStdErr
ReDim baOutput(BUFSIZE - 1) As Byte
With sa
.nLength = Len(sa)
.bInheritHandle = 1 ' get inheritable pipe handles
End With
If CreatePipe(hPipeRead, hPipeWrite1, sa, BUFSIZE) = 0 Then
Exit Function
End If
hCurProcess = GetCurrentProcess()
' Replace our inheritable read handle with an non-inheritable. Not that it
' seems to be necessary in this case, but the docs say we should.
Call DuplicateHandle(hCurProcess, hPipeRead, hCurProcess, hPipeRead, 0&, _
0&, DUPLICATE_SAME_ACCESS Or DUPLICATE_CLOSE_SOURCE)
' If both STDOUT and STDERR should be redirected, get an extra handle.
If fTwoHandles Then
Call DuplicateHandle(hCurProcess, hPipeWrite1, hCurProcess, hPipeWrite2, 0&, _
1&, DUPLICATE_SAME_ACCESS)
End If
With si
.cb = Len(si)
.dwFlags = STARTF_USESHOWWINDOW Or STARTF_USESTDHANDLES
.wShowWindow = SW_HIDE ' hide the window
If fTwoHandles Then
.hStdOutput = hPipeWrite1
.hStdError = hPipeWrite2
ElseIf blnStdOut Then
.hStdOutput = hPipeWrite1
Else
.hStdError = hPipeWrite1
End If
End With
If CreateProcess(vbNullString, sCommandLine, ByVal 0&, ByVal 0&, 1, 0&, _
ByVal 0&, vbNullString, si, pi) Then
' Close thread handle - we don't need it
Call CloseHandle(pi.hThread)
' Also close our handle(s) to the write end of the pipe. This is important, since
' ReadFile will *not* return until all write handles are closed or the buffer is full.
Call CloseHandle(hPipeWrite1)
hPipeWrite1 = 0
If hPipeWrite2 Then
Call CloseHandle(hPipeWrite2)
hPipeWrite2 = 0
End If
Do
' Add a DoEvents to allow more data to be written to the buffer for each call.
' This results in fewer, larger chunks to be read.
'DoEvents
If ReadFile(hPipeRead, baOutput(0), BUFSIZE, lBytesRead, ByVal 0&) = 0 Then
Exit Do
End If
If blnOEMConvert Then
' convert from "DOS" to "Windows" characters
sNewOutput = String$(lBytesRead, 0)
Call OemToCharBuff(baOutput(0), sNewOutput, lBytesRead)
Else
' perform no conversion (except to Unicode)
sNewOutput = Left$(StrConv(baOutput(), vbUnicode), lBytesRead)
End If
GetCommandOutput = GetCommandOutput & sNewOutput
' If you are executing an application that outputs data during a long time,
' and don't want to lock up your application, it might be a better idea to
' wrap this code in a class module in an ActiveX EXE and execute it asynchronously.
' Then you can raise an event here each time more data is available.
'RaiseEvent OutputAvailabele(sNewOutput)
Loop
' When the process terminates successfully, Err.LastDllError will be
' ERROR_BROKEN_PIPE (109). Other values indicates an error.
Call CloseHandle(pi.hProcess)
Else
GetCommandOutput = "Failed to create process, check the path of the command line."
End If
' clean up
Call CloseHandle(hPipeRead)
If hPipeWrite1 Then
Call CloseHandle(hPipeWrite1)
End If
If hPipeWrite2 Then
Call CloseHandle(hPipeWrite2)
End If
End Function
As the code comment in the code you linked suggests, you could pull this out-of-process into an ActiveX exe to wrap this.
To keep it in-process, you would need to use MsgWaitForMultipleObjects (https://learn.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-msgwaitformultipleobjects).
Google located a VB6 sample: http://www.freevbcode.com/ShowCode.asp?ID=4322
The idea here is that the function will wait until either data is available on the handle OR there is a UI event (in which case DoEvents is used to dispatch).
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.