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

  1. Wrap the additional filter in parentheses: 'objectClass=computer' -> '(objectClass=computer)'
  2. Combine multiple conditions as a single parenthesized expression: '(&(objectClass=computer)(name=a*))'
  3. Strip any redundant outer AND wrapper so the string starts with '(' and ends with ')'
  4. 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

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)