The independent review of the delta found the tests below weak. The verbose and debug rows of PipelineControl ran their command without an error action; the CI runner sets Stop, under which the handler that reports a later command's exception as an item error ended the pipeline with it, so the mutations that stop recording the verbose or the debug exception escaped (M19 in two configurations, M20 in all four). The rows now say -ErrorAction SilentlyContinue. The Enable-Privileges tests for a script named NTFSSecurity.Init.ps1 could not fail for the branch they name when the module setting was true, because the module enables the privileges itself before the cmdlet runs; they now also assert the verbose message that only the cmdlet writes, and they disable the privileges before each test so that the child process inherits none.
The first-nested-folder test asserts the first folder, not either. Set-TestNullDacl refuses an item that is a link, which the native call would follow out of the sandbox. A table pins the three outcomes that the filter documentation lists for a dot (Report.*, a trailing dot, an empty value), so a change of those rules is a decision.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Add-NTFSAccess, Remove-NTFSAccess, and Add-NTFSAudit report an AddAceError or RemoveAceError for each item when .NET refuses an entry without rights, change nothing, and return nothing with -PassThru. Get-NTFSAccess returns the one entry that .NET reports for a NULL DACL without a source; the new helper Set-TestNullDacl writes it through SetNamedSecurityInfo inside the sandbox guard. Get-NTFSHardLink lists the names of a file whose read rights are denied, which is why its UnauthorizedAccessException handler has no trigger. The public Extensions.ForEach and GetParent helpers, which a static scan listed as unused although cmdlets call them, are tested directly.
All of these characterize behavior that was already correct, so none was red before; the mutations that prove their detection run against the frozen measurement commit.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
A throw in a later command, or an error with -ErrorAction Stop, reaches a cmdlet through its Write call as an ordinary exception. The catch for the failures of an item reported it as the error of that item and went on, so that Remove-Item2 -PassThru removed the next item after a throw, and the caller never saw the exception. The earlier check found only the end of the pipeline and a break or continue. BaseCmdlet now notes the exception that its WriteObject, WriteVerbose, and WriteDebug raised, and every catch that can enclose a write passes it on; Get-DiskSpace writes outside its try. Set-NTFSSecurityDescriptor and Get-FileHash2 also caught it at a verbose message.
Get-ChildItem2 -Filter *.* returns every item, as Get-ChildItem does. The cmdlet compared each name with the pattern again and dropped the items without a dot, most folders among them; the dot stays an ordinary character in other patterns.
The failed lookup of InheritedFrom frees its native buffer. The help paragraph of -Filter has no pair of asterisks, which platyPS turns into emphasis, and the page has an example for *.*.
Review of the independent pass: the restored-owner test asserts that a plain write is denied, the drive-mapping helper has guard tests and takes letters that the no-volume tests do not, and the pipeline tests cover a throw, an error with -ErrorAction Stop, and the verbose and debug streams for every cmdlet that can reach the code.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
From the security-reviewer pass over be04cb7..4ee01e5 (Nit 8): the
probe for the administrative share and the conversion of a sandbox path
to \\localhost\C$\... were copied into ItemCmdlets.Tests.ps1 and
Links.Tests.ps1, and only the local path passed Assert-TestSandboxPath.
TestHelpers.psm1 now has Test-AdminShareAvailable and
ConvertTo-TestAdminSharePath, which checks the local path against the
sandbox before it returns the share path, with tests of their own.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Set-Acl compares AreAuditRulesProtected of the new descriptor with
AreAccessRulesProtected of the item (FileSystemSecurity.cs in PowerShell),
so it also writes the audit section of an item whose DACL is protected.
Without the Security privilege that fails with PrivilegeNotHeldException:
after Disable-NTFSAccessInheritance, Add-TestDenyRule added nothing, and
the take-ownership test of PathErrors.Tests.ps1 failed as a basic user.
With the privilege, Set-Acl wrote all sections and dropped the audit
entries of the item.
Add-TestDenyRule now writes only the DACL with SetAccessControl. Two
regression tests cover both effects; each failed before the change in its
configuration.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
- Remove-NTFSAccess and Remove-NTFSAudit with -RemoveSpecific are tested
also with -Path, not only with -SecurityDescriptor.
- Copy-Item2 and Move-Item2 name the destination of a folder in their
verbose message, not only of a file.
- The Enable-Privileges count compares with the privileges of the token,
so that it passes as a basic user, whose token holds one; the tests of
-PassThru restore the privilege states that they found.
- The type name comparison of Get-FileHash2 is exact, the inherited-entry
counts must be greater than zero, and the braces test (#3) checks the
verbose message that raised the FormatException.
- Remove-TestSandbox removes paths longer than 260 characters in Windows
PowerShell, through the \\?\ prefix and rd, so the long-path test of
Test-Path2 no longer cleans up itself; after a failed setup, it returns
instead of stopping AfterAll with a binding error.
Not reachable by a test: a second path whose SACL read fails while the
Security privilege is enabled; a declined -Confirm takes the code path of
-WhatIf. The tests that need a session without privileges get the CI run
as a basic user.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
- #41: for the root of a drive, the cmdlets read and changed the security
descriptor of the drive, a device object. FileSystemSecurity2 now routes
drive roots through the path-based AlphaFS methods, which keep the
trailing backslash; the removal and inheritance helpers use it too.
- #109: Add-, Remove-, and Clear-NTFSAudit report a security descriptor
without the audit entries like Get-NTFSAudit, through one helper, and
Get-NTFSEffectiveAccess names the cause that Windows reported instead
of a missing Security privilege.
- #108: Copy-Item2 and Move-Item2 check the destination only for an
operation that runs; with -WhatIf, a verbose message names the conflict.
- #111: Disable-Privileges skips the privileges that the token doesn't
hold, the privilege messages are spelled right, and Get-FileHash2
declares the type name of its objects; 05-Releasing.md documents the
release metadata tests.
- rc3 review leftovers: Remove-NTFSAudit writes nothing for an item
without a SACL, the owner retry of Set-NTFSSecurityDescriptor restores
the previous owner in a finally block and keeps an owner that the
descriptor sets, and FileSystemSecurity2.Write with another item writes
only the sections that were read.
Each fix has a test that failed first, in Windows PowerShell 5.1 and
PowerShell 7; writing a drive root was checked once on a temporary VHD.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
- Set-NTFSSecurityDescriptor -Verbose names the sections that it writes,
or says that it writes nothing for an unchanged descriptor; its page
says "since it was read or last written" (review F-02).
- The pages of Enable-NTFSAccessInheritance, Disable-NTFSAccessInheritance,
and Set-NTFSInheritance get the #34 note, like the other fixed cmdlets
(review F-06).
- Set-TestOwner throws its own error when icacls fails, also when the
caller uses -ErrorAction Stop in Windows PowerShell (review F-07).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The access and audit cmdlets wrote the owner of an item back with the
entries they changed. For a DACL without the auto-inherit flag, Windows
returns the owner and the group even when only the DACL is read, and the
cmdlets wrote every section that the descriptor held. Without the Restore
privilege, or on a file server that refuses the owner, the write failed
with error 1307 (#34).
- Add-NTFSAccess, Clear-NTFSAccess, Add-NTFSAudit, and Clear-NTFSAudit
read only the DACL or the SACL. FileSystemSecurity2.Write(),
Remove-NTFSAccess, and Remove-NTFSAudit write only the sections they
read, which also fixes the access inheritance cmdlets.
- Read together with the SACL, the inherited entries of such a DACL lose
their inherited flag when the parent folder has no SACL, and the
cmdlets stored them as explicit copies. Get-NTFSSecurityDescriptor now
reads the DACL in a separate call.
- Set-NTFSSecurityDescriptor writes only the sections that changed since
they were read; an unchanged descriptor writes nothing (maintainer
decision of 2026-10-06).
- Clear-NTFSAudit writes nothing for an item without a SACL, and reports
an error without the Security privilege (maintainer decision).
The regression tests failed before and pass after the fix in Windows
PowerShell 5.1 and PowerShell 7, elevated and as a basic user.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Security review of this branch, Major findings:
- M4: Get-NTFSAudit took ownership of an item whose SACL it couldn't
read. Ownership grants no access to the SACL, so the retry always
failed, and it left the owner changed. The cmdlet now writes a
ReadSecurityError with the category PermissionDenied, as Get-NTFSOwner
does since defect 9; the page says so.
- M1: Remove-TestSandbox reset the ACLs recursively before it removed the
links. Measured: icacls /reset /T did not follow the junction (the
target's explicit entry stayed), but the links now go first anyway; a
folder that denies listing gets a reset without /T. New test: the ACL
of a junction target stays unchanged.
- m4: Assert-TestSandboxPath now rejects a path below a link, which can
point outside the sandbox (new test, failed before).
- M5: the Inherits column reads one ACL per displayed item; the
Get-ChildItem2 page names the cost and how to avoid it.
- M2: the comment of the CI-only Get-NTFSAudit repeat test states what it
guards; Access.Tests.ps1 guards the same loop fix without elevation.
- M3, the stale hash of Get-FileHash2, is fixed on ai/defects-c.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Tests/TestHelpers.psm1 lets a test that changes files, links, ACLs,
owners, audit entries, or inheritance work in its own sandbox below
$env:TEMP\NTFSSecurity.Tests:
- New-TestSandbox creates the folder; Assert-TestSandboxPath throws
unless every target, relative ones resolved against the location, is
inside it.
- Remove-TestSandbox removes the links first without following them
(Windows PowerShell 5.1 follows directory links when it removes a
folder), resets ACL changes, and deletes the folder.
- Test-IsElevated and Test-PrivilegeHeld decide which tests can run; CI
runners are elevated, the workstation isn't.
Tests/TestHelpers.Tests.ps1 (14 tests) covers the helpers. CI runs every
*.Tests.ps1 file in Tests, so new test files need no workflow change.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>