article

Getting Service data from QueryServiceConfig and QueryServiceConfig2 Correctly

Email
Submitted on: 8/7/2015 3:17:48 PM
By: William W. 
Level: Intermediate
User Rating: Unrated
Compatibility: VB 5.0, VB 6.0
Views: 7018
 
     Getting Service data from QueryServiceConfig and QueryServiceConfig2 Correctly Recently I was working on my program for editing and querying the parameters for a service, I had found a few examples in for QueryServiceConfig and QueryServiceConfig2 in the MSDN Database they were even in VB! So I figured hey great just a little editing and I can fly through this... Let me tell you I was WRONG rather the examples were wrong. First off let me preface this article by saying there aren't many places to find this info on the web especially not for vb6 and while you may decide I didn't do this correctly, I did do it and it works PROPERLY! Now if you have a better way feel free to put it somewhere that someone can find it... Preferably also send it to me because I’d like to know. Without any further delay, on to the items at hand, *See Included Source Code for a working example of this article

This article has accompanying files

 
				Getting Service data from QueryServiceConfig and QueryServiceConfig2 Correctly
Recently I was working on my program for editing and querying the parameters for a service, I had found a few examples in for QueryServiceConfig and QueryServiceConfig2 in the MSDN Database they were even in VB! So I figured hey great just a little editing and I can fly through this... 
Let me tell you I was WRONG rather the examples were wrong. 
First off let me preface this article by saying there aren't many places to find this info on the web especially not for vb6 and while you may decide I didn't do this correctly, I did do it and it works PROPERLY!
Now if you have a better way feel free to put it somewhere that someone can find it...
Preferably also send it to me because I’d like to know
Without any further delay, on to the items at hand,
*See Included Source Code for a working example of this article
QueryServiceConfig- Description right from MSDN
Retrieves the configuration parameters of the specified service. Optional configuration parameters are available using the QueryServiceConfig2 function.
QueryServiceConfig(hService As Long, lpServiceConfig As Any, cbBufSize As Long, pcbBytesNeeded As Long) As Long
Now first off I’m not just going to give you some totally completed code here in the article but I will give you all the info you need
lpServiceConfig = vbnull and cbbufsize =0 will get the returned buffer size then we will use the pcbBytesNeeded to set our buffer sizes.
Next we will pass a pointer to a QUERY_SERVICE_CONFIG structure along with cbBufferSize being set to the proper buffer size we retrieved from pcbBytesNeeded 
Now if you notice the MSDN definition on QUERY_SERVICE_CONFIG on lpDependencies look at the bold print here is where the MSDN VB example falls on its face. It treated it as just a pointer to a null terminated string
So if there were any dependencies you only got the first one…
If you ask me that’s a pretty big discrepancy.
lpDependencies 
A pointer to an array of null-separated names of services or load ordering groups that must start before this service. The array is doubly null-terminated. If the pointer is NULL or if it points to an empty string, the service has no dependencies. If a group name is specified, it must be prefixed by the SC_GROUP_IDENTIFIER (defined in Winsvc.h) character to differentiate it from a service name, because services and service groups share the same name space. Dependency on a service means that this service can only run if the service it depends on is running. Dependency on a group means that this service can run if at least one member of the group is running after an attempt to start all members of the group.
Now how in the world are we going to get this data out of there with VB6?
With .Net we can make a custom marshaler but not in good old VB6
Now the solution depends on if we are using the ANSI Version (QueryServiceConfigA)
Or the UNICODE Version (QueryServiceConfigW)
For the ANSI version of QueryServiceConfig we can just use lstrCpyW which is the UNICODE version or lstrcpy, whereas all the rest of the pointers will use the ANSI version (lstrcpyA). The reason for this is the nulls separating the individual strings, lstrcpyA will stop at the first one whereas lstrcpyW will go to the double null at the end and stop, returning a big null separated string which you can split at the nulls or replace the nulls with commas or whatever you feel like doing to the string to parse it to your liking.
 lstrcpyA PathName, QSCfg.lpBinaryPathName
 lstrcpyA DisplayName, QSCfg.lpDisplayName
 lstrcpyA LoadOrderGroup, QSCfg.lpLoadOrderGroup
 lstrcpyA ServiceStartName, QSCfg.lpServiceStartName
 lstrcpyW Dependencies, QSCfg.lpDependencies
 
For the Unicode version of QueryServiceConfig we run into some problems 
Getting the data once again. Now lpDependencies is a double Null separated triple Null terminated string (try to find that info in MSDN). That gives a new host of problems because there isn’t an Api that does that easily or is there?
Well here we can use CopyMemory but it requires a length and there isn’t a length returned well we have two options:
1. Read through 3 characters at a time from the start of the pointer till we find the triple null terminating the string (hope for no buffer over runs).
2. find which field comes after the lpdependencies pointer and read the data between the two (hope the order is always the same)
1.
Public Function PtrToDNTS(Pointer As Long) As String
'this is for the Unicode version of QueryServiceConfig
 Dim Buffer() As Byte
 Dim nLen As Long
 Dim QStr As String
'make sure there is some data there before we go searching
If lstrlenW(Pointer) > 0 Then
 'start at the begining of the location the pointer points
 nLen = 0
 'look for the triple null that terminates the string
 ReDim Buffer(0 To 5) As Byte
 Do Until QStr = String(3, vbNullChar)
 CopyMemory Buffer(0), ByVal Pointer + nLen, 3
 QStr = Buffer
 nLen = nLen + 4
 Loop
End If
If Pointer <> 0 And nLen <> 0 Then
 ReDim Buffer(0 To nLen - 1) As Byte
 CopyMemory Buffer(0), ByVal Pointer, nLen
 PtrToDNTS = Buffer
End If
Example:
Dependencies= PtrToDNTS(QSCfg lpDependencies)
2.
 Public Function GetDATABetweenPointers(PntStart As Long, PntEnd As Long) As String
'gets all data between two pointers
Dim Buffer() As Byte
Dim nLen As Long
If PntStart And PntStart < PntEnd Then
 nLen = PntEnd - PntStart
 If nLen Then
ReDim Buffer(0 To nLen - 1) As Byte
CopyMemory Buffer(0), ByVal PntStart, nLen
GetDATABetweenPointers = Buffer
 End If
End If
End Function
Example:
Dependencies= GetDATABetweenPointers(QSCfg lpDependencies, QSCfg lpServiceStartName)
Note, this is the actual way to call this at least in Windows XP as lpServiceStartName is the next pointer after lpDependencies
QueryServiceConfig2-Description right from MSDN
Retrieves the optional configuration parameters of the specified service.
QueryServiceConfig2(hService As Long, dwInfoLevel As INFO_LEVEL, lpBuffer As Any, cbBufSize As Long, pcbBytesNeeded As Long) As Long
dwInfoLevel
SERVICE_CONFIG_DESCRIPTION=1
SERVICE_CONFIG_FAILURE_ACTIONS=2
*ANYTHING HIGHER THAN 2 IS ONLY SUPPORTED IN SERVER 2008 AND VISTA.
What we are looking at today is the SERVICE_FAILURE_ACTIONS Structure which gets used if dwinfoLevel = SERVICE_CONFIG_FAILURE_ACTIONS
I only found one example of this on the web and it didn’t work right…
So I will Just Paste A working example below:
Note: this goes in a Module (.Bas)
*See Included Source Code for a working example of this article
'----------------Beginning of Bas Module Code-------------------
Option Explicit
Private Type SERVICE_FAILURE_ACTIONS
dwResetPeriod As Long
lpRebootMsg As Long
lpCommand As Long
cActions As Long
lpsaActions As Long
End Type
Private Type SC_ACTION
dwType As Long
dwDelay As Long
End Type
Public Type FailureActions
Actions As Long
ResetPeriod As Long
Command As String
RebootMsg As String
saActions() As SC_ACTION
End Type
Private Type QUERY_SERVICE_CONFIG
dwServiceType As Long
dwStartType As Long
dwErrorControl As Long
lpBinaryPathName As Long
lpLoadOrderGroup As Long
dwTagId As Long
lpDependencies As Long
lpServiceStartName As Long
lpDisplayName As Long
End Type
Public Type SvcReturn
Error As Long
Account As String
DisplayName As String
Dependencies As String
ErrorControl As String
TagId As String
LoadOrderGroup As String
PathName As String
StartType As String
ServiceType As String
End Type
Private Const SC_MANAGER_CONNECT = &H1
Private Const SERVICE_QUERY_CONFIG As Long = &H1
Private Const SERVICE_CONFIG_FAILURE_ACTIONS As Long = 2
Private Const ERROR_INSUFFICIENT_BUFFER = 122
Private Declare Function OpenSCManager Lib "advapi32" _
 Alias "OpenSCManagerW" ( _
 ByVal lpMachineName As String, _
 ByVal lpDatabaseName As String, _
 ByVal dwDesiredAccess As Long) As Long
Private Declare Function OpenService Lib "advapi32.dll" _
 Alias "OpenServiceW" ( _
 ByVal hSCManager As Long, _
 ByVal lpServiceName As String, _
 ByVal dwDesiredAccess As Long) As Long
Private Declare Function QueryServiceConfig Lib "advapi32.dll" _
 Alias "QueryServiceConfigW" ( _
 ByVal hService As Long, _
 ByRef lpServiceConfig As Any, _
 ByVal cbBufSize As Long, _
 ByRef pcbBytesNeeded As Long) As Long
Private Declare Function QueryServiceConfig2 Lib "advapi32.dll" _
 Alias "QueryServiceConfig2W" ( _
 ByVal hService As Long, _
 ByVal dwInfoLevel As Long, _
 lpBuffer As Any, _
 ByVal cbBufSize As Long, _
 pcbBytesNeeded As Long) As Long
Private Declare Function CloseServiceHandle Lib "advapi32" (ByVal hSCObject As Long) As Long
Private Declare Function lstrcpyW Lib "kernel32.dll" ( _
 ByVal lpString1 As String, _
 ByVal lpString2 As Long) As Long
Private Declare Function lstrlenA Lib "kernel32.dll" (ByVal lpString As Any) As Long
Private Declare Function lstrlenW Lib "kernel32.dll" (ByVal lpString As Any) As Long
Private Declare Sub CopyMemory Lib "kernel32" _
 Alias "RtlMoveMemory" ( _
 pTo As Any, _
 uFrom As Any, _
 ByVal lSize As Long)
Public Function GetDATABetweenPointers(PntStart As Long, PntEnd As Long, Optional DlmtChar As _
String = ",") As String
'gets all data between two pointers
 Dim Buffer() As Byte
 Dim nLen As Long
If PntStart And PntStart < PntEnd Then
 nLen = PntEnd - PntStart
 If nLen Then
 ReDim Buffer(0 To nLen - 1) As Byte
 CopyMemory Buffer(0), ByVal PntStart, nLen
 GetDATABetweenPointers = Buffer
 End If
 If Len(GetDATABetweenPointers) > 2 Then
 'get rid of the null at the end of the string
 GetDATABetweenPointers = Mid(GetDATABetweenPointers, 1, Len(GetDATABetweenPointers) - 2)
 'replace remaining nulls with the delimiter
 GetDATABetweenPointers = Replace(GetDATABetweenPointers, vbNullChar, DlmtChar)
 'if there is a , at the end get rid of it
 If Mid(GetDATABetweenPointers, Len(GetDATABetweenPointers) - Len(DlmtChar) + 1) = DlmtChar _
Then GetDATABetweenPointers = Mid(GetDATABetweenPointers, 1, _
Len(GetDATABetweenPointers) - Len(DlmtChar))
Else
 GetDATABetweenPointers = ""
 End If
End If
End Function
Public Function GetServiceConfig(ServiceName As String, _
 ByRef ConfigReturn As SvcReturn, _
 Optional DependenciesDecode1 As Boolean = True) As Long
 Const STRUCT_SIZE = 36
'returns Svcreturn Structure with service configuration
 Dim hSCManager As Long
 Dim hService As Long
 Dim SCfg() As QUERY_SERVICE_CONFIG
 Dim lBuffer As Long
 Dim lBytesNeeded As Long
 Dim lStructNeeded As Long
hSCManager = OpenSCManager(vbNullString, vbNullString, SC_MANAGER_CONNECT)
'Open the service manager with desired access
If hSCManager <> 0 Then
 hService = OpenService(hSCManager, StrConv(ServiceName, vbUnicode), SERVICE_QUERY_CONFIG)
 If hService <> 0 Then
 'open selected service to get or set desired state
 Call QueryServiceConfig(hService, ByVal vbNull, &H0, lBytesNeeded)
 If Err.LastDllError <> ERROR_INSUFFICIENT_BUFFER Then
GetServiceConfig = Err.LastDllError
Exit Function
 End If
 'Calculate the buffer sizes
 lStructNeeded = lBytesNeeded / STRUCT_SIZE + 1
 ReDim SCfg(lStructNeeded - 1)
 lBuffer = lStructNeeded * STRUCT_SIZE
 Call QueryServiceConfig(hService, SCfg(0), lBuffer, lBytesNeeded) 'fix later
 With ConfigReturn
.Account = PtrToStringW(SCfg(0).lpServiceStartName)
'.Dependencies = PtrToStringW(SCfg(0).lpDependencies) 'this is what the msdn example
'shows
If DependenciesDecode1 = True Then
.Dependencies = PtrToDNTS(SCfg(0).lpDependencies, ",")
 Else
.Dependencies = GetDATABetweenPointers(SCfg(0).lpDependencies, _
 SCfg(0).lpServiceStartName)
End If
.DisplayName = PtrToStringW(SCfg(0).lpDisplayName)
.ErrorControl = SCfg(0).dwErrorControl
.LoadOrderGroup = PtrToStringW(SCfg(0).lpLoadOrderGroup)
.PathName = PtrToStringW(SCfg(0).lpBinaryPathName)
.ServiceType = SCfg(0).dwServiceType
.StartType = SCfg(0).dwStartType
.TagId = SCfg(0).dwTagId
 End With
 CloseServiceHandle hService
Else
 GetServiceConfig = Err.LastDllError
 End If
 'CloseServiceHandle hSCManager
Else
 GetServiceConfig = Err.LastDllError
End If
End Function
Public Function GetServiceFailureActions(ServiceName As String, _
 ByRef ServiceFailActions As FailureActions) As Long
'Retrieves Service Failure data with queryserviceconfig2
 Dim hSCManager As Long
 Dim hService As Long
 Dim STRUCT_SIZE As Long
 Dim lBuffer As Long
 Dim lBytesNeeded As Long
 Dim lStructNeeded As Long
 Dim SCfgSFA() As SERVICE_FAILURE_ACTIONS
STRUCT_SIZE = Len(SCfgSFA(0)) '20
hSCManager = OpenSCManager(vbNullString, vbNullString, SC_MANAGER_CONNECT)
'Open the service manager with desired access
If hSCManager <> 0 Then
 hService = OpenService(hSCManager, StrConv(ServiceName, vbUnicode), SERVICE_QUERY_CONFIG)
 If hService <> 0 Then
 'open selected service to get or set desired state
 Call QueryServiceConfig2(hService, SERVICE_CONFIG_FAILURE_ACTIONS, vbNull, 0, lBytesNeeded)
 If Err.LastDllError <> ERROR_INSUFFICIENT_BUFFER Then
GetServiceFailureActions = Err.LastDllError
'Exit Function
 End If
 'Calculate the buffer sizes
 lStructNeeded = lBytesNeeded / STRUCT_SIZE + 1
 ReDim SCfgSFA(lStructNeeded - 1)
 lBuffer = lStructNeeded * STRUCT_SIZE
 Call QueryServiceConfig2(hService, SERVICE_CONFIG_FAILURE_ACTIONS, SCfgSFA(0), lBuffer, _
lBytesNeeded)
 With SCfgSFA(0)
ServiceFailActions.Actions = .cActions
ServiceFailActions.ResetPeriod = .dwResetPeriod
ServiceFailActions.Command = PtrToStringW(.lpCommand)
ServiceFailActions.RebootMsg = PtrToStringW(.lpRebootMsg)
If SCfgSFA(0).lpsaActions <> 0 Then
ReDim ServiceFailActions.saActions(.cActions - 1)
CopyMemory ServiceFailActions.saActions(0), ByVal .lpsaActions, _
 Len(ServiceFailActions.saActions(0)) * .cActions _
'copy the data into the servicefailactions.saActions structure
'the length is 2 longs ie 8 bytes * number of elements
End If 'if there are actions
 End With
 CloseServiceHandle hService
Else
 GetServiceFailureActions = Err.LastDllError
 End If
 CloseServiceHandle hSCManager
Else
 GetServiceFailureActions = Err.LastDllError
End If
End Function
Public Function PtrToDNTS(Pointer As Long, Optional DlmtChar As String = ",") As String
'this is for the Unicode version of QueryServiceConfig
On Error GoTo Failed
'hate to pull some poo like this but i can't figure out any other way to get
'double null separated strings to show correctly
'turns a pointer into the double null terminated string it points to and optionally
'turns the single nulls into dlmtchar
 Dim Buffer() As Byte
 Dim nLen As Long
 Dim QStr As String
'make sure there is some data there before we go searching
If lstrlenW(Pointer) > 0 Then
 'start at the begining of the location the pointer points
 nLen = 0
 'look for the triple null that terminates the string
 ReDim Buffer(0 To 5) As Byte
 Do Until QStr = String(3, vbNullChar)
 CopyMemory Buffer(0), ByVal Pointer + nLen, 3
 QStr = Buffer
 nLen = nLen + 4
 Loop
End If
If Pointer <> 0 And nLen <> 0 Then
 ReDim Buffer(0 To nLen - 1) As Byte
 CopyMemory Buffer(0), ByVal Pointer, nLen
 PtrToDNTS = Buffer
End If
If Len(PtrToDNTS) > 2 Then
 'get rid of the null at the end of the string
 PtrToDNTS = Mid(PtrToDNTS, 1, Len(PtrToDNTS) - 2)
 'replace remaining nulls with the delimiter
 PtrToDNTS = Replace(PtrToDNTS, vbNullChar, DlmtChar)
 'if there is a , at the end get rid of it
 If Mid(PtrToDNTS, Len(PtrToDNTS) - Len(DlmtChar) + 1) = DlmtChar Then PtrToDNTS = _
 Mid(PtrToDNTS, 1, Len(PtrToDNTS) - Len(DlmtChar))
Else
 PtrToDNTS = ""
End If
Exit Function
Failed:
PtrToDNTS = -1
End Function
Public Function PtrToStringW(PointerW As Long) As String
'turns a pointer into the Unicode string it points to
 Dim Buffer() As Byte
 Dim nLen As Long
If PointerW Then
 ' Double the retval of lstrlenW
 ' for proper buffer size.
 nLen = lstrlenW(PointerW) * 2
 If nLen Then
 ReDim Buffer(0 To (nLen - 1)) As Byte
 CopyMemory Buffer(0), ByVal PointerW, nLen
 PtrToStringW = Buffer
 End If
End If
End Function
'-----------------------END of Bas Module Code-------------------
If anyone has a better way to do this let me know but I think this is about the only ways to get the info from these functions in VB6
*Bilgus*

winzip iconDownload article

Note: Due to the size or complexity of this submission, the author has submitted it as a .zip file to shorten your download time. Afterdownloading it, you will need a program like Winzip to decompress it.Virus note:All files are scanned once-a-day by Planet Source Code for viruses, but new viruses come out every day, so no prevention program can catch 100% of them. For your own safety, please:
  1. Re-scan downloaded files using your personal virus checker before using it.
  2. NEVER, EVER run compiled files (.exe's, .ocx's, .dll's etc.)--only run source code.
  3. Scan the source code with Minnow's Project Scanner

If you don't have a virus scanner, you can get one at many places on the net including:McAfee.com


Other 7 submission(s) by this author

 


Report Bad Submission
Use this form to tell us if this entry should be deleted (i.e contains no code, is a virus, etc.).
This submission should be removed because:

Your Vote

What do you think of this article (in the Intermediate category)?
(The article with your highest vote will win this month's coding contest!)
Excellent  Good  Average  Below Average  Poor (See voting log ...)
 

Other User Comments


 There are no comments on this submission.
 

Add Your Feedback
Your feedback will be posted below and an email sent to the author. Please remember that the author was kind enough to share this with you, so any criticisms must be stated politely, or they will be deleted. (For feedback not related to this particular article, please click here instead.)
 

To post feedback, first please login.