BaseCmdletWithPrivControl.BeginProcessing and EndProcessing read
(bool)privateData["EnablePrivileges"] without checking it first; a
setting that is not a Boolean, such as the string '$true' from a typo
in the module manifest, or a missing key, made every cmdlet derived
from the class fail with an InvalidCastException or a
NullReferenceException before anything useful was said.
Enable-Privileges had its own try/catch for the same cast that raised a
different message (a ParseException), but it was unreachable, because
BeginProcessing always failed first.
A new helper, GetEnablePrivilegesSetting, centralizes the check in the
base class: a setting that is missing or not a Boolean now stops the
cmdlet with one terminating error, id EnablePrivilegesSettingInvalid,
category InvalidData, that names the setting, says it must be $true or
$false, and names the module manifest. BeginProcessing, EndProcessing,
and Enable-Privileges all use it now; the dead try/catch of
Enable-Privileges is removed, so a mistaken setting no longer produces
two different messages depending on which cmdlet hit it first. The
valid settings $true and $false keep today's behavior.
GetInheritedFrom (used by Get-NTFSAccess and Get-NTFSAudit),
GetFileSystemModeProperty, and IdentifyHardLinks (both used by
Get-ChildItem2) read their own setting the same unchecked way and share
this weakness; they are not changed here; see the final report.
Breaking: a script that caught the old InvalidCastException,
NullReferenceException, or ParseException for a malformed
EnablePrivileges setting now needs to catch the terminating error with
id EnablePrivilegesSettingInvalid instead.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
AddFileSystemAccessRule(string, List<IdentityReference2>, ...) and
AddFileSystemAuditRule(string, List<IdentityReference2>, ...) were
iterator methods (yield return), so nothing happened until the caller
enumerated the result: called as a statement, or assigned to a variable
that the caller never reads, they changed nothing. Their siblings, the
overloads for FileSystemInfo and FileSystemSecurity2 with a list, and
the single-account overloads, already built a list and wrote at once.
Both overloads now build the list and add the entries before returning,
like their siblings; a call that is never enumerated now adds the
entries just the same. An exception that adding an entry raises changes
only in timing: it is now thrown by the call itself, not by the later
enumeration of its result. The returned collection still holds one rule
per account, in the order of the account list, for both a file path and
a folder path.
Public signatures are unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Both methods built a LINQ query that filtered the explicit entries down
to the ones whose account was in the given list, but never used the
result: the loop right below removed every explicit entry of the
descriptor regardless of the list, including an empty one. The cmdlets
never pass a list, so only a .NET caller of Security2.dll that does is
affected.
A non-null list now removes only the explicit entries whose account is
one of the listed accounts (compared like the rest of the class does,
by IdentityReference2 equality of the entry's SID); null still removes
every explicit entry, and an empty list now removes none. Inherited
entries are still never touched, as before. Both the FileSystemSecurity2
overload (in-memory only, until a caller writes the descriptor) and the
FileSystemInfo overload (which also writes) are fixed, for access and
audit entries alike.
Public signatures are unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Converting a FileSecurity or DirectorySecurity object to
FileSystemSecurity2 (for example `[Security2.FileSystemSecurity2]
$fileSecurity`) always threw, because the conversion ignored the
descriptor it was given and built the result from an empty path, which
AlphaFS rejects. The conversion now wraps the descriptor that was
passed: SecurityDescriptor is that same instance, IsFile reflects
whether it came from a FileSecurity or a DirectorySecurity, the round
trip back to FileSecurity/DirectorySecurity returns the same instance,
and Equals/GetHashCode keep comparing descriptors by reference.
The resulting object has no item, since it was never read from a file
or folder: FullName and Name are now null instead of throwing a
NullReferenceException, and Write() throws a clear
InvalidOperationException instead of a NullReferenceException deep in
SetSecurity. A caller can still write such an object by setting its
Item property first, or by calling Write(FileSystemInfo) or
Write(string) directly, neither of which depends on an item being set.
No cmdlet reaches this conversion; only a .NET caller of Security2.dll
that converts a FileSecurity/DirectorySecurity it built itself is
affected. Public signatures are unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The CI of #129 ran the new tests on the GitHub runner (windows-2025,
run 39551726767): the four configurations gave the counts of the local
run with no failure, and each of the 29 test files has the same number
of executed and skipped cases as in the local run.
The report said that the runner had not run the tests yet; it now gives
the result and states the one thing that is still open, that the new
cases ran on Windows Server 2025 only. The Evidence CSV lists the log
and the four NUnit files of the run, whose retention on GitHub ends.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Adds the report of the tests-only round of the quality gate: 89 new It
blocks that run as 121 cases per configuration, so that the suite goes
from 1,494 to 1,615 cases per configuration with no failure in Windows
PowerShell 5.1 and PowerShell 7, elevated and as a basic user.
The ParameterSets CSV has the 64 rows with the status before and after:
38 are closed (7 before), 26 have something left (16 Limit, 7
MaintainerDecision, 3 would-pin), and the report names every leftover.
The Evidence CSV lists the files outside git with size and SHA-256.
No product code, cmdlet page, or CI file changes.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Adds 15 It blocks (25 instances, Tests\ParameterSets.Inheritance.Tests.ps1) closing the budget
items of worklist-inheritance.md rows 1-16:
- The documented no-op / current-location behavior without -Path, pinned with Push-Location into
a dedicated sandbox folder, for Disable/Enable-NTFSAccessInheritance, Disable/Enable-
NTFSAuditInheritance, Set-NTFSInheritance, Get-NTFSOwner, Set-NTFSOwner, and
Get-NTFSSecurityDescriptor.
- The OpenError category for a path that does not exist, for Disable/Enable-NTFSAccessInheritance
and Set-NTFSInheritance.
- Owner unchanged after Disable-NTFSAccessInheritance -SecurityDescriptor protects the DACL; an
independent Get-Acl -Audit oracle for Set-NTFSInheritance; the -RemoveInheritedAuditRules /
-RemoveExplicitAuditRules switches on a -SecurityDescriptor with the owner and DACL confirmed
unchanged after the write.
- The Name property and a DACL protected without the module for Get-NTFSInheritance (-Path and
-SecurityDescriptor); Get-NTFSOwner on a descriptor read without its Owner section; the SACL
unchanged after Set-NTFSOwner changes the owner; the DACL unchanged and the -PassThru type for
Set-NTFSOwner -SecurityDescriptor.
- The by-property-name pipeline and the category/target of the -PassThru ReadSecurityError for
Set-NTFSSecurityDescriptor.
Every assertion reads state with an independent .NET oracle (Get-Acl, Get-Acl -Audit) or asserts an
ErrorId/category/target triple; none repeats an existing test. Items marked Limit, would-pin, or
lab in the worklist are out of scope and untouched.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Points the comment at Move-Item2.md's -Force and Notes sections, which
document the overwrite as a replace, instead of only describing the probe
that showed it. No test logic changed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Adds pipeline/positional binding, long-path, bracket/wildcard literal-path,
drive-root, omitted-path-default, and error category/target coverage for
Copy-Item2, Get-ChildItem2, Get-Item2, Move-Item2, Remove-Item2, Test-Path2,
Get-FileHash2, and Get-DiskSpace; positional binding, FileInfo pipeline input,
and share-error category coverage for New-NTFSHardLink and Get-NTFSHardLink;
positional binding and folder LinkType/target coverage for
New-NTFSSymbolicLink; and -PassThru privilege-state coverage for
Enable-Privileges and Disable-Privileges. No product code changed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Add 20 new It blocks (32 executed cases once the 13-scope -ForEach of
Add-NTFSAccess/SDSimple expands) to ParameterSets.Access.Tests.ps1, closing
every REMAINING item that worklist-access.md marks budget for rows 1-13:
- Add-NTFSAccess: several accounts and positional binding (PathComplex); a
Deny entry's exact rights with no auto-added Synchronize (PathComplex and
PathSimple); a denied write and a Deny entry without rights through
PathSimple; all 13 -AppliesTo scopes of a security descriptor read back
with .NET, piped and several (SDSimple); piped and several descriptors
asserted for Add itself, not only as a fixture (SDComplex).
- Remove-NTFSAccess: the -PassThru output's count against the complete
survivor list for several piped paths; several accounts and positional
binding (PathComplex); a Deny entry's rights-reduction without
-RemoveSpecific; a missing and a denied path, piped, several (PathSimple);
rights-reduction without -RemoveSpecific and disk independence until
Set-NTFSSecurityDescriptor, piped and several descriptors (SDSimple); the
on-disk write-through only after Set-NTFSSecurityDescriptor, piped and
several descriptors (SDComplex).
- Get-NTFSAccess: what the cmdlet returns without -Path, asserted on an
actual emitted object's type (Path).
- Get-NTFSOrphanedAccess: the category of ReadSecurityError after a failed
ownership retry (Path); -ExcludeInherited and an entry added to a
descriptor only in memory (SD).
- Get-NTFSSimpleAccess: the category and target of a missing path's
ReadError (Path).
- Get-NTFSEffectiveAccess: one call combining a failing and a succeeding
path (Path).
Items the REASON marks MaintainerDecision, Limit, or would-pin are left
untouched, per the group notes of the brief. No product code changed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Closes the eight remaining 'budget' parameter-set gaps that
Quality-Gate-Unknowns-2026-10-10-ParameterSets.csv lists for the audit
cmdlets, in Tests\ParameterSets.Audit.Tests.ps1 only:
- Add-NTFSAudit PathComplex: binds -Account (several accounts) and
-AuditFlags from a single piped object's property names in one call.
- Remove-NTFSAudit PathComplex: removes for several accounts named in one
call, and leaves the empty Audit SDDL and the DACL unchanged for an item
that has no SACL.
- Remove-NTFSAudit SDSimple and SDComplex: proves the item on disk keeps
its audit section until Set-NTFSSecurityDescriptor writes the change,
and that -PassThru returns FileSystemAuditRule2 objects for both sets.
- Clear-NTFSAudit Path: asserts the cleared state of the succeeding path
that follows a missing one in the same pipeline.
- Get-NTFSAudit SD: asserts the output's type, account, rights, flags, and
inherited state directly, independent of the Path set's output.
- Get-NTFSOrphanedAudit Path: adds the OpenError category of the
ReadSecurityError without the Security privilege (PermissionDenied needs
a remote/lab target and is not in scope), and -ExcludeExplicit.
- Get-NTFSOrphanedAudit SD: adds -Account and -ExcludeInherited/
-ExcludeExplicit filters against a piped security descriptor.
Every new test reads state back with an independent oracle (Get-Acl
-Audit or the .NET SACL-only accessor), and the Set-NTFSSecurityDescriptor
cases confirm the SD cmdlets change the item only in memory. The owner
retry after access denied (Limit) and the open maintainer item on
exceptions from the SD change itself (MaintainerDecision) are left out, as
the worklist directs.
9 new It blocks (10 executed cases; one is split SDSimple/SDComplex by
-ForEach) were verified test-first: a mutated copy with one flipped
expectation per new It failed only those 10 cases in the configurations
where they run, and nothing else; three repeated runs of the real file in
all four configurations (Windows PowerShell 5.1 and PowerShell 7, elevated
and basic user) gave identical, all-green results; and
Invoke-ScriptAnalyzer reports nothing, matching the base commit's file.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The published rc8 passed the identity check (tag, CI run, Gallery hash, GitHub
zip), the three cells of the operating-system matrix (1,374 passed, 0 failed,
12 skipped, every end state CLEAN), the module's own suite on six machine
classes (24 runs, 0 failed, the same skipped tests as the host), and the first
lab with case 9 (245 passed, 0 failed, 1 skipped per edition, CLEAN). The counts
of the live controller equal those of the published rc7, and the live tests and
the controller are the same Git blobs, so the cells measure the changed module.
The record also shows that the boundary of the new GetEffectiveAccessError, a
computer name of 32,718 characters, holds on Windows Server 2019, 2022, and
2025 and on Windows 11 26H1, which backs the wording of the CHANGELOG and the
cmdlet page.
The four tables follow the record of the published rc7. The README names the
record and says that saved machines are restored first, the domain controllers
before the others.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Set the prerelease label to rc8 and add 5.0.0-rc7, which the PowerShell
Gallery has had since 2026-10-10, to the versions that the Gallery has
(Docs/Contributing/05-Releasing.md).
A separate commit, so that it can be dropped if the changes of this branch
go into another prerelease instead.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Get-NTFSEffectiveAccess returned no access and no error when Windows refused
to start the authorization manager for the computer of -ServerName.
Win32.GetEffectiveAccess ignored what the initialization threw (an outer
catch { }), so the result looked like an account without any rights. It
reproduces on Windows Server 2025 in both editions with a name of 32,718
characters or more, which fails with error 14, where a shorter name gets RPC
server unavailable and falls back to this computer.
One try/catch now records every exception, and the cmdlet writes a
GetEffectiveAccessError. The warning that the result is based on this computer
comes only with a result now: it also preceded an error, such as for an
account that Windows can't resolve when the computer can't be reached, which
the cmdlet page and the lab tests contradict.
Two tests failed first in Windows PowerShell 5.1 and PowerShell 7, as an
administrator and as a basic user, and pass in all four configurations. The
cmdlet page, the help file, and the CHANGELOG say what changed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The report of Handoff 5 now tells what came after the final measurement of
61e936e:
- why a piped AppliesTo was dropped for a path (the default parameter set),
the fix (93ed2c7), the defect of that fix that the third independent review
found (the scope of a piped object reached the next one, 9783d2a), and the
corrections of what the documents said about empty and unconvertible
values (7bc7925, 2112ee3);
- three independent reviews and two checks of the third reviewer, with every
finding and its resolution, including the seven Minor findings of the
second check of 7bc7925 and of this report;
- the red and green runs of the tests, among them the runs of the tests of
the CSV route on the build before the fix and on 93ed2c7;
- the measurements of 9783d2a, 7bc7925, and 2112ee3 (1,492 cases per
configuration, 3,266 of 3,668 sequence points, the same 183 unvisited
methods) on the bytes of one build;
- the acceptance of the final package in the labs: the first lab (245, 0, 1
per edition), the unit suite on the three file servers and the client (16
configurations, 1,189 cases each), and the three cells of the matrix lab
(229, 0, 2 per edition), every end state clean; and the first start of the
cells, which did nothing because the command that I had given the
maintainer did not set the module path.
The Runs, Evidence, and ParameterSets tables are regenerated. The lines that
the ParameterSets table cites are mapped to the tests of 6143545.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
A cell of the matrix did nothing when the maintainer started
Run-MatrixSequence.ps1 with Start-Process powershell.exe from a PowerShell 7
session: Windows PowerShell 5.1 inherits the PSModulePath of PowerShell 7,
cannot load Microsoft.PowerShell.Utility from it, the script stops at its
first call of Get-FileHash, and a hidden window leaves no log. The README of
the lab kit now says to start the scripts in a Windows PowerShell window, or
from PowerShell 7 to set the module path to the three WindowsPowerShell
folders first.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The follow-up review of 7bc7925 found that the two rows added with the fix
of the piped AppliesTo (an object with AppliesTo and InheritanceFlags,
piped to Add-NTFSAudit and to Remove-NTFSAudit) said that they change
nothing but compared the DACL, which these cmdlets never write. The older
row of the named call to Add-NTFSAudit had the same title and the same
comparison. A rejected call fails while the parameters bind, before
ProcessRecord, so the error is the behavior under test.
The three rows now say and assert the error only. The rows of the access
cmdlets compare the DACL, which is what those cmdlets write, and stay as
they are. The focused run of the two ParameterSets files on the package of
9783d2a passes in the four configurations (89 cases; 88, 88, 53, 53 passed).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The follow-up review of 9783d2a found that "an empty AppliesTo fails to
bind", in the changelog entry and in the four cmdlet pages, holds only for
$null. A blank cell that Import-Csv reads is an empty string, which the
binder cannot convert: PathSimple does not bind, and the row gets the
default scope, as an object without the property does. An AppliesTo that
is not the name of a scope is ignored the same way (probe
p-empty-appliesto.ps1, both editions, on the package of 9783d2a and on
the baseline). Only $null fails to bind, with
ParameterArgumentValidationErrorNullNotAllowed, for that object.
The pages, the help file, and the changelog entry now say so.
The tests of the CSV route read a CSV file with Import-Csv, the route
that the documentation shows, instead of ConvertFrom-Csv of strings,
which makes a trailing blank cell $null. They pin the blank cell as the
default scope and the $null object as a binding error for that object
only, with the other objects processed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Independent review of 93ed2c7: ProcessRecord of Add-NTFSAccess,
Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit wrote the scope
that it converts from AppliesTo into the fields behind InheritanceFlags
and PropagationFlags. PowerShell resets only the parameters that the
previous piped object bound, so the next object that had no scope, or
only one flag, took the scope of the one before it: AppliesTo
ThisFolderOnly, then none gave None/None twice, and Remove left the
InheritOnly part of every entry. Without the default set (93ed2c7) a
piped path object with AppliesTo reaches that code; for a piped
-SecurityDescriptor object it was a defect of rc7 already.
The flags of a record are locals now. The loop of the two Remove
cmdlets converted the scope a second time into the same fields; the
locals make that redundant, so it is gone.
The tests pipe objects with and without a scope, a CSV file, and
descriptor objects into the four cmdlets: red on the build of 93ed2c7
(8 of 87 cases of the two files, in every elevated configuration; 4 in
the basic-user ones, where the audit rows skip), green on this build.
An empty AppliesTo cell fails to bind for that row only; a test pins it.
The Remove-NTFSAudit PathSimple row cannot tell the sets apart (its
entry is on a file), and its comment says so. Docs/Examples.md no longer
says that a scope is needed so that PowerShell can choose the parameter
set (true before 5.0.0 only); the changelog entry names the new errors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Add-NTFSAccess, Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit
declared PathComplex as the default parameter set. An object that had
Path, Account, and AccessRights satisfied the mandatory parameters of
the default set, so PowerShell never tried PathSimple and the cmdlet
dropped the AppliesTo property of the object. Since 4b1abc9 made
-AppliesTo mandatory in the Simple sets, the default set is redundant
for every call that names its parameters, and it masked PathSimple for
the pipeline.
Without a default set, an object that has AppliesTo binds PathSimple,
one that has InheritanceFlags or PropagationFlags binds PathComplex, and
one that has neither binds PathComplex with the default scope. An
object that has AppliesTo and InheritanceFlags now fails with
AmbiguousParameterSet, like the same two named parameters. Named calls,
-SecurityDescriptor calls without AppliesTo, and the round trip
Get-NTFSAccess | Add-NTFSAccess keep their behavior.
The tests pipe objects of each shape and fail without the change. The
cmdlet pages and the help file follow the build.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The tr-TR test reset the culture in its own finally block, so a cmdlet
that did not restore it, or that wrote an item inside the scope of the
invariant culture, would still pass. The test now reads the culture in
the next pipeline stage for each item and once more after the call.
Both mutations fail it: writing the item inside the scope, and leaving
the scope without restoring the culture.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The maintainer decided on 2026-10-11 that Server Core, ARM64, a localized
operating system, and non-Windows file servers are not needed by the audience
of the tool. They leave the open items, the completion matrix, and the limits
of the report, and Decision 25 records the choice (Decision 23 and #34 are not
changed).
Open item 3 of the report now gives the cause of the dropped AppliesTo for a
path: the default parameter set PathComplex wins when a piped object satisfies
its mandatory parameters. A stand-alone model with the four sets and no default
binds PathSimple for an object with AppliesTo and keeps every named call; the
real cmdlets were not changed. debugging-insights.md keeps the lesson.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
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>