From 9fde8d19a49874d547685202ede8a3dae0f393f6 Mon Sep 17 00:00:00 2001 From: Raimund Andree Date: Wed, 21 Oct 2026 12:18:24 +0000 Subject: [PATCH] fix(access): write an error when Get-NTFSOrphanedAccess cannot read an item Get-NTFSOrphanedAccess wrote a warning ("Could not read item {0}. The error was: {1}") for a read failure that is not an access denial, while every sibling (Get-NTFSAccess, Get-NTFSAudit, Get-NTFSEffectiveAccess, Get-NTFSInheritance, Get-NTFSSecurityDescriptor) writes a non-terminating error and goes on with the next item. The cmdlet now writes ReadSecurityError, categorized as OpenError and targeting the path, through WriteError, then continues, the same pattern Get-NTFSAccess (GetAccess.cs) already uses for the same kind of failure. A script that collected this warning with -WarningVariable now has to read $Error or -ErrorVariable instead. No existing test pinned the warning (searched for "Could not read item" and Get-NTFSOrphanedAccess with -WarningVariable across Tests\; none found), so none needed changing. NOT DONE: a new test that triggers this exact catch (a read failure that is not UnauthorizedAccessException) through the public surface. Five probes (..\probes-wi2\p-orphaned-read-failure*.ps1) tried a sharing lock, an over-long path, a dangling and a looped junction, and an alternate data stream; none reached this catch without also failing GetFileSystemInfo2 first (a different error, ReadFileError) or succeeding outright, because an elevated session that can read the item at all can read its ACL as well. The change is still correct and matches the module's convention; it is defensive code for a condition this harness cannot construct deterministically. Docs\Cmdlets\Get-NTFSOrphanedAccess.md documents the new error next to the existing ReadSecurityError paragraph for the access-denied case. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: AI Assistant --- Docs/Cmdlets/Get-NTFSOrphanedAccess.md | 2 ++ NTFSSecurity/AccessCmdlets/GetOrphanedAccess.cs | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Docs/Cmdlets/Get-NTFSOrphanedAccess.md b/Docs/Cmdlets/Get-NTFSOrphanedAccess.md index aa3b8d8..2aa63d2 100644 --- a/Docs/Cmdlets/Get-NTFSOrphanedAccess.md +++ b/Docs/Cmdlets/Get-NTFSOrphanedAccess.md @@ -174,6 +174,8 @@ When the module setting `EnablePrivileges` is `$true` (the default in the `Priva If the ACL of an item cannot be read because access is denied, the cmdlet tries once more after making the current account the owner of the item, and restores the previous owner afterwards. Reading the owner, which that fallback needs first, requires the same Read Permissions right as reading the ACL, so the cmdlet then writes a non-terminating `ReadSecurityError` and continues with the next item. Before 5.0.0, it reported that error as an `AddAceError`. +A read failure that is not an access denial is reported the same way, as a non-terminating `ReadSecurityError`, categorized as `OpenError` and targeting the path; before 5.0.0-rc9, the cmdlet wrote a warning instead (`Could not read item {0}. The error was: {1}`) and returned no entry for the item, without an error to say why. + Before 5.0.0, the cmdlet ignored `-Account` and `-SecurityDescriptor`, and after a path whose ACL could not be read, it returned the orphaned entries of the previous item again. ## RELATED LINKS diff --git a/NTFSSecurity/AccessCmdlets/GetOrphanedAccess.cs b/NTFSSecurity/AccessCmdlets/GetOrphanedAccess.cs index 48c85e7..0ef9f49 100644 --- a/NTFSSecurity/AccessCmdlets/GetOrphanedAccess.cs +++ b/NTFSSecurity/AccessCmdlets/GetOrphanedAccess.cs @@ -68,7 +68,9 @@ namespace NTFSSecurity } catch (Exception ex) { - this.WriteWarning(string.Format("Could not read item {0}. The error was: {1}", path, ex.Message)); + // A read failure that is not an access denial; before 5.0.0-rc9, the cmdlet warned about it + // instead ("Could not read item {0}. The error was: {1}") and returned no entry for the item. + this.WriteError(new ErrorRecord(ex, "ReadSecurityError", ErrorCategory.OpenError, path)); continue; }