Question:
I would like to be able to gain access to NT server local groups for enum, member enum, add member, delete member.
We have a lot of servers as well as NT and OS/2 domains. Because of this, there is a need to synchronize users and group memberships across multiple servers.
I have not found anything that will allow me to access a local group on a server from a VB program running on a client. Can you point me in the right direction?
Answer:
None of the NT Unicode network functions are particularly easy to use. In fact, they are a real pain. However, the following code will do what you want:
' ************************************************' In a form' ************************************************Option ExplicitPrivate Sub Command1_Click()On Error Resume Next Dim p_vntRtn As Variant Dim p_lngNum As Long Dim p_lngLoop As Long Dim p_strGroup As String p_strGroup = Trim$(Me.List1.List(Me.List1.ListIndex)) If Len(p_strGroup) <= 0 Then MsgBox "You must select a group first!" Exit Sub End If Me.List2.Clear p_vntRtn = GetLocalGroupMembers("", p_strGroup) p_lngNum = UBound(p_vntRtn) If p_lngNum > 0 Then For p_lngLoop = 1 To p_lngNum Me.List2.AddItem p_vntRtn(p_lngLoop) Next p_lngLoop End If End SubPrivate Sub Command2_Click()On Error Resume Next Dim p_vntRtn As Variant Dim p_lngNum As Long Dim p_lngLoop As Long Me.List1.Clear p_vntRtn = GetLocalGroups("") p_lngNum = UBound(p_vntRtn) If p_lngNum > 0 Then For p_lngLoop = 1 To p_lngNum Me.List1.AddItem p_vntRtn(p_lngLoop) Next p_lngLoop End If End SubPrivate Sub Command3_Click() Dim p_blnRtn As Boolean Dim p_strGroup As String Dim p_strUser As String p_strGroup = Trim$(Me.List1.List(Me.List1.ListIndex)) If Len(p_strGroup) <= 0 Then MsgBox "You must select a group first!" Exit Sub End If p_strUser = Trim$(Me.Text1.Text) If Len(p_strUser) <= 0 Then MsgBox "You must add a user name first!" Exit Sub End If p_blnRtn = AddDelUserToLocal(p_strGroup, _ p_strUser, _ "", True)End SubPrivate Sub Command4_Click() Dim p_blnRtn As Boolean Dim p_strGroup As String Dim p_strUser As String p_strGroup = Trim$(Me.List1.List(Me.List1.ListIndex)) If Len(p_strGroup) <= 0 Then MsgBox "You must select a group first!" Exit Sub End If p_strUser = Trim$(Me.Text2.Text) If Len(p_strUser) <= 0 Then MsgBox "You must add a user name first!" Exit Sub End If p_blnRtn = AddDelUserToLocal(p_strGroup, _ p_strUser, _ "", False)End Sub' ************************************************' In a BAS file' ************************************************Option ExplicitPrivate Const NERR_Success As Long = 0&Private Const LocalGroupInfo0 As Long = 0&Private Const LocalGroupInfo1 As Long = 1&Private Const LocalGroupMembersInfo0 As Long = 0&Private Const LocalGroupMembersInfo1 As Long = 1&Private Const LocalGroupMembersInfo2 As Long = 2&Private Const LocalGroupMembersInfo3 As Long = 3&Private Const SidTypeUser As Long = 1&Private Const SidTypeGroup As Long = 2&Private Const SidTypeDomain As Long = 3&Private Const SidTypeAlias As Long = 4&Private Const SidTypeWellKnownGroup As Long = 5&Private Const SidTypeDeletedAccount As Long = 6&Private Const SidTypeInvalid As Long = 7&Private Const SidTypeUnknown As Long = 8& Public Declare Sub CopyMem _ Lib "kernel32" Alias "RtlMoveMemory" _ (pTo As Any, _ pFrom As Any, _ ByVal lCount As Long)Private Declare Function lstrlenW _ Lib "kernel32" _ (ByVal psString As Long) As LongPrivate Declare Function NetApiBufferFree _ Lib "netapi32.dll" _ (ByVal p_lngPtrBuffer As Long) As LongPrivate Type LOCALGROUP_INFO_0 PtrGroupName As LongEnd TypePrivate Type LOCALGROUP_MEMBERS_INFO_0 PtrSidID As LongEnd TypePrivate Type LOCALGROUP_MEMBERS_INFO_1 PtrSidID As Long SidUsage As Long PtrName As LongEnd TypePrivate Type LOCALGROUP_MEMBERS_INFO_3 DomainAndName As LongEnd TypePrivate Declare Function NetLocalGroupEnum _ Lib "netapi32" _ (ByVal psServer As Long, _ ByVal level As Long, _ pPtrBuffer As Long, _ ByVal prefmaxlen As Long, _ EntriesRead As Long, _ TotalEntries As Long, _ ResumeHandle As Long) As Long Private Declare Function NetLocalGroupGetMembers _ Lib "netapi32" _ (ByVal psServer As Long, _ ByVal psLocalGroupName As Long, _ ByVal level As Long, _ pPtrBuffer As Long, _ ByVal prefmaxlen As Long, _ EntriesRead As Long, _ TotalEntries As Long, _ ResumeHandle As Long) As Long Private Declare Function NetLocalGroupAddMembers _ Lib "netapi32" _ (ByVal psServer As Long, _ ByVal psLocalGroupName As Long, _ ByVal level As Long, _ pPtrBuffer As Long, _ ByVal membercount As Long) As Long Private Declare Function NetLocalGroupDelMembers _ Lib "netapi32" _ (ByVal psServer As Long, _ ByVal psLocalGroupName As Long, _ ByVal level As Long, _ pPtrBuffer As Long, _ ByVal membercount As Long) As Long ' ================================================' Get all the memebers of a selected local group' ================================================Public Function GetLocalGroupMembers(ByVal xi_strServerName As String, _ ByVal xi_strLocalGroupName As String) As Variant Dim p_lngRtn As Long Dim p_lngPtrBuffer As Long Dim p_lngPtrServerName As Long Dim p_lngPtrLocalGroupName As Long Dim p_lngPreferredMaxLen As Long Dim p_lngEntriesRead As Long Dim p_lngTotalEntries As Long Dim p_lngResumeHwnd As Long Dim p_lngLoop As Long Dim p_lngSidType As Long Dim p_strUserName As String Dim p_strSidType As String Dim p_strSID As String Dim p_typLocalGroupMembers1 As LOCALGROUP_MEMBERS_INFO_1 Dim p_alngMemberInfo() As Long Dim p_astrMemberInfo() As String ' Convert the server name to a pointer If Len(Trim$(xi_strServerName)) = 0 Then p_lngPtrServerName = 0& Else p_lngPtrServerName = StrPtr(xi_strServerName) End If ' Convert the user name to a pointer If Len(Trim$(p_lngPtrLocalGroupName)) = 0 Then MsgBox "You must enter a local group name!", vbExclamation, "Input Error" Exit Function Else p_lngPtrLocalGroupName = StrPtr(xi_strLocalGroupName) End If p_lngPreferredMaxLen = 4096& p_lngResumeHwnd = 0& p_lngRtn = NetLocalGroupGetMembers(p_lngPtrServerName, _ p_lngPtrLocalGroupName, _ LocalGroupMembersInfo1, _ p_lngPtrBuffer, _ p_lngPreferredMaxLen, _ p_lngEntriesRead, _ p_lngTotalEntries, _ p_lngResumeHwnd) If p_lngRtn = NERR_Success Then ReDim p_alngMemberInfo(0 To (p_lngEntriesRead * 3) - 1) As Long ReDim p_astrMemberInfo(1 To p_lngEntriesRead) As String ' This gets the pointers to the structures CopyMem p_alngMemberInfo(0), _ ByVal p_lngPtrBuffer, _ p_lngEntriesRead * 4 * 3 If p_lngEntriesRead > 0 Then For p_lngLoop = 0 To p_lngEntriesRead - 1 p_strSidType = TranslateSidUsage(p_alngMemberInfo((p_lngLoop * 3) + 1)) p_strUserName = PointerToStringW(p_alngMemberInfo((p_lngLoop * 3) + 2)) p_astrMemberInfo(p_lngLoop + 1) = p_strUserName & " [" & p_strSidType & "]" Next p_lngLoop End If If p_lngPtrBuffer Then NetApiBufferFree p_lngPtrBuffer End If GetLocalGroupMembers = p_astrMemberInfo Else GetLocalGroupMembers = "" End If End Function' ================================================' Get all of the local groups' ================================================Public Function GetLocalGroups(ByVal xi_strServerName As String) As Variant Dim p_lngRtn As Long Dim p_lngLoop As Long Dim p_lngPtrBuffer As Long Dim p_lngPtrServerName As Long Dim p_lngPreferredMaxLen As Long Dim p_lngResumeHwnd As Long Dim p_lngEntriesRead As Long Dim p_lngTotalEntries As Long Dim p_alngGroupInfo() As Long Dim p_astrGroupInfo() As String ' Convert the server name to a pointer If Len(Trim$(xi_strServerName)) = 0 Then p_lngPtrServerName = 0& Else p_lngPtrServerName = StrPtr(xi_strServerName) End If p_lngPreferredMaxLen = 4096& p_lngResumeHwnd = 0& p_lngRtn = NetLocalGroupEnum(p_lngPtrServerName, _ LocalGroupInfo0, _ p_lngPtrBuffer, _ p_lngPreferredMaxLen, _ p_lngEntriesRead, _ p_lngTotalEntries, _ p_lngResumeHwnd) If p_lngRtn = NERR_Success Then ReDim p_alngGroupInfo(0 To p_lngEntriesRead - 1) As Long ReDim p_astrGroupInfo(1 To p_lngEntriesRead) As String ' This gets the pointers to the structures CopyMem p_alngGroupInfo(0), _ ByVal p_lngPtrBuffer, _ p_lngEntriesRead * 4 If p_lngEntriesRead > 0 Then For p_lngLoop = 0 To p_lngEntriesRead - 1 p_astrGroupInfo(p_lngLoop + 1) = PointerToStringW(p_alngGroupInfo(p_lngLoop)) Next p_lngLoop End If If p_lngPtrBuffer Then NetApiBufferFree p_lngPtrBuffer End If GetLocalGroups = p_astrGroupInfo Else GetLocalGroups = "" End If End Function' ================================================' Either add or delete a user from a selected' local group (depends upon xi_blnAddUser)' ================================================Function AddDelUserToLocal(ByVal xi_strGroupName As String, _ ByVal xi_strUserName As String, _ ByVal xi_strServerName As String, _ ByVal xi_blnAddUser As Boolean) As Boolean Dim p_lngPtrGroupName As Long Dim p_lngPtrUserName As Long Dim p_lngPtrServerName As Long Dim p_lngMemberCount As Long Dim p_lngRtn As Long ' Convert the server name to a pointer If Len(Trim$(xi_strServerName)) = 0 Then p_lngPtrServerName = 0& Else p_lngPtrServerName = StrPtr(xi_strServerName) End If ' Convert the group name to a pointer p_lngPtrGroupName = StrPtr(xi_strGroupName) ' Convert the user name to a pointer p_lngPtrUserName = StrPtr(xi_strUserName) ' Add the user p_lngMemberCount = 1 If xi_blnAddUser = True Then p_lngRtn = NetLocalGroupAddMembers(p_lngPtrServerName, _ p_lngPtrGroupName, _ LocalGroupMembersInfo3, _ p_lngPtrUserName, _ p_lngMemberCount) Else p_lngRtn = NetLocalGroupDelMembers(p_lngPtrServerName, _ p_lngPtrGroupName, _ LocalGroupMembersInfo3, _ p_lngPtrUserName, _ p_lngMemberCount) End If If p_lngRtn = NERR_Success Then AddDelUserToLocal = True Else AddDelUserToLocal = False End If End Function' ================================================' Convert the SID type to a text string' ================================================Private Function TranslateSidUsage(ByVal xi_lngSidType As Long) As String Dim p_strTmp As String Select Case xi_lngSidType Case SidTypeUser p_strTmp = "User Account" Case SidTypeGroup p_strTmp = "Global Group Account" Case SidTypeDomain p_strTmp = "Domain Group Account" Case SidTypeAlias p_strTmp = "Alias Account" Case SidTypeWellKnownGroup p_strTmp = "Well-known Group Account (ie, Everyone)" Case SidTypeDeletedAccount p_strTmp = "Deleted Account" Case SidTypeInvalid p_strTmp = "Invalid Account" Case SidTypeUnknown p_strTmp = "Account Type Cannot Be Determined" Case Else p_strTmp = "Bad Parameter for Sid Usage" End Select ' Set the return value TranslateSidUsage = p_strTmp End Function' ================================================' Convert a pointer to a string into a string' ================================================Public Function PointerToStringW(ByVal xi_lngPtrToString As Long) As String Dim p_strText As String Dim p_lngLength As Long If xi_lngPtrToString Then p_lngLength = lstrlenW(xi_lngPtrToString) If p_lngLength > 0 Then p_strText = Space$(p_lngLength) CopyMem ByVal StrPtr(p_strText), ByVal xi_lngPtrToString, p_lngLength * 2 End If End If PointerToStringW = p_strTextEnd Function' ================================================' Convert a pointer to a string into a string via ' a byte array' ================================================Private Function PointerToStringWByte(ByVal xi_lngPtrToString As Long) As String Dim p_abytBuffer() As Byte Dim p_lngLen As Long If xi_lngPtrToString <> 0 Then p_lngLen = lstrlenW(xi_lngPtrToString) * 2 If p_lngLen Then ReDim p_abytBuffer(0 To (p_lngLen - 1)) As Byte CopyMem p_abytBuffer(0), ByVal xi_lngPtrToString, p_lngLen PointerToStringWByte = p_abytBuffer End If End If End Function