FrancescoDiMuro Posted April 16, 2020 Posted April 16, 2020 (edited) Good morning Forums In these days, I am working on a project that involved me to use some Windows APIs to obtain some information about Terminal Servers. I'm doing this using wtsapi32.dll in a VBA Project, but, the lack of knowledge about few things threated in the articles make this quite difficult to implement and understand at the same time. The most difficult thing I'm facing is "translating" C/C++ functions or struct in VBA when pointers are used, or pointers of pointers, and so on. Since VBA seems to not have a pointer type, to make those functions work I need to implement other functions taken from other DLLs, and this confuses me a lot. For example, starting from this code, I splitted all the functions and all the definitions to understand why they are there, and why I need to use them. At the end, I've found out that the code I was going to implement starting from the functions provided in the Microsoft Docs won't be ever be able to work without some supplementary functions which are not mentioned anywhere. So, I was wondering if someone would please point me out to a good and practical exaplanation about pointers (in general) or specifically for VBA, because I need to use them quite often in these days, and I'd like to understand what I am doing. Thanks in advance. Best Regards and Stay at home 🏡 Edited April 17, 2020 by FrancescoDiMuro Click here to see my signature: Spoiler ALWAYS GOOD TO READ: Forum Rules Forum Etiquette
FrancescoDiMuro Posted April 17, 2020 Author Posted April 17, 2020 Bump. Click here to see my signature: Spoiler ALWAYS GOOD TO READ: Forum Rules Forum Etiquette
Danyfirex Posted April 17, 2020 Posted April 17, 2020 Hello. Maybe this , this , this and this can help you. Saludos FrancescoDiMuro 1 Danysys.com AutoIt... UDFs: VirusTotal API 2.0 UDF - libZPlay UDF - Apps: Guitar Tab Tester - VirusTotal Hash Checker Examples: Text-to-Speech ISpVoice Interface - Get installed applications - Enable/Disable Network connection PrintHookProc - WINTRUST - Mute Microphone Level - Get Connected NetWorks - Create NetWork Connection ShortCut
FrancescoDiMuro Posted April 17, 2020 Author Posted April 17, 2020 Thanks @Danyfirex. I will study what you provided. Have a good day Click here to see my signature: Spoiler ALWAYS GOOD TO READ: Forum Rules Forum Etiquette
FrancescoDiMuro Posted April 19, 2020 Author Posted April 19, 2020 (edited) Good afternoon @Danyfirex I looked at the links you provided to me, and they have been really helpful about the topic, but, I have a question I can't find an answer for. Let me show you some code: Spoiler Sub StringPointer() Dim lngServer As Long 'Server Handle Dim lngSessionId As Long 'SessionId Dim lngBuffer As Long 'Buffer to the pointer of the information Dim lngBytes As Long 'Lenght (in Bytes) of the information returned by Dim strWTSUserName As String 'Obtaining Server Handle lngServer = WTSOpenServerA("") 'Insert the name of your PC here, or skip this function call and use 0& in WTSQuerySessionInformationA call 'If the handle of the Server is valid If lngServer Then 'Set the SessionId for the Query lngSessionId = 0& 'Insert the SessionId by going to Task Manager -> Users -> right-click on columns -> show column "ID" 'Checking for a valid return value of WTSQuerySessionInformationA 'I'm calling lngBuffer and lngBytes since they are two pointers If WTSQuerySessionInformationA(ByVal lngServer, ByVal lngSessionId, ByVal WTSUserName, lngBuffer, lngBytes) 'Buffer address and size (in Bytes) of the information WTSUserName Debug.Print "Buffer : " & lngBuffer & vbNewLine & _ "Bytes del buffer: " & lngBytes 'Output of the converted WTSUserName Debug.Print "WTSUserName: " & PointerToStringA(lngBuffer) 'Buffer clean-up WTSFreeMemory lngBuffer 'Closing server handle WTSCloseServer lngServer End If End If End Sub If you run this function on your PC, you'd probably note that the value of lngBytes is not the lenght in Bytes of the information returned, but the lenght in characters of that, and I've found it strange since in the documentation about WTSQuerySessionInformationA() it says that pBytes stores the lenght in Bytes of the information returned. Said that, let's see the PointerToStringA() function: Spoiler Function PointerToStringA(ByVal lngStringPointer As Long) Dim lngTempBuffer() As Byte 'Temporary buffer Dim lngStringLenght As Long 'String lenght (expressed in number of characters) Dim strString As String 'Output string 'Obtaining the number of characters of the string (without the null character at the end of the string) lngStringLenght = lstrlenA(lngStringPointer) 'Buffer ReDim ReDim lngTempBuffer(lngStringLenght) 'Content string copy [0000]([X])[\0] starting from the first element of the Bytes array, of lngStringLenght Bytes CopyMemory lngTempBuffer(0&), ByVal lngStringPointer, lngStringLenght 'For each element in the Bytes array, convert it to Char and compose the string For i = 0 To lngStringLenght - 1 Step 1 strString = strString & Chr(lngTempBuffer(i)) Next 'Return value of PointerToStringA PointerToStringA = strString End Function From here too, I am a little confused. Since a string in VBA is a pointer to a BSTR data type, couldn't I get the lenght of the string returned by WTSQuerySessionInformationA taking the 4 Bytes data before the start of the string content? Practically, no, but why? Then, when I ReDim the Bytes array, shouldn't I use the lenght of the string in characters * the number of Bytes of each character in a VB String (2 Bytes)? Here too, the answer is "Practically no", but I tried to modify the above code to what "theory says", and the app crashes. I am asking these questions just to "completely" understand what's going on when strings and pointers are used in VB. Here it is the Wtsapi32 Module: Spoiler Enum WTS_CONNECTSTATE '[_First] => Se non viene specificato, il valore di [_First] e' 0 WTSActive WTSConnected WTSConnectQuery WTSShadow WTSDisconnected WTSIdle WTSListen WTSReset WTSDown WTSInit '[_Last] End Enum Type WTS_SESSION_INFOA SessionId As Long pWinStationName As Long State As WTS_CONNECTSTATE End Type Enum WTS_INFO_CLASS WTSInitialProgram WTSApplicationName WTSWorkingDirectory WTSOEMId WTSSessionId WTSUserName WTSWinStationName WTSDomainName WTSConnectState WTSClientBuildNumber WTSClientName WTSClientDirectory WTSClientProductId WTSClientHardwareId WTSClientAddress WTSClientDisplay WTSClientProtocolType WTSIdleTime WTSLogonTime WTSIncomingBytes WTSOutgoingBytes WTSIncomingFrames WTSOutgoingFrames WTSClientInfo WTSSessionInfo WTSSessionInfoEx WTSConfigInfo WTSValidationInfo WTSSessionAddressV4 WTSIsRemoteSession End Enum 'typedef struct _WTSINFOA { ' WTS_CONNECTSTATE_CLASS State; ' DWORD SessionId; ' DWORD IncomingBytes; ' DWORD OutgoingBytes; ' DWORD IncomingFrames; ' DWORD OutgoingFrames; ' DWORD IncomingCompressedBytes; ' DWORD OutgoingCompressedBy; ' CHAR WinStationName[WINSTATIONNAME_LENGTH]; ' CHAR Domain[DOMAIN_LENGTH]; ' CHAR UserName[USERNAME_LENGTH + 1]; ' LARGE_INTEGER ConnectTime; ' LARGE_INTEGER DisconnectTime; ' LARGE_INTEGER LastInputTime; ' LARGE_INTEGER LogonTime; ' LARGE_INTEGER CurrentTime; '} WTSINFOA, *PWTSINFOA; Type WTSINFOA State As WTS_CONNECTSTATE SessionId As Long IncomingBytes As Long OutgoingBytes As Long IncomingFrames As Long OutgoingFrames As Long IncomingCompressedBytes As Long OutgoingCompressedBy As Long WinStationName As Long Domain As Long UserName As Long ConnectTime As LongPtr DisconnectTime As LongPtr LastInputTime As LongPtr LogonTime As LongPtr CurrentTime As LongPtr End Type Public Declare Function WTSOpenServerA Lib "Wtsapi32.dll" (ByVal pServerName As String) As Long '**************************************************************************************************** ' 'BOOL WTSEnumerateSessionsA( ' IN HANDLE hServer, ' IN DWORD Reserved, ' IN DWORD Version, ' PWTS_SESSION_INFOA *ppSessionInfo, ' DWORD *pCount '); Public Declare Function WTSEnumerateSessionsA Lib "Wtsapi32.dll" _ (ByVal lngServer As Long, _ ByVal Reserved As Integer, _ ByVal Version As Integer, _ ppSessionInfo As Long, _ pCount As Long) As Boolean '**************************************************************************************************** ' 'BOOL WTSQuerySessionInformationA( ' IN HANDLE hServer, ' IN DWORD SessionId, ' IN WTS_INFO_CLASS WTSInfoClass, ' LPSTR *ppBuffer, ' DWORD *pBytesReturned '); Public Declare Function WTSQuerySessionInformationA Lib "Wtsapi32.dll" _ (ByVal hServer As Long, _ ByVal SessionId As Long, _ ByVal WTSInfoClass As WTS_INFO_CLASS, _ ppBuffer As Long, _ pBytesReturned As Long) As Boolean '**************************************************************************************************** Public Declare Sub WTSCloseServer Lib "Wtsapi32.dll" (ByVal hServer As Long) Public Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" _ (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long) Public Declare Sub WTSFreeMemory Lib "Wtsapi32.dll" (ByVal pMemory As Long) Public Declare PtrSafe Function lstrlenA Lib "kernel32.dll" (ByVal lpString As LongPtr) As Long Could you (or anyone) please answer me, even with an example, to those questions?EDIT: Added a string test. Spoiler Sub TestString() Dim strTestString As String Dim ptrBSTR As Long Dim ptrString As Long Dim lngStringLenght As Long Dim arrBuffer() As Byte Dim strStringFromBufferANSI As String: strStringFromBufferANSI = "" Dim strStringFromBufferUNICODE As String: strStringFromBufferUNICODE = "" 'Setting a test string strTestString = "This is a test string" 'Pointer to the BSTR Data Type ptrBSTR = VarPtr(strTestString) 'Pointer to the start of the string and the ending vbNull character ptrString = StrPtr(strTestString) 'Lenght of the string directly from the string (In Bytes) CopyMemory lngStringLenght, ByVal ptrString - 4&, 4 'ReDim of the Buffer array ReDim arrBuffer(lngStringLenght) As Byte 'Copy the content of the string and the ending vbNull character CopyMemory arrBuffer(0&), ByVal ptrString, lngStringLenght 'Creating the string in ANSI format (from the 0th element to the i - 2) => 2 Bytes vbNull ? For i = 0 To UBound(arrBuffer) - 2 Step 1 strStringFromBufferANSI = strStringFromBufferANSI & Chr(arrBuffer(i)) Next 'Creating the string in Unicode format (from the 0th element to the i - 1) => 1 Byte vbNull ? For i = 0 To UBound(arrBuffer) - 1 Step 2 strStringFromBufferUNICODE = strStringFromBufferUNICODE & Chr(arrBuffer(i)) Next 'Output some information Debug.Print "The string is : " & strTestString & vbNewLine & _ "The pointer to the BSTR is : " & ptrBSTR & vbNewLine & _ "The pointer to the string is : " & ptrString & vbNewLine & _ "The lenght of the string is : " & lngStringLenght & " [Bytes]" & vbNewLine & _ "The actual lenght of the string is : " & lngStringLenght / 2 & " [Characters]" & vbNewLine & _ "The first Byte of the array is : " & arrBuffer(0) & ", which is the character '" & Chr(arrBuffer(0)) & "'" & vbNewLine & _ "The string from the buffer is : '" & strStringFromBufferANSI & "' [ANSI]" & vbNewLine & _ "The string from the buffer is : '" & strStringFromBufferUNICODE & "' [UNICODE]" End Sub P.S.: To the reader: Spoiler I kindly ask you sorry if some question may be "absurd", and this is the reason why I am here asking you these questions Thanks in advance Edited April 19, 2020 by FrancescoDiMuro Added string test Click here to see my signature: Spoiler ALWAYS GOOD TO READ: Forum Rules Forum Etiquette
Danyfirex Posted April 19, 2020 Posted April 19, 2020 Hello. Quote Since a string in VBA is a pointer to a BSTR data type, couldn't I get the length of the string returned by WTSQuerySessionInformationA taking the 4 Bytes data before the start of the string content? yes You can get the length of a string from the BSTR structure But It's not the right/correct way. But using WTSQuerySessionInformationA you will get the length in lngBytes in bytes. So If you use WTSQuerySessionInformationW You will get lngBytes * 2 Quote Then, when I ReDim the Bytes array, shouldn't I use the lenght of the string in characters * the number of Bytes of each character in a VB String (2 Bytes)?Here too, the answer is "Practically no", but I tried to modify the above code to what "theory says", and the app crashes.I am asking these questions just to "completely" understand what's going on when strings and pointers are used in VB. It's correct you can use the lngBytes for redim and subtract 2, (1 for NULL Char at the end of the string and the other because vba array bound starts from 0.) It's what lstrlenA does. in your code you could do this instead using PointerToStringA. Dim aByteArray() As Byte ReDim aByteArray(lngBytes - 2) CopyMemory aByteArray(0), ByVal lngBuffer, lngBytes - 1 Dim sStr As String sStr = StrConv(aByteArray, vbUnicode) Saludos FrancescoDiMuro 1 Danysys.com AutoIt... UDFs: VirusTotal API 2.0 UDF - libZPlay UDF - Apps: Guitar Tab Tester - VirusTotal Hash Checker Examples: Text-to-Speech ISpVoice Interface - Get installed applications - Enable/Disable Network connection PrintHookProc - WINTRUST - Mute Microphone Level - Get Connected NetWorks - Create NetWork Connection ShortCut
FrancescoDiMuro Posted April 19, 2020 Author Posted April 19, 2020 2 hours ago, Danyfirex said: So If you use WTSQuerySessionInformationW You will get lngBytes * 2 Now it is much clearer. I didn't know the difference between A (ANSI) and W (Wide Char or Unicode). By default, I saw that VBA uses ANSI format for strings, so better to use all the functions and structures with the final "A" in order to don't have any problem of conversion from/to Unicode format. Is it a Wise choice? I'm gonna read some articles about ANSI and Unicode to understand the difference. For the code you posted, I will try ASAP and let you know the results. As always, thanks dear @Danyfirex P.S.: your dog looks amazing! Click here to see my signature: Spoiler ALWAYS GOOD TO READ: Forum Rules Forum Etiquette
FrancescoDiMuro Posted April 20, 2020 Author Posted April 20, 2020 Good morning I just tested the WTSOpenServerW, and, even if it returns a Long variable, it doesn't work when used with WTSQuerySessionInformationW; I had to use 0& when calling WTSQuerySessionInformationW, but seems that it loads a little bit more then the *A functions (~1 second more). The discussion about A and W is not very clear on these functions. I have to study more, but, for now, I think that I'm going to use A functions and structures, even if they are stangely faster than then W ones. Click here to see my signature: Spoiler ALWAYS GOOD TO READ: Forum Rules Forum Etiquette
Danyfirex Posted April 20, 2020 Posted April 20, 2020 The reason that You don't get working the Wide version is because You'll need to copy the Unicode string directly to your vba string. Like this way. Dim sStr As String sStr = String((lngBytes / 2) - 1, vbNullChar) CopyMemory ByVal StrPtr(sStr), ByVal lngBuffer, lngBytes - 2 Saludos Danysys.com AutoIt... UDFs: VirusTotal API 2.0 UDF - libZPlay UDF - Apps: Guitar Tab Tester - VirusTotal Hash Checker Examples: Text-to-Speech ISpVoice Interface - Get installed applications - Enable/Disable Network connection PrintHookProc - WINTRUST - Mute Microphone Level - Get Connected NetWorks - Create NetWork Connection ShortCut
FrancescoDiMuro Posted April 20, 2020 Author Posted April 20, 2020 @Danyfirex Thanks for the reply as always. The part that it's not working is the one with WTSQuerySessionInformationW() function, which is returning 0. To get the handle of the Server, I used WTSOpenServerW, which takes a string as input, and it does return an handle, so, the problem seems to be (and practically too) with WTSQuerySessionInformationW() function. I feel quite confident with Bytes array and CopyMemory for now. Later I am going to test these functions in a big environment, and try to catch the LogonTime of the Session. There are a lot of things that I'm learning right now, and I feel quite confused about all of these information, and even a bit struggled. Click here to see my signature: Spoiler ALWAYS GOOD TO READ: Forum Rules Forum Etiquette
Danyfirex Posted April 21, 2020 Posted April 21, 2020 Hello. W API works correctly for me. Let me know your result when you do your tests. Saludos FrancescoDiMuro 1 Danysys.com AutoIt... UDFs: VirusTotal API 2.0 UDF - libZPlay UDF - Apps: Guitar Tab Tester - VirusTotal Hash Checker Examples: Text-to-Speech ISpVoice Interface - Get installed applications - Enable/Disable Network connection PrintHookProc - WINTRUST - Mute Microphone Level - Get Connected NetWorks - Create NetWork Connection ShortCut
FrancescoDiMuro Posted April 26, 2020 Author Posted April 26, 2020 (edited) Hey @Danyfirex I finally got the *W functions working. The error was on the function WTSOpenServerW(), which takes as "in parameter" a pointer to a Wide String, and so, I had to convert the string parameter in Unicode: Spoiler Public Declare Function WTSOpenServerW Lib "Wtsapi32.dll" (ByVal pServerName As String) As Long 'Getting the Server Handle lngServer = WTSOpenServerW(StrConv("MyPCName", vbUnicode)) But, this lead me to make another question... Why did I have to convert the string in Unicode, if VBA handles the string in Unicode format natively? Because I modified the StringFromPointerW() function like this: Spoiler Public Function StringFromPointerW(lngStringPointer As LongPtr) As String Dim byteTempBuffer() As Byte Dim lngBytesCount As Long lngBytesCount = lstrlenW(lngStringPointer) If lngBytesCount > 0 Then ReDim byteTempBuffer(0 To lngBytesCount * 2 - 1) As Byte CopyMemory VarPtr(byteTempBuffer(0&)), ByVal lngStringPointer, lngBytesCount * 2 StringFromPointerW = byteTempBuffer End If End Function and you can see that I'm assigning the value of Byte array directly to the function return's value, so, how strings in VBA are handled? Did you have to pass an Unicode string as parameter of WTSOpenServerW() in order to have it working, and how did you handle the obtainment of the value of the string from the pointer? Thank you and have a good day Best Regards. Edited April 26, 2020 by FrancescoDiMuro Click here to see my signature: Spoiler ALWAYS GOOD TO READ: Forum Rules Forum Etiquette
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now