Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## v0.6.1 - TBD

+ Remove length check for `sAMAccountName` when used in the `-Identity` parameter
+ Fix the search base and scope when using `Get-OpenAD*` with the `-Identity` parameter, it should now be possible to find objects not in the `defaultNamingContext`

## v0.6.0 - 2025-03-12

Expand Down
37 changes: 24 additions & 13 deletions src/PSOpenAD.Module/Commands/GetOpenAD.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ public abstract class GetOpenADOperation<T> : OpenADSessionCmdletBase

protected override void ProcessRecordWithSession(OpenADSession session)
{
string searchBase = SearchBase ?? session.DefaultNamingContext;
SearchScope searchScope = SearchScope;
LDAPFilter finalFilter;
if (!string.IsNullOrWhiteSpace(LDAPFilter))
{
Expand Down Expand Up @@ -153,7 +155,17 @@ protected override void ProcessRecordWithSession(OpenADSession session)
}
else if (Identity != null)
{
finalFilter = new FilterAnd(new[] { FilteredClass, Identity.LDAPFilter });
if (string.IsNullOrEmpty(Identity.DistinguishedName))
{
finalFilter = new FilterAnd(new[] { FilteredClass, Identity.LDAPFilter });
}
else
{
// If searching by DN we want to match just that object.
finalFilter = FilteredClass;
searchBase = Identity.DistinguishedName;
searchScope = SearchScope.Base;
}
}
else
{
Expand Down Expand Up @@ -216,18 +228,17 @@ protected override void ProcessRecordWithSession(OpenADSession session)
return;
}

string searchBase = SearchBase ?? session.DefaultNamingContext;
bool outputResult = false;

HashSet<string> finalObjectProperties = requestedProperties
.Where(v =>
v != "*" &&
(showAll || explicitProperties.Contains(v, _caseInsensitiveComparer)))
.ToHashSet();

foreach (SearchResultEntry result in SearchRequest(session, searchBase, finalFilter,
requestedProperties.ToArray(), serverControls))
bool noSuchObject = true;
foreach (SearchResultEntry result in SearchRequest(session, searchBase, searchScope, finalFilter,
requestedProperties.ToArray(), serverControls, (r) => r.ResultCode == LDAPResultCode.NoSuchObject))
{
noSuchObject = false;
OpenADEntity adObj = CreateOutputObject(
session,
result,
Expand All @@ -236,13 +247,12 @@ protected override void ProcessRecordWithSession(OpenADSession session)
this
);
ProcessOutputObject(PSObject.AsPSObject(adObj));
outputResult = true;
WriteObject(adObj);
}

if (ParameterSetName.EndsWith("Identity") && !outputResult)
if (noSuchObject && ParameterSetName.EndsWith("Identity"))
{
string msg = $"Cannot find an object with identity filter: '{finalFilter}' under: '{searchBase}'";
string msg = $"Cannot find an object with identity filter '{finalFilter}' under search base '{searchBase}' with scope {searchScope}";
ErrorRecord rec = new(new ItemNotFoundException(msg), "IdentityNotFound",
ErrorCategory.ObjectNotFound, finalFilter.ToString());
WriteError(rec);
Expand All @@ -252,13 +262,14 @@ protected override void ProcessRecordWithSession(OpenADSession session)
internal virtual IEnumerable<SearchResultEntry> SearchRequest(
OpenADSession session,
string searchBase,
SearchScope searchScope,
LDAPFilter filter,
string[] attributes,
IList<LDAPControl>? serverControls
)
IList<LDAPControl>? serverControls,
Func<LDAPResult, bool> errorHandler)
{
return Operations.LdapSearchRequest(session.Connection, searchBase, SearchScope, 0, session.OperationTimeout,
filter, attributes, serverControls, CancelToken, this, false);
return Operations.LdapSearchRequest(session.Connection, searchBase, searchScope, 0, session.OperationTimeout,
filter, attributes, serverControls, CancelToken, this, false, errorHandler);
}

internal virtual void ProcessOutputObject(PSObject obj) { }
Expand Down
14 changes: 8 additions & 6 deletions src/PSOpenAD.Module/Commands/OpenADGroupMember.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using PSOpenAD.LDAP;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
Expand Down Expand Up @@ -28,14 +29,15 @@ internal override OpenADObject CreateADObject(Dictionary<string, (PSObject[], bo
internal override IEnumerable<SearchResultEntry> SearchRequest(
OpenADSession session,
string searchBase,
SearchScope searchScope,
LDAPFilter filter,
string[] attributes,
IList<LDAPControl>? serverControls
)
IList<LDAPControl>? serverControls,
Func<LDAPResult, bool> errorHandler)
{
foreach (SearchResultEntry group in Operations.LdapSearchRequest(session.Connection, searchBase,
SearchScope, 1, session.OperationTimeout, filter, new[] { "primaryGroupToken" }, serverControls,
CancelToken, this, false))
searchScope, 1, session.OperationTimeout, filter, new[] { "primaryGroupToken" }, serverControls,
CancelToken, this, false, errorHandler))
{
// use memberOf rather than member to make recursive search easier & avoid paging
LDAPFilter memberOfFilter;
Expand Down Expand Up @@ -77,8 +79,8 @@ internal override IEnumerable<SearchResultEntry> SearchRequest(
_currentGroupDN = group.ObjectName;
try
{
foreach (SearchResultEntry result in Operations.LdapSearchRequest(session.Connection, searchBase,
SearchScope, 0, session.OperationTimeout, memberOfFilter, attributes, serverControls, CancelToken,
foreach (SearchResultEntry result in Operations.LdapSearchRequest(session.Connection, session.DefaultNamingContext,
SearchScope.Subtree, 0, session.OperationTimeout, memberOfFilter, attributes, serverControls, CancelToken,
this, false))
{
yield return result;
Expand Down
14 changes: 8 additions & 6 deletions src/PSOpenAD.Module/Commands/OpenADPrincipalGroupMembership.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using PSOpenAD.LDAP;
using PSOpenAD.Security;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
Expand Down Expand Up @@ -28,14 +29,15 @@ internal override OpenADObject CreateADObject(Dictionary<string, (PSObject[], bo
internal override IEnumerable<SearchResultEntry> SearchRequest(
OpenADSession session,
string searchBase,
SearchScope searchScope,
LDAPFilter filter,
string[] attributes,
IList<LDAPControl>? serverControls
)
IList<LDAPControl>? serverControls,
Func<LDAPResult, bool> errorHandler)
{
foreach (SearchResultEntry principal in Operations.LdapSearchRequest(session.Connection, searchBase,
SearchScope, 1, session.OperationTimeout, filter, new[] { "memberOf", "objectSid", "primaryGroupID" },
serverControls, CancelToken, this, false))
searchScope, 1, session.OperationTimeout, filter, new[] { "memberOf", "objectSid", "primaryGroupID" },
serverControls, CancelToken, this, false, errorHandler))
{
FilterEquality? primaryGroupFilter = null;
LDAPFilter groupMembershipFilter;
Expand Down Expand Up @@ -80,8 +82,8 @@ internal override IEnumerable<SearchResultEntry> SearchRequest(

try
{
foreach (SearchResultEntry result in Operations.LdapSearchRequest(session.Connection, searchBase,
SearchScope, 0, session.OperationTimeout, groupMembershipFilter, attributes, serverControls,
foreach (SearchResultEntry result in Operations.LdapSearchRequest(session.Connection, session.DefaultNamingContext,
SearchScope.Subtree, 0, session.OperationTimeout, groupMembershipFilter, attributes, serverControls,
CancelToken, this, false))
{
yield return result;
Expand Down
9 changes: 2 additions & 7 deletions src/PSOpenAD/ADIdentity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public ADObjectIdentity(string value)
}
else
{
LDAPFilter = DistinguishedNameFilter(value);
LDAPFilter = new FilterPresent("objectClass");
DistinguishedName = value;
}

Expand Down Expand Up @@ -53,11 +53,6 @@ internal static LDAPFilter ObjectGUIDFilter(Guid objectGuid)
{
return new FilterEquality("objectGUID", objectGuid.ToByteArray());
}

internal static LDAPFilter DistinguishedNameFilter(string dn)
{
return new FilterEquality("distinguishedName", LDAPFilter.EncodeSimpleFilterValue(dn));
}
}

public class ADPrincipalIdentity : ADObjectIdentity
Expand All @@ -76,7 +71,7 @@ public ADPrincipalIdentity(string value)
LDAPFilter = filter;
else
{
LDAPFilter = DistinguishedNameFilter(value);
LDAPFilter = new FilterPresent("objectClass");
DistinguishedName = value;
}
}
Expand Down
9 changes: 7 additions & 2 deletions src/PSOpenAD/Operations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ public static ModifyDNResponse LdapModifyDNRequest(
/// <param name="cancelToken">Token to cancel any network IO waits</param>
/// <param name="cmdlet">The PSCmdlet that is running the operation.</param>
/// <param name="ignoreErrors">Ignore errors and do not write to the error stream.</param>
/// <param name="state">Optional search state to track if errors occurred during the search.</param>
/// <returns>Yields each returned result containing the attributes requested from the search request.</returns>
public static IEnumerable<SearchResultEntry> LdapSearchRequest(
IADConnection connection,
Expand All @@ -183,8 +184,8 @@ public static IEnumerable<SearchResultEntry> LdapSearchRequest(
IList<LDAPControl>? controls,
CancellationToken cancelToken,
PSCmdlet? cmdlet,
bool ignoreErrors
)
bool ignoreErrors,
Func<LDAPResult, bool>? errorHandler = null)
{
cmdlet?.WriteVerbose($"Starting LDAP search request at '{searchBase}' for {scope} - {filter}");

Expand Down Expand Up @@ -236,6 +237,10 @@ bool ignoreErrors
error.ErrorDetails.RecommendedAction = "Perform request on one of the referral URIs";
cmdlet?.WriteError(error);
}
else if (errorHandler is not null && errorHandler(resultDone.Result))
{
break;
}
else if (!ignoreErrors && resultDone.Result.ResultCode != LDAPResultCode.Success)
{
ErrorRecord error = new(
Expand Down
12 changes: 9 additions & 3 deletions tests/Get-OpenADObject.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ Describe "Get-OpenADObject cmdlets" -Skip:(-not $PSOpenADSettings.Server) {
}

It "Fails to find entry with identity" {
$actual = Get-OpenADObject -Session $session -Identity invalid-id -ErrorAction SilentlyContinue -ErrorVariable err
$actual = Get-OpenADObject -Session $session -Identity CN=invalid-id -ErrorAction SilentlyContinue -ErrorVariable err
$actual | Should -BeNullOrEmpty
$err.Count | Should -Be 1
$err[0].Exception.Message | Should -BeLike "Cannot find an object with identity filter: '(&(objectClass=*)(distinguishedName=invalid-id))' under: *"
$err[0].Exception.Message | Should -BeLike "Cannot find an object with identity filter '(objectClass=*)' under search base 'CN=invalid-id' with scope Base"
}

It "Requests non-default property with case insensitive name" {
Expand All @@ -63,6 +63,12 @@ Describe "Get-OpenADObject cmdlets" -Skip:(-not $PSOpenADSettings.Server) {
$actual = Get-OpenADObject -Session $session -LDAPFilter '(objectClass=some-invalid-class)'
$actual | Should -BeNullOrEmpty
}

It "Finds by identity in non default naming context" {
$dse = Get-OpenADRootDSE -Session $session
$actual = Get-OpenADObject -Session $session -Identity "CN=Directory Service,CN=Windows NT,CN=Services,$($dse.ConfigurationNamingContext)" -Properties sPNMappings
$actual.sPNMappings | Should -Not -BeNullOrEmpty
}
}

Context "Get-OpenADComputer" {
Expand Down Expand Up @@ -285,7 +291,7 @@ Describe "Get-OpenADObject cmdlets" -Skip:(-not $PSOpenADSettings.Server) {
$actual = Get-OpenADUser -Session $session -Identity MyTestContact -ErrorAction SilentlyContinue -ErrorVariable err
$actual | Should -BeNullOrEmpty
$err.Count | Should -Be 1
$err[0].Exception.Message | Should -BeLike "Cannot find an object with identity filter: '(&(&(objectCategory=person)(objectClass=user))(sAMAccountName=MyTestContact))' under: *"
$err[0].Exception.Message | Should -BeLike "Cannot find an object with identity filter '(&(&(objectCategory=person)(objectClass=user))(sAMAccountName=MyTestContact))' under search base * with scope Subtree"
}
finally {
$contact | Remove-OpenADObject -Session $session
Expand Down