---
title: Search Active Directory users
summary: Builds an LDAP filter from caller-supplied search criteria and returns matching EXTERNAL-domain users.
status: draft
owner: SAIF
last_reviewed: 2026-07-30
library: SAIF.Business.Security.SystemUser
reference_type: package
operation_id: OP-SU-02
trigger: Caller calls clsSystemUser.FindUsers(...)
---

# Search Active Directory users

`FindUsers` lets a caller locate one or more EXTERNAL-domain accounts by user ID, ID number, name, and an optional test-user flag, without needing to construct an LDAP filter directly. The method assembles the filter string and delegates the actual directory search to `clsDirectoryServer`.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-SU-02 |
| **Trigger** | Caller calls `clsSystemUser.FindUsers(inUserID, inIDNumber, inName, [inTestUserIDFlag], [inDomain])` |
| **Inputs** | `inUserID`, `inIDNumber`, `inName` search criteria; optional `inTestUserIDFlag` (`"Y"`/`"N"`); optional `inDomain` (defaults to `"EXTERNAL"`, accepted but not used to alter the query) |
| **Outputs** | `clsUserCollection` of AD entries matching the assembled filter; also cached on `m_DirUserCol` |
| **Primary components** | `clsSystemUser.FindUsers(...)`, `clsSystemUser.BuildLDAPQuery(...)` |
| **Entry point** | `Business\SAIF.Business.Security.SystemUser\clsSystemUser.vb` |

## Steps

1. **Build the LDAP filter (`BuildLDAPQuery`).** Starting from an empty string, the method appends filter clauses only for criteria that were actually supplied:
   - If `inIDNumber` is non-empty: append `(Info=<inIDNumber>)`.
   - If `inUserID` is non-empty and not `"*"`: append `(CN=<inUserID>)`.
   - If `inName` is non-empty and not `"*"`: append `(Company=<inName>)`.
   - If `inTestUserIDFlag` is `"Y"`/`"y"`: append `(description=Y)` to restrict results to test users.
   - If `inTestUserIDFlag` is `"N"`/`"n"`: append `(!(description=Y))` to exclude test users.
   - Otherwise (any other value, including the default empty string): no test-user clause is appended, and results are not filtered by the flag.
2. **Note the unused domain parameter.** `inDomain` is accepted by both `FindUsers` and `BuildLDAPQuery` but is never read inside either method body — the search always executes against whatever directory context `m_DirServer` was bound to during construction, not a domain selected at search time.*
3. **Execute the search.** `FindUsers` passes the assembled filter string to `m_DirServer.FindUsers(ldapQuery)`.
4. **Cache and return the results.** The resulting `clsUserCollection` is stored on `m_DirUserCol` (also exposed via the `UserCollection` read-only property) and returned to the caller.

## Process flow

The following diagram visualizes this operation's control flow.

```mermaid
flowchart TD
	Start([Caller calls FindUsers]) --> Build[Build LDAP filter from supplied criteria]
	Build --> IDNum{IDNumber supplied?}
	IDNum -->|Yes| AddInfo[Append Info clause]
	IDNum -->|No| UserID
	AddInfo --> UserID{UserID supplied and not '*'?}
	UserID -->|Yes| AddCN[Append CN clause]
	UserID -->|No| Name
	AddCN --> Name{Name supplied and not '*'?}
	Name -->|Yes| AddCompany[Append Company clause]
	Name -->|No| TestFlag
	AddCompany --> TestFlag{Test-user flag value?}
	TestFlag -->|Y| AddDescY[Append description=Y clause]
	TestFlag -->|N| AddDescN[Append negated description=Y clause]
	TestFlag -->|other/empty| Search[Execute m_DirServer.FindUsers]
	AddDescY --> Search
	AddDescN --> Search
	Search --> Cache[Cache result on m_DirUserCol]
	Cache --> Return([Return clsUserCollection])
```

## Failure and edge-case behavior

- Supplying no criteria at all produces an empty filter string, so the resulting search behavior depends entirely on how `clsDirectoryServer.FindUsers` interprets an empty query.**
- The `inDomain` parameter is dead in both `FindUsers` and `BuildLDAPQuery` — passing a value other than `"EXTERNAL"` has no observable effect on the query or the directory context searched.*
- A wildcard (`"*"`) for `inUserID` or `inName` is treated the same as an empty value: the corresponding clause is omitted rather than translated into an LDAP wildcard filter.
- Test-user filtering only recognizes `"Y"`/`"y"` and `"N"`/`"n"`; any other value (including whitespace) silently skips the test-user clause rather than raising a validation error.

## Related documentation

- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)

> **Note:** Statements marked with `*` were inferred from code structure, naming
> conventions, or contextual evidence where direct confirmation was not available
> in source artifacts. Statements marked with `**` could not be confirmed; treat
> as unknown until verified.

## Sources

- `Business\SAIF.Business.Security.SystemUser\clsSystemUser.vb`
