BornToBeRoot/NETworkManager · error · InvalidOperationException
Additional LDAP filter must be a valid LDAP expression…
Error message
Additional LDAP filter must be a valid LDAP expression starting with '(' and ending with ')'. What it means
BuildLdapFilter composes the base computer filter with an optional user-supplied AdditionalFilter. Because the additional filter is inserted verbatim into an LDAP AND expression (&...), it must itself be a parenthesized LDAP expression; otherwise the resulting filter would be malformed, so the method throws InvalidOperationException.
Solutions
- Wrap the additional filter in parentheses: 'objectClass=computer' -> '(objectClass=computer)'
- Combine multiple conditions as a single parenthesized expression: '(&(objectClass=computer)(name=a*))'
- Strip any redundant outer AND wrapper so the string starts with '(' and ends with ')'
- Trim whitespace before validation (the code trims, but stray inner characters still break LDAP parsing)
Example fix
// before options.AdditionalFilter = "objectClass=computer"; // after options.AdditionalFilter = "(objectClass=computer)";
Defensive patterns
Strategy: validation
Validate before calling
var additional = options.AdditionalFilter?.Trim();
bool valid = string.IsNullOrEmpty(additional) || (additional.StartsWith('(') && additional.EndsWith(')'));
if (!valid) throw new ArgumentException("Additional filter must be a parenthesized LDAP expression."); Type guard
bool IsValidLdapFragment(string f) { var t = f?.Trim(); return string.IsNullOrEmpty(t) || (t.StartsWith('(') && t.EndsWith(')')); } Try / catch
try { var computers = searcher.GetComputersInSubtree(path); }
catch (InvalidOperationException ex) when (ex.Message.Contains("Additional LDAP filter")) { /* surface a validation message to fix the filter setting */ } Prevention
- Always write additional filters as '(attr=value)'
- Never append multiple bare conditions; wrap combined expressions in '(&...)'
- Validate user-supplied filter strings at input time
- Test composed filters with a tool like ldp.exe
When it happens
Trigger: Setting ActiveDirectorySearchOptions.AdditionalFilter to a value that, after trimming, does not start with '(' or end with ')' — e.g. 'objectClass=computer', '(objectClass=computer) AND (name=a*)', or an empty expression built dynamically.
Common situations: Users entering raw LDAP attribute conditions without parentheses in the settings UI; concatenating multiple filters without wrapping each in parens; copying a full filter string that already has an outer (&...) wrapper.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
AI-assisted analysis of BornToBeRoot/NETworkManager@2780d65469 (2026-09-12).
Data as JSON: /api/errors/b6eea1298723ed0a.
Report an issue: GitHub.
Appendix: source
Thrown at Source/NETworkManager.Utilities/ActiveDirectory/ActiveDirectoryComputerSearcher.cs:144
/// If an <see cref="ActiveDirectorySearchOptions.AdditionalFilter"/> is supplied it is AND-combined
/// with the base filter.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown when <see cref="ActiveDirectorySearchOptions.AdditionalFilter"/> is not a valid LDAP
/// expression (must start with <c>(</c> and end with <c>)</c>).
/// </exception>
private static string BuildLdapFilter(ActiveDirectorySearchOptions options)
{
var baseFilter = options.ExcludeDisabledAccounts
? "(&(objectCategory=computer)(objectClass=computer)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))"
: "(&(objectCategory=computer)(objectClass=computer))";
var additional = options.AdditionalFilter?.Trim();
if (string.IsNullOrEmpty(additional))
return baseFilter;
if (!additional.StartsWith('(') || !additional.EndsWith(')'))
throw new InvalidOperationException(
"Additional LDAP filter must be a valid LDAP expression starting with '(' and ending with ')'.");
return $"(&{baseFilter}{additional})";
}
/// <summary>
/// Returns <see langword="true"/> when <paramref name="value"/> begins with a recognised
/// LDAP protocol prefix (<c>LDAP://</c>, <c>LDAPS://</c>, or <c>GC://</c>), indicating
/// that the search base already contains a fully-qualified path.
/// </summary>
private static bool StartsWithProtocol(string value)
{
return value.StartsWith("LDAP://", StringComparison.OrdinalIgnoreCase) ||
value.StartsWith("LDAPS://", StringComparison.OrdinalIgnoreCase) ||
value.StartsWith("GC://", StringComparison.OrdinalIgnoreCase);
}
/// <summary>View on GitHub (pinned to 2780d65469)