The final commit 61e936e was built once, at 8a625c8, and measured on those
bytes: 1,459 cases per configuration, 3,258 of 3,664 sequence points, 1,090 of
1,168 explicit branch points, and the same 187 unvisited methods, all
classified. The first lab accepts the final build (245 passed, 1 skipped per
edition), the 22 files of the matrix suite pass on Windows Server 2019, 2022,
and 2025 and on Windows 11 in 16 configurations, and both labs are clean.
The report adds the second culture defect that the independent review found
(Get-ChildItem2 -Filter in tr-TR), the review and how each finding was
resolved, the probe that tells which parameter set binds for a piped
AppliesTo, and the corrected description of the branch summary of the tool,
which adds one point per method and is not read. The Runs table lists every
run, the Evidence table hashes the evidence outside git, and the ParameterSets
table cites the lines of the final tests.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Items: the comparison of Get-Privileges with whoami compares the State only
where whoami prints the English words, because the column is localized. The
rows 'as an array with an empty element' of Copy-Item2 and Move-Item2 now pass
an array: a switch unrolls @(''), so they had passed the plain empty string. The
empty-path rows of both cmdlets run in a folder of their own, so that a guard
that regressed would copy or move an empty folder and not the sandbox into
itself.
Audit: a piped AppliesTo property does not select PathSimple, the default set
PathComplex binds and the property is dropped (a descriptor object does select
SDSimple), so the rows named PathSimple bound PathComplex. The PathSimple rows
of Add-NTFSAudit and Remove-NTFSAudit now name -AppliesTo and pipe the rest,
and the Add rows use a folder and assert the scope of the new entry (None, not
the default ContainerInherit and ObjectInherit). The descriptor rows assert the
entry in the descriptor and, after Set-NTFSSecurityDescriptor, on disk.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The change of the previous commit was necessary but not enough. AlphaFS 2.2
also matches the filter: it builds a case-insensitive regular expression from
it that takes the culture of the thread when it is created. With tr-TR the
enumeration itself returned nothing for index* in a folder with Index.txt, in
both editions (Directory.EnumerateFileSystemEntries of AlphaFS returned an
empty list, .NET returned Index.txt), so the new test still failed on a build
that had the previous change alone.
The enumeration, that is the creation of the enumerator and every MoveNext, now
runs with the invariant culture, which is restored before an item reaches the
pipeline, so that no later command sees it. No other cmdlet passes a pattern of
the user to AlphaFS.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
With the culture of the thread tr-TR, the case-insensitive WildcardPattern that
compares each name with the filter treats I and i as different letters. The
enumeration had already matched the name without regard to the culture, so the
cmdlet dropped the items whose names differ from the filter in the case of an
I: -Filter 'index*' returned nothing for Index.txt, and -Filter '*.ini' did not
return SETTINGS.INI. Both editions. The independent review of the coverage
report found it; the scan of culture-sensitive calls had not listed
WildcardPattern.
The test sets the culture of the thread to tr-TR. On the previous build it
fails in the four configurations (3 of 134 cases of ItemCmdlets.Tests.ps1:
index*, *.ini, and *.INI; INDEX* is the control). The pattern is now
WildcardOptions.CultureInvariant.
The changelog entry of the identity comparison named the operator -eq, which
tr-TR never affected (probes on the previous build: -eq, -contains, and -in
return True for every case); it now names the method Equals.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The report gets the section on the acceptance of the fixed build in the first
lab (both editions 245 passed, 0 failed, 1 skipped), the matching row of the
completion matrix, and the bullet of the summary. The Memory Bank carries the
Handoff 5 evidence, the coverage baselines and the live-coverage method.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Get-Acl -Audit reads the SACL together with the other sections. On a domain
member (Windows Server 2019, 2022, and 2025 of the operating-system matrix)
that read reports the SACL of an item without audit entries as protected from
inheritance, where the read of the SACL alone and Get-NTFSInheritance report
it as not protected (FileSystemSecurity2 documents the same difference).
The test 'Disable-NTFSAuditInheritance -SecurityDescriptor should take two
piped descriptors ...' asserted the on-disk flag of a fresh file with
Get-Acl -Audit, and failed in all six elevated configurations of the three
file servers. Diagnostic runs on 2019 and 2025 show both reads side by side.
Four more assertions read the flag of an item that ends without audit entries
and expect "protected"; there they could not tell a written flag from none.
The five assertions now read the SACL alone through .NET, as the module does.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Handoff 5: the coverage of rc7 (fa0701b), the live tests and a domain member, the judged matrix of the 64 parameter sets and the tests that it led to, the cultures, and the final commit. The tables are the unvisited methods, the cmdlets, the reconciliation, the parameter sets, the mutations, the roles of the live suite, the cultures, and the runs. The evidence table follows with the lab acceptance of the fixed build.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Run-MatrixLocalSuite.ps1 stages the behavior test files of the repository. Add the four ParameterSets files of the quality gate (access, audit, inheritance, items), so that the next acceptance proves them on every machine, in both editions, elevated and as a basic user.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The analyzer flags -Algorithm MD5 as a broken hash algorithm. The test compares the output of Get-FileHash2 with Get-FileHash and uses no hash for security, so the algorithm comes from a variable, like the data of FileHash.Tests.ps1.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Close the TestNeeded gaps of judged-inheritance.csv for the inheritance,
owner, and security-descriptor cmdlets (Disable/Enable-NTFSAccessInheritance,
Disable/Enable-NTFSAuditInheritance, Get/Set-NTFSInheritance, Get/Set-NTFSOwner,
Get-NTFSSecurityDescriptor) in one new file, Tests\ParameterSets.Inheritance.Tests.ps1.
20 new It blocks (48 data rows) assert, in the matrix's priority order:
- State: independent Get-Acl/Get-Acl -Audit oracles for access and audit
inheritance transitions, the -RemoveInherited/-RemoveExplicit switches
with a -SecurityDescriptor, Set-NTFSInheritance setting both flags in one
call, and Set-NTFSOwner leaving the Access entries and group unchanged.
- Error: ErrorId/category/target triples for a denied write, a missing
path, a denied read, and a refused owner, each continuing to the next
item; RestoreOwnerError for the three Path cmdlets that protect or set
access inheritance, which had none before.
- Pipeline: every -Path and -SecurityDescriptor set that no existing test
piped, including the direct, named Get-NTFSOwner -SecurityDescriptor
call the scan could not classify, and the documented Set-NTFSInheritance
round trip.
No existing test file, helper, or production source changed. Validated
test-first (each new assertion checked for a real, failable precondition)
and proven with 10 one-statement C# mutations in a separate, discarded
worktree: 9 detected, 1 honestly undetected (ValueFromPipelineByPropertyName
alone still binds Get-ChildItem2 output through its native FullName
property, so removing ValueFromPipeline from -Path is invisible to an
object-only pipeline test). All four configurations (elevated/basic user x
Windows PowerShell 5.1/PowerShell 7) pass with zero failures; audit- and
Restore-privilege-dependent rows skip in basic user and run elevated.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Adds Tests/ParameterSets.Access.Tests.ps1, closing the TestNeeded rows of
paramsets/judged-access.csv for Add-NTFSAccess, Remove-NTFSAccess,
Clear-NTFSAccess, Get-NTFSAccess, Get-NTFSOrphanedAccess,
Get-NTFSSimpleAccess, and Get-NTFSEffectiveAccess: 20 new It blocks (26
executed cases) covering pipeline binding (ValueFromPipeline and
ValueFromPipelineByPropertyName, including positional file/folder objects),
-PassThru combined with one denied and one succeeding path, error
ErrorId/category/target for read and write failures, continuation after a
per-item error, state read back with .NET (not -PassThru/Get-NTFSAccess),
SecurityDescriptor-set pipelines and memory-vs-disk behavior, and the
declared output types and PathSimple/SDSimple vs PathComplex/SDComplex
parameter-set boundaries. No row with GapKind=MaintainerDecision is pinned:
the -SecurityDescriptor sets of Add/Remove keep their open per-item error
behavior untested either way.
Finding: a plain piped object with an AppliesTo property does not steer
Add-NTFSAccess/Remove-NTFSAccess to the PathSimple/SDSimple parameter set by
itself; PowerShell resolves the ambiguity to the default set (PathComplex/
SDComplex) and silently drops the unmatched property. The proposed test for
that exact shape (notes section (b), item 2c) could not be written as
specified; the pipeline test for PathSimple instead binds -Path through the
pipeline while -AppliesTo is named explicitly, which does select the set.
Validated in all four configurations (elevated/basic user, Windows
PowerShell 5.1/PowerShell 7): 26 passed, 0 failed, 0 skipped in each. Eight
one-statement production mutations across all seven cmdlets were applied in
an isolated worktree, built, and run against this file; each was detected by
at least one new test, then the source was restored exactly and rebuilt.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Add Tests\ParameterSets.Audit.Tests.ps1, closing the TestNeeded gaps that
judged-audit.csv/judged-audit-notes.md recorded for Add-NTFSAudit,
Remove-NTFSAudit, Clear-NTFSAudit, Get-NTFSAudit, and Get-NTFSOrphanedAudit:
- Pipeline binding by value and by property name for every parameter set of
all five cmdlets, including the four sets the matrix flagged as bound only
by splat or pipe calls (Add-NTFSAudit SDSimple, Remove-NTFSAudit PathSimple
and SDSimple, Get-NTFSOrphanedAudit SD), each of which also gets a direct,
named call.
- Continuation past a failing path or a descriptor without an audit section,
asserting the ErrorId, category, and target of the error and the state or
output of the item that follows.
- State assertions beyond -PassThru: SACL/DACL SDDL before and after, the
in-memory descriptor versus the item on disk until Set-NTFSSecurityDescriptor
writes it, and the protected/unprotected SACL after Clear-NTFSAudit
-DisableInheritance.
- Narrowing of an audit entry's rights when Remove-NTFSAudit runs without
-RemoveSpecific, and the AmbiguousParameterSet error when -AppliesTo and
-InheritanceFlags are combined.
No production code, existing test file, or helper module changed. Fixture
DACLs use .NET SetAccessControl through the existing TestHelpers.psm1
helpers; no test pins the open item of the -SecurityDescriptor sets of Add
and Remove having no per-item handler around the change itself.
Verified in all four configurations (elevated/basic user x Windows
PowerShell 5.1/PowerShell 7): 0 failures. Mutation-tested with 9 one-statement
changes to the production C# in a separate, detached worktree; 8 were caught
by the new tests and one (the -AppliesTo-to-flags conversion for the Simple
sets, masked on a file target) was not, which the pre-existing
PermissionScopes.Tests.ps1 already catches on a folder target.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Add Tests\ParameterSets.Items.Tests.ps1, closing TestNeeded gaps the judged
parameter-set matrix found for Copy-Item2, Get-ChildItem2, Get-Item2,
Move-Item2, Remove-Item2, Test-Path2, Get-FileHash2, Get-DiskSpace,
New-NTFSHardLink, Get-NTFSHardLink, New-NTFSSymbolicLink, Enable-Privileges,
Disable-Privileges, and Get-Privileges:
- Pipeline input by value for Copy-Item2, Move-Item2, Get-ChildItem2,
Get-Item2, and Remove-Item2 (never exercised before), including
continuation past a missing item and AlphaFS objects piped from
Get-ChildItem2.
- An empty string, $null, a piped empty string, or an empty array element
for -Path/-Target is rejected by ValidateNotNullOrEmpty and never falls
back to the current location, for Copy-Item2, Move-Item2, Remove-Item2,
New-NTFSHardLink, and New-NTFSSymbolicLink. Verified by running against
the built module in both PowerShell editions before being pinned.
- Get-NTFSHardLink error categories, output type, and positional binding.
- Pipeline continuation (Select-Object -First 1) for New-NTFSHardLink and
New-NTFSSymbolicLink, and a successful link after an earlier failure in
the same New-NTFSSymbolicLink call.
- Enable-Privileges and Disable-Privileges error paths (SecurityError,
Security2.AdjustPriviledgeException), runnable on any token.
- Get-DiskSpace with a valid and an invalid drive letter together in either
order, and positional/lower-case binding.
- Get-Privileges compared against whoami /priv for the five privileges
that both name the same way.
- Get-FileHash2 pipeline objects with -Algorithm, and a locked path before
a valid one with its error category and a relative-path hash.
Every It asserts persisted state, an ErrorId/category/target, continuation,
or an output type; none asserts only an English message. All four runtime
configurations (elevated/basic user x Windows PowerShell 5.1/PowerShell 7)
pass with 0 failures; two symbolic-link-privilege-gated tests skip on the
basic-user configurations and run on both elevated ones. Mutation testing
against 8 one-statement changes in a separate, discarded worktree confirmed
each targeted test fails for the right reason; production sources were
restored exactly after every mutation and no production code changed here.
Does not pin the Get-ChildItem2 -Filter dot rules, the Decision 22 link and
move/copy behaviors, the PrivilegeEnabler class, or the owner-restore
handlers. A probe of Get-NTFSHardLink and New-NTFSHardLink -PassThru
through a subst-mapped drive found a pre-existing defect (reported
separately, not fixed or pinned here): both combine the apparent drive
root with the volume-relative hard-link names from Windows, which throws
FileNotFoundException once the drive's root differs from the real NTFS
volume root.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
IdentityReference2.Equals(string) lowercased both names with ToLower(), which uses the
culture of the thread. In tr-TR, I becomes U+0131, so an account whose name holds an
uppercase I (NT AUTHORITY\SYSTEM, BUILTIN\Administrators) no longer matched the same name in
another case, for example $entry.Account -eq 'nt authority\system'. Compare with
StringComparison.OrdinalIgnoreCase.
The test sets the culture of the thread to tr-TR and compares the system account with its name
in three cases. Before the fix it fails in all four configurations (elevated and basic user,
Windows PowerShell 5.1 and PowerShell 7); after it, all rows pass. The unit suite under tr-TR
did not find it, because no test compares a name that holds an uppercase I.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The manifest was refreshed after the final log line and the exit file
were added, so a fixed count in the record would be wrong.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The published prerelease passed every stage of the lab acceptance on
2026-10-10, with the counts of the final local candidate: the three cells
of the operating-system matrix (1,374 passed, 0 failed, 12 skipped), the
module's own suite in 24 runs on six machine classes without a failure,
and the first lab with case 9 (245 passed, 0 failed, 1 skipped per
edition). Every end state was verified clean. Each controller run used the
package that the identity check had verified byte for byte.
Add the record with four tables, link it from the lab README and describe
the order of the stages, which follows the one-hour life of the evaluation
client. Update Decision 24, the progress, the active context, and the
deployment notes for the state after the acceptance.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The built-in security-review agent (the custom security-reviewer still can't
start: its model isn't offered, and it wasn't overridden) read the branch and
found no exploitable vulnerability in the module changes. It reported two LOW
items that are not changed and are left for the maintainer: the swallowed
initialization exceptions of GetEffectiveAccess (older than the fixes and not
reproducible on any machine of the matrix) and the ACL of the stage folders
under C:\ in the lab kit. The record, the Memory Bank, and the next steps say
so, and the record says how the decision of "this computer" was tested.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The four ConvertTo-SecureString -AsPlainText calls of Probe-EffectiveAccess.ps1
and Run-MatrixLocalSuite.ps1 get a SuppressMessageAttribute with a
justification, as the controller already has for the same case: the lab
installation password comes from the AutomatedLab lab definition and the
passwords of the probe users are random and exist only in memory.
The header of Test-MatrixCleanup.ps1 says that Repair matches the prefixes of
the kit in the whole domain and on the whole machine, which the security review
pointed out, and that an unresolved S-1-5-21-* member of Performance Log Users
can be a real principal of a trust that is down.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Run-MatrixSequence.ps1 -Version 5.0.0-rc6 ran once on OSFile19 in
Windows PowerShell as a dry run. The controller used the published
module, the validation reported LIVE_RESULT_NOT_ACCEPTED as it must
(151 passed, 78 failed, 2 skipped: the live tests that rc6 predates), and
the cleanup verdict was CLEAN. It tests the mechanics and accepts nothing.
The deployment notes say how to pass the file servers and when to start
the evaluation client.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The second follow-up review found no Blocker and no Major, and four
Minors that are corrected. Test-MatrixCleanup.ps1 and the README say
that every unresolved S-1-5-21-* member of Performance Log Users counts
as an entry of the probe; a Verify with the new script found none on
the two machines of the first lab. The record says that the replay
shows that the module doesn't decide the outcome and that six runs can't
rule out a small effect, which the result bullet and the summary of the
evidence had left out; it lists the first-lab counts among its tables,
names the controller blob of rc7d, says that the first-lab check ran
before the profile and log-group fields existed, counts nine restarts
inside the series, and mentions the first attempt of the cleanup test
that died. The controller comment says "for the baseline and for the
final candidate alike" instead of "whichever version"; no code changed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The follow-up review found no Blocker and no Major, and five Minors, all
verified against the raw logs and all corrected: rc7e's first cell was
4.6 minutes after the removal of rc7d, which created and removed
accounts without a test (the timeline now includes rc7d); one lifetime
that fits both tests is 9.95 to 10.25 minutes, not 9.35 to 10.20; the
client restarted at 05:34, which matters for the counterfactual of ab7
only; and five statements that the record's own data contradicted
(the age of the entries in failing and passing cells, the blob of the live
tests of the replay, the exporter's scope, the folders that the check
counts, and the blob of the controller of ab7 to ab10).
The record says that six replay runs cannot rule out a small effect of the
module, that the window of the client test is 0.3 minute wide, and that
the cleanup check ran with real residue of every kind.
The final candidate also passed the live controller in the first lab,
where case 9 runs: 245 passed, 0 failed, 1 skipped per edition, with the
fixture removed and verified clean.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Test-MatrixCleanup.ps1 now also counts and removes the profiles and the
profile folders C:\Users\NtfsProbe* (retried, because a profile that the
last task used stays loaded for a few seconds) and the entries of the
account probe in Performance Log Users. net.exe lists a local user by its
bare name and a deleted domain account by its SID or its cached name, so
the check matches NtfsProbe anywhere in the line or a SID.
Export-CellTimeline.ps1 takes the account of a cell that stopped before its
tests from the snapshot of its fixture, so that the series can include
such a cell, and reports SameNameAsPreviousCell and SameAccountAsPreviousCell
instead of one column that compared names only.
Test-StaleAuthzModel.ps1 computes its grid of lifetimes by index (the
accumulated step lost the grid point 10.25), prints a range as segments,
and prints the range of one lifetime for both tests.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The effective-access failures of the Admin role in the Windows Server
2022 cell don't depend on the module. A replay of seven cells with the
baseline and the final candidate alternating failed the baseline in two
of three cells and the final candidate in one of three, not counting the
warm-up. In a failing cell the remote authorization managers answer as
if the account had no groups while the name resolution, the Kerberos
logon, and the local manager are right in the same second.
One model with one lifetime (9.35 to 10.20 minutes) fits all 43 Admin
role runs of 27 cells, and none of 5,000 random assignments of the
outcomes does. Four more cells with unique account names pass, two of
them where the model predicts a failure for a reused name.
The record, the README, and a timeline CSV now say what the evidence
supports and what it doesn't establish, that the cleanup check ran with
real residue, and that fdd7a8b reverts cleanly while 962887a conflicts
with it.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Test-MatrixCleanup.ps1 now counts and repairs the probe folders
(C:\NtfsProbeRecreation and C:\NtfsProbeModules), the local NtfsProbe*
users with their profiles, and the NtfsProbe* objects of the directory.
The scripts of the matrix default to the client OSWin11E.
Export-CellTimeline.ps1 writes one row for every cell, edition, and
Admin role: the module, the account and its relative ID, the times, and
the three effective-access tests. Test-StaleAuthzModel.ps1 replays such a
timeline against a model of the failures: the fit, a listing of the runs,
the cells of a controller that reuses the account name, and a permutation
test.
The comment in the controller says what the replay showed. The code of
the controller is unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The record, its tables (the suite per candidate, the failing tests, and the
controller cells), and the README of the lab. The final local candidate passes the
module's suite on Windows Server 2019, 2022, and 2025, Windows 11 22H2 and 26H1,
and the host in both editions, elevated and as a basic user, and the live
controller in three cells. The record states what a local build does not prove.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The suite runner removes its stage on a machine after it has copied the results
back; after an early stop, the stage stays for the diagnosis. The end-state check
counts the items in the stage folders and the scheduled tasks and standard users
of the kit, and -Mode Repair removes what is left of the stage and the tasks.
Probe-AccountRecreation.ps1 deletes an account and creates it again with the same
name in a loop, logs the user on with Kerberos S4U on the domain controller, the
client, and the file server, and asks Get-NTFSEffectiveAccess of each module under
test. It shows that Windows returns the old SID and groups, whichever version of
the module asks.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
When an account is deleted and created again with the same name, a Kerberos S4U
logon for it keeps returning the SID and the groups of the deleted account for
a while, on the domain controller, the client, and the file server. The matrix
deletes the fixture after each cell and creates it for the next, so the
effective-access tests of the Admin role found no access for the new account in
cells that followed within minutes (Windows Server 2022 cell, candidate and
baseline alike, shown by a probe that creates the accounts in a loop). A new
fixture now gets NtfsLiveSubject and four digits; a fixture that exists keeps
its account.
The end-state check of the matrix also reports leftover scheduled tasks, stage
folders, standard users, and probe accounts, which the review asked for.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The review of the matrix kit found no Blocker or Major issue and these
Minor ones, all fixed here:
- Add-OsMatrixMachine.ps1 assigned the path of the AutomatedLab disk
deployment lock before it checked that the lock exists, so a refusal because
another deployment held the lock made the finally block delete that foreign
lock. The path is kept until this script has created the lock.
- Repair-OsMatrixBoot.ps1 tested the switches of the machine with an array
-ne, which is false for a machine without an adapter, so the guard that is
meant to refuse a machine outside the lab let it through and the script
turned it off. The guard counts the switches now.
- Deploy-OsMatrixLab.ps1 took the installation and domain administrator
password of the lab from Get-Random, which isn't a cryptographic generator.
It uses RandomNumberGenerator without a remainder bias, as the controller
does.
- Run-MatrixLocalSuite.ps1 removed its scheduled tasks, which store the
password of the account that runs them, only after a successful poll. The
finally block of the machine removes the tasks of the run now.
- Probe-EffectiveAccess.ps1 cleaned up the domain controller before the machine
without a try block, so a failure there skipped the cleanup of the machine.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Probe-EffectiveAccess.ps1 runs Invoke-EffectiveAccessProbe.ps1 on one machine
of the operating-system matrix under up to four tokens and copies the output
back: the lab account in a scheduled task at the highest run level, the same
account with the token of a basic user (SAFER level Normal User), a local
standard user, and a standard user of the domain. The standard users are
created for the run with a random password that exists only in memory, get
the batch logon right through Performance Log Users, and are removed again
with their profiles and group memberships; the names carry a time stamp,
because Windows keeps the SID of a deleted account for its name for a while.
For the account of the token and for well-known SIDs and the accounts of the
domain, the probe asks the cmdlet for the default server name, localhost, an
empty name, the names of this computer, and other computers, and writes the
result, the warnings, and the native error with the failing method. It showed
that the remote interface of the authorization manager of a computer in a
domain refuses every user who isn't an administrator, which is the defect
that the previous commit fixes.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
On a computer in a domain, Get-NTFSEffectiveAccess wrote "Access is denied"
and no result for every user who wasn't an administrator of the computer,
also for the default -ServerName localhost and for every other name of this
computer. The remote interface of the authorization manager of a computer
answers only its administrators and the members of Access Control Assistance
Operators, and a computer in a domain offers that interface to every caller.
On a computer outside a domain the interface is not reachable, so the cmdlet
already used the local authorization manager there, which is why the tests
passed on the development host and on the CI runners.
For a name of this computer, the cmdlet now uses the local authorization
manager when the remote one refuses the user. That manager is the one the name
asks for, and it answered correctly in every probe on Windows Server 2019,
2022, and 2025 and on Windows 11: for a standard domain user, a local standard
user, and an administrator with a filtered token, for the user's own account,
Everyone, the Administrator of the computer, and the Administrator and Domain
Users of the domain. For the name of another computer, the denial stays an
error, as the cmdlet page and the live test of the delegated account describe.
Twenty tests of the suite failed in the basic-user mode on every domain-joined
machine of the operating-system matrix, with the published 5.0.0-rc7 code and
with the code before this change, and pass with it (Windows Server 2019: basic
user 782 and 780 passed, 0 failed, in Windows PowerShell and PowerShell 7). A
new live test runs the case as the administrator of the file server, who isn't
an administrator of the client.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Tests\Lab\Acceptance gets the scripts of Decision 24, which extends the
acceptance of the live tests from one lab to several operating-system builds:
- Deploy-OsMatrixLab.ps1, Add-OsMatrixMachine.ps1, Complete-OsMatrixLab.ps1
and Repair-OsMatrixBoot.ps1 build NtfsSecurityOsMatrixLab with AutomatedLab
(Server 2019, 2022 and 2025 file servers, Windows 11 clients) and repair a
base image whose host-side bcdboot left an empty EFI system partition.
- Test-MatrixReadiness.ps1 and Test-MatrixCleanup.ps1 gate each run and prove
that it left nothing behind (fixture accounts, shares, folders, group
members, orphaned SIDs, profiles); -Mode Repair removes what is left.
- Run-MatrixSequence.ps1 runs the live controller for every cell of the matrix
and stops after an infrastructure failure.
- Run-MatrixLocalSuite.ps1 and Invoke-LocalSuite.ps1 run the module's own
Pester suite on each machine in both editions, elevated and as a basic user,
as scheduled tasks: a process started from a remoting session gets every
privilege enabled, which eight of the tests do not expect.
- Export-MatrixResults.ps1 collates the cells, the skipped tests and the
package hashes into the results table.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The operating-system matrix runs on Windows Server 2019, 2022 and 2025 found
three defects in the fixture setup and removal of Invoke-NTFSSecurityLabTest.ps1:
- A native command's stderr that is redirected with 2>&1 is a terminating
error in Windows PowerShell 5.1 under $ErrorActionPreference = 'Stop'. The
first "The directory is not empty" line from PowerShell 7 ended the removal
on Server 2019 before any retry. The removal is now a bounded loop whose
PowerShell 7 command writes its errors to its output.
- Get-LocalGroupMember fails with "Failed to compare two elements in the
array" when a group holds an orphaned SID, for example that of an account an
earlier run deleted. The setup adds the members with Add-LocalGroupMember and
ignores MemberExistsException instead of checking the members first.
- A user profile that is gone in the meantime no longer fails the client
cleanup, and the retries are reported to the host.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The module's own tests ran on Windows Server 2019, 2022, and 2025 and on
Windows 11 in the operating-system matrix. Two tests failed on every
domain-joined machine and passed on the development host:
- Get-NTFSInheritance -SecurityDescriptor reported AuditInheritanceEnabled
as $false for an item without audit entries, where -Path reported $true.
On these computers Windows reports the SACL as protected from
inheritance when it reads all sections together, and as not protected
when it reads the SACL alone. The descriptor now takes the audit section
from a separate read, like it already did for the access section. Write()
stores the sections that were read, so a descriptor with the wrong flag
would also have written the SACL back as protected.
- Get-NTFSEffectiveAccess -ServerName '' wrote an "Access is denied"
error on computers where Windows takes an empty name for this computer.
An empty name no longer asks the remote interface of the authorization
manager; the cmdlet warns and returns the result of this computer, like
for any name that can't be reached.
The existing tests that found them stay as the regression guards: they were
red on three virtual machines (Windows Server 2022 and 2025, Windows 11)
and are green after the change. Three tests are new: the audit state of a
file and a folder without audit entries, and of a file and a folder with
disabled audit inheritance, by path and by descriptor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The independent review of the acceptance record found no Blocker or Major
issue. This commit corrects what it found: the commit that fixed the break
row, what the State test shows, the baseline failures that carry no message,
the count of results, the wording about the folders before the first run, the
truncated messages in the results file, the README row of case 10, and the
review section of the record.
The 42 messageless baseline failures are now explained by a diagnostic that
runs the bodies of those tests in a TEMP sandbox: on the base, the second item
is removed, copied, moved, re-owned, or rewritten after Select-Object -First 1
or a throw; on the candidate it stays. The diagnostic, the check of the result
files, and a read-only check of a published version are in Tests/Lab/Acceptance.
Decision 22 no longer says that Copy-Item2 is like Copy-Item for a folder: the
built-in Copy-Item creates the missing parent folders of a folder copy,
Copy-Item2 of rc6 and of the candidate doesn't. The choice stays, the reference
point is corrected, and the question is left to the maintainer. The migration
hint of item 8 is stated as it is.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Decision 23 separates what the reporters of #34 said from what was tested
(Windows only), names the gaps, and lays out the maintainer's options: wait
for a report on the published candidate, or accept the untested risk with a
release-note caveat. It accepts nothing and keeps the gate open. It also holds
a draft comment for the issue, which the maintainer posts.
The checklist in Tests/Lab tells a storage administrator and a delegated user
how to run the commands of case 1 against a disposable folder on a NetApp, EMC,
or IBM file server, what to report, and what to keep out of the report.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Case 10 of the lab tests checks, over SMB and on the file server, what the
fixes of ai/quality-gate-paths changed: the owner restore, InheritedFrom, a
later command that ends the pipeline or throws (also at the verbose, debug,
and error streams), Get-ChildItem2 -Filter, and the privileges that a
stopped cmdlet left enabled. The fixture adds the folders that the tests
need. 78 tests per edition are new.
The record compares the candidate 83149ee with its base f11ff41 in the lab:
the candidate passed 486 tests and failed none, the base failed 148 of the
same tests, and each of them passes on the candidate. The fixture was removed
and the end state verified independently.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Handoff 1 of the pre-5.0.0 quality gate. The report classifies every
C# method that no test visited at 5a5d58b (231 methods, 442 sequence
points: 223 explained, 8 open for the maintainer) and records the
measurement (3,192/3,634 sequence points, 87.84%), 26 bounded
mutations, a red/green matrix of the fix commits over ten states of
the branch, the open items, the handoff to gate 3, and nine static
review passes. The CSV files hold the method, cmdlet, and parameter
set tables, the guard rows of the matrix with their test files, and
the SHA-256 of each of its 40 logs.
The Memory Bank notes (active context, progress, tech context, system
patterns) and the lab README point to it. No code, test, or help
changes.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
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>
The CI runner sets $ErrorActionPreference to Stop, so three new PipelineControl tests that take the error of a nested folder through 2>&1 ended at the first error before the later command saw it (elevated Windows PowerShell 5.1 of the frozen run: three failures). They now pass -ErrorAction Continue. The focused runner of this work did not set the preference and missed it; it does now.
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>
Get-ChildItem2 swallowed what a later command threw for the error of a nested folder, for example `Get-ChildItem2 -Recurse 2>&1 | ForEach-Object { throw 'x' }`: the recursion took it for a failure of the folder above, wrote a verbose message, and left the loop over the folders, so the listing ended early and the caller never saw the exception. BaseCmdlet now notes the exception that WriteError raises, as it does for WriteObject, WriteVerbose, and WriteDebug. The report that -ErrorAction Stop is swallowed there too was not reproducible (the error reaches the caller), and a break or Select-Object -First was already passed on by type; the tests keep both.
A cmdlet that enables the privileges wrote the debug message "..enabled" before it noted the privilege, so a later command that ended the pipeline or threw at that message left the privilege enabled: Dispose disables only what is noted. TryEnablePrivilege also took the exception of a later command for a failure to enable the privilege and went on with the next one, so `Get-NTFSOwner 5>&1 | ForEach-Object { if ($_.Message -eq '..enabled') { throw 'x' } }` enabled all four privileges and hid the exception. The privilege is noted before the message, and TryEnablePrivilege passes the exception on.
Red before the fix in the Release build of 7aa8315: three tests (the throw on the error stream, in four configurations the first, in the two elevated ones the two privilege tests). Also covers the typed-throw rows that fail if PowerShell stops wrapping a thrown exception, and Enable-Privileges in a script named NTFSSecurity.Init.ps1.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The catch for an UnauthorizedAccessException of Get-ChildItem2 had an exception filter that passes on what a later command raises. A bounded mutation that removed the filter was not detected: PowerShell wraps the exception of a throw, so a cmdlet never sees the type that was thrown, and no later command can raise a raw UnauthorizedAccessException through a Write call. The filter and the test style that was meant to reach it are removed instead of leaving a branch that nothing can enter.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The Write wrappers of BaseCmdlet note what a later command raises, so PipelineControl.IsEnd is reached only for an exception that did not pass through one, and its branches had no test. The tests call it through reflection: for a PipelineStoppedException, for a stand-in type that derives from a class named FlowControlException, as PowerShell keeps the exceptions of break and continue internal, and for failures of an item, also one that has an inner exception that ends the pipeline.
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>
The escape of brackets in the pattern read the filter before the pattern could reject it, so a null filter ended in a NullReferenceException. The parameter now rejects null with a validation error that names it; an empty filter still matches no item.
The cmdlet compares each name that the enumeration returns with the pattern again. The two disagree for *.*: the enumeration returns every item, as Get-ChildItem does, and the comparison drops the names without a dot, files and folders alike. A test pins this and reaches the branch that drops an item; the help says that a dot is an ordinary character.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The restore test of Set-NTFSSecurityDescriptor used a deny entry for the user, which a member of the owner group Administrators does not feel, so the first write succeeded and the ownership retry never ran. A deny entry for OWNER RIGHTS stops that write also for the owner; taking ownership drops the entry, the cmdlet writes the descriptor, and the user sets the group back without the Restore privilege. A probe shows the plain write is denied in that setup in both editions.
Remove-NTFSAccess with -SecurityDescriptor and -AccessType Deny had no test, although the overload for a descriptor adds Synchronize to allow entries only.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The cases for the retry of Set-NTFSSecurityDescriptor covered a descriptor that sets the owner, an owner that did not change, and an owner that the user cannot assign without the Restore privilege. The success path was not tested: the user can set a group of its access token back as the owner, such as Administrators in an elevated session, so the cmdlet restores the owner and reports nothing. The test runs elevated only, because the filtered token of the basic user cannot assign that group.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The cmdlet compares the name of every item that the enumeration returns with the pattern again, and it built that comparison with the wildcard syntax of PowerShell. A bracket then began a character class, so Report[1].txt was returned by the enumeration, which treats a bracket as itself, and dropped by the comparison, and a file with brackets in its name could not be found for its name with -Filter, which Get-ChildItem does. The documentation names only * and ? as wildcards, so a bracket and a backtick now stand for themselves in the comparison.
The regression test fails without the fix in all four configurations. The probe also showed that AlphaFS compares only the long name, so the test for *.htm guards the documented contract and no 8.3 behavior. The page of the cmdlet names the rule and the help file is generated again.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
New guards cover the default AllAccess token handle, token handles that lack the right to query or to adjust privileges, and the finalizer of an abandoned PrivilegeEnabler, which closes the handle that it owns; writes to the root of a drive that subst maps to a sandbox folder, which skip for the restricted token of the basic-user runner that cannot define a drive letter; Get-ChildItem2 -Filter against a match by short name; retargeting a descriptor with Item; the path overloads of the inheritance helpers for a missing path and of the audit rule reader for folders; removing a generic right while another account holds an exact entry; and an empty -ServerName.
Follow-ups of the independent review: dispose the enabler in the finally block before its handle, don't pin that the iterator overloads write only when enumerated, arrange the previous owner of the RestoreOwnerError test, and describe what ObjectApis.Tests.ps1 does.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
A catch for the failures of an item wrapped the write of its object. When a later command ended the pipeline, a break or continue in a script block or Select-Object -First, the exception passed through that catch, which reported it as an error of the item and went on with the next one. Remove-Item2, Copy-Item2, and Move-Item2 with -PassThru then removed, copied, or moved every item although the caller had ended the pipeline, and Set-NTFSOwner and Set-NTFSSecurityDescriptor changed every item. Get-NTFSSecurityDescriptor, Get-NTFSSimpleAccess, and Get-DiskSpace ignored the break, and Get-ChildItem2 ignored it for items below the first folder.
A helper recognizes the end of the pipeline and the control-flow exceptions of PowerShell by their base type, and these catches pass them on. The new table-driven tests run every cmdlet that writes objects: 26 cases for the nine cmdlets fail without the fix in all four configurations and the 64 cases of the others pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>