locked
type mismatch error in hta RRS feed

  • Question

  • All-

    I am trying to build an HTA...mostly for audit purposes that will allow me to get group membership in based on varying query approaches.  this is my first attempt at an HTA, and I think I have a good start (I always think highly of my work :-) ) but it isnt quite good enough yet. (mostly because its not working).  I keep getting a type mismatch error when the hta goes to display the results in a msgbox. the test user that I am testing against is a member of multiple groups.  take a look at my code below...tell me what you think please!

    <html>
    
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
    <title>AD GroupReporter</title>
    <script language="vbscript">
    
    Sub QueryMethod
      If UserOption(0).Checked Then
        strUser = window.prompt("Please enter the username to query.", "%username%")
    			If IsNull(strUser) Then
    				MsgBox "You clicked the Cancel button"
    			Else
    			
    				'set up the AD connection
    					Const ADS_SCOPE_SUBTREE = 2
    					Set objConnection = CreateObject("ADODB.Connection")
    					Set objCommand =  CreateObject("ADODB.Command")
    					objConnection.Provider = "ADsDSOObject"
    					objConnection.Open "Active Directory Provider"
    					Set objCommand.ActiveConnection = objConnection
    					objCommand.Properties("Page Size") = 1000
    					objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE 
    				'the AD query
    					objCommand.CommandText = _
      				"SELECT memberOf FROM 'LDAP://dc=domain,dc=org' WHERE objectCategory='user' " & _
        			"AND sAMAccountName='"& strUser &"'"
    				'run the AD query and put the results in a recordset
    					Set objRecordSet = objCommand.Execute
    				'Validate the results
    					If objRecordSet.RecordCount = 0 Then
    						MsgBox "No membership data found. Possible problems include: " & VbCrLf & "- Invalid user name" & VbCrLf & "- User is a member of no groups, or is only a member of the primary group"
    						Exit Sub
    					End If
    				
    				'go to the first record
     					objRecordSet.MoveFirst
     					Do Until objRecordSet.EOF
     						strMember = objRecordSet.Fields("memberOf").Value
     		' TYPE MISMATCH ERROR ON NEXT LINE				MsgBox strMember
     						objRecordSet.MoveNext
     					Loop
    			End If
      End If
      
      If UserOption(1).Checked Then
        Msgbox "This option will get group membership from a known group."
      End If
      
      If UserOption(2).Checked Then
        Msgbox "This option will get group membership from the ACL of a specified directory."
      End If
    End Sub
    
    
    </script>
    <hta:application
    	applicationname="ADGroupReporter"	
    	border="dialog"
    	borderstyle="normal"
    	caption="Active Directory Group Reporter"
    	contextmenu="no"
    	icon="myicon.ico"
    	maximizebutton="no"
    	minimizebutton="yes"
    	navigable="no"
    	scroll="yes"
    	selection="no"
    	showintaskbar="yes"
    	singleinstance="yes"
    	sysmenu="yes"
    	version="1.0"
    	windowstate="normal"
    >
    </head>
    <body>
    
    <!-- Query Method Radio Buttons -->
    <input type="radio" name="UserOption" value="1">Get Group Membership from known user<BR>
    <input type="radio" name="UserOption" value="2">Get Group Membership from known group<BR>
    <input type="radio" name="UserOption" value="3">Get Group Membership from ACL on specified directory<BR>
    
    <!-- Submit button -->
    <input id="runbutton" class="button" type="button" value="Execute Query" name="execute_button" onclick="QueryMethod">
    
    </body>
    </html>
    

    -K. Mortensen
    Tuesday, June 22, 2010 3:38 PM

Answers

  • The memberOf attribute is multi-valued. ADO returns the value as a Variant() (an array of distinguished names), unless there is no value (the object is a member only of the "primary" group, which is never included in memberOf). You must enumerate the array. Perhaps:

    arrMembers = objRecordset.Fields("memberOf").Value
    strDisplay = "Member Of Groups:"
    If IsNull(arrMembers) Then
      strDisplay = strDisplay & vbCrLf & "<None>"
    Else
      For Each strMember In arrMembers
        strDisplay = strDisplay & vbCrLf & strMember
      Next
    End If
    MsgBox strDisplay

     

    Richard Mueller


    MVP ADSI
    • Marked as answer by K. Mortensen Tuesday, June 22, 2010 8:16 PM
    Tuesday, June 22, 2010 4:47 PM

All replies

  • The memberOf attribute is multi-valued. ADO returns the value as a Variant() (an array of distinguished names), unless there is no value (the object is a member only of the "primary" group, which is never included in memberOf). You must enumerate the array. Perhaps:

    arrMembers = objRecordset.Fields("memberOf").Value
    strDisplay = "Member Of Groups:"
    If IsNull(arrMembers) Then
      strDisplay = strDisplay & vbCrLf & "<None>"
    Else
      For Each strMember In arrMembers
        strDisplay = strDisplay & vbCrLf & strMember
      Next
    End If
    MsgBox strDisplay

     

    Richard Mueller


    MVP ADSI
    • Marked as answer by K. Mortensen Tuesday, June 22, 2010 8:16 PM
    Tuesday, June 22, 2010 4:47 PM
  • I took some liberties with your script, but it appears to work:

    <html>

    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
    <title>AD GroupReporter</title>
    <script language="vbscript">

    Sub QueryMethod
      If UserOption(0).Checked Then
        strUser = window.prompt("Please enter the username to query.", "%username%")
       If IsNull(strUser) Then
        MsgBox "You clicked the Cancel button"
       Else
       
        'set up the AD connection
         Const ADS_SCOPE_SUBTREE = 2
         Set objConnection = CreateObject("ADODB.Connection")
         Set objCommand =  CreateObject("ADODB.Command")
         objConnection.Provider = "ADsDSOObject"
         objConnection.Open "Active Directory Provider"
         Set objCommand.ActiveConnection = objConnection
         objCommand.Properties("Page Size") = 1000
         objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE
        'the AD query
         DNC = GetObject("LDAP://RootDSE").Get("defaultNamingContext")
              
         objCommand.CommandText = _    
          "SELECT Adspath FROM 'LDAP://"  & DNC & "'" & _
          " WHERE objectCategory='user' " & _
           "AND sAMAccountName='"& strUser &"'"
        'run the AD query and put the results in a recordset
         Set objRecordSet = objCommand.Execute
        'Validate the results
         If objRecordSet.RecordCount = 0 Then
          MsgBox "No membership data found. Possible problems include: " & VbCrLf & "- Invalid user name" & VbCrLf & "- User is a member of no groups, or is only a member of the primary group"
          Exit Sub
         End If
        Set objRecordSet = objCommand.Execute
          objRecordSet.MoveFirst
        TxtADSPath.Value = objRecordSet.Fields("Adspath").Value
        Set objUser1 = GetObject(objRecordSet.Fields("Adspath").Value)
        
        Set objGroupList1 = GetGroups(objUser1)
        
         arrAD = Split(objRecordSet.Fields("AdsPath").Value, ",")
        
        For Each strGroup1 In objGroupList1.Keys
        u1 = strUser
           g1 = strGroup1  
        statval.value = u1 & vbCrLf
        statval.value = g1 & vbCrLf
        Next
       End If
      End If
     
      If UserOption(1).Checked Then
        Msgbox "This option will get group membership from a known group."
      End If
     
      If UserOption(2).Checked Then
        Msgbox "This option will get group membership from the ACL of a specified directory."
      End If
    End Sub

    Function GetGroups(objUser)
        ' Retrieve direct group memberships for a user and
        ' return a dictionary object of the memberships.
        Dim objGroup

        ' Dictionary object for groups the user belongs to.
        Set GetGroups = CreateObject("Scripting.Dictionary")
        GetGroups.CompareMode = vbTextCompare

        ' Enumerate direct group memberships for user.
        For Each objGroup In objUser.Groups
            ' Add each group to dictionary object.
            GetGroups.Add objGroup.sAMAccountName, True
        Next

    End Function

    Sub ExitHTA
        self.close()
    End Sub

    </script>
    <hta:application
     applicationname="ADGroupReporter" 
     border="dialog"
     borderstyle="normal"
     caption="Active Directory Group Reporter"
     contextmenu="no"
     icon="myicon.ico"
     maximizebutton="no"
     minimizebutton="yes"
     navigable="no"
     scroll="yes"
     selection="no"
     showintaskbar="yes"
     singleinstance="yes"
     sysmenu="yes"
     version="1.0"
     windowstate="normal"
    >
    </head>
    <body>

    <!-- Query Method Radio Buttons -->
    <input type="radio" name="UserOption" value="1">Get Group Membership from known user<BR>
    <input type="radio" name="UserOption" value="2">Get Group Membership from known group<BR>
    <input type="radio" name="UserOption" value="3">Get Group Membership from ACL on specified directory<BR>

    <!-- Submit button -->
    <input id="runbutton" class="button" type="button" value="Execute Query" name="execute_button" onclick="QueryMethod">
    <p align="center">
    <textarea rows="2" name="TxtADSPath" READONLY cols="75" style="text-align:center">AdsPath</textarea></p>
    <p align="center">
        <textarea rows="6" name="statval" READONLY cols="75" style="text-align:center">Status Box</textarea></p>
    <p align="center">
    <input type="BUTTON" name="button1" value="Exit" onclick=self.close>
    </body>
    </html>

     

    • Proposed as answer by OldDog1 Tuesday, June 22, 2010 6:31 PM
    • Unproposed as answer by K. Mortensen Tuesday, June 22, 2010 8:28 PM
    Tuesday, June 22, 2010 6:29 PM
  • OldDog1-

    I like where you have gone with this.  There just seems to be a few issues with me using it:

    - I see that you have pulled out just the common name of the groups that the user is a member of, but I am unsure on the procedure you used in order to accomplish that.  Can you explain your code a bit please? (I'm not a fan of using something I dont understand/cant support)

    - The code that you have posted here doesnt seem to be listing all of the groups that the user is a member of.  After testing this on multiple users, it seems to only be listing the group that is listed first on the 'MemberOf' tab for the user. In the end I will have to set this up not only so that it lists explicit groups in the memberof tab, but also so  that it is recursive...which it looks like you seem to have the start of, but its not working.

     

    Thanks so much for your help!


    -K. Mortensen
    Tuesday, June 22, 2010 8:28 PM
  • I have some example VBScript programs to enumerate the nested group memberships of a user. First, using the memberOf attribute and a recursive subroutine:

    http://www.rlmueller.net/List%20User%20Groups.htm

    This example accepts the DN of a user as a parameter and outputs the DN of the groups. You could output the sAMAccountName (pre-Windows 2000 name) instead. This reveals all groups, except the "primary" group of the user, which by default is the group "Domain Users".

    Another method is to use the tokenGroups attribute. This multi-valued attribute is a collection of the SID's of all security groups the user is a member of. It includes the "primary" group, plus all nested groups, but does not include distribution groups. There is no need for a recursive subroutine. The tokenGroups attribute is operational (also called constructed), plus the SID value is OctetString, which is a byte array, so it requires special handling. An example follows:

     

    Option Explicit
    
    Dim objSysInfo, strUserDN, objUser, arrbytGroups, arrstrGroupSids()
    Dim j, objGroup
    
    ' Retrieve DN of current user.
    Set objSysInfo = CreateObject("ADSystemInfo")
    strUserDN = objSysInfo.UserName
    
    ' Bind to user object.
    Set objUser = GetObject("LDAP://" & strUserDN)
    
    ' Retrieve tokenGroups attribute.
    objUser.GetInfoEx Array("tokenGroups"), 0
    arrbytGroups = objUser.Get("tokenGroups")
    
    ' Enumerate the security groups.
    If (TypeName(arrbytGroups) = "Byte()") Then
      ' There is one SID value in the array.
      ReDim arrstrGroupSids(0)
      arrstrGroupSids(0) = OctetToHexStr(arrbytGroups)
      Set objGroup = GetObject("LDAP://<SID=" & arrstrGroupSids(0) _
        & ">")
      Wscript.Echo objGroup.sAMAccountName
      Wscript.Quit
    End If
    If (UBound(arrbytGroups) = -1) Then
      ' There are no SID values in the array.
      Wscript.Echo "No groups"
      Wscript.Quit
    End If
    
    ' There is more than one SID value in the array.
    ReDim arrstrGroupSids(UBound(arrbytGroups))
    For j = 0 To UBound(arrbytGroups)
      arrstrGroupSids(j) = OctetToHexStr(arrbytGroups(j))
      Set objGroup = GetObject("LDAP://<SID=" & arrstrGroupSids(j) _
        & ">")
      Wscript.Echo objGroup.sAMAccountName
    Next
    
    Function OctetToHexStr(ByVal arrbytOctet)
      ' Function to convert OctetString (byte array) to Hex string.
    
      Dim k
      OctetToHexStr = ""
      For k = 1 To Lenb(arrbytOctet)
        OctetToHexStr = OctetToHexStr _
          & Right("0" & Hex(Ascb(Midb(arrbytOctet, k, 1))), 2)
      Next
    End Function
    

    This example retrieves the DN of the current user from the ADSystemInfo object, and outputs the sAMAccountName's of the groups. You could output the distinguishedName instead.

    Richard Mueller


    MVP ADSI
    Tuesday, June 22, 2010 9:19 PM
  • Sorry about that, I was writting over the group names:

    <html>

    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
    <title>AD GroupReporter</title>
    <script language="vbscript">

    Sub QueryMethod 
      If UserOption(0).Checked Then
        strUser = window.prompt("Please enter the username to query.", "%username%")
       If IsNull(strUser) Then
        MsgBox "You clicked the Cancel button"
       Else
       
        'set up the AD connection
         Const ADS_SCOPE_SUBTREE = 2
         Set objConnection = CreateObject("ADODB.Connection")
         Set objCommand =  CreateObject("ADODB.Command")
         objConnection.Provider = "ADsDSOObject"
         objConnection.Open "Active Directory Provider"
         Set objCommand.ActiveConnection = objConnection
         objCommand.Properties("Page Size") = 1000
         objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE
        'the AD query
         DNC = GetObject("LDAP://RootDSE").Get("defaultNamingContext")
              
         objCommand.CommandText = _    
          "SELECT Adspath FROM 'LDAP://"  & DNC & "'" & _
          " WHERE objectCategory='user' " & _
           "AND sAMAccountName='"& strUser &"'"
        'run the AD query and put the results in a recordset
         Set objRecordSet = objCommand.Execute
        'Validate the results
         If objRecordSet.RecordCount = 0 Then
          MsgBox "No membership data found. Possible problems include: " & VbCrLf & "- Invalid user name" & VbCrLf & "- User is a member of no groups, or is only a member of the primary group"
          Exit Sub
         End If
        Set objRecordSet = objCommand.Execute
          objRecordSet.MoveFirst
        TxtADSPath.Value = objRecordSet.Fields("Adspath").Value
        Set objUser1 = GetObject(objRecordSet.Fields("Adspath").Value)
        
        Set objGroupList1 = GetGroups(objUser1)
        statval.value = strUser & vbCrLf
        For Each strGroup1 In objGroupList1.Keys  
        statval.value = statval.value & strGroup1 & vbCrLf     ' this keeps the old value and adds the new
        Next
       End If
      End If
     
      If UserOption(1).Checked Then
        Msgbox "This option will get group membership from a known group."
      End If
     
      If UserOption(2).Checked Then
        MsgBox "This option will get group membership from the ACL of a specified directory."
      End If
    End Sub

    Function GetGroups(objUser)
        ' Retrieve direct group memberships for a user and
        ' return a dictionary object of the memberships.
        Dim objGroup

        ' Dictionary object for groups the user belongs to.
        Set GetGroups = CreateObject("Scripting.Dictionary")
        GetGroups.CompareMode = vbTextCompare

        ' Enumerate direct group memberships for user.
        For Each objGroup In objUser.Groups
            ' Add each group to dictionary object.
            GetGroups.Add objGroup.sAMAccountName, True
        Next

    End Function

    Sub ExitHTA
        self.close()
    End Sub

    </script>
    <hta:application
     applicationname="ADGroupReporter" 
     border="dialog"
     borderstyle="normal"
     caption="Active Directory Group Reporter"
     contextmenu="no"
     icon="myicon.ico"
     maximizebutton="no"
     minimizebutton="yes"
     navigable="no"
     scroll="yes"
     selection="no"
     showintaskbar="yes"
     singleinstance="yes"
     sysmenu="yes"
     version="1.0"
     windowstate="normal"
    >
    </head>
    <body>

    <!-- Query Method Radio Buttons -->
    <input type="radio" name="UserOption" value="1">Get Group Membership from known user<BR>
    <input type="radio" name="UserOption" value="2">Get Group Membership from known group<BR>
    <input type="radio" name="UserOption" value="3">Get Group Membership from ACL on specified directory<BR>

    <!-- Submit button -->
    <input id="runbutton" class="button" type="button" value="Execute Query" name="execute_button" onclick="QueryMethod">
    <p align="center">
    <textarea rows="2" name="TxtADSPath" READONLY cols="75" style="text-align:center">AdsPath</textarea></p>
    <p align="center">
        <textarea rows="6" name="statval" READONLY cols="75" style="text-align:center">Status Box</textarea></p>
    <p align="center">
    <input type="BUTTON" name="button1" value="Exit" onclick=self.close>
    </body>
    </html>

    As for the procedure I used in order to accomplish that, well this Function does that:

    Function GetGroups(objUser)
        ' Retrieve direct group memberships for a user and
        ' return a dictionary object of the memberships.
        Dim objGroup

        ' Dictionary object for groups the user belongs to.
        Set GetGroups = CreateObject("Scripting.Dictionary")
        GetGroups.CompareMode = vbTextCompare

        ' Enumerate direct group memberships for user.
        For Each objGroup In objUser.Groups
            ' Add each group to dictionary object.
            GetGroups.Add objGroup.sAMAccountName, True
        Next

    End Function

    And objGroup.sAMAccountName is the common name for the group.

    OldDog

    • Proposed as answer by OldDog1 Friday, June 25, 2010 12:04 AM
    Wednesday, June 23, 2010 2:50 PM
  • Richard-

    Thanks for the contribution! I really like how this code filters out only the security group membership...very handy!  I have combined pieces of my original code, along with some of yours, and OldDog1 to get a pretty functional result.  Quick question though: what if I wanted to do a query that would just show me Distribution Group membership?  (Essentially the same thing you have listed above, just show only DistLists and no security groups)

    Again, thanks for all your help...its been a great opportunity for me to learn from!


    -K. Mortensen
    Thursday, June 24, 2010 1:40 PM
  • The only way I see to retrieve distribution group memberships for a user requires binding to each group the user is a member of. Then you can check the groupType attribute of the group object to see if the group is a distribution group (not a security group). You can use the Groups method of the user object to get a collection of group objects (direct membership, not nested, similar to memberOf). For example:

    Const ADS_GROUP_TYPE_SECURITY_ENABLED = &H8000000
    
    ' Bind to user object.
    strUserDN = "cn=Jim Smith,ou=West,dc=MyDomain,dc=com"
    Set objUser = GetObject("LDAP://" & strUserDN)
    
    ' Enumerate all direct group memberships.
    For Each objGroup In objUser.Groups
      ' Check if distribution group.
      If (objGroup.groupType And ADS_GROUP_TYPE_SECURITY_ENABLED = 0) Then
        ' Distribution group, output "pre-Windows 2000" name.
        Wscript.Echo objGroup.sAMAccountName
      End If
    Next

     

    Using the Groups method means it will work whether there are no groups, one group, or more than one in the collection.

    Richard Mueller


    MVP ADSI
    Thursday, June 24, 2010 3:38 PM
  • The code you offered for enumerating security groups is working great.  I am running into difficulty with formatting though.  I have been requested to show group membership in a hierarchical structure. for example:

    UserA is a member of:
              Group1 which is a member of:
                        NestedGroup1
                        NestedGroup2
             Group2 which is a member of: 
                        NestedGroup3
             Group3 which is a member of:
                        NONE

    The problem in this case is that the tokenGroups attribute at this point just tells me final group membership for the user. Do you have any suggestions?


    -K. Mortensen
    Monday, June 28, 2010 6:57 PM