Answered by:
type mismatch error in hta

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. MortensenTuesday, 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 SubFunction 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
NextEnd 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. MortensenTuesday, 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 ADSITuesday, 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 SubFunction 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
NextEnd 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
NextEnd 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. MortensenThursday, 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 ADSIThursday, 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:
NONEThe 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. MortensenMonday, June 28, 2010 6:57 PM