dsquery and dsget in Windows: command-line Active Directory queries







You need to find every disabled user account in the Sales OU, and you are logged on to a Server Core box where the ActiveDirectory PowerShell module is not installed. You could install RSAT, or RDP somewhere else, or run a remote PowerShell session, or you could type one line: dsquery user "OU=Sales,DC=corp,DC=contoso,DC=com" -disabled. dsquery and dsget ship with the AD DS Tools on every Domain Controller and every machine with RSAT, no module imports needed.

Most guides on these commands give you a list of recipes to copy. This one teaches you how to construct your own queries: the three components every dsquery has, when to use a built-in filter, and when to drop into raw LDAP filter syntax for things the built-in flags do not cover.

Applies to: Windows Server 2016 / 2019 / 2022 / 2025


Quick answer

These three commands cover the most common dsquery tasks. They are a shortcut for readers who already know dsquery basics. If you want to understand how to build your own queries, read from the top.

rem Find a user by name (wildcard supported)
dsquery user -name "John*"

rem Find all disabled users in the domain (overriding the default 100-result cap)
dsquery user -disabled -limit 0

rem List the members of a group, with attributes
dsquery group -name "IT-Admins" | dsget group -members

What dsquery and dsget actually do

dsquery and dsget are a pair of CLI tools that talk to Active Directory using LDAP. They live in %SystemRoot%\System32 on any machine where the AD DS Tools are installed: every Domain Controller has them by default, and any Windows client or member server gets them with RSAT (Remote Server Administration Tools). They communicate with the nearest Domain Controller on port 389 (LDAP) or 636 (LDAPS), no extra configuration needed.

The split between the two tools is deliberate: dsquery finds objects and returns their distinguished names (DNs), dsget reads attributes from those DNs. The contract between them is the pipe character. dsquery outputs DNs one per line, dsget consumes DNs the same way. Any other tool that produces DNs (a script, a file) can feed dsget the same way.

The object types dsquery understands

Object typeUse for
userUser accounts (excludes built-in computer accounts)
computerComputer accounts in the domain
groupSecurity and distribution groups
ouOrganizational units
contactContact objects (mail-only entries, no logon)
siteAD sites
serverServers registered in AD (typically DCs)
subnetSubnet objects associated with sites
quotaDirectory quota specifications
partitionDirectory partitions
*LDAP filter mode (escape hatch for queries the built-in types do not cover)

Anatomy of a full pipeline

Reading a real command in parts makes everything that follows easier to understand. Here is what a typical dsquery and dsget pipeline looks like with each component labeled:

dsquery user -name "John*" -limit 0 | dsget user -samid -display -disabled
└─────┬─────┘ └────┬────┘ └───┬───┘   └─────┬─────┘ └────────┬────────┘
   tool +     filter      result      pipe to       attributes to read
  object type             cap         dsget

Every dsquery has three logical pieces: the object type (user), a filter (-name "John*"), and an optional result cap (-limit 0 removes the default 100-result limit). The output of dsquery is a list of DNs. Pipe those DNs into dsget and pick which attributes you want back: -samid for the login name, -display for the friendly name, -disabled for the enabled/disabled state.

Set up the test environment

Every example below operates on the same lab so the output is predictable. The lab is one DC in a domain called corp.contoso.com with the following objects:

  • Three OUs: OU=IT, OU=Sales, OU=ServiceAccounts
  • Five user accounts: jsmith, tjohnson, mwilson (all enabled), bcooper (disabled), oldservice (enabled, never logged on)
  • Two security groups: IT-Admins (contains jsmith and bcooper), Sales-Team (contains tjohnson and mwilson)

You can reproduce the lab in your own environment by running this PowerShell script on a fresh DC. It is idempotent, so re-running it after a partial failure is safe:

# Run elevated on the DC of corp.contoso.com
Import-Module ActiveDirectory

$DomainDN = "DC=corp,DC=contoso,DC=com"
$Pwd = ConvertTo-SecureString "P@ssw0rd123!" -AsPlainText -Force

# Three OUs
foreach ($ou in 'IT','Sales','ServiceAccounts') {
    if (-not (Get-ADOrganizationalUnit -Filter "Name -eq '$ou'" -ErrorAction SilentlyContinue)) {
        New-ADOrganizationalUnit -Name $ou -Path $DomainDN -ProtectedFromAccidentalDeletion $false
    }
}

# Five users - bcooper is disabled, oldservice will never have logged on
$Users = @(
    @{ Sam='jsmith';     Given='John';  Sur='Smith';   Dept='IT';              OU='IT';              Enabled=$true  },
    @{ Sam='tjohnson';   Given='Tom';   Sur='Johnson'; Dept='Sales';           OU='Sales';           Enabled=$true  },
    @{ Sam='mwilson';    Given='Mary';  Sur='Wilson';  Dept='Sales';           OU='Sales';           Enabled=$true  },
    @{ Sam='bcooper';    Given='Brian'; Sur='Cooper';  Dept='IT';              OU='IT';              Enabled=$false },
    @{ Sam='oldservice'; Given='Old';   Sur='Service'; Dept='ServiceAccounts'; OU='ServiceAccounts'; Enabled=$true  }
)
foreach ($u in $Users) {
    if (-not (Get-ADUser -Filter "SamAccountName -eq '$($u.Sam)'" -ErrorAction SilentlyContinue)) {
        New-ADUser -Name "$($u.Given) $($u.Sur)" -GivenName $u.Given -Surname $u.Sur `
            -SamAccountName $u.Sam -UserPrincipalName "$($u.Sam)@corp.contoso.com" `
            -Department $u.Dept -Path "OU=$($u.OU),$DomainDN" `
            -AccountPassword $Pwd -Enabled $u.Enabled -PasswordNeverExpires $true
    }
}

# Two groups with members
New-ADGroup -Name 'IT-Admins'  -GroupScope Global -GroupCategory Security -Path "OU=IT,$DomainDN"    -ErrorAction SilentlyContinue
New-ADGroup -Name 'Sales-Team' -GroupScope Global -GroupCategory Security -Path "OU=Sales,$DomainDN" -ErrorAction SilentlyContinue
Add-ADGroupMember -Identity 'IT-Admins'  -Members jsmith,bcooper      -ErrorAction SilentlyContinue
Add-ADGroupMember -Identity 'Sales-Team' -Members tjohnson,mwilson    -ErrorAction SilentlyContinue

Verify the lab is ready by running dsquery user -name * from CMD. You should see the five lab users plus the built-in Administrator, Guest, and krbtgt accounts. ADUC shows the same objects organized into their OUs:

Active Directory Users and Computers showing corp.contoso.com with OUs IT, Sales, ServiceAccounts and dsquery lab user accounts
The lab structure under corp.contoso.com that every dsquery example operates on

Practical examples

Example 1: Find a user by name

The problem: A ticket lands on your desk with just a first name. You need to find the matching user account in the directory.

The solution: dsquery user -name with a wildcard returns every matching user as a DN, ready to feed into ADUC, dsget, or anything else.

rem -name matches against the CN attribute, wildcards supported
dsquery user -name "John*"
"CN=John Smith,OU=IT,DC=corp,DC=contoso,DC=com"

Returning a DN is useful, but most queries want the answer narrowed before you read it. The next example introduces scope.

Example 2: Find disabled users in a specific OU

The problem: Your security baseline says disabled accounts must be reviewed monthly. You need a list of disabled users in the IT OU only, not the whole domain.

The solution: Pass an OU DN as the first positional argument to scope the search, then add the -disabled built-in filter. Always include -limit 0 on bulk queries to override the default cap.

rem First positional arg = start node DN (scope), -limit 0 disables the 100-result cap
dsquery user "OU=IT,DC=corp,DC=contoso,DC=com" -disabled -limit 0
CMD output of dsquery user -disabled showing Brian Cooper in the IT OU of corp.contoso.com
dsquery returns the disabled user’s distinguished name, ready to feed into dsget
Warning: The default -limit is 100. In a real domain with thousands of users this silently truncates your results and you may not notice. Set -limit 0 explicitly for any export or audit query.

Built-in filters cover the common cases. When you need attributes back, not just DNs, dsget takes over.

Example 3: Pipe dsquery into dsget for attributes

The problem: A manager asks for “the list of people in IT-Admins, with their login names and whether their accounts are still enabled.” DNs alone are not useful, you need attribute values.

The solution: dsquery finds the group, the first dsget extracts its members as DNs, the second dsget reads attributes from each member.

rem dsget group -members lists DNs of users in the group
rem A second dsget user resolves each DN into the requested attributes
dsquery group -name "IT-Admins" | dsget group -members | dsget user -samid -display -disabled
CMD showing dsquery piped into two dsget commands returning samid, display name, and disabled state for IT-Admins members
Chaining dsget twice resolves group members into user attributes in one line

Built-in filters and dsget cover roughly 80% of queries. For the remaining 20% (combinations of attributes, OR conditions, negations, attribute-not-present checks) you need LDAP filter syntax.

Building your own dsquery query

Once you have seen three examples, the pattern is the same: pick an object type, set a scope, apply a filter. The trick is knowing which knob to turn for which question. Every dsquery has three logical components:

  • Object type (user, group, computer, etc, or * for LDAP filter mode)
  • Scope (a start node DN as the first positional arg, or omit to search the whole domain)
  • Filter (a built-in flag like -disabled or -name, OR a raw LDAP filter via -filter "(...)")

Work through a concrete request to see how the components combine. Say someone asks: “find all enabled users in the Sales OU whose department is set to Sales.” Walk through it:

  • Object type: user would work, but we need to combine three conditions (enabled AND in OU AND department=Sales), and -disabled is a flag, not a filter expression. Use * with an LDAP filter instead.
  • Scope: the Sales OU is the start node, so the first positional arg is "OU=Sales,DC=corp,DC=contoso,DC=com".
  • Filter: three AND-ed conditions: object class is user, account is not disabled, department equals Sales.

Combine them into a single command:

dsquery * "OU=Sales,DC=corp,DC=contoso,DC=com" -filter "(&(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2)(department=Sales))" -limit 0

Decoding the filter piece by piece: the outer (&...) means logical AND across all conditions inside it. (objectClass=user) restricts to user objects. (!userAccountControl:1.2.840.113556.1.4.803:=2) reads as “userAccountControl does NOT have bit 2 set” (bit 2 is the ACCOUNTDISABLE flag). The = in (department=Sales) is exact-match, no wildcards.

Note: The string 1.2.840.113556.1.4.803 is an LDAP matching rule OID called LDAP_MATCHING_RULE_BIT_AND. It tells AD to do a bitwise AND comparison against the userAccountControl integer instead of a normal equality check. The same OID is used for any bit-flag attribute test.

Once the three-component model clicks, you can construct any query without looking up examples first. The next example uses the same pattern on a different attribute.

Example 4: LDAP filter with dsquery *

The problem: You need every user whose department attribute is set to IT, regardless of which OU they live in. None of the built-in dsquery flags filter by department, so a built-in flag query cannot answer this.

The solution: Switch to dsquery * and write the condition as an LDAP filter. Use -attr to ask for specific attributes back without piping through dsget.

rem -attr returns attribute values directly (skips dsget entirely)
dsquery * -filter "(&(objectClass=user)(department=IT))" -attr sAMAccountName department -limit 0
CMD output of dsquery LDAP filter showing sAMAccountName and department columns for bcooper and jsmith
The -attr flag skips dsget and returns LDAP attributes directly

Cross-reference the result against ADUC by opening Brian Cooper’s account properties. The disabled checkbox confirms what dsquery reported in Example 2, and the Organization tab shows the same department value the LDAP filter just queried:

ADUC properties dialog of bcooper with Account is disabled checkbox ticked on the Account tab
The dsquery -disabled flag finds exactly what ADUC shows on the Account tab

Knowing what is in the directory is one thing. Doing something with the results is the next step.

Example 5: Export results to a file for scripting

The problem: Quarterly access review. You need every user in the domain with login name, display name, and disabled state, in a file you can hand to a manager or feed into another script.

The solution: Use -limit 0 to get every match, pipe to dsget for the attributes, redirect to a file. dsget separates columns with whitespace, suitable for fixed-width parsing or import into Excel as text.

rem -limit 0 returns ALL users; redirect captures dsget output
dsquery user -limit 0 | dsget user -samid -display -disabled > C:\bat\users.txt

rem Verify the file
type C:\bat\users.txt
  samid          display              disabled
  Administrator  Administrator        no
  Guest          Guest                yes
  krbtgt                              yes
  jsmith         John Smith           no
  tjohnson       Tom Johnson          no
  mwilson        Mary Wilson          no
  bcooper        Brian Cooper         yes
  oldservice     Old Service          no
dsget succeeded

One pitfall: if a user object is deleted between dsquery returning its DN and dsget trying to read it (unlikely but possible during large exports), dsget prints dsget failed:Element not found. for that DN and continues with the rest. The exit code reflects success even with individual failures, so always inspect the output rather than relying on the return code alone.


Hidden gems

The -inactive flag silently misses never-logged-on accounts

The -inactive N flag finds users whose lastLogonTimestamp attribute is older than N weeks. It does NOT find accounts that have never logged on at all, because their lastLogonTimestamp is empty rather than old. On a fresh lab where no user has authenticated yet, dsquery user -inactive 1 returns nothing, which looks like the command is broken.

To find never-logged-on accounts, switch to an LDAP filter that checks for the attribute being absent:

rem !(lastLogonTimestamp=*) matches users whose lastLogonTimestamp is NOT set to anything
dsquery * -filter "(&(objectCategory=person)(objectClass=user)(!(lastLogonTimestamp=*)))" -limit 0
CMD showing dsquery LDAP filter for absent lastLogonTimestamp returning oldservice and built-in accounts
When -inactive returns nothing, an absent-attribute LDAP filter finds the accounts that have never logged on
Common mistake: Combining -inactive with the assumption that “no result = no problem” can leave dormant service accounts visible in your domain for years. For audit work, always pair -inactive with a separate never-logged-on LDAP filter and exclude disabled accounts from both.

DN output is quoted by default

dsquery wraps every DN in double quotes. This is the right format for piping into dsget, but breaks plain text processing with findstr or your own scripts because the quote becomes part of the matched string. Two workarounds: use -o rdn to print only the relative DN without quotes, or use -o samid for users to get just the SAM account name.

rem -o samid returns just sAMAccountName, no quotes, ready for grep/findstr
dsquery user -disabled -o samid -limit 0

Three-stage pipelines for joined data

dsget commands can be chained. dsquery group | dsget group -members | dsget user walks from group name to member DNs to user attributes in one line. This is the closest dsquery and dsget come to a JOIN in SQL, and it works for any reasonable group size.

Note: The -attr flag on dsquery * returns raw LDAP values, including binary attributes like objectSid and objectGUID as unprintable bytes. Stick to dsget when you need readable output and only use dsquery * -attr for text attributes.

Diagnose the DC, not just query it

dsquery answers what is in the directory. When the directory itself is misbehaving, secure channels are broken, or DC discovery is failing, the nltest Command Builder writes the exact diagnostic command for your scenario.

Open the nltest Command Builder

Where this matters

Server Core without RSAT PowerShell: dsquery and dsget ship with the AD DS Tools that install automatically when you promote a server to DC, so they work on Server Core where you would otherwise need to install or import additional modules.

Bulk audits before quarterly access reviews: Export every enabled user, their OU, and last logon attribute to a text file in one piped command, then hand the file to security or compliance without writing a script.

Scripting against legacy systems: Older Server 2012 boxes, restricted batch contexts, or environments where you cannot install PowerShell modules can still run dsquery and dsget. Both have been stable since Windows Server 2003.

Cross-checking ADUC: When the GUI shows one thing and you suspect a filtered view or stale replication is hiding objects, dsquery on the same DC confirms exactly what is in the directory right now.

PowerShell equivalents

The ActiveDirectory PowerShell module is the modern alternative when it is available. Both toolsets remain supported. For quick reference, here is how each example maps:

dsquery and dsgetPowerShell equivalent
dsquery user -name "John*"Get-ADUser -Filter "Name -like 'John*'"
dsquery user "OU=IT,..." -disabledGet-ADUser -SearchBase "OU=IT,..." -Filter {Enabled -eq $false}
dsquery group -name "IT-Admins" | dsget group -membersGet-ADGroupMember -Identity "IT-Admins"
dsquery * -filter "(&(objectClass=user)(department=IT))"Get-ADUser -LDAPFilter "(&(objectClass=user)(department=IT))"
dsquery user -limit 0 | dsget user -samidGet-ADUser -Filter * | Select-Object SamAccountName

Both tools speak LDAP underneath. The PowerShell cmdlets are more flexible (object output, pipeline composability with the wider .NET ecosystem), but they require the ActiveDirectory module to be present. dsquery and dsget have no such dependency.

Tips and limitations

  • dsquery and dsget still ship in Windows Server 2025 but are no longer the recommended tools for new scripts. Microsoft directs new development toward the ActiveDirectory PowerShell module.
  • Read access to AD is enough for most queries. Some attributes (passwords, sensitive operational data) are not returned regardless of the query because the directory itself does not expose them.
  • The default -limit 100 is the most common gotcha. Set -limit 0 for audits, exports, or any query where missing results would be a problem.
  • dsget is strict about its input format: one quoted DN per line, no extra whitespace. Sanitize any input that did not come from dsquery itself.
  • LDAP filter syntax in dsquery * is the most powerful mode but the least documented in the Microsoft pages. The RFC 4515 spec is the canonical reference for filter syntax.
  • For multi-domain forests, dsquery searches the domain of the DC it connects to. Use -d <domain> or -s <server> to target a different domain or specific DC.

References and related reading

Official documentation

Related tools on zaur.it

  • nltest Command Builder – construct the right nltest command for DC discovery, trust diagnostics, and secure channel testing, the natural partner to dsquery when AD itself is misbehaving

Related guides