Record that the branch ai/rc9 ends at 696ac38 (36 commits over d0cb57c, 2,162
cases per configuration, 0 failed), that the last two checks of round 2 found
four Nits in the message formatter and in the reading of a sequence (all fixed
test-first, the last one at its root), that both reviewers closed with no
finding, and that a collection that holds itself still ends the host through
the three flags parameters, as in rc8, which stays a known limit. The lesson
for the next agent: a failing Dispose must never change what was read.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The previous commit made a sequence whose enumerator throws in Dispose after
the second element count as a list, and left every other count to the catch,
which hands the collection to the conversion of PowerShell. A sequence of one
element whose enumerator fails in Dispose therefore got through when that
element was a comma list or an array of two values: PowerShell combined the
numbers bit by bit and the entry was created with a value that nobody named.
The reading is done with an explicit enumerator that is released in its own
guard now, so a failing release changes nothing of what was read, whatever the
number of elements. The catch is back to what it was: a reading that fails
leaves the collection to the conversion, which rejects it with its own error.
Tests, written first: 8 rows failed on the previous commit in all four
configurations and none fails now (a compiled stand-in whose enumerator throws
in Dispose; a sequence of one comma list and of one array of two values, for
AccessType and AppliesTo, named and piped). Two rows pin that a sequence of
one valid value stays valid.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The check of a list for -AccessType and -AppliesTo stops reading a collection
after the second element. The loop then releases the enumerator, and when that
failed the catch handed the collection to the conversion of PowerShell with
isList still false. PowerShell combines the numbers of a list bit by bit, so a
sequence of Allow and Deny whose enumerator throws in Dispose created a Deny
entry, and one of SubfoldersOnly and FilesOnly a scope that nobody named. The
catch keeps what the loop knows: two elements read make a list, which is
rejected like every other list. Only a custom enumerator can do this; the last
review of round 2 found it.
Tests, written first: 4 rows failed on the previous commit in all four
configurations and none fails now (a compiled stand-in whose enumerator throws
in Dispose, for AccessType and AppliesTo, named and piped).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The fallback of the message of a bad parameter value used the full name of the
type. For a generic type that spells out the assembly of every type argument,
so a LINQ iterator whose selector throws gave a message of 330 characters, where
the code before the last commit printed the short form. The fallback uses
Type.ToString now: System.Collections.Generic.List`1[System.Object].
Two rows pin the short form (a generic list of one object whose ToString
throws, for AccessType and InheritanceFlags of Add-NTFSAccess, piped). They
failed on the previous commit in all four configurations and pass now.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The message of the error is built inside the catch block of the conversion, and
Describe formatted the one element of a collection of one element outside of
any guard. An element whose ToString throws (a script method, for example)
ended the command with a terminating ExtendedTypeSystemException instead of
the InvalidParameterValue error for that item, and a piped command stopped at
that object, where the pages say that the error is reported for just that
item. Describe now falls back to the type name whatever the text or the
enumeration of the value does, for the five parameters of the four cmdlets
alike.
Tests, written first: 24 rows failed on the previous build in all four
configurations and none fails now (an array of one object whose ToString
throws, piped for every parameter and named for one parameter of each kind).
New rows also pin what the previous commit claimed but did not test: the
message names the first ten elements of a list and ends with an ellipsis from
the eleventh, and a sequence of 100,000 elements is read only up to its first
elements (a counting sequence, named and piped). A build without the two limits
fails 4 of them and a build with a limit of nine fails 6, in all four
configurations. The comment of the nesting limit now says exactly what it
counts.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Record that the branch ai/rc9 is complete at de67883 (32 commits over d0cb57c,
2,116 cases per configuration, 0 failed), what the two review rounds found,
that the maintainer chose to reject a list for -AccessType and -AppliesTo, and
what is left before the stable pull request (push, pull request, merge, and tag
of rc9 by the maintainer, then the lab acceptance of the published bytes).
Lessons for the next agent: PowerShell combines the numbers of a list for an
enumeration that is not a flags enumeration bit by bit, and code that opens a
one-element collection needs a depth limit.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The pages of -AccessType and -AppliesTo of Add-NTFSAccess, Remove-NTFSAccess,
Add-NTFSAudit, and Remove-NTFSAudit, and the changelog, said that PowerShell
adds up the numbers of a list for an enumeration that is not a flags
enumeration. It combines them bit by bit (a logical or), which is why one of
the values wins silently or a scope that nobody named is selected. The help
file is regenerated from the pages, and the pages and the file are a fixpoint
after a rebuild.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The check that rejects a list for -AccessType and -AppliesTo opened every level
of a collection of one element, so that a collection that holds itself never
returned. It opens at most 32 levels now and counts a deeper nesting as a list,
and it stops reading a collection after the second element, so that a long or
endless sequence is not read to its end.
The message names what the caller wrote: the one element of a collection of one
element (also when that element is a collection itself), the first ten elements
of a longer collection followed by "...", and otherwise the text of the value.
Before, a collection of one misspelled name was named by its type.
Tests, written first: 28 rows failed on the previous build in all four
configurations and none fails now. They cover an array of one misspelled name,
a collection nested 40 levels deep for both parameters of both cmdlets, and an
array that holds an array of one name, which stays valid. The assertion of the
value text expects "The value '<text>'", because the old pattern could also
match the name of a type. The comments say that PowerShell combines the numbers
of a list bit by bit, which is what it does, and not that it adds them.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Say that a list for -AccessType or -AppliesTo lets one of the values win
silently or, for -AppliesTo, selects a scope that nobody named (the sum of
two Allow and Deny numbers is always one of the two), and that every kind of
collection counts. State the third edge case of Get-ChildItem2 -Filter (blanks
at the end of the pattern, also before a final dot), limit the error of
Windows PowerShell 5.1 to an even number of dots at the end, and name the
pattern of a single dot, for which the built-in filter of PowerShell 7
returns every item. The help file is regenerated, and the pages and the file
are a fixpoint after a rebuild.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The rejection of a list for the enumerations that are not flags
enumerations looked only at a string with a comma and at an IList of more
than one element. A collection that is not an IList (a HashSet, a Queue, the
keys of a hashtable, a LINQ sequence), a collection of one element that
holds a comma list (@('Allow,Deny'), such as a JSON array of one string), and
a collection that holds a collection of several elements still reached
LanguagePrimitives.ConvertTo, which added up the numbers: -AppliesTo
$scopes.Keys created a ThisFolderAndFiles entry that nobody named.
ParameterConversion takes the one value out of the argument now: a wrapped
PSObject counts as what it wraps, a collection of one element converts as
its element (also when that element is a collection of one element), and a
collection of several elements or an element with a comma is the list that is
rejected. A dictionary, an empty collection, and a collection that cannot be
enumerated keep going to the conversion, which reports them as before. A
blank value inside a collection of one element is reported as blank.
The tests add the shapes to the bad-value tables of the four cmdlets (32
rows, red before the change and green after it in four configurations) and
pin that a list of one name and a hash set of one name stay valid. The
valid-value matrices of rc8 and this build still differ in the nine list rows
only, and a probe of 50 shapes shows every collection of several values
rejected and every single value accepted, in both editions.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Say in the pages and in the CHANGELOG what a script block, a named $null of
-AppliesTo, a list for -AccessType or -AppliesTo, and a piped value that
PowerShell cannot convert did before 5.0.0 and do now; name the carry-over of
the scope between piped descriptors for the objects that it affected; mark
the descriptor sets that go on after a failing descriptor and the checks of
the object with both parameter groups as breaking, with the hint that a
script that relied on the stop needs -ErrorAction Stop.
Describe where Get-ChildItem2 -Filter differs from the built-in filter of
PowerShell 7 with the shapes that really differ (a pattern that ends in a dot,
an asterisk followed by a dot and question marks) and state the two cases that
are not like Windows PowerShell 5.1: a blank at the end of the pattern is part
of it, and a pattern that ends in two dots is accepted.
The pages and the help file are a fixpoint again after a rebuild.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
-AccessType and -AppliesTo of Add-NTFSAccess, Remove-NTFSAccess,
Add-NTFSAudit (-AppliesTo), and Remove-NTFSAudit (-AppliesTo) take one value,
but PowerShell adds up the numbers of a comma list or an array for an
enumeration, so that -AppliesTo 'ThisFolderOnly,FilesOnly' created a
FilesOnly entry and -AccessType 'Allow,Deny' created a Deny entry, without a
message. The conversion in ParameterConversion now rejects a string with a
comma and a collection of more than one element for an enumeration that is
not a flags enumeration, with the same InvalidParameterValue error as for a
misspelled name (terminating for a named value, one error per object for a
piped one). A single name and a collection of one element convert as before,
and the flags parameters still take a list. The valid-value matrices of both
published rc8 and this build give the same entries in both editions, named,
piped by property name, and splatted, apart from the nine list rows.
The completers of the five parameters compared with WildcardOptions.IgnoreCase
only, so that a word typed in capitals did not complete while the culture of
the thread was Turkish (an I and an i are different letters there, and
THISFOLDERO did not match ThisFolderOnly). They compare culture invariant now,
like the filter of Get-ChildItem2.
The tests pin both, red before the change and green after it in four
configurations, and the headers of the two test files no longer say that the
-SecurityDescriptor sets have no handler per descriptor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Set the prerelease label to rc9 and add 5.0.0-rc8, which the PowerShell
Gallery has had since 2026-10-20, 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>
Regenerate NTFSSecurity.dll-Help.xml from the pages with New-ExternalHelp
and let platyPS wrap the syntax of the four add and remove pages for the
parameters that are Object now. The pages and the file are a fixpoint: after
a rebuild, Update-MarkdownHelp changes no page and New-ExternalHelp changes
no file.
State two sentences more plainly: what a blank value of the other parameter
group does to the four add and remove cmdlets, and where Get-ChildItem2
-Filter differs from the built-in Get-ChildItem in PowerShell 7.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Remove the labels of review findings and the references to probe scripts
and reports that are not in the repository from the comments of the tests
that rc9 adds, and say what each test pins in words that stand alone. Only
comments and the name of one Context in each of two files change; no
assertion does.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
R1-1 (major): the four pages claimed that a blank cell of AppliesTo, AccessType or
AuditFlags, InheritanceFlags, or PropagationFlags is an InvalidParameterValue error
for every shape. That is wrong when the blank, $null, or DBNull value belongs to
the group that -AppliesTo did not select: PowerShell counts a property that is
present, even blank, as offered for choosing the parameter set, the same as a
real value, so AppliesTo together with a blank InheritanceFlags or
PropagationFlags (for example a short CSV row, whose missing trailing columns
Import-Csv fills with $null) is AmbiguousParameterSet, and rc8 created the
entry. The pages now say that a CSV file should have the columns of only one
group, that mixing both groups fails with AmbiguousParameterSet, that a literal
$null named or piped for -AppliesTo itself fails with its own, unrelated,
pre-existing error because -AppliesTo is mandatory, and keep the sentence that a
bad value within the chosen group is InvalidParameterValue.
R1-3 (minor): the pages now also say that a script block is a plain value for
these parameters and is not evaluated once for each piped object the way rc8
evaluated it, so a named script block is a terminating InvalidParameterValue
error.
Neither finding needed a code change; both already read this way. Pinned with
new table-driven tests, over the four cmdlets and their Path and SD parameter
sets, for AppliesTo valid combined with a $null, an empty string, or DBNull
InheritanceFlags or PropagationFlags and the reverse, and for a named script
block of each of the five parameters: the error id the binder already gives,
nothing changed, and, where applicable, the next piped object still processed.
Confirmed unchanged against the pre-R1-2/R1-4 build.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Add the entries for the filter of Get-ChildItem2, the error of
Get-NTFSOrphanedAccess, the position of -SecurityDescriptor of
Set-NTFSSecurityDescriptor, the conversions from FileSecurity and
DirectorySecurity (and what they mean for the cmdlets: Get-Acl objects now
bind), the account list of the RemoveAll methods, the path overloads of the
Add methods, the EnablePrivileges setting, the 17 owner-restore handlers, the
piped values that the add and remove cmdlets could not convert, and the
descriptors of those cmdlets that fail one by one.
Say what the object type of the five parameters costs (a script block is no
longer evaluated per object; an empty value of the other group makes an
object ambiguous) and replace the sentence that said such a value is ignored.
Each statement was checked by a probe against the published rc8 and the build
of this branch, in both editions.
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
had the same flaw as the 13 handlers fixed before: when a later command
of the pipeline throws exactly at the RestoreOwnerError record that
InvokeAsOwner writes after a failed restore of the owner, the catch around
the call reported the exception as the item's own AddAceError or
RemoveAceError and went on with the next path, so the caller never saw it.
The four handlers now check IsFromLaterCommand first, like the others, so
all 17 handlers of item 7 of Decision 27 pass the exception on.
The table of Tests\PipelineControl.Tests.ps1 gets two rows, for
Add-NTFSAccess and Remove-NTFSAccess, which use the scenario of the
inheritance cmdlets (a ChangePermissions deny for the user and an owner
that only the Restore privilege can set back). The two audit cmdlets can't
be pinned this way: Windows lets this elevated session write the SACL
whatever the DACL says, so their action never fails to begin with.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The completers for AccessType, AuditFlags, InheritanceFlags,
PropagationFlags, and AppliesTo returned nothing for a quoted word
(-AccessType "D) or a wildcard word (-AppliesTo *Only, -AccessType
D*), which rc8's built-in enum-parameter completion still offered.
PowerShell auto-closes an unterminated quote before it reaches the
completer, so the word a completer receives for -AccessType "D is
"D", not D.
EnumArgumentCompletion.Complete now strips a leading quote and its
matching trailing quote from the word before matching, matches the
enumeration's names with WildcardPattern(word + "*",
WildcardOptions.IgnoreCase) the way PowerShell's own enum completion
does, and, when the word was quoted, returns each match in the same
quote character (CompletionText 'FilesOnly' for a leading single
quote). An unquoted, non-wildcard word is unaffected: appending "*"
to it only turns an exact prefix match into a prefix-wildcard match.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
A named $null of AccessType, AuditFlags, InheritanceFlags,
PropagationFlags, or AppliesTo (a literal $null, a variable that holds
$null such as a misspelled property, the alias -AccessControlType,
splatting, or a null entry of $PSDefaultParameterValues) silently kept
the parameter's default instead of stopping: Add-NTFSAccess -AccessType
$null created an Allow entry, Add-NTFSAudit -AuditFlags $null audited
Success and Failure, and Remove-NTFSAccess -AccessType $null removed
nothing. Before the five parameters became object (Decision 27, fix
B), the same named $null ended with a terminating ParameterBindingFailed.
Each cmdlet's BeginProcessing now checks, for a value that is null,
whether MyInvocation.BoundParameters has the parameter's name; a piped
property that is null is not bound yet at that point and still keeps
the default, but a value that is already bound and null is reported
the same way as a bad named value: a terminating InvalidParameterValue
error naming the parameter and its valid values. AppliesTo is Mandatory
and a named $null of it was already a terminating
ParameterArgumentValidationErrorNullNotAllowed from the binder itself,
unchanged by this fix.
ParameterConversion gains the message for this case next to the
existing blank-value message, so that the wording is built in one
place.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
In the four -SecurityDescriptor parameter sets of Add-NTFSAccess,
Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit, a change that
throws, such as a Deny entry without rights, ended the cmdlet with a
terminating error, while the -Path parameter sets already wrote an
AddAceError or RemoveAceError for the failing item and went on to the
next one. The two kinds of parameter sets now behave the same way.
Each descriptor's change is wrapped in its own try/catch: a failure
writes a non-terminating AddAceError or RemoveAceError, category
WriteError, that targets the descriptor, and the cmdlet continues
with the next descriptor in the pipeline instead of stopping. -PassThru
stays outside the try, so what a later command raises when the
cmdlet writes its output is never caught there. The existing
TestAuditSection check of the audit cmdlets, and everything else
about a successful change, is unchanged.
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
silently kept the default value of AccessType, AuditFlags,
InheritanceFlags, PropagationFlags, and AppliesTo when a piped object's
property could not be converted to the parameter's type: a misspelled
AccessType such as "Denyy", or a blank CSV cell, created an Allow entry
with no message. For a permissions tool, a typo for Deny that silently
grants access is a security-relevant defect (Decision 27).
The five parameters are now declared as object and converted inside
the cmdlet, the way the PowerShell binder converts a typed parameter
(names in any case, comma-separated flag lists, numbers, enum
instances). A value that cannot be converted, including a blank one,
is now an error named InvalidParameterValue: terminating and
catchable when it is named on the command line, exactly as the binder
reported it before; a non-terminating error targeting that path or
descriptor when it comes from a piped object, which leaves that item
unchanged and lets the pipeline continue with the next one. A $null
value is still not an error and keeps the parameter's default. Tab
completion of the five parameters keeps working through explicit
argument completers. Choosing between the Simple and Complex
parameter sets is unaffected and still rejects AppliesTo together
with InheritanceFlags or PropagationFlags as AmbiguousParameterSet,
for every mix of string and enum values.
The four cmdlet pages document the parameters as Type: Object, list
the valid values, and describe the new error; a sentence that said a
piped AppliesTo typo or blank cell was silently ignored is corrected.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Decision 27 gets the review findings of rc9 and what became of each, with
the known limits that the maintainer is told about. debugging-insights gets
the lessons of the piped optional parameters (the binder skips a value that
it cannot convert, and the price of an object parameter), of a differential
oracle that covers only part of the input, of agents in parallel worktrees
(one shared stash), and of the help generation.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The coverage record of Handoff 5 listed getters of parameters that take
pipeline input by property name and that no test bound that way: -Destination
of Copy-Item2 and Move-Item2, -Account of Set-NTFSOwner, and -Account of
Get-NTFSEffectiveAccess (the getters of AccessType, AuditFlags, and the scope
and flag parameters of the access and audit cmdlets, and -Algorithm of
Get-FileHash2, are bound by tests now). PowerShell calls the getter of such a
parameter once per command when a piped object binds it, so a test has to
pipe an object that has the property.
Each test pipes two objects that name different values and checks that each
item gets its own: two destinations for the copied and the moved file
(Copy-Item2 and Move-Item2, read back with Get-Content and Test-Path), two
owners that only the Restore privilege can assign (Set-NTFSOwner, read back
with Get-Acl; elevated only), and the effective access of Everyone, which an
allow entry grants, and of Guests, which a deny entry takes away
(Get-NTFSEffectiveAccess).
Tests only; no product change.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Get-NTFSEffectiveAccess already falls back to the local authorization
manager for a name of this computer when the remote one refuses the
caller, which covers every account except a user account of the domain:
Windows still answers "Access is denied" for that one case, because the
local manager has no domain credentials to resolve it. The cmdlet
already writes this as a non-terminating GetEffectiveAccessError and
returns no result for the item; nothing in the behavior changes.
Docs\Cmdlets\Get-NTFSEffectiveAccess.md gets one more NOTES paragraph,
in the style of the neighboring "Before 5.0.0..." paragraphs, saying so
and naming the accounts that still get a result (accounts of this
computer, and domain groups such as Everyone or Domain Users). Evidence:
Tests\Lab\Acceptance-2026-10-10-os-matrix.md, "The probe", row "Local
standard user". No code change; Tests\Help.Tests.ps1 (unchanged) already
covers the page's synopsis, parameters, examples, and links.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Set-NTFSSecurityDescriptor declared -SecurityDescriptor at Position = 2,
its only positional parameter; every sibling that takes a security
descriptor or a path takes its first positional parameter at 1. Moved
to Position = 1 for consistency. A positional call with one argument
already bound to it, because it was the only positional parameter in
its only parameter set, so no script that calls the cmdlet positionally
can notice a difference; this is cosmetic, as Decision 27 describes it.
Tests\SecurityDescriptor.Tests.ps1 adds a characterization-turned-pin
test: the declared Position is 1, and Set-NTFSSecurityDescriptor $sd
still works positionally. RED before the change (Position was 2), GREEN
after, in all four configurations.
Docs\Cmdlets\Set-NTFSSecurityDescriptor.md: Position: 1 in the yaml
block of -SecurityDescriptor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Get-NTFSOrphanedAccess wrote a warning ("Could not read item {0}. The
error was: {1}") for a read failure that is not an access denial, while
every sibling (Get-NTFSAccess, Get-NTFSAudit, Get-NTFSEffectiveAccess,
Get-NTFSInheritance, Get-NTFSSecurityDescriptor) writes a non-terminating
error and goes on with the next item. The cmdlet now writes
ReadSecurityError, categorized as OpenError and targeting the path,
through WriteError, then continues, the same pattern
Get-NTFSAccess (GetAccess.cs) already uses for the same kind of failure.
A script that collected this warning with -WarningVariable now has to
read $Error or -ErrorVariable instead.
No existing test pinned the warning (searched for "Could not read item"
and Get-NTFSOrphanedAccess with -WarningVariable across Tests\; none
found), so none needed changing.
NOT DONE: a new test that triggers this exact catch (a read failure that
is not UnauthorizedAccessException) through the public surface. Five
probes (..\probes-wi2\p-orphaned-read-failure*.ps1) tried a sharing
lock, an over-long path, a dangling and a looped junction, and an
alternate data stream; none reached this catch without also failing
GetFileSystemInfo2 first (a different error, ReadFileError) or succeeding
outright, because an elevated session that can read the item at all can
read its ACL as well. The change is still correct and matches the
module's convention; it is defensive code for a condition this harness
cannot construct deterministically.
Docs\Cmdlets\Get-NTFSOrphanedAccess.md documents the new error next to
the existing ReadSecurityError paragraph for the access-denied case.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Get-NTFSAccess writes ReadSecurityError with category WriteError when a
read succeeds only after the cmdlet takes ownership and the restored
owner is irrelevant to that path; Get-NTFSAudit, Get-NTFSEffectiveAccess,
Get-NTFSInheritance, and Get-NTFSSecurityDescriptor already report the
same failure the same way, so this is the module's convention, unlike
Get-NTFSOrphanedAccess, whose own category for the same failure is
ReadError. No script could notice: the behavior does not change. A
characterization test in Tests\ParameterSets.Access.Tests.ps1, next to
the orphaned-access test that already pins the ReadError side of the
same convention, is green at once and runs in all four configurations.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
InvokeAsOwner (and Set-NTFSSecurityDescriptor's WriteChangesAsOwner) take
ownership of an item, run the action, and in a finally restore the
previous owner, writing a non-terminating RestoreOwnerError through
WriteError when that restore fails. WriteError runs the later commands
of the pipeline while it writes, so in the rare case that a later
command throws exactly when it sees that RestoreOwnerError record (for
example `... 2>&1 | ForEach-Object { if ($_.FullyQualifiedErrorId -like
'RestoreOwnerError,*') { throw } else { $_ } }`), that exception used to
leave InvokeAsOwner through the finally and reach the handler around the
call, which reported it as the item's own error (AddAceError,
ReadSecurityError, ModifySdError, and so on) and moved on to the next
path. The caller never saw the exception.
Thirteen handlers now check IsFromLaterCommand as the first statement of
their catch, the same pattern SetSecurityDescriptor.cs already used in
its second handler and GetSecurityDescriptor.cs already used: Add/Clear
and Get-NTFSAccess, Get-NTFSOrphanedAccess, Get-NTFSEffectiveAccess,
Clear-NTFSAudit, Get-FileHash2, the six inheritance cmdlets, and the
owner-retry of Set-NTFSSecurityDescriptor. The four add/remove access and
audit cmdlets have the same flaw and belong to another work item.
Tests\PipelineControl.Tests.ps1 adds one table-driven It, elevated only,
that denies the current user the right a cmdlet's action needs, gives
the item an owner only the Restore privilege can set back (which the
test turns off), and asserts that a later command's exception reaches
the caller, no item-error record was written for it, and the next path
is untouched. Six of the thirteen handlers are pinned this way (the
other seven need the current user to both fail and succeed the same
access check at once, which Windows does not allow; see the comment
above the Describe block and the probes under ..\probes-wi2\ for the
empirical evidence). The four add and remove cmdlets, which have the same
flaw, follow in the next commit of this item.
Get-NTFSInheritance, the one of the thirteen that writes its result in a
finally, writes it after the try instead, so that the object does not go to
the later command that has just thrown.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
An independent review found that platyPS 0.14.2, which generates the
shipped help file from these pages, pairs up every two literal
asterisks within one paragraph as markdown emphasis and drops them,
even inside backticks: the -Filter parameter description and the
matching paragraph of NOTES each had three or four literal asterisks
in one paragraph, so `Get-Help Get-ChildItem2 -Parameter Filter` and
the NOTES section showed `Report. ` instead of `` Report.* `` and
` . ` instead of a plain asterisk pattern, silently losing the
character that the sentence was about.
Both paragraphs are now several short paragraphs, each with at most
one literal asterisk; a pattern that itself needs more than one
asterisk, such as a plain asterisk, a dot, and an asterisk, is spelled
out in words instead, the way the page already did before this
feature. Verified by running `New-ExternalHelp` from platyPS 0.14.2
into a temporary folder (not the repository's help file) and
confirming every pattern renders intact in the generated XML, then
removing the folder.
The same two paragraphs also said PowerShell 7's own `Get-ChildItem
-Filter` returns nothing for any pattern that ends in a dot and
returns every item only for a pattern of an asterisk, a dot, and one
question mark. Measured directly on PowerShell 7.6.7, both overstated
the difference: a trailing dot returns nothing there only when the
character right before the dot is not an asterisk (a star right
before the dot behaves the same as Windows PowerShell 5.1 and as
Get-ChildItem2 there too), and a pattern of an asterisk, a dot, and
two question marks also returns every item, not only one question
mark. Both paragraphs now say exactly that.
Also removed "(Decision 27, item 4)" from both paragraphs and from two
test comments (the maintainer's agent already removed it from the
integration branch; this avoids a conflicting edit), and changed one
sentence of the DESCRIPTION section that said every -Path value must
name a folder: a value that names a file returns that file, as the
PARAMETERS section and NOTES already said and the cmdlet already did.
No test changes: these are documentation wording changes only, and the
page keeps its existing structure (headings, parameter block, NOTES
list). Re-ran Tests\ItemCmdlets.Tests.ps1 (no code changed in this
commit) to confirm the suite is still green: elevated-Desktop
193/0/0, elevated-Core 192/0/1, basic-Desktop 187/0/6, basic-Core
186/0/7 (passed/failed/skipped), three times, identical.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
An independent review found that a very long -Filter pattern (for
example, a run of 120,000 question marks, or 45,000 asterisks) crashes
the whole host process with an uncatchable StackOverflowException: the
matcher recurses roughly once per character of the pattern, and a long
enough pattern exhausts the stack. A pattern this long serves no
purpose that a shorter one could not also serve, and the review
confirmed that patterns of 32,767 characters or fewer, of any shape
(literal, "?" repeated, "*" repeated, "*?" repeated, "*." repeated),
do not come close to exhausting the stack.
Added `[ValidateLength(0, 32767)]` next to the existing
`[ValidateNotNull]` on -Filter, so that a pattern longer than 32,767
characters is rejected at parameter binding, before it ever reaches
the matcher, with the same `ParameterArgumentValidationError` and
`'Filter'` in the message as the existing null rejection; shorter
values, including an empty one, are unaffected.
Tests (Tests\ItemCmdlets.Tests.ps1, "Get-ChildItem2" > "Recursion, type
filters, and depth"): a -Filter of 32,768 characters now gives
ParameterArgumentValidationError, and one of exactly 32,767 characters
("?" repeated, in a folder with one file) returns the right result
without crashing. The crash itself was confirmed in a disposable child
process (not through the shared Pester harness, to avoid taking that
process down too) with the reviewer's probe
(C:\Temp\ntfs-rc9\review-probes-r3\p9-longpattern.ps1 -N 120000 -Kind
qm): the process terminated with a StackOverflowException before this
fix, and after it, the same probe at the exact boundaries (32,767 and
32,768 characters, every pattern kind the probe offers) neither
crashes nor is accepted past the limit.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
An independent review found that the matcher of Get-ChildItem2 -Filter
(commit 69cd8ed) bounded a star in front of a dot token at the FIRST
dot of the name instead of the LAST one (Win32 DOS_STAR). The
differential oracle of 69cd8ed used only 8.3-compliant names, which
have at most one dot, so the first dot and the last dot are always the
same position there and the bug could not show up. With a name that
has two or more dots, such as "archive.tar.gz" or "jquery.min.js", the
star stopped too early and the rest of the pattern could not reach the
final segment of the name, so a pattern such as "*.?", "*.??", or
"*.?.?" wrongly returned nothing for these names, although Windows
PowerShell 5.1 returns them.
What a user sees changing: -Filter patterns of the shape "a star
followed by a dot" (including "*.?", "*.??", "*.???", a star in a
multi-part pattern such as "a*.?" or "x.*.?") now also match names
with two or more dots, the same as Windows PowerShell 5.1, in both
editions. A name with at most one dot is unaffected (that case was
already correct).
Fixed by bounding the star at `name.LastIndexOf('.')` instead of the
first dot at or after the current position, and corrected the two
comments that described the old, narrower rule.
Also added a discriminating row to the existing short-name test
(`-Filter 'LONGFI~1*'` against a plain file named `LongFileName`,
expected to return nothing): the three rows already in that test
happen to give the same result whether the matcher reads the real
name or the short name, so they could not have caught a regression to
short-name matching; this row can.
Tests (Tests\ItemCmdlets.Tests.ps1, "Get-ChildItem2" > "Recursion, type
filters, and depth"): added a 9-row test with 10 names that have two or
three dots (including the realistic ones from the review: archive.tar.gz,
jquery.min.js, my.file.name.txt, v1.2.3, index.d.ts), expected values
pinned from Windows PowerShell 5.1 with `fsutil file setshortname` (and,
for one row, `fsutil 8dot3name strip`) so that a short name the volume
assigns cannot also produce a match; and the LONGFI~1* row above. Both
fail on the build of 69cd8ed and pass after this fix.
The fix was checked with the reviewer's differential harness
(C:\Temp\ntfs-rc9\review-probes-r3\diff5.ps1) against this build: 0
mismatches over two independent fixtures (732,630 real-name/pattern
pairs with names of two or more dots, short names overridden with
`fsutil file setshortname` so that a hit can only come from the real
name), in addition to the 109,308 pairs of 8.3-compliant names checked
for 69cd8ed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
Get-ChildItem2 -Filter now applies a single matcher, in the cmdlet, that
follows the Win32 "DOS" wildcard rules Windows PowerShell 5.1 applies to
Get-ChildItem -Filter, on the real name of the item, in both editions
(Decision 27, item 4, option B). Before this change the pattern was
matched twice: once by the AlphaFS enumeration and once by the cmdlet
with a WildcardPattern that read a dot as an ordinary character, so the
two editions, and Get-ChildItem itself, disagreed on patterns with a
dot. AlphaFS now always enumerates with "*", and the new matcher decides
alone.
What a user sees changing:
- "Report.*" also returns "Report", which has no dot (previously only
names with a dot matched a pattern ending in ".*").
- A pattern that ends in a dot, such as "Report." or "Rep*.", now
returns the names without a dot, instead of nothing.
- "*.*.*" now returns every item, the same as "*.*" and a plain "*",
instead of only names with two literal dots.
- Breaking: an empty -Filter ('') now returns every item, like no
filter at all, instead of nothing. This matches the policy of
Windows PowerShell 5.1 for every other dot pattern; a script that
relied on an empty -Filter returning nothing should pass -Filter '*'
explicitly to keep that behavior from before.
- A single dot ('.') as -Filter also returns every item, matching
Get-ChildItem.
- Get-ChildItem2 deliberately no longer follows PowerShell 7's own
Get-ChildItem -Filter for a pattern that ends in a dot (which there
returns nothing) or for "*.?" (which there returns every item): it
follows the Windows PowerShell 5.1 rules in both editions instead, so
the two editions of Get-ChildItem2 agree with each other.
- Unaffected: -Filter matching by the real name and not the 8.3 short
name, brackets and backticks as ordinary characters, case-insensitive
matching with the invariant culture, the null-Filter error, and every
other parameter and its interaction with -Filter.
The matcher was derived and checked against a differential oracle of
real Windows PowerShell 5.1 Get-ChildItem -Filter runs: two independent
sets of 8.3-compliant names (22 and 19 names) against 54,654 generated
patterns each (alphabet a, b, c, ., *, ? up to length 6; the remaining
1,333 of the 55,987 generated patterns per set are refused by the .NET
enumeration API itself for ending in ".." and are not part of either
product's -Filter semantics), with zero differences in both sets.
Tests (Tests\ItemCmdlets.Tests.ps1, "Get-ChildItem2" > "Recursion, type
filters, and depth"):
- Changed the dot-rules table (pins the new values for "Report.*",
"Report.", "Rep*.", "*.", and an empty -Filter) and the dedicated
"*.*.*" test (now expects every item), both of which pinned the old,
changed-on-purpose behavior.
- Added three rows to the existing short-name-sensitive test, to pin
that the new matcher still reads the real name, not the short name,
for the trailing-dot and "X.*" rule families.
- Added a 36-row truth table (22 fixed 8.3-compliant names, patterns
from every family the WI calls out) with the literal result of
Windows PowerShell 5.1 as static, hand-verified data; the test runs
no Get-ChildItem of its own.
- Added -Recurse with a selective -Filter across three folder levels,
a dot -Filter with -File and -Directory, an empty -Filter, and a
-Filter on a -Path that names a file.
Docs\Cmdlets\Get-ChildItem2.md: restated the -Filter parameter
description and the matching note in NOTES with the Windows PowerShell
5.1 policy and the deliberate PowerShell 7 difference, and removed the
sentences that described the dot rules as a limitation or a mismatch
with Get-ChildItem in Windows PowerShell 5.1.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
PowerShell's parameter binder uses the conversion that 3.1 fixed for
every -SecurityDescriptor parameter, so a descriptor that Get-Acl
returns, never meant for these cmdlets, now binds where it used to be
a binding error. Two more consequences of that surfaced:
With the default module setting GetInheritedFrom ($true), Get-NTFSAccess,
Get-NTFSAudit, Get-NTFSOrphanedAccess, and Get-NTFSOrphanedAudit stopped
with the InvalidOperationException that a previous fix added for a
descriptor without an item; a cmdlet user has no parameter to avoid it.
GetFileSystemAccessRules and GetFileSystemAuditRules now treat
getInheritedFrom as false for such a descriptor instead of throwing: the
entries come back with no source, the same as an entry whose source
Windows cannot name.
The private constructor that wraps a descriptor directly marked every
section, including Audit, as read; Get-Acl never reads Audit unless
-Audit is given. This made HasAuditSection wrongly report true, so the
existing #109 check let Get-NTFSAudit go on with no audit data instead
of reporting that the descriptor has none, and let Get-NTFSInheritance
report AuditInheritanceEnabled as a real value instead of null
(unknown). The constructor now includes Audit in the sections only when
the descriptor's binary form actually carries a SACL, the same test
HasSystemAcl already uses.
Get-NTFSOwner, Get-NTFSSimpleAccess, Get-NTFSOrphanedAccess,
Get-NTFSEffectiveAccess, and Add-/Remove-NTFSAccess with such a
descriptor already worked; Set-NTFSSecurityDescriptor already reported
a clear, non-terminating error. A review finding on the earlier fix is
also addressed: two assertions read a property through reflection
instead of a plain PowerShell property access, which silently turns a
throwing getter into $null without a trace and so could not tell a
getter that throws from one that legitimately returns null.
Every existing behavior for an item that is not null is unchanged.
Public signatures are unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3.1 made a FileSystemSecurity2 without an item reachable from a .NET
caller (the conversions from FileSecurity and DirectorySecurity), but
six sibling call sites still dereferenced the item without checking it
first, so a caller who converted a FileSecurity or DirectorySecurity
and then read its entries, owner, inheritance, or effective access met
a NullReferenceException instead of a usable result.
GetFileSystemAccessRules and GetFileSystemAuditRules (the
FileSystemSecurity2 overload) now give each returned entry the same
null FullName that the descriptor itself reports; asking for the
source of an inherited entry (getInheritedFrom) needs the path of the
item, so it now throws one clear InvalidOperationException instead,
naming the reason, when the descriptor has none. FileSystemOwner and
FileSystemInheritanceInfo give a null FullName (and Name) the same
way; FileSystemOwner.ToString, which must never return null, gives an
empty string instead, since there is no other representation to fall
back to. The FileSystemAccessRule2(rule, item) constructor, which
EffectiveAccess.GetEffectiveAccess uses to build its result, now
accepts a null item the same way, so effective access can be
calculated for such a descriptor and the result names no path.
Every existing behavior for an item that is not null is unchanged;
only a .NET caller that reads such a descriptor can notice the
difference, and only for the better: a usable result or a clear error
instead of an unrelated NullReferenceException. Public signatures are
unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
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 designs of Decision 27 are chosen, and four work items run in worktrees
under C:\Temp\ntfs-rc9 on the branch ai/rc9-before-stable. The inventory of
open decisions in progress.md now points to Decision 27.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The maintainer chose the designs of the open items on 2026-10-21: fix
3.1, 3.2, 3.3, and 3.10 of the library items, the dot rules of
Get-ChildItem2 -Filter (option B), keep WriteError for Get-NTFSAccess and pin
it, an error for Get-NTFSOrphanedAccess, and position 1 for
Set-NTFSSecurityDescriptor. Fix B of the piped optional parameters is revised
before the build: a bad value on the command line stays a terminating error,
a bad piped value is an error per path.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
A probe on the published rc8 found that a piped optional enum parameter
silently ignores a value that PowerShell cannot convert: a typo for Deny
in AccessType creates an Allow entry, AuditFlags, the flags, and AppliesTo
fall back to their defaults, and Get-FileHash2 runs SHA256. Named calls
fail loudly. The maintainer decided on 2026-10-21 that rc9 fixes the
family in the four access and audit cmdlets, changes the items 3 to 7 of
the sheet, and gets an independent review.
Decision 27 holds the probes, the options, the designs that five read-only
research threads proposed, and the recommended defaults. The lab suite run
of the new tests (24 runs on six machine classes, 0 failed) is recorded
as the agent's flagged assumption. activeContext and progress follow.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
The maintainer merged the tests-only round (#129, 2026-10-21 08:24Z) and
the Memory Bank (#130, 08:47Z); master is d0cb57c. The new tests gave the
same counts on the runner in three CI runs (the first PR head, the second
PR head, and master after #129).
The notes no longer list the merge as open. What is open before the
stable pull request is the decision on #34 (2026-11-02), the check of
the PSGALLERY_API_KEY, and the decision on the open items, among them
the 10 rows (7 MaintainerDecision, 3 would-pin) that wait for the
maintainer. last-verified moves to 2026-10-21 in the three files.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
#128 (the rc8 record) merged on 2026-10-21 at 05:48Z, and the tests-only
round and this Memory Bank are #129 and #130 with a green CI. On the
runner the four configurations give the counts of the local run in each
of the 29 test files, so the notes no longer say that the branch is not
pushed or that the runner has not run the tests.
Records that no plain test gap is left and that the 10 rows that need a
decision (7 MaintainerDecision, 3 would-pin) wait for the maintainer.
The status paragraph of progress.md drops the list of the merged pull
requests of 2026-10-10 that the milestones already carry, which keeps
the file within its line budget.
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>