Quick answer
You can't filter on constructed attributes in LDAP queries — remove them from the filter expression and fetch them as attributes instead. The error 0X0000216B is Active Directory telling you exactly that.
What's actually happening here
Constructed attributes like memberOf, canonicalName, tokenGroups, or msDS-PrincipalName aren't stored in the directory. AD computes them on the fly when it returns a search result. That's why you can request them as returned attributes and they work fine — but the LDAP filter operates against the database index, which simply doesn't contain those values. So the server can't evaluate the filter without computing the attribute for every object in scope, which is a performance nightmare. Microsoft decided to block it outright rather than let your query tank the DC.
You'll see this error when a tool or script tries something like (&(objectClass=user)(memberOf=CN=Admins,OU=Groups,DC=corp,DC=com)) — that filter uses memberOf, a constructed attribute. The moment the LDAP server parses the filter, it throws 0X0000216B and returns nothing.
Fix steps — replace the filter logic
- Identify the constructed attribute in your filter. Common ones:
memberOf,canonicalName,tokenGroups,msDS-PrincipalName,objectSid(yes, even objectSid is constructed). Check Microsoft's list of constructed attributes in the Active Directory schema. - Rewrite the query to search on the actual stored attribute. For group membership, you don't filter on
memberOf— you filter on the group'smemberattribute. Invert the logic: instead of finding users wherememberOf=GroupX, find the group and read itsmemberattribute. Or, if you need a one-shot list of users in a group, do a base-scoped search on the group's DN with(objectClass=group)and requestmemberas the attribute. - If you must filter on membership for a dynamic list, use a two-step approach. First query the group's
memberattribute to get the DNs, then for each DN run an LDAP query with that specific DN in the filter, like(distinguishedName=CN=John,OU=Users,DC=corp,DC=com). This is not elegant, but it's the only way to get an exact membership roster without pulling the entire domain. - For nested groups, use the
LDAP_MATCHING_RULE_IN_CHAINOID on thememberattribute. That filter(member:1.2.840.113556.1.4.1941:=CN=Group,OU=Groups,DC=corp,DC=com)works because it operates on the storedmemberattribute, not the constructed one. It's slower than a flatmembersearch but still legal. - Test your filter using LDP or PowerShell before deploying:
Get-ADUser -LDAPFilter "(member:1.2.840.113556.1.4.1941:=CN=Group,OU=Groups,DC=corp,DC=com)"— if that returns results, you're good.
If the main fix doesn't work
Sometimes the constructed attribute is buried inside a complex filter — you might have nested conditions or an OR that includes a constructed attribute on one branch. Review the entire filter, not just the obvious part. Also check if you're using a tool that builds filters automatically (like an old VB script or a third-party admin tool). Those tools often hardcode memberOf filters. If you can't edit the tool's filter, you'll need to use a different tool or write a custom PowerShell script to do the query in two steps.
Another scenario: you're not filtering on a constructed attribute directly, but on a computed attribute that the server treats as constructed. For example, objectGUID is stored, but objectSid is not. If your filter uses objectSid, you'll get this error. Replace it with objectGUID or use the SID in the filter as a string — but note that the string representation is also constructed. The correct approach is to use the binary format in an octetString match, which is rarely done. Most people just query by distinguishedName instead.
If you absolutely must filter on a constructed attribute like canonicalName, the only workaround is to write a script that enumerates objects and computes the attribute client-side, then filters in memory. That's inefficient, so only do it for small OUs.
Prevention
Know the schema. Before writing an LDAP filter, check whether each attribute you reference is a constructed attribute in the schema. You can see this in ADSI Edit or via PowerShell: Get-ADObject -SearchBase (Get-ADRootDSE).schemaNamingContext -Filter {Name -eq 'memberOf'} -Properties attributeSyntax, isMemberOfPartialAttributeSet, systemFlags. The systemFlags property includes a flag that indicates the attribute is constructed (bit 0x00000004 for constructed). If it's constructed, don't put it in the filter. Use it only as a returned attribute.
Also, when writing code that generates LDAP filters dynamically, add a validation step that checks the filter string for known constructed attribute names and rejects it before it hits the server. That turns a cryptic 0X0000216B into a clear “filter uses constructed attribute” message in your own logs.
One more thing: if you're using the built-in member attribute with a chain OID, remember it's a performance hit on large groups. For huge groups (50,000+ members), consider caching the membership list in your application rather than querying it every time.
Bottom line: constructed attributes are for reading, not for filtering. You can ask AD to compute them for you, but you can't ask it to search on them. Adjust your query logic and the error goes away.