diff --git a/.memory-bank/activeContext.md b/.memory-bank/activeContext.md index 4218a31..c35e545 100644 --- a/.memory-bank/activeContext.md +++ b/.memory-bank/activeContext.md @@ -1,6 +1,6 @@ --- status: current -last-verified: 2026-10-09 +last-verified: 2026-10-10 owner: active-agent source: current task evidence --- @@ -9,27 +9,51 @@ source: current task evidence ## Current focus -Handoff 1 of the quality gate is finished locally on `ai/quality-gate-paths`, -from the reviewed head `f11ff41` of #117: 28 commits, nothing pushed. Both -stacked PRs stay open and green; rc6 remains the latest published -candidate and 4.2.6 the stable Gallery version. Every C# method that no test +The maintainer asked on 2026-10-09 at 21:21 UTC to continue with the release-gate +handoffs and to decide and report later. On 2026-10-10 at 09:10 UTC he merged +#116 (rc7, merge commit `8a6be9f`; rc7 is neither tagged nor published). His +`--delete-branch` also deleted the base branch of #117, so GitHub closed #117 +unmerged; nothing is lost (`ai/quality-gate-coverage` is intact at `f11ff41`, +and the merge into `master` is conflict-free by simulation), and a new pull +request replaces it (Next step 1). Handoff 1 (paths) is draft #118 (`83149ee`, +CI green, base `ai/quality-gate-coverage`; rc6 is the latest published +candidate, 4.2.6 the stable Gallery version). Handoff 2 (operating-system +matrix) is draft #119 (`49734ef`, CI green, base `ai/quality-gate-paths`): the +lab `NtfsSecurityOsMatrixLab`, three fixes of the module in two commits +that the matrix found (`962887a`, `fdd7a8b`), the kit, the controller changes, and the +record `Tests/Lab/Acceptance-2026-10-10-os-matrix.md` (Decision 24, proposed). +The final local candidate `fdd7a8b` passes the module's suite on five operating +systems and the host (24 runs, no failure) and the live controller in three +cells of the matrix (1,374 passed, 0 failed, 12 skipped) and in the first lab, +where case 9 runs (245 passed, 0 failed, 1 skipped per edition). Handoff 3: Decision 22 was confirmed +under the delegation and stays proposed; nothing is published. Handoff 4: +Decision 23 (the #34 dossier); the risk acceptance is the maintainer's. The +agent's decisions of the night are D1 to D46 in +`decisions-night-2026-10-09.md` of the session files. Stable 5.0.0 stays gated. + +The earlier state of handoff 1, from the reviewed head `f11ff41` of #117: 28 +commits, which the maintainer pushed as draft #118. Every C# method that no test visits is classified (223 explained, 8 open for the maintainer), and the other paths have behavior tests. Eleven defects were fixed, ten of them with a regression guard that is red before the fix and green after it (owner restore, `InheritedFrom`, a later command that ends the pipeline or throws, also at the error, verbose, and debug streams, `-Filter` brackets, null, and `*.*`, public object APIs, a privilege left enabled); the leak of a native -buffer has no observable guard. Gate 3 must repeat the affected lab acceptance -before the next candidate is published. Decisions 21/22 and stable 5.0.0 -remain gated; Decision 22 is proposed, not accepted. +buffer has no observable guard. The lab acceptance of those fixes was repeated +on 2026-10-09 (below); the published package still needs its own acceptance +in gate 3. Decisions 21/22 and stable 5.0.0 +remain gated; Decision 22 is proposed: the agent confirmed all ten choices +on 2026-10-09 under the maintainer's delegation, and his own confirmation is +open. ## Evidence - rc6 Release run `37839669028`, attempt 2, succeeded; GitHub prerelease with zip appeared 2026-10-09 07:01:34 UTC. First attempt proves HTTP 409 after Gallery publication, not the previously assumed retry chronology. -- #116 is open, base master, head `d25647d`, CI build/wiki passed. rc7 - publication is pending. The follow-up does not change that PR's head. +- #116 was merged into `master` on 2026-10-10 at 09:10:12Z (merge commit + `8a6be9f`, head `d25647d`); rc7 isn't tagged or published. #117 was closed + unmerged at 09:10:16Z (events `base_ref_deleted`, `closed`). - Local changes: deletion/ownership guards (`a97e46f`); all scopes and inheritance (`e7ee203`); absolute basic-user results (`51dec86`); exact-package publication recovery (`95b827e`); first-hidden-item fix @@ -86,23 +110,85 @@ remain gated; Decision 22 is proposed, not accepted. - Six checkpoints exist but report Standard, even after a successful temporary ProductionOnly probe; policy restored, no restore performed. Do not claim verified Production rollback evidence. +- Lab acceptance of the paths fixes, 20:41 to 21:42 UTC on 2026-10-09: the + candidate `83149ee` and its base `f11ff41` ran the same 244 tests per + edition (78 new, case 10) from their extracted packages. Candidate 486 + passed, 0 failed, 2 expected skips; baseline 338 passed, 148 failed, each + green on the candidate; both editions gave the same counts. Fixture removal + verified by a separate read-only check in four domains and on both file + machines; six checkpoints (Standard type, no restore). Record, results CSV, + and limits: `Tests/Lab/Acceptance-2026-10-09-quality-gate-paths.md`. This + covers the gate-3 handoff table of the path report, except the published + package and the other operating systems. - Wider matrix not deployed: 13 Server 2025 VMs; Windows 11/2019/2022 media present, OS detection cache empty. #34 has no reply since Oct 6. - Full evidence: session artifact `quality-gate-3442194-20261009`; repository report `Tests/Lab/Acceptance-2026-10-09-quality-gate.md`. +- Operating-system matrix, 2026-10-10 (record `Tests/Lab/Acceptance-2026-10-10-os-matrix.md` + with CSV tables): the suite of the final candidate `fdd7a8b` on OSFile19, + OSFile22, OSFile25, OSWin11E, OSWin11, and the host, four configurations each, + zero failures, skipped tests identical to the host's; the baseline `83149ee` + (run on OSFile22 and OSFile25) fails 4 elevated and 20 basic-user tests. Live: run + `rc7l`, three cells, 1,374 passed, 0 failed, 12 skipped. First lab (run `fl1`, + case 9 included, both editions): 245 passed, 0 failed, 1 skipped per edition, + fixture removed and verified clean. The Admin-role + effective-access failures of the earlier cells were not the module: in a replay + (`ab0` to `ab6`) the baseline failed two of three cells and the final candidate + one of three (not counting the warm-up `ab0`), and one model (the remote + authorization managers answer for an account name for about ten minutes after + the account was created again) fits all 43 Admin-role runs of 27 cells; the + Windows mechanism is unknown. The controller now names the account of case 3 + anew for each fixture (`1dec389`); four more cells with it (`ab7` to `ab10`) + passed, two of them where the model predicts a failure for a reused name. + Reviewed by the built-in code-review agent (custom `security-reviewer` + unavailable): approve with Minor, fixed; a second review found one Major + (record accuracy), addressed by the replay; a follow-up review found no + Blocker or Major and five Minors, corrected; a second follow-up review found no + Blocker or Major and four Minors, corrected. The built-in `security-review` + agent (the custom reviewer is still unavailable) found no exploitable + vulnerability in the module changes and two LOW items that are not changed + (Next step 6). A dry run of `Run-MatrixSequence.ps1 -Version 5.0.0-rc6` on + OSFile19 showed that the published-package path works. State of the labs at + 07:50 UTC on 2026-10-10: no fixture and no probe residue in either lab + (`Verify` of the matrix lab 07:45, of the first lab 07:29); the six VMs of the + matrix run, and `OSWin11E` (restarted 07:36) shuts itself down about an hour + after its start. ## Next step -1. The maintainer reviews and integrates `ai/quality-gate-paths` (stacked on - #117; no remote change was made here) and decides the open items listed - in the report: `FileSecurity` conversions, `RemoveAll` account filters, - lazy path overloads, abandoned `PrivilegeEnabler`, dot patterns of - `Get-ChildItem2 -Filter`, the 17 owner-restore handlers without the - later-command check, unused classes (Decisions 21/22). -2. Gate 3: repeat the affected packaged acceptance (the report's handoff - table: owner restore, `InheritedFrom`, later-command exceptions, filter, - privileges, public objects) before the next candidate is published. No - local upload. -3. Retain stacked-PR order (15), obtain Decision 22 review, finish the OS - matrix and obtain or explicitly accept #34 feedback through other gates. -4. Do not release stable 5.0.0 or equate a percentage with gate closure. +1. The maintainer integrates the rest of the stack (Decision 15; commands in + the deployment notes). A **new** pull request from `ai/quality-gate-coverage` + to `master` replaces #117; then #118 and, if its module fixes go into rc7, + #119 are retargeted with `gh pr edit --base master`, marked ready, and + merged. No `--delete-branch` while another open pull request uses the branch + as its base; the head branches are deleted last. He decides which module + fixes of the matrix branch belong to rc7 (two commits: `962887a` holds two + fixes, `fdd7a8b` one) and reviews them (Decision 24). +2. He decides the open items listed in the paths report: `FileSecurity` + conversions, `RemoveAll` account filters, lazy path overloads, abandoned + `PrivilegeEnabler`, dot patterns of `Get-ChildItem2 -Filter`, the 17 + owner-restore handlers without the later-command check, unused classes + (Decisions 21/22). +3. Gate 3: accept the published package in the first lab and in every cell of + the matrix (`Run-MatrixSequence.ps1 -Version`) before the candidate counts as + accepted; no local upload. The paths fixes were accepted locally (record + above). +4. Retain stacked-PR order (15): #116 (merged), the replacement of #117, #118, + then #119; a simulated merge in that order gives exactly the tree of the + matrix branch (`62aa1ae`). Confirm or change Decision 22. +5. #34 stays open (Decision 23): the maintainer chooses between waiting for a + test of the published candidate on the NetApp, EMC, and IBM ESS servers of + the reporters (checklist: `Tests/Lab/Non-Windows-File-Server-Test.md`) and + accepting the untested risk with a release-note caveat. No agent can + accept it. +6. He decides the two LOW findings of the security review (record, Limits): + the swallowed initialization errors of `GetEffectiveAccess` (a false "no + access" instead of an error on an OS that refuses at initialization, and + neither warning nor error when the local fallback fails; fix: record the + initialization exceptions in `authzException`, with a regression test and a + check of the sentence "the error stays" in the help and CHANGELOG), and the + ACL of the stage folders under `C:\` in the lab kit (they inherit + Authenticated Users: Modify; protecting them is a design change of the + controller and needs a new acceptance). The help could also say that a local + standard user who asks about a domain account still gets "Access is denied". +7. Do not release stable 5.0.0 or equate a percentage with gate closure. diff --git a/.memory-bank/decisions/0022-phase-2-behavior-changes.md b/.memory-bank/decisions/0022-phase-2-behavior-changes.md index f59d750..d66e59f 100644 --- a/.memory-bank/decisions/0022-phase-2-behavior-changes.md +++ b/.memory-bank/decisions/0022-phase-2-behavior-changes.md @@ -1,9 +1,9 @@ --- status: proposed date: 2026-10-08 -last-verified: 2026-10-08 +last-verified: 2026-10-09 owner: shared -source: agent choices in autopilot on 2026-10-08, for the maintainer's review +source: agent choices in autopilot on 2026-10-08, for the maintainer's review; confirmed by the agent under his delegation on 2026-10-09 --- # Decision 22: The behavior changes of Phase 2 @@ -30,7 +30,7 @@ source: agent choices in autopilot on 2026-10-08, for the maintainer's review | 7 | The link cmdlets stopped with terminating errors | **Breaking:** a non-terminating error per link, and the next link | | 8 | `-Path` and `-Target` of the link cmdlets were optional | **Breaking:** required. An omitted `-Path` failed with an index error, an omitted `-Target` meant the current location | | 9 | Entries and descriptors are equal only as the same .NET object | Kept the equality of .NET; the FAQ shows `Compare-Object -Property` | -| 10 | `Copy-Item2` doesn't create the missing destination folders (rc6) | Kept, like `Copy-Item` and `Move-Item2` | +| 10 | `Copy-Item2` doesn't create the missing destination folders (rc6) | Kept, like `Move-Item2`, and for a file like `Copy-Item`. Corrected on 2026-10-09: for a folder, `Copy-Item` creates the missing parent folders and `Copy-Item2` doesn't | - Found on the way and fixed: every object piped to the link cmdlets failed with `GetDefaultValueFailed` (item 8). Item 6 is not the cause of @@ -48,6 +48,60 @@ source: agent choices in autopilot on 2026-10-08, for the maintainer's review - Rationale: an error instead of a result that looks valid (1, 2, 6); per-item errors, as in the other cmdlets (7); no silent default for a path that creates something (8); no new features before the archive - (3); the conventions of .NET and PowerShell (4, 9, 10). + (3); the conventions of .NET and PowerShell (4, 9); no implicit creation + of folders (10; `Copy-Item` creates them for a folder, see the correction + below). - Open: the maintainer accepts or reverts each choice; then this record becomes `accepted`. + +## Confirmed under delegation, 2026-10-09 + +- Context: on the evening of 2026-10-09 the maintainer went to bed and told + the agent to continue with the next work and, for any decision that comes + up, to "do it and report about it later". The handoff for this record asks + for one question per item, which nobody could answer overnight. The agent + compared each choice with the changelog and the cmdlet pages, and checked + item 10 against the source and against the built-in `Copy-Item`; it did + not run the tests of the other items again for this record (they ran with + the suite of rc7 and of the later branches). An independent read-only + review checked the statements of the table below against the same pages + and found them accurate except two, which are corrected here (item 10, and + the migration hint of item 8). The agent confirmed all ten choices. This is + the agent's decision under that delegation, not the maintainer's own, so + the status stays `proposed` until he confirms it or reverts an item. + Nothing in the code, the tests, or the help changed. +- Impact for a caller, and where the choice is documented (the changelog + under [Unreleased], and the page of each cmdlet in `Docs\Cmdlets`): + +| # | Impact for a caller | Documented | +| --- | --- | --- | +| 1 | Without the Security privilege, `Get-NTFSOrphanedAudit` writes a non-terminating `ReadSecurityError` per item and goes on; an empty result no longer hides unread items. A script that took empty output for "nothing orphaned" now sees errors | `Get-NTFSOrphanedAudit` page, notes | +| 2 | A recursive `Get-NTFSSimpleAccess` reports the folders that earlier versions left out, with their subfolders; the output can have more rows | `Get-NTFSSimpleAccess` page, notes | +| 3 | None: `New-NTFSSymbolicLink` still needs the right to create symbolic links; Developer Mode doesn't help | `New-NTFSSymbolicLink` page, notes | +| 4 | With `-WhatIf`, a conflict at the destination is a verbose message, so `-WhatIf -ErrorAction Stop` no longer stops on it | `Move-Item2` page, description; the changelog | +| 5 | The warning of `Get-NTFSEffectiveAccess` names the computer; a script that matches the old text must change | the changelog | +| 6 | `Move-Item2` writes a `MoveError` for a folder on another volume and leaves the folder in place; before, AlphaFS copied and deleted it, which lost empty folders | `Move-Item2` page, notes; the changelog | +| 7 | **Breaking:** the link cmdlets write a non-terminating error per link and go on; a script that relies on the stop needs `-ErrorAction Stop` | both link pages, notes; the changelog, **Breaking** | +| 8 | **Breaking:** `-Path` and `-Target` are required; a script that omitted one must pass it | both link pages, notes; the changelog, **Breaking** | +| 9 | None: entries and descriptors are equal only as the same .NET object, as in .NET; `Compare-Object -Property` compares values | `Docs\FAQ.md` | +| 10 | Only against the earlier 5.0.0 prereleases, which created the missing parent folders of a folder copy: `Copy-Item2` writes an error that names the missing folder, as `Move-Item2` does. It differs from `Copy-Item`, which creates the missing parents of a folder copy (checked in both editions on 2026-10-09; for a file, `Copy-Item` writes an error as well). A script that relied on the prerelease behavior creates the folder first. Published rc6 and the candidate both write a `CopyError` and create nothing (checked in both editions on 2026-10-09) | `Copy-Item2` page; the changelog | + +- Why all ten stand: 5.0.0 is a major version, so documented breaking + changes are allowed (7 has a **Breaking:** entry with a migration hint, + `-ErrorAction Stop`; the **Breaking:** entry of 8 names the old behavior, + and the migration is to pass both parameters); 1, 2, and 6 replace a + result that looked valid with an error or a complete result; 3, 4, and 9 + follow the conventions of .NET and PowerShell and add no feature before + the archive; 5 is a clearer message. Item 10 adds no feature either, but + its reference point was wrong: it isn't like `Copy-Item` for a folder. + The stricter behavior is the safer default and matches `Move-Item2`, and + creating missing parents would flip the behavior of rc6 and rc7 again, so + the agent keeps it and leaves the question, whether `Copy-Item2` should + create the missing parents of a folder copy like `Copy-Item`, to the + maintainer. Reverting item 8 would bring back the failure for every object + piped to the link cmdlets (found on the way, above). +- To revert an item: revert its commit (the range in Context), regenerate + the help from `Docs`, adjust the changelog and the cmdlet page, and run + the four test configurations again; a later commit on the same page or + test can conflict. +- Open: the maintainer confirms (`accepted`) or reverts each item. diff --git a/.memory-bank/decisions/0023-non-windows-file-servers.md b/.memory-bank/decisions/0023-non-windows-file-servers.md new file mode 100644 index 0000000..71bfa2c --- /dev/null +++ b/.memory-bank/decisions/0023-non-windows-file-servers.md @@ -0,0 +1,74 @@ +--- +status: proposed +date: 2026-10-09 +last-verified: 2026-10-09 +owner: shared +source: agent assessment for the maintainer (Handoff 4), from #34 read on 2026-10-09 21:33 UTC +--- + +# Decision 23: Non-Windows file servers before 5.0.0 (#34) + +- Context: Decision 21 leaves to the maintainer how to cover file servers that + aren't Windows (#34). The issue is open (labels Bug and Help Wanted, 26 + comments, last activity 2026-10-06 16:05 UTC). This record separates what + the reporters said from what we tested, and states what the maintainer has + to decide. It accepts no risk: the gate stays open until a tester reports + on the exact candidate or the maintainer accepts the risk in his own words. +- Reporter evidence (text of the issue and its comments, treated as data): + +| Date | Who | System and claim | +| --- | --- | --- | +| 2018-08 | deftleft | NetApp Clustered Data ONTAP 9.3P6, Windows 10 1607 and 1709: error 1307 from `Add-NTFSAccess` only on the UNC path of the filer, not on a local drive; the folder is owned by `BUILTIN\Administrators`, the account is a member; `icacls` works | +| 2018-09, 2019-01 | dt1ll0ts0n, FrisbeeGolfer | 1307 on UNC paths; Windows Server 2012 R2 file servers with DFS (a Windows server); builds from 4.0 fail, 3.2.3 works; service account has Full Control and isn't an administrator | +| 2019-10, 2020-01 | Bi00, Marc408 | NetApp behind DFS; it works when the running account owns the folder | +| 2020-01, 2023-11 | jcardel | EMC filer: the owner can be set only through a share that impersonates root; other permissions work over SMB; the owner entry "Owner Rights" is his workaround | +| 2023-05 | tberta | EMC NAS, 4.2.6, no administrator rights on the NAS: 1307; Process Monitor shows the owner written in addition to the DACL, while `icacls` writes only the DACL | +| 2023-11-28 | maintainer | reproduced with a customer: the user isn't a local administrator and lacks the backup and restore privileges | +| 2026-10-05, -06 | maintainer | the fix writes only the changed section (Decision 19); rc3 announced as published on 2026-10-06 13:40 UTC; asked for a tester on NetApp or EMC | +| 2026-10-06 16:05 | jcardel | moving to IBM ESS (UNIX), owner issue "still the same" there; will test both systems and report "tomorrow" | + + No reply followed by 2026-10-09 21:33 UTC. Silence is not success. The + 2020 comments of Kluk and agonzalezm describe other causes (a script that + wasn't run as administrator; a name that can't be translated). +- What we tested: only Windows. The lab comparison of 2026-10-07 reproduced + the error over SMB with rc2 and showed rc4 passing; every candidate since, + including `83149ee` on 2026-10-09, passes case 1 (a delegated account that + doesn't own the folder: add, remove, clear, disable and enable inheritance, + set inheritance, and security descriptor, with the owner kept) in both + editions on Windows Server 2025. CI reproduces the error without a file + server. The matrix of Decision 21 adds Server 2019, 2022, and Windows 11. +- What it doesn't show: whether NetApp ONTAP, Dell EMC, or IBM ESS accepts a + write of the DACL alone from an account that isn't their administrator, and + what else they refuse. Hypotheses, not facts: they may refuse a flag that + the cmdlets set (for example the protected-DACL flag when the inheritance + changes), or map the ACL differently. `Set-NTFSOwner` can't work where the + server doesn't allow assigning an owner; that is a server policy. +- Options for the maintainer: + 1. Wait for a report on the exact candidate (rc7 once published) from both + reporters. Safest; the stable release waits for an unknown time. + 2. Accept the untested risk, and release with the caveat below. The + decision is security-relevant, so only the maintainer can take it; it + doesn't go through a "not sure, you pick" answer. + 3. Both: publish rc7, ask for tests with the checklist, and choose a date + after which you decide between 1 and 2. +- Recommendation: option 3. Nothing in the module changes for #34 meanwhile. +- Caveat for the release notes, if the risk is accepted (adjust the list of + operating systems to what the matrix has tested): "The fix for #34, which + writes only the section of the security descriptor that a command changes, + was tested on Windows file servers. NetApp, EMC, and IBM ESS file servers + weren't available for 5.0.0. If a command still fails there with error + 1307, tell us in #34. `icacls` is the fallback." +- Open: the maintainer's choice between the options, and the date. Until + then the gate of Decision 21 for #34 is open. +- Tester checklist: `Tests/Lab/Non-Windows-File-Server-Test.md`. It uses a + new folder that the tester controls and asks for sanitized evidence. +- Draft comment for #34 (for the maintainer to post; the agent posts + nothing; replace the version and the link when they exist): + +```text +Thanks again for offering to test, @jcardel, and thanks @tberta for the Process Monitor capture that showed the owner write. Since 5.0.0-rc3, we have published more prereleases. The one to test is 5.0.0-rc7, because it is the candidate that becomes 5.0.0. Please test it on both systems you mentioned, with an account that is not an administrator or root of the file server. + +Use a new folder that you create for the test, never real data, a share root, or a home folder. The steps take about 20 minutes: . Please report the package version, the file server product and version, and for each command whether it worked, the first line of any error, and the owner before and after. Please don't post passwords, keys, file contents, or complete security descriptors, and replace names with placeholders. + +A report of "it works" without these details can't tell us which command ran on which setup. If something fails, that is just as useful: the error and the owner before and after show what the file server refuses. We would like to have your result before 5.0.0. NTFSSecurity will be archived after 5.0.0. +``` diff --git a/.memory-bank/decisions/0024-os-matrix-lab.md b/.memory-bank/decisions/0024-os-matrix-lab.md new file mode 100644 index 0000000..c36e8ab --- /dev/null +++ b/.memory-bank/decisions/0024-os-matrix-lab.md @@ -0,0 +1,150 @@ +--- +status: proposed +date: 2026-10-09 +last-verified: 2026-10-10 +owner: shared +source: agent decisions under the maintainer's delegation of 2026-10-09 (Handoff 2); the scope follows Decision 21, phase 3 +--- + +# Decision 24: The operating-system matrix lab + +- Context: Decision 21 requires the live tests on more operating systems, + "such as a Windows 11 client and Server 2019 and 2022 file servers", and the + published package must pass them. Every machine of `WindowsAccessControlLab` + is Server 2025. Handoff 2 asks for the maintainer's approval of scope and + topology before new VMs. On 2026-10-09 at 21:21 UTC the maintainer, going to + bed, wrote "you can do whatever is required with the lab" and told the agent + to decide and report later. The agent took that as the approval for the + minimal matrix below and for nothing broader. It is the agent's decision, so + the status stays `proposed` until the maintainer confirms it. +- Choice: + 1. Cells: a Windows 11 client with each file server (Server 2019 Datacenter + 10.0.17763.1217, Server 2022 Datacenter 10.0.20348.4773, Server 2025 + Datacenter 10.0.26100.32690), the module in Windows PowerShell 5.1 and + PowerShell 7 in every cell. The client was planned as Windows 11 Pro + 26H1 (10.0.28000.1836). It cannot keep a secure channel to the Server 2025 + domain controller (see "What the deployment showed"), so the domain client + is `OSWin11E`, Windows 11 Enterprise Evaluation 22H2 (10.0.22621.525), and + the 26H1 machine `OSWin11` stays in the lab outside the domain for runs of + the module's own tests. The reference cell of Decision 20 (Server 2025 + client and file server in `WindowsAccessControlLab`) stays as it is. + Server 2019 and 2022 as clients are extra cells, to run the module on the + older .NET Framework builds (Server 2019 has 4.7.2). + 2. Topology: a separate AutomatedLab lab `NtfsSecurityOsMatrixLab` with its + own internal switch (`192.168.12.0/24`) and its own forest `osmatrix.net`: + `OSDC1` (Server 2025, root domain controller), `OSFile19`, `OSFile22`, + `OSFile25`, `OSWin11E`, and `OSWin11`. `Deploy-OsMatrixLab.ps1` deploys it + with the maintainer's AutomatedLab and the VM path `V:\AutomatedLab-VMs`; + `Add-OsMatrixMachine.ps1` adds a machine to the deployed lab; the + payloads of the existing lab (PowerShell 7.6.3 and Pester 5.7.1 from the + host, because the VMs have no internet) come from + `Complete-OsMatrixLab.ps1`. + 3. Case 9 (accounts of other domains and forests) needs trusts to the + forests of the existing lab, so the matrix cells run with + `-ForeignDomainController @()`; the existing lab keeps that case. The final + candidate ran it there (run `fl1`: 245 passed, 0 failed, 1 skipped per + edition, 16 case-9 tests per edition). + 4. The controller of the repository runs in every cell with `-LabName`, + `-DomainController`, `-FileServer`, and `-Client`. The matrix showed three + defects of its setup and removal, fixed in `7d47316`: a recursive delete + fails with "The directory is not empty" on Windows Server 2019 (and the + stderr line ended the script before any retry, because `2>&1` under `Stop` + is terminating in Windows PowerShell 5.1), `Get-LocalGroupMember` fails on + an orphaned SID, and a vanished profile failed the client cleanup. + 5. The module's own behavior tests run on every machine as well + (`Run-MatrixLocalSuite.ps1`), elevated and as a basic user, in both + editions, as scheduled tasks so that the token matches a CI runner. This + found three defects of the module, fixed in two commits on + `ai/quality-gate-lab-matrix`: `Get-NTFSInheritance -SecurityDescriptor` + and `Get-NTFSEffectiveAccess -ServerName ''` (`962887a`, two fixes), and + `Get-NTFSEffectiveAccess` for a user who isn't an administrator on a + computer in a domain (`fdd7a8b`, with a live test for the ServerAdmin + role). The maintainer decides which of them belong to rc7. +- Why a separate lab: AutomatedLab 5.61 refuses to add machines to an + imported lab, and defining a lab under an existing name would overwrite the + metadata of its 13 machines. A separate lab leaves every shared machine, + switch, domain, and account untouched, which Handoff 2 requires. Inside the + new lab, a machine can be added with `Import-LabDefinition`, + `Add-LabMachineDefinition`, and `Export-LabDefinition`, followed by the steps + that `Install-Lab` runs for one machine; `Add-OsMatrixMachine.ps1` does this + after it copies the lab metadata. +- Cost and rollback: six VMs (4 GB for the domain controller and both clients, + 3 GB for each file server), four new base images, measured at 17.8 GB for + the differencing disks and 42.4 GB for the base images (about 60 GB on `V:`; + my first estimate of 100 GB was too high). The deployment added twelve lines + to the hosts file of the host, which `Remove-Lab` removes. To remove the + matrix, run `Remove-Lab -Name NtfsSecurityOsMatrixLab` from AutomatedLab; + nothing else depends on it. No existing machine, checkpoint, or lab was + changed. A copy of the lab metadata from before the sixth machine is in + `C:\ProgramData\AutomatedLab\Backups` (administrators only). +- What the deployment showed (the agent's decisions D11 to D23 of the night + log, each reversible): + - The base image of a Server 2019 or a Windows 11 22H2 machine had an empty + EFI system partition: the `bcdboot` of the Server 2025 host fails with + exit code 193 on their boot files, and AutomatedLab ignores the exit code, + so the generation 2 machine fails with Hyper-V event 18603. The images of + Server 2022 and Windows 11 26H1 are fine. `Repair-OsMatrixBoot.ps1` runs + the `bcdboot` of the image itself on the differencing disk of the one + machine and starts it. + - The AutomatedLab driver sat in its file-server job wait with idle remote + runspaces after all features were installed, so I stopped it and ran the + rest by script. + - Windows 11 26H1 (10.0.28000.1836) joins the domain but cannot keep the + Netlogon secure channel to the Server 2025 domain controller + (10.0.26100.32690). The client calls `NetrLogonGetCapabilities` with query + level 2, which the protocol document describes as a check of the flags the + client sent; the controller answers `STATUS_ACCESS_DENIED` (level 1 and + `NetrServerAuthenticate` succeed), and the client denies the channel + (`NlConfirmRequestedCapabilities: denying access ... 0xc0000022`). + `Test-ComputerSecureChannel -Repair` can't help. Windows 11 22H2 against the + same controller works (secure channel, Kerberos, readiness). This is an + environment finding about two Microsoft builds, not about NTFSSecurity; the + maintainer may want to know it for his own labs. + - The Windows 11 Enterprise Evaluation 22H2 image (`OSWin11E`) is in + notification mode from its first day and shuts down an hour after every + start (`wlms.exe`, 0xC004F009 "grace time expired"): its install time was + recorded on a clock about seven hours ahead, which was then corrected. One + of its two rearms didn't help. After an unplanned shutdown its machine + account password no longer matched (domain logons fail with 0xC000018D, + `nltest /sc_verify` says `ERROR_INVALID_PASSWORD`); + `Test-ComputerSecureChannel -Repair` with the lab account fixed it, and + `/sc_verify` kept showing the old status afterwards, so test a domain + session instead. A run on this machine has to stay under an hour from its + start. + - A profile of an account that a probe's scheduled task used stayed loaded on + one server until it restarted; the probe now uses a new account name for + every run. + - The matrix cells of the live controller failed in the effective-access tests + of the Admin role in the Windows Server 2022 cell (`rc7f`, `rc7h`, `rc7i`, + and `rc7j`) in cells that followed each other, where the fixture was removed + after a cell and created again with the same account names. This looked like + a regression of the module (the audit read of `962887a` was the first suspect) + until a replay of the same cells with the baseline and the final candidate + alternating (`ab0` to `ab6`) failed the baseline in two of three cells and + the final candidate in one of three (not counting the warm-up `ab0`). In a + failing cell the remote + authorization managers (the client's and the file server's) returned no + groups for the current account while the Kerberos S4U logon, the name + resolution, and the local manager were right in the same second. One model, + in which a remote manager answers for an account name for about ten minutes + after its first request, fits all 43 Admin-role runs of 27 cells; the + predictions that I wrote down before three of the replay cells held (the + weakest is `ab6`, 10.5 minutes after its entry, above the lifetimes that + fit). The mechanism in Windows isn't known. The controller now gives a new + fixture a new name for the account of case 3 (`1dec389`); four more cells + with it (`ab7` to `ab10`) passed, two of them at positions where the model + predicts a failure for a reused name. The record has the evidence. +- Result: [the record](../../Tests/Lab/Acceptance-2026-10-10-os-matrix.md). The + final candidate (`fdd7a8b`) passes the module's suite on all five machines + and the host in all four configurations, and the live cells (see the record). +- Open: the maintainer confirms or changes the matrix and decides whether to + keep the VMs after 5.0.0. Local `-ModulePath` runs are validation; the gate + needs the published package in every cell (Handoff 3, stage D). The newest + Windows 11 build that can join a Server 2025 domain here is 22H2; a domain + cell with 26H1 needs a newer domain controller build or a fix of the + mismatch. The maintainer also decides which of the module fixes belong to + rc7 (two commits: `962887a` holds two fixes, `fdd7a8b` one; `fdd7a8b` reverts + cleanly on its own, `962887a` conflicts with it in `Lib.cs` and `CHANGELOG.md` + if `fdd7a8b` stays), and whether the + evaluation client stays (it needs a start shortly before every run) or is + replaced by a client with a license that doesn't expire. diff --git a/.memory-bank/deployment-notes.md b/.memory-bank/deployment-notes.md new file mode 100644 index 0000000..681e479 --- /dev/null +++ b/.memory-bank/deployment-notes.md @@ -0,0 +1,171 @@ +--- +status: current +last-verified: 2026-10-10 +owner: software-engineer +source: release gates of 5.0.0 (lab acceptance, OS matrix, publication plan), repository evidence +--- + +# Deployment notes + +## Publish the next prerelease (rc7) + +State on 2026-10-10 at 09:59 UTC: #116 (rc7, head `d25647d`) is merged into +`master` (merge commit `8a6be9f`, 09:10:12Z). #117 (head `f11ff41`, base +`ai/release-5.0.0-rc7`) was **closed without a merge** at 09:10:16Z: the +`--delete-branch` of `gh pr merge 116` deleted its base branch, and GitHub +closed it (events `base_ref_deleted`, then `closed`) instead of retargeting it. +Nothing is lost: `ai/quality-gate-coverage` is intact at `f11ff41`, and +`master` still lacks its change (25 files). #118 (draft, head `83149ee`, base +`ai/quality-gate-coverage`) and #119 (draft, head `49734ef`, base +`ai/quality-gate-paths`) are open and green. A simulated merge chain +(`git merge-tree --write-tree`, no ref written) with merge commits +(Decision 15) is conflict-free at every step: the coverage branch into `master` +gives the tree of `f11ff41`, #118 then gives `b1dc006`, and #119 gives +`62aa1ae`, the tree of the matrix branch. The manifest says `5.0.0` with +`Prerelease = 'rc7'`, and `$publishedVersions` in `Tests/Repository.Tests.ps1` +lists the versions up to rc6, as it must before rc7 is published. + +The branch `ai/quality-gate-lab-matrix` (draft #119) is stacked on #118. It +holds three fixes of the module in two commits (`962887a` has two, `fdd7a8b` +one). `fdd7a8b` reverts cleanly on its own; `962887a` doesn't +revert while `fdd7a8b` stays (the two conflict in `Security2/Win32/Lib.cs` and +`CHANGELOG.md`), and its two fixes go together. The branch also holds the kit of the +operating-system matrix, the changes of the live controller, and the record +(Decision 24). rc7 contains the module fixes only if the branch is merged after +#118 and before the tag; otherwise they go to the next prerelease. The +maintainer decides. + +Do not delete a head branch while another open pull request uses it as its +base. On 2026-10-10 the deletion through `gh pr merge --delete-branch` closed +#117 instead of retargeting it. The open reports `cli/cli#1168` and +`cli/cli#14223` show the same two events and say that GitHub retargets only +when the branch is deleted with the button on the pull request page. The +latter also reports, and this was not tried here, that `gh pr edit --base` +refuses a closed pull request and that `gh pr reopen` refuses while the base +branch is missing. A new pull request from the same head is the repair. + +1. Open a new pull request from `ai/quality-gate-coverage` to `master` (it + replaces #117, same head and title), wait for its CI, and merge it with + **Create a merge commit**. Do not delete the branch yet. +2. Retarget #118 (`gh pr edit 118 --base master`), mark it ready, and merge it + the same way. Then do the same for #119 if its module fixes go into rc7. + A retarget doesn't start CI again (`pull_request` in `ci.yml` has the + default event types), and the merge result is the tree that CI tested. +3. Delete the head branches only after the last pull request that uses one as + its base is merged or retargeted. +4. Tag the merge commit on `master` with `5.0.0-rc7` and push the tag. The + `release` job checks the tag against the manifest and builds nothing new: + it publishes the package that the `build` job tested. Approve the + deployment of the `powershell-gallery` environment if it asks. +5. After the publication, add `5.0.0-rc7` to `$publishedVersions` with the + next change that goes to `master`. + +## Accept a published package + +Local `-ModulePath` runs are validation; the gate needs the published bytes. + +1. `Tests/Lab/Acceptance/Test-PublishedRelease.ps1 -Version + -OutputPath ` (read-only): tag and commit on `master`, the CI run + of the tag, the Gallery's SHA-512 against the downloaded nupkg (ordinal, + case-sensitive base64), the nupkg against the GitHub zip file by file, and + the identity of the manifest. Dry run on rc6: all checks passed. +2. `Tests/Lab/Invoke-NTFSSecurityLabTest.ps1 -Version ` in the + existing lab, both editions, and `Tests/Lab/Acceptance/Run-MatrixSequence.ps1 + -Version ` for each cell of the matrix (Decision 24; pass the file + servers as one quoted string, `-FileServer 'OSFile19,OSFile22,OSFile25'`, and + start `OSWin11E` shortly before, because its license period ends an hour + after each start). Check every role from the result files with + `Validate-LabResults.ps1`, never from the marker `DONE` of the controller. + Dry run of the `-Version` path of the sequence runner with the published rc6 + on OSFile19 (Desktop): the mechanics work, the failing tests are the newer + ones that rc6 predates, and the cleanup verdict was CLEAN. The first lab ran + the final local candidate through the same stages (readiness, controller, + `Validate-LabResults.ps1`, snapshot, `-RemoveFixture`, `Test-MatrixCleanup.ps1` + with the four domain controllers and both machines) in one detached driver. +3. Remove the fixture and check the end state independently with + `Test-MatrixCleanup.ps1`, which takes the lab name and the machine names + (`-LabName WindowsAccessControlLab -DomainController F1ADC1, F1BDC1, F2DC1, + F3DC1 -Machine F1AFile1, F1AFile2` for the existing lab). +4. If the published binary changes, repeat the cells; never combine runs of + different binaries into one matrix. + +## Lab lessons + +- AutomatedLab 5.61 can't add machines to an imported lab (`Add-LabMachineDefinition` + throws "Lab is already imported"), and `New-LabDefinition` under an existing + name overwrites its metadata. New machines go into a new lab with its own + switch and domain, and `-LabName` of the controller selects it. +- `Install-Lab -NetworkSwitches -BaseImages` creates the switch and the base + images first; the base images of Server 2019, Server 2022, and Windows 11 Pro + took about two to four minutes each from the ISO files. AutomatedLab + adds records to the hosts file of the host, which `Remove-Lab` removes. +- The VMs have no internet: take PowerShell 7 and Pester 5.7.1 from the host + (`Copy-LabFileItem`, `Install-LabSoftwarePackage`). +- AutomatedLab ignores the exit code of `bcdboot` when it builds a base image. + The Server 2019 image that it built on this Server 2025 host got an empty + EFI system partition: the `bcdboot` of the host fails with exit code 193, + "Failure when attempting to copy boot files", on the 2019 boot files, and the + VM failed to boot (Hyper-V event 18603, "failed to boot an operating + system"; no memory demand, no IP, heartbeat `NoContact`). Check the EFI + system partition of a new base image before the first VM: mount the image + read-only (`Mount-DiskImage -Access ReadOnly`) and look for + `EFI\Microsoft\Boot\bootmgfw.efi` (the images of Server 2022 and Windows 11 + Pro had 140 and 149 files). Repair a VM, not the base: stop the VM, mount its + own differencing disk, run the `bcdboot.exe` of the image (`D:\Windows\System32\bcdboot.exe + D:\Windows /s H: /f UEFI`), copy `bootmgfw.efi` to `EFI\Boot\bootx64.efi`, + dismount, and start the VM. A changed base image would invalidate its + differencing disks. +- The tool output of the agent masks text that looks like a secret, such as + `-Password $password`, in what it shows. Test such a line by parsing the + file, and don't repair it from the displayed text. +- A script that a detached process runs needs its own log, an exit marker, and + an end-state check of its own; verify cleanup from the end state, not from + its marker. +- Extend a deployed lab with one machine like this: in a process that never ran + `Import-Lab`, call `Import-LabDefinition`, `Add-LabMachineDefinition`, and + `Export-LabDefinition`, then `New-LabBaseImages` and `New-LabVM -Name `. + `Install-Lab` has no per-machine selector, and `Add-LabMachineDefinition` + throws as soon as `Get-Lab` returns a lab. `Add-OsMatrixMachine.ps1` does it + after it copies the lab metadata to `C:\ProgramData\AutomatedLab\Backups`. +- Windows 11 22H2 (10.0.22621) has the empty EFI system partition problem too + (`bcdboot` exit code 193 on the host); `Repair-OsMatrixBoot.ps1` repairs it. + After `Mount-VHD` the host gives the NTFS partition a letter on its own; don't + assign a second one. +- Run AutomatedLab processes one after the other. Two `Import-Lab` calls at the + same time corrupt each other (XML errors, "No machines imported"). +- Check the secure channel of every domain client before the first run + (`Test-MatrixReadiness.ps1`). Windows 11 26H1 (10.0.28000.1836) joins a + Server 2025 domain (10.0.26100.32690) but loses the channel: the client asks + `NetrLogonGetCapabilities` for query level 2, the domain controller answers + `0xC0000022`, and the client denies the channel. Rejoining doesn't help. +- A process that starts from a remoting session has every privilege enabled + and no credentials of its own, so tests that expect disabled privileges fail + (eight per edition). Run the suite of a VM as a scheduled task with a batch + logon at the highest run level (`Register-ScheduledTask -RunLevel Highest + -User -Password`): that token matches a CI runner. A restricted token (a basic + user) can't run Pester's NUnit export, because it asks WMI for the + environment, so write the JSON summary first. +- In Windows PowerShell 5.1, `$PSScriptRoot` is empty in a parameter default of a + script that runs with `-File`; compute it in the body. `-File` passes an array + as one string, so split on commas. With `$ErrorActionPreference = 'Stop'`, a + line that a native command writes to stderr and that `2>&1` redirects is a + terminating error; let the command write its errors to stdout. +- `Get-LocalGroupMember` fails with "Failed to compare two elements in the array" + when the group holds an orphaned SID. Add members with `Add-LocalGroupMember` + and ignore `MemberExistsException`; read and remove members with + `net localgroup ` and `net localgroup /delete`. The SIDs + of a deleted account can't be found afterwards, so keep `fixture-sids.json` + from before the removal of the organizational unit. +- An account that is deleted and created again with the same name made the + remote authorization managers (the client's and the file server's) answer for + about ten minutes as if it had no groups, so `Get-NTFSEffectiveAccess` returned + no access; when the accounts are created again within seconds, the Kerberos S4U + logons returned the old account on the domain controller and member servers for + 7 to 15 minutes. The five remedies tried (a ticket purge, `nltest /sc_reset`, a + DNS flush, a restart of the Kerberos service, and waiting) helped only by + waiting (see `techContext.md`). The controller names the account of case 3 anew + for each new fixture; a script of your own that recreates accounts needs unique + names too. +- Restart the evaluation client (`OSWin11E`) right before a sequence or a suite, + not before several: it shuts down an hour after each start. The restart takes + about two and a half minutes and may need the repair of the secure channel. diff --git a/.memory-bank/progress.md b/.memory-bank/progress.md index 3621123..ae7ccbf 100644 --- a/.memory-bank/progress.md +++ b/.memory-bank/progress.md @@ -1,6 +1,6 @@ --- status: current -last-verified: 2026-10-09 +last-verified: 2026-10-10 owner: active-agent source: repository and validation evidence --- @@ -10,11 +10,16 @@ source: repository and validation evidence ## Current status 5.0.0-rc6 is published on the Gallery and GitHub; its failed Release job -recovered in attempt 2 on 2026-10-09. #116 (rc7, `d25647d`, base `master`) -is open and green, not merged or published. Further quality-gate work is -local: `ai/quality-gate-coverage` (#117) and `ai/quality-gate-paths`, which -classifies every remaining unvisited path; the open items are the -maintainer's decisions. Stable Gallery version: 4.2.6. +recovered in attempt 2 on 2026-10-09. #116 (rc7, `d25647d`) was merged into +`master` on 2026-10-10 (`8a6be9f`); rc7 is neither tagged nor published. #117 +was closed unmerged when its base branch was deleted, so a new pull request +from `ai/quality-gate-coverage` replaces it. Further quality-gate work is +`ai/quality-gate-paths` (draft #118), which classifies every remaining +unvisited path (the open items are the maintainer's decisions), and +`ai/quality-gate-lab-matrix` (draft #119, stacked on #118): the +operating-system matrix, three fixes of the module found by it, and the +controller changes (Decision 24, proposed). +Stable Gallery version: 4.2.6. After 5.0.0, archive in favor of WindowsAccessControl (Decision 18). ## Recent milestones @@ -74,6 +79,46 @@ After 5.0.0, archive in favor of WindowsAccessControl (Decision 18). through the error stream) and a privilege left enabled. Nine static passes of the built-in code-review agent: no Blocker or Major. Report in `Tests/Coverage`. +- 2026-10-09: lab acceptance of those fixes, `83149ee` against its base + `f11ff41` with the same 244 tests per edition (78 new, case 10): candidate + 486 passed, 0 failed, 2 expected skips; baseline 338 passed, 148 failed, all + 148 green on the candidate; fixture removed and verified clean on six + machines. Record: `Tests/Lab/Acceptance-2026-10-09-quality-gate-paths.md`. +- 2026-10-09 to 10: handoffs 2 to 4 under the maintainer's delegation (decisions + D1 to D46 in the night log of the session files). The matrix lab + `NtfsSecurityOsMatrixLab` (Server 2019, 2022, and 2025 file servers, Windows 11 + Enterprise 22H2 client, Windows 11 26H1 suite only) found three defects of the + module, fixed in `962887a` and `fdd7a8b`: audit inheritance by descriptor, + `Get-NTFSEffectiveAccess -ServerName ''`, and the same cmdlet for a user who + isn't an administrator on a domain member. The final candidate passes the + module's suite on every machine (24 runs, no failure) and the live controller + in three cells (1,374 passed, 0 failed, 12 skipped) and in the first lab with + case 9 (245 passed, 0 failed, 1 skipped per edition). The failures of the + effective-access tests in the Server 2022 cell were not a defect of the module: + in a replay of the same cells the baseline failed two of three and the final + candidate one of three (not counting the warm-up cell), and one model (the + remote authorization managers answer for an account name for about ten minutes + after the account was created again) fits all 43 Admin-role runs of 27 cells; + the Windows mechanism is unknown. The controller + names the account of case 3 anew for each fixture (`1dec389`). A read-only + built-in review of the kit and the fixes approved with Minor findings, fixed in + `db04ef2`. A second review of the later commits found one Major (the record + called the cause settled without a baseline replay), addressed by the replay, + `9344ff7`, and `ab0d8e1`; a follow-up review of those fixes found no Blocker or + Major and five Minors, corrected in `e2384e5` and `70f494a`; a second + follow-up review (the first-lab run and the cleanup changes) found no Blocker or + Major and four Minors, corrected in `b78784d` and `dbb4bd6`; the built-in + `security-review` agent found no exploitable vulnerability and two LOW items + that are not changed (record, Limits). Record: + `Tests/Lab/Acceptance-2026-10-10-os-matrix.md`; the agent pushed nothing. +- 2026-10-10: the maintainer merged #116 (`8a6be9f`, 09:10:12Z) with `gh pr + merge --delete-branch` and pushed the matrix branch as draft #119 (`49734ef`). + The deletion removed the base branch of #117, and GitHub closed #117 + unmerged three seconds later instead of retargeting it (events + `base_ref_deleted`, `closed`; the same pair is in `cli/cli#14223`). The + earlier guidance, which relied on a retarget, was wrong. Nothing is lost; a + new pull request from `ai/quality-gate-coverage` replaces #117 (deployment + notes). ## Stable capabilities @@ -123,9 +168,12 @@ After 5.0.0, archive in favor of WindowsAccessControl (Decision 18). tests and exact artifact SHA-512 verification. Original upload errors remain errors for missing/different/unverifiable outcomes. Not deployed until the maintainer merges/pushes; no publication was performed here. -9. Phase 3: choose OS scope (proposed Windows 11 client/2019/2022 servers), - detect ISO editions, provision without repurposing shared VMs, then run - published-package acceptance. #34 has no new reply since 2026-10-06. +9. Operating-system matrix (Decision 24, proposed): the lab and the cells exist + and the final local candidate passes them. Open: the acceptance of the + published rc7 in every cell, keeping or replacing the VMs (about 60 GB) and + the evaluation client (it shuts down every hour), which module fixes go to + rc7, and a domain cell for Windows 11 26H1. #34 has no new reply since + 2026-10-06. 10. Lab rollback evidence: new checkpoints exist but report Standard even after a successful temporary ProductionOnly probe. Classification is unresolved; original VM policy restored, no checkpoint restored. Do diff --git a/.memory-bank/systemPatterns.md b/.memory-bank/systemPatterns.md index 50ba1e2..8372541 100644 --- a/.memory-bank/systemPatterns.md +++ b/.memory-bank/systemPatterns.md @@ -47,6 +47,8 @@ Read only task-relevant records; the index controls routing. | 20 | [Live tests in a lab live in Tests\Lab](decisions/0020-live-tests-in-tests-lab.md) | | 21 | [A quality gate before 5.0.0](decisions/0021-quality-gate-before-5.0.0.md) | | 22 | [The behavior changes of Phase 2 (proposed)](decisions/0022-phase-2-behavior-changes.md) | +| 23 | [Non-Windows file servers before 5.0.0, #34 (proposed)](decisions/0023-non-windows-file-servers.md) | +| 24 | [The operating-system matrix lab (proposed)](decisions/0024-os-matrix-lab.md) | ## Patterns @@ -112,6 +114,23 @@ Read only task-relevant records; the index controls routing. example code blocks, and check the generated XML. - Live tests use only approved lab targets, SMB then independent server state; Get/SetFileSecurity preserves stored DACLs; rights oracles use S4U tokens. +- A suite that is green on the development host and on CI says little about a + feature that the environment lacks. The matrix found three defects that every + earlier run had missed because the host is outside a domain and the CI + runner's token differs: run the suite on a domain member, on other builds, and + as a basic user before a release, and classify a failure by a probe under the + real tokens (elevated, filtered, local standard, domain standard) before + calling it a defect or a design. +- A fixture that deletes an account and creates it again with the same name can + get a wrong answer for about ten minutes: the remote authorization managers + answered `0x100000` for the current SID while the local manager and a Kerberos + logon were right in the same second, and, when the accounts are created again + within seconds, the Kerberos S4U logons returned the old account (7 to 15 + minutes). A failure that follows the order of the cells and not the version of + the module points to such state: run the baseline and the candidate in cells + that follow each other and alternate them (the replay of the record) before + blaming the code. The controller names the account of case 3 anew for each new + fixture. ### CI results and publication diff --git a/.memory-bank/techContext.md b/.memory-bank/techContext.md index 8d66d37..881aa48 100644 --- a/.memory-bank/techContext.md +++ b/.memory-bank/techContext.md @@ -175,5 +175,74 @@ source: repository and executable evidence - Remote Authz answers administrators and Access Control Assistance Operators (S-1-5-32-579); other accounts get access denied. Check firewall when remote resource-manager RPC fails. Expected rights use S4U tokens. + A computer in a domain offers the remote interface to every caller, so the + denial also hit the default `-ServerName localhost` for a user who isn't an + administrator; a computer outside a domain doesn't offer it, which is why the + tests passed on the development host and on CI. Since `fdd7a8b`, the local + manager answers for a name of this computer when the remote one refuses; the + denial stays for another computer (live test of the Delegate role). +- A live test is evidence of a fix only when it fails on the build without + the fix: run the same tests, controller, and lab against the candidate and + the base of the branch, a new process per edition, and join both result + sets by edition, role, and full test name; the tests that pass on both are + controls (`Tests\Lab\Acceptance-2026-10-09-quality-gate-paths.md`). A + validator must not name a loop variable like a typed parameter: PowerShell + variables ignore case, so `$edition` overwrote `$Edition` and every edition + in the CSV became `System.String[]`. - RemoveFixture after the run; verify OUs/accounts, share, folders, local memberships, and test profiles removed. Credentials must never be printed. + +### Operating-system matrix (Decision 24) + +- Lab `NtfsSecurityOsMatrixLab`: OSDC1 (Server 2025), OSFile19/22/25 (Server + 2019/2022/2025), OSWin11E (Windows 11 Enterprise Evaluation 22H2, the domain + client), OSWin11 (Windows 11 Pro 26H1, suite only). Kit: `Tests\Lab\Acceptance`; + record: `Tests\Lab\Acceptance-2026-10-10-os-matrix.md`. +- Run the module's own suite on every machine class before the controller + (`Run-MatrixLocalSuite.ps1`, elevated and basic, both editions, as scheduled + tasks with a batch logon at the highest run level): a child of a remoting + session has every privilege enabled and fails eight tests that expect them + disabled. Skipped lists are compared as multisets against the host. +- AutomatedLab: one `Import-Lab` at a time, and none while a controller + sequence runs (it re-imports the lab); `Wait-LabVM` waits for a heartbeat that + a client may not report, so retry `New-LabPSSession`. The host's `bcdboot` + leaves the ESP of a Server 2019 or Windows 11 22H2 base image empty. +- Windows PowerShell 5.1: `$PSScriptRoot` is empty in a parameter default under + `-File`; `2>&1` on a native command under `Stop` makes its stderr line + terminating; `Get-LocalGroupMember` fails on an orphaned SID; `net localgroup + /delete` refuses the SID of a name that its cache still + resolves (use `Remove-LocalGroupMember -SID`). +- Windows 11 26H1 (28000.1836) loses the secure channel to a Server 2025 domain + controller (`NetrLogonGetCapabilities` level 2, 0xC0000022): suite only. + The 22H2 evaluation client shuts down every hour (license grace expired) and + can lose its machine password after an unplanned shutdown: keep a run under + an hour from its start, test a domain session (not `nltest /sc_verify`, which + stays stale), repair with `Test-ComputerSecureChannel -Repair`. +- Builds are not byte-reproducible (two unchanged assemblies differ per build): + hash each candidate and its package separately. +- The fixture's account for case 3 gets a new name for each new fixture + (`NtfsLiveSubject` and four digits). In the matrix lab, after an account was + deleted and created again with the same name, the remote authorization + managers (the client's for the default `-ServerName`, the file server's for its + name) answered for about ten minutes as if it had no groups (`0x100000`), for + the baseline and for the final candidate alike, while the Kerberos S4U logon of + the oracle, the + name resolution, and the local manager were right in the same second. A replay + 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 + cell). The mechanism in Windows is unknown; a model with one lifetime (9.95 to + 10.25 minutes for both tests, to within 0.05 minute) fits all 43 Admin-role runs + of 27 cells. When the accounts are created again within seconds, the S4U + logon itself returns the old account for 7 to 15 minutes. A `klist purge`, + `nltest /sc_reset`, a DNS flush, and a restart of the Kerberos service didn't + help. `Probe-AccountRecreation.ps1`, `Export-CellTimeline.ps1`, and + `Test-StaleAuthzModel.ps1` show it. +- `net.exe localgroup` lists a local user by its bare name and the entry of a + deleted domain account as its SID (or as its cached name for a while); + deleting a local user removes its entries from the local groups, so only the + entries of domain accounts stay orphaned. `Test-MatrixCleanup.ps1` finds the + entries of the account probe in Performance Log Users by a name with + `NtfsProbe` or by any unresolved `S-1-5-21-…` SID (every such member counts as + the probe's), and its profiles by their folder `C:\Users\NtfsProbe*`. The + cached-name form met real residue in a test; the bare-SID form and a profile + that stays loaded didn't. diff --git a/CHANGELOG.md b/CHANGELOG.md index 98d03e8..59e851e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -185,6 +185,28 @@ The format is based on - Fix `Get-NTFSInheritance -SecurityDescriptor`, which reported `AuditInheritanceEnabled` as `$true` for a security descriptor that was read without its audit section; it now reports `$null`, like `-Path` +- Fix `Get-NTFSInheritance -SecurityDescriptor` for an item without audit + entries on computers where Windows reports its audit entries as protected + from inheritance when it reads all sections of the security descriptor at + once, as it did on domain-joined Windows Server 2022 and 2025 and on + Windows 11: the cmdlet reported `AuditInheritanceEnabled` as `$false`, + while `-Path` reported `$true`. The descriptor now takes the state of the + audit entries from a read of that section alone, like `-Path`, and the + cmdlets that start from such a descriptor no longer see the audit entries + as protected +- Fix `Get-NTFSEffectiveAccess -ServerName ''`, which wrote an "Access is + denied" error instead of the warning for a computer that can't be reached, + on computers where Windows takes an empty name for this one. An empty name + never asks the remote interface of the authorization manager now: the + cmdlet warns and returns the result of this computer on every computer +- Fix `Get-NTFSEffectiveAccess` for a user who isn't an administrator on a + computer in a domain. For a name of this computer, such as the default + `localhost`, the cmdlet wrote the error "Access is denied" and no result for + every account, because the remote authorization manager of a computer + answers only its administrators and the members of Access Control Assistance + Operators. It now uses the local authorization manager of this computer when + the remote one refuses the user, as it already did when the remote one can't + be reached. For the name of another computer, the error stays - Fix `Get-NTFSOwner`, which wrote a "The pipeline has been stopped" error for every path when a command such as `Select-Object -First 1` stopped the pipeline, and which repeated a failed read instead of reporting the diff --git a/Docs/Cmdlets/Get-NTFSEffectiveAccess.md b/Docs/Cmdlets/Get-NTFSEffectiveAccess.md index 7f1ca96..756bfe9 100644 --- a/Docs/Cmdlets/Get-NTFSEffectiveAccess.md +++ b/Docs/Cmdlets/Get-NTFSEffectiveAccess.md @@ -31,7 +31,7 @@ Calculates the rights an account really has on a file or a folder and writes the The calculation covers the NTFS permissions of the item only. Share permissions are stored in a separate security descriptor and are not part of the result, so access over a network share can be more restrictive than this cmdlet reports. -When `-Account` is omitted, the account that runs the session is used. `-ServerName` selects the computer whose authorization manager resolves the group memberships of the account and defaults to `localhost`; when the remote authorization manager of the named computer cannot be reached, the cmdlet falls back to the local one and warns that the result is based on the group memberships known on this computer and may be inaccurate. The warning names the computer that couldn't be reached. For a name of this computer, such as `localhost`, `.`, or its computer name, the local authorization manager gives the result of the named computer, so the cmdlet doesn't warn. The authorization manager of the named computer answers only the administrators of that computer and the members of its local group Access Control Assistance Operators; for any other account, the cmdlet writes an error and doesn't fall back. Reading effective access relies on the Security privilege, and the cmdlet warns when the account does not hold it or the privilege is disabled. +When `-Account` is omitted, the account that runs the session is used. `-ServerName` selects the computer whose authorization manager resolves the group memberships of the account and defaults to `localhost`; when the remote authorization manager of the named computer cannot be reached, the cmdlet falls back to the local one and warns that the result is based on the group memberships known on this computer and may be inaccurate. The warning names the computer that couldn't be reached. For a name of this computer, such as `localhost`, `.`, or its computer name, the local authorization manager gives the result of the named computer, so the cmdlet doesn't warn. The remote authorization manager of a computer answers only the administrators of that computer and the members of its local group Access Control Assistance Operators. For a name of this computer, the cmdlet uses the local authorization manager when the remote one refuses the user who runs the cmdlet, so a user who isn't an administrator gets the result on a computer in a domain, too; for the name of another computer, the cmdlet writes an error and doesn't fall back. Reading effective access relies on the Security privilege, and the cmdlet warns when the account does not hold it or the privilege is disabled. When `-Path` is omitted, the cmdlet calculates the effective access to the current location. In the `SecurityDescriptor` parameter set, it calculates the effective access from a `Security2.FileSystemSecurity2` object that `Get-NTFSSecurityDescriptor` returned, without reading the item again. @@ -139,7 +139,7 @@ Accept wildcard characters: False ### -ServerName -Specifies the computer whose authorization manager resolves the group memberships of the account. The default is `localhost`. Name the computer that stores the item when you query a network path, because the group memberships known there determine the result; if that computer cannot be reached, the cmdlet falls back to the local authorization manager and warns that the result may be inaccurate. The authorization manager of a computer answers only its administrators and the members of its local group Access Control Assistance Operators; for any other account, the cmdlet writes a non-terminating `GetEffectiveAccessError` with the message "Access is denied" and returns no result for the item. +Specifies the computer whose authorization manager resolves the group memberships of the account. The default is `localhost`. Name the computer that stores the item when you query a network path, because the group memberships known there determine the result; if that computer cannot be reached, the cmdlet falls back to the local authorization manager and warns that the result may be inaccurate. The remote authorization manager of a computer answers only its administrators and the members of its local group Access Control Assistance Operators. For the name of another computer, any other user gets a non-terminating `GetEffectiveAccessError` with the message "Access is denied" and no result for the item; for a name of this computer, the cmdlet uses the local authorization manager instead. ```yaml Type: String @@ -186,6 +186,8 @@ Before 5.0.0, `-ExcludeNoneAccessEntries` had no effect, and the cmdlet returned Before 5.0.0-rc7, the warning about a computer that couldn't be reached didn't name the computer, and the cmdlet warned for every name of this computer except `localhost` in lowercase, such as `.`, `LOCALHOST`, or the computer name. +Before 5.0.0-rc7, a user who wasn't an administrator got the error "Access is denied" and no result on a computer in a domain, also for a name of this computer, such as the default `localhost`. + ## RELATED LINKS [Get-NTFSAccess](Get-NTFSAccess.md) diff --git a/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml b/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml index 04d5949..53d4966 100644 --- a/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml +++ b/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml @@ -4957,7 +4957,7 @@ PS C:\> Get-NTFSAudit -SecurityDescriptor $sd Calculates the rights an account really has on a file or a folder and writes the result as a single `Security2.FileSystemAccessRule2` object per item. The cmdlet evaluates the complete discretionary access control list (DACL) of the item against the group memberships of the account with the Windows Authorization API, so allow entries, deny entries, and inherited entries are combined the same way the Windows access check combines them. This is the equivalent of the "Effective Access" tab of the advanced security dialog. The calculation covers the NTFS permissions of the item only. Share permissions are stored in a separate security descriptor and are not part of the result, so access over a network share can be more restrictive than this cmdlet reports. - When `-Account` is omitted, the account that runs the session is used. `-ServerName` selects the computer whose authorization manager resolves the group memberships of the account and defaults to `localhost`; when the remote authorization manager of the named computer cannot be reached, the cmdlet falls back to the local one and warns that the result is based on the group memberships known on this computer and may be inaccurate. The warning names the computer that couldn't be reached. For a name of this computer, such as `localhost`, `.`, or its computer name, the local authorization manager gives the result of the named computer, so the cmdlet doesn't warn. The authorization manager of the named computer answers only the administrators of that computer and the members of its local group Access Control Assistance Operators; for any other account, the cmdlet writes an error and doesn't fall back. Reading effective access relies on the Security privilege, and the cmdlet warns when the account does not hold it or the privilege is disabled. + When `-Account` is omitted, the account that runs the session is used. `-ServerName` selects the computer whose authorization manager resolves the group memberships of the account and defaults to `localhost`; when the remote authorization manager of the named computer cannot be reached, the cmdlet falls back to the local one and warns that the result is based on the group memberships known on this computer and may be inaccurate. The warning names the computer that couldn't be reached. For a name of this computer, such as `localhost`, `.`, or its computer name, the local authorization manager gives the result of the named computer, so the cmdlet doesn't warn. The remote authorization manager of a computer answers only the administrators of that computer and the members of its local group Access Control Assistance Operators. For a name of this computer, the cmdlet uses the local authorization manager when the remote one refuses the user who runs the cmdlet, so a user who isn't an administrator gets the result on a computer in a domain, too; for the name of another computer, the cmdlet writes an error and doesn't fall back. Reading effective access relies on the Security privilege, and the cmdlet warns when the account does not hold it or the privilege is disabled. When `-Path` is omitted, the cmdlet calculates the effective access to the current location. In the `SecurityDescriptor` parameter set, it calculates the effective access from a `Security2.FileSystemSecurity2` object that `Get-NTFSSecurityDescriptor` returned, without reading the item again. @@ -5001,7 +5001,7 @@ PS C:\> Get-NTFSAudit -SecurityDescriptor $sd ServerName - Specifies the computer whose authorization manager resolves the group memberships of the account. The default is `localhost`. Name the computer that stores the item when you query a network path, because the group memberships known there determine the result; if that computer cannot be reached, the cmdlet falls back to the local authorization manager and warns that the result may be inaccurate. The authorization manager of a computer answers only its administrators and the members of its local group Access Control Assistance Operators; for any other account, the cmdlet writes a non-terminating `GetEffectiveAccessError` with the message "Access is denied" and returns no result for the item. + Specifies the computer whose authorization manager resolves the group memberships of the account. The default is `localhost`. Name the computer that stores the item when you query a network path, because the group memberships known there determine the result; if that computer cannot be reached, the cmdlet falls back to the local authorization manager and warns that the result may be inaccurate. The remote authorization manager of a computer answers only its administrators and the members of its local group Access Control Assistance Operators. For the name of another computer, any other user gets a non-terminating `GetEffectiveAccessError` with the message "Access is denied" and no result for the item; for a name of this computer, the cmdlet uses the local authorization manager instead. String @@ -5052,7 +5052,7 @@ PS C:\> Get-NTFSAudit -SecurityDescriptor $sd ServerName - Specifies the computer whose authorization manager resolves the group memberships of the account. The default is `localhost`. Name the computer that stores the item when you query a network path, because the group memberships known there determine the result; if that computer cannot be reached, the cmdlet falls back to the local authorization manager and warns that the result may be inaccurate. The authorization manager of a computer answers only its administrators and the members of its local group Access Control Assistance Operators; for any other account, the cmdlet writes a non-terminating `GetEffectiveAccessError` with the message "Access is denied" and returns no result for the item. + Specifies the computer whose authorization manager resolves the group memberships of the account. The default is `localhost`. Name the computer that stores the item when you query a network path, because the group memberships known there determine the result; if that computer cannot be reached, the cmdlet falls back to the local authorization manager and warns that the result may be inaccurate. The remote authorization manager of a computer answers only its administrators and the members of its local group Access Control Assistance Operators. For the name of another computer, any other user gets a non-terminating `GetEffectiveAccessError` with the message "Access is denied" and no result for the item; for a name of this computer, the cmdlet uses the local authorization manager instead. String @@ -5116,7 +5116,7 @@ PS C:\> Get-NTFSAudit -SecurityDescriptor $sd ServerName - Specifies the computer whose authorization manager resolves the group memberships of the account. The default is `localhost`. Name the computer that stores the item when you query a network path, because the group memberships known there determine the result; if that computer cannot be reached, the cmdlet falls back to the local authorization manager and warns that the result may be inaccurate. The authorization manager of a computer answers only its administrators and the members of its local group Access Control Assistance Operators; for any other account, the cmdlet writes a non-terminating `GetEffectiveAccessError` with the message "Access is denied" and returns no result for the item. + Specifies the computer whose authorization manager resolves the group memberships of the account. The default is `localhost`. Name the computer that stores the item when you query a network path, because the group memberships known there determine the result; if that computer cannot be reached, the cmdlet falls back to the local authorization manager and warns that the result may be inaccurate. The remote authorization manager of a computer answers only its administrators and the members of its local group Access Control Assistance Operators. For the name of another computer, any other user gets a non-terminating `GetEffectiveAccessError` with the message "Access is denied" and no result for the item; for a name of this computer, the cmdlet uses the local authorization manager instead. String @@ -5168,6 +5168,7 @@ PS C:\> Get-NTFSAudit -SecurityDescriptor $sd Reading effective access needs the Security privilege. In a session that does not hold it, the cmdlet warns before it starts and the calculation may fail with an error. Use `Enable-Privileges` in an elevated session to enable the privilege, and `Get-Privileges` to see which privileges the session holds. When the calculation fails, the error names the cause that Windows reported, such as a security descriptor without an owner; before 5.0.0, it blamed a missing Security privilege whenever the privilege wasn't enabled. Before 5.0.0, `-ExcludeNoneAccessEntries` had no effect, and the cmdlet returned nothing without `-Path` or for `-SecurityDescriptor`. When the computer of `-ServerName` couldn't be reached, the cmdlet warned that it had calculated the result on this computer, but returned no access instead of that result. Before 5.0.0-rc7, the warning about a computer that couldn't be reached didn't name the computer, and the cmdlet warned for every name of this computer except `localhost` in lowercase, such as `.`, `LOCALHOST`, or the computer name. + Before 5.0.0-rc7, a user who wasn't an administrator got the error "Access is denied" and no result on a computer in a domain, also for a name of this computer, such as the default `localhost`. diff --git a/Security2/FileSystem/FileSystemSecurity2.cs b/Security2/FileSystem/FileSystemSecurity2.cs index b984d7d..cf905ba 100644 --- a/Security2/FileSystem/FileSystemSecurity2.cs +++ b/Security2/FileSystem/FileSystemSecurity2.cs @@ -66,10 +66,18 @@ namespace Security2 // Read together with the SACL, the inherited entries of a DACL without the auto-inherit flag lose their // inherited flag when the parent folder has no SACL, and writing such a DACL back stores them as explicit // entries. Read alone, the DACL keeps the flags. + // + // The same goes for the SACL: read together with the other sections, the SACL of an item without audit + // entries is reported as protected from inheritance on some computers (seen on domain-joined Windows Server + // 2022 and 2025 and on Windows 11), while the read of the SACL alone, which Get-NTFSInheritance uses for a + // path, reports it as not protected. The state of the item has to be the same by path and by descriptor. if (HasAuditSection) { var accessSecurity = GetSecurity(item, AccessControlSections.Access); sd.SetSecurityDescriptorBinaryForm(accessSecurity.GetSecurityDescriptorBinaryForm(), AccessControlSections.Access); + + var auditSecurity = GetSecurity(item, AccessControlSections.Audit); + sd.SetSecurityDescriptorBinaryForm(auditSecurity.GetSecurityDescriptorBinaryForm(), AccessControlSections.Audit); } RememberSections(); diff --git a/Security2/Win32/Lib.cs b/Security2/Win32/Lib.cs index 957584b..9a5c32b 100644 --- a/Security2/Win32/Lib.cs +++ b/Security2/Win32/Lib.cs @@ -18,6 +18,10 @@ namespace Security2 IntPtr pGrantedAccess = IntPtr.Zero; IntPtr pErrorSecObj = IntPtr.Zero; + // Whether the remote resource manager is the one of this computer. Its remote interface answers only the + // administrators of the computer and the members of Access Control Assistance Operators. + bool remoteResourceManagerIsLocal; + #region GetInheritedFrom // Returns the source of each entry of the DACL, or of the SACL for audit entries, in the order of the ACL. Before // 5.0.0-rc6, a descriptor with a SACL returned the sources of the audit entries also for the access entries. @@ -177,21 +181,36 @@ namespace Security2 { remoteServerAvailable = false; - var rpcInitInfo = new AUTHZ_RPC_INIT_INFO_CLIENT(); + // An empty name names no computer. Windows takes it for this computer on some computers, where the remote + // interface then refuses the check with "Access is denied", and for an unreachable one on others. So the + // remote interface isn't asked, and the local authorization manager calculates the result, like for any + // name that can't be reached. + if (!string.IsNullOrWhiteSpace(serverName)) + { + var rpcInitInfo = new AUTHZ_RPC_INIT_INFO_CLIENT(); - rpcInitInfo.version = AuthzRpcClientVersion.V1; - rpcInitInfo.objectUuid = AUTHZ_OBJECTUUID_WITHCAP; - rpcInitInfo.protocol = RCP_OVER_TCP_PROTOCOL; - rpcInitInfo.server = serverName; + rpcInitInfo.version = AuthzRpcClientVersion.V1; + rpcInitInfo.objectUuid = AUTHZ_OBJECTUUID_WITHCAP; + rpcInitInfo.protocol = RCP_OVER_TCP_PROTOCOL; + rpcInitInfo.server = serverName; + + SafeHGlobalHandle pRpcInitInfo = SafeHGlobalHandle.AllocHGlobalStruct(rpcInitInfo); + if (AuthzInitializeRemoteResourceManager(pRpcInitInfo.ToIntPtr(), out authzRM)) + { + remoteServerAvailable = true; + remoteResourceManagerIsLocal = IsLocalComputer(serverName); + return; + } - SafeHGlobalHandle pRpcInitInfo = SafeHGlobalHandle.AllocHGlobalStruct(rpcInitInfo); - if (!AuthzInitializeRemoteResourceManager(pRpcInitInfo.ToIntPtr(), out authzRM)) - { int error = Marshal.GetLastWin32Error(); + bool isLocalComputer = IsLocalComputer(serverName); // The computer can't be resolved or reached (RPC server unavailable), or it doesn't offer the remote // interface (endpoint not registered); the local authorization manager calculates the result instead. - if (error != Win32Error.EPT_S_NOT_REGISTERED && error != Win32Error.RPC_S_SERVER_UNAVAILABLE) + // This computer can also refuse the caller, who isn't one of its administrators; its own manager + // answers then, too. + if (error != Win32Error.EPT_S_NOT_REGISTERED && error != Win32Error.RPC_S_SERVER_UNAVAILABLE && + !(isLocalComputer && error == Win32Error.ERROR_ACCESS_DENIED)) { throw new Win32Exception(error); } @@ -199,28 +218,29 @@ namespace Security2 // The local authorization manager is the one of this computer, so its result is accurate for any name // of this computer. Before 5.0.0-rc7, only localhost in lowercase counted, and the cmdlet warned for // the others, such as ., the computer name, or LOCALHOST. - if (IsLocalComputer(serverName)) + if (isLocalComputer) { remoteServerAvailable = true; } - - // - // As a fallback we do AuthzInitializeResourceManager. But the results can be inaccurate. - // - if (!AuthzInitializeResourceManager( - AuthzResourceManagerFlags.NO_AUDIT, - IntPtr.Zero, - IntPtr.Zero, - IntPtr.Zero, - "EffectiveAccessCheck", - out authzRM)) - { - throw new Win32Exception(Marshal.GetLastWin32Error()); - } } - else + + GetEffectivePermissions_AuthzInitializeLocalResourceManager(); + } + + private void GetEffectivePermissions_AuthzInitializeLocalResourceManager() + { + // + // As a fallback we do AuthzInitializeResourceManager. But the results can be inaccurate. + // + if (!AuthzInitializeResourceManager( + AuthzResourceManagerFlags.NO_AUDIT, + IntPtr.Zero, + IntPtr.Zero, + IntPtr.Zero, + "EffectiveAccessCheck", + out authzRM)) { - remoteServerAvailable = true; + throw new Win32Exception(Marshal.GetLastWin32Error()); } } @@ -242,6 +262,21 @@ namespace Security2 { Win32Exception win32Expn = new Win32Exception(Marshal.GetLastWin32Error()); + // A computer in a domain offers the remote interface of its authorization manager to every caller, but + // answers only its administrators and the members of Access Control Assistance Operators; any other + // account gets "Access is denied", whichever account the check is for. For a name of this computer, the + // local authorization manager is the manager of that computer and answers every caller. For another + // computer, the denial stays an error: no access instead would be a wrong result. + if (win32Expn.NativeErrorCode == Win32Error.ERROR_ACCESS_DENIED && remoteResourceManagerIsLocal) + { + remoteResourceManagerIsLocal = false; + userClientCtxt = IntPtr.Zero; + authzRM.Dispose(); + GetEffectivePermissions_AuthzInitializeLocalResourceManager(); + GetEffectivePermissions_AuthzInitializeContextFromSid(id); + return; + } + if (win32Expn.NativeErrorCode != Win32Error.RPC_S_SERVER_UNAVAILABLE) { throw win32Expn; diff --git a/Tests/Inheritance.Tests.ps1 b/Tests/Inheritance.Tests.ps1 index e50746d..d961598 100644 --- a/Tests/Inheritance.Tests.ps1 +++ b/Tests/Inheritance.Tests.ps1 @@ -59,6 +59,37 @@ Describe 'Get-NTFSInheritance' { $bySecurityDescriptor.AuditInheritanceEnabled | Should -Be $byPath.AuditInheritanceEnabled } + # Windows reports the SACL of an item without audit entries as protected from inheritance on some computers when it + # reads all sections together, and as not protected when it reads the SACL alone (domain-joined Windows Server 2022 + # and 2025, Windows 11). The state by descriptor has to follow the state of the item. + It 'Should report the same state as for the path of a without audit entries' -ForEach @( + @{ Type = 'file' } + @{ Type = 'folder' } + ) { + $item = New-TestSandboxItem -Sandbox $sandbox -Name 'Descriptor' -Directory:($Type -eq 'folder') + + $byPath = Get-NTFSInheritance -Path $item + $bySecurityDescriptor = Get-NTFSInheritance -SecurityDescriptor (Get-NTFSSecurityDescriptor -Path $item) + + $bySecurityDescriptor.AccessInheritanceEnabled | Should -Be $byPath.AccessInheritanceEnabled + $bySecurityDescriptor.AuditInheritanceEnabled | Should -Be $byPath.AuditInheritanceEnabled + } + + It 'Should report the disabled audit inheritance of a as for its path' -Skip:(-not $canChangeAudit) -ForEach @( + @{ Type = 'file' } + @{ Type = 'folder' } + ) { + $item = New-TestSandboxItem -Sandbox $sandbox -Name 'Descriptor' -Directory:($Type -eq 'folder') + Disable-NTFSAuditInheritance -Path $item -ErrorAction Stop + + $byPath = Get-NTFSInheritance -Path $item + $bySecurityDescriptor = Get-NTFSInheritance -SecurityDescriptor (Get-NTFSSecurityDescriptor -Path $item) + + $byPath.AuditInheritanceEnabled | Should -BeFalse + $bySecurityDescriptor.AuditInheritanceEnabled | Should -BeFalse + $bySecurityDescriptor.AccessInheritanceEnabled | Should -Be $byPath.AccessInheritanceEnabled + } + It 'Should report the audit inheritance as $null for a security descriptor without the audit entries' { $sd = New-Object -TypeName 'Security2.FileSystemSecurity2' -ArgumentList ( (Get-Item2 -Path $file), [System.Security.AccessControl.AccessControlSections]::Access diff --git a/Tests/Lab/Acceptance-2026-10-09-quality-gate-paths-Results.csv b/Tests/Lab/Acceptance-2026-10-09-quality-gate-paths-Results.csv new file mode 100644 index 0000000..2881427 --- /dev/null +++ b/Tests/Lab/Acceptance-2026-10-09-quality-gate-paths-Results.csv @@ -0,0 +1,157 @@ +"Edition","Role","Test","Baseline","Candidate","BaselineFailure" +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Copy-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Copy-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Move-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Move-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Remove-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Remove-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSOwner should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSOwner should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSSecurityDescriptor should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSSecurityDescriptor should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Get-FileHash2 should stop at the verbose message for Select-Object -First 1 and change nothing else","Failed","Passed","Expected 0, but got 1." +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Get-FileHash2 should stop at the verbose message for throw and change nothing else","Failed","Passed","Expected 1, but got 2." +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSOwner should stop at the debug message for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSOwner should stop at the debug message for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSSecurityDescriptor should stop at the verbose message for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSSecurityDescriptor should stop at the verbose message for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Admin","Get-ChildItem2 -Filter on a share folder.Should find a file whose name contains brackets by that name with -Filter","Failed","Passed","Expected a collection with size 1, but got an empty collection." +"Core","Admin","Get-ChildItem2 -Filter on a share folder.Should reject a null -Filter","Failed","Passed","Expected an exception with message like '*'Filter'*' and FullyQualifiedErrorId 'ParameterArgumentValidationError,NTFSSecurity.GetChildItem2' to be thrown, but the message was 'Cannot process argument because the value of argument ""pattern"" is null. Change the value of argument ""pattern"" to a non-null value.' and the FullyQualifiedErrorId was 'ArgumentNull,NTFSSecurity.GetChildItem2'. from C:\NTFSSecurityLab\Tests\NTFSSecurity.Live.Tests.ps1:1333 char:11" +"Core","Admin","Get-ChildItem2 -Filter on a share folder.Should return every item for -Filter *.*, also the ones without a dot in their names","Failed","Passed","Expected strings to be the same, but they were different." +"Core","Admin","InheritedFrom of access entries that Windows cannot resolve on a share.Should name an unknown parent for an inherited entry and no source for an explicit entry when the file is gone","Failed","Passed","Expected strings to be the same, but they were different." +"Core","Admin","InheritedFrom of audit entries that Windows cannot resolve on a share.Should name an unknown parent for an inherited audit entry and no source for an explicit entry when the file is gone","Failed","Passed","Expected strings to be the same, but they were different." +"Core","Admin","Privileges when a later command takes the debug messages of the cmdlet on a share.Should disable the privilege when Select-Object -First ends the pipeline at the message after its enabling","Failed","Passed","Expected $null or empty, but got 'TakeOwnership'." +"Core","Admin","Privileges when a later command takes the debug messages of the cmdlet on a share.Should hold the four privileges that the cmdlets enable","Passed","Passed", +"Core","Admin","Privileges when a later command takes the debug messages of the cmdlet on a share.Should pass on what a later command throws at the message after the enabling and disable the privileges","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Delegate","A later command and the error of a folder that Get-ChildItem2 cannot read on a share.Should leave the loop for a break of a later command that takes the error of a nested folder","Failed","Passed","Expected $false, but got $true." +"Core","Delegate","A later command and the error of a folder that Get-ChildItem2 cannot read on a share.Should pass on what a later command throws when it takes the error of a nested folder","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Delegate","A later command and the error of a folder that Get-ChildItem2 cannot read on a share.Should start with folders that the account cannot list","Passed","Passed", +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Copy-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Copy-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Move-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Move-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Remove-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Remove-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSOwner should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSOwner should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSSecurityDescriptor should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSSecurityDescriptor should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Get-FileHash2 should stop at the verbose message for Select-Object -First 1 and change nothing else","Failed","Passed","Expected 0, but got 1." +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Get-FileHash2 should stop at the verbose message for throw and change nothing else","Failed","Passed","Expected 1, but got 2." +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSOwner should stop at the debug message for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSOwner should stop at the debug message for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSSecurityDescriptor should stop at the verbose message for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSSecurityDescriptor should stop at the verbose message for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Delegate","An item that the account owns and whose owner may not change its permissions on a share.Clear-NTFSAccess -DisableInheritance should take ownership, clear and protect the DACL, and report no RestoreOwnerError","Failed","Passed","Expected $null or empty, but got 'RestoreOwnerError,NTFSSecurity.ClearAccess: (5) Access is denied: [\\F1AFile2.a.forest1.net\NTFSSecurityLive\local-Core-20261009213136\Case10\Delegate\Owner\Clear.txt]'." +"Core","Delegate","An item that the account owns and whose owner may not change its permissions on a share.Set-NTFSSecurityDescriptor should write the cleared DACL and report no RestoreOwnerError","Failed","Passed","Expected $null or empty, but got 'RestoreOwnerError,NTFSSecurity.SetSecurityDescriptor: (5) Access is denied: [\\F1AFile2.a.forest1.net\NTFSSecurityLive\local-Core-20261009213136\Case10\Delegate\Owner\Descriptor.txt]'." +"Core","Delegate","Get-ChildItem2 -Filter on a share folder.Should find a file whose name contains brackets by that name with -Filter","Failed","Passed","Expected a collection with size 1, but got an empty collection." +"Core","Delegate","Get-ChildItem2 -Filter on a share folder.Should reject a null -Filter","Failed","Passed","Expected an exception with message like '*'Filter'*' and FullyQualifiedErrorId 'ParameterArgumentValidationError,NTFSSecurity.GetChildItem2' to be thrown, but the message was 'Cannot process argument because the value of argument ""pattern"" is null. Change the value of argument ""pattern"" to a non-null value.' and the FullyQualifiedErrorId was 'ArgumentNull,NTFSSecurity.GetChildItem2'. from C:\NTFSSecurityLab\Tests\NTFSSecurity.Live.Tests.ps1:1333 char:11" +"Core","Delegate","Get-ChildItem2 -Filter on a share folder.Should return every item for -Filter *.*, also the ones without a dot in their names","Failed","Passed","Expected strings to be the same, but they were different." +"Core","Delegate","InheritedFrom of access entries that Windows cannot resolve on a share.Should name an unknown parent for an inherited entry and no source for an explicit entry when the file is gone","Failed","Passed","Expected strings to be the same, but they were different." +"Core","Delegate","InheritedFrom of an item below a folder on a share whose permissions the account cannot read.Get-NTFSAccess should name an unknown parent for an inherited entry and no source for an explicit entry","Failed","Passed","Expected strings to be the same, but they were different." +"Core","Delegate","InheritedFrom of an item below a folder on a share whose permissions the account cannot read.Should start with a folder whose permissions the account cannot read, and an item that it can","Passed","Passed", +"Core","Delegate","Privileges when a later command takes the debug messages of the cmdlet on a share.Should disable the privilege when Select-Object -First ends the pipeline at the message after its enabling","Failed","Passed","Expected $null or empty, but got 'TakeOwnership'." +"Core","Delegate","Privileges when a later command takes the debug messages of the cmdlet on a share.Should hold the four privileges that the cmdlets enable","Passed","Passed", +"Core","Delegate","Privileges when a later command takes the debug messages of the cmdlet on a share.Should pass on what a later command throws at the message after the enabling and disable the privileges","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Server","Security descriptors on the file server after the runs on the client.Should have changed only the first item of Admin for each cmdlet that a later command stopped","Failed","Passed","Expected $true, because Remove-Item2 left the second item (Select), but got $false." +"Core","Server","Security descriptors on the file server after the runs on the client.Should have changed only the first item of Delegate for each cmdlet that a later command stopped","Failed","Passed","Expected $true, because Remove-Item2 left the second item (Select), but got $false." +"Core","Server","Security descriptors on the file server after the runs on the client.Should have changed only the first item of ServerAdmin for each cmdlet that a later command stopped","Failed","Passed","Expected $true, because Remove-Item2 left the second item (Select), but got $false." +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Copy-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Copy-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Move-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Move-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Remove-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Remove-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSOwner should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSOwner should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSSecurityDescriptor should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSSecurityDescriptor should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Get-FileHash2 should stop at the verbose message for Select-Object -First 1 and change nothing else","Failed","Passed","Expected 0, but got 1." +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Get-FileHash2 should stop at the verbose message for throw and change nothing else","Failed","Passed","Expected 1, but got 2." +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSOwner should stop at the debug message for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSOwner should stop at the debug message for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSSecurityDescriptor should stop at the verbose message for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSSecurityDescriptor should stop at the verbose message for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","ServerAdmin","Get-ChildItem2 -Filter on a share folder.Should find a file whose name contains brackets by that name with -Filter","Failed","Passed","Expected a collection with size 1, but got an empty collection." +"Core","ServerAdmin","Get-ChildItem2 -Filter on a share folder.Should reject a null -Filter","Failed","Passed","Expected an exception with message like '*'Filter'*' and FullyQualifiedErrorId 'ParameterArgumentValidationError,NTFSSecurity.GetChildItem2' to be thrown, but the message was 'Cannot process argument because the value of argument ""pattern"" is null. Change the value of argument ""pattern"" to a non-null value.' and the FullyQualifiedErrorId was 'ArgumentNull,NTFSSecurity.GetChildItem2'. from C:\NTFSSecurityLab\Tests\NTFSSecurity.Live.Tests.ps1:1333 char:11" +"Core","ServerAdmin","Get-ChildItem2 -Filter on a share folder.Should return every item for -Filter *.*, also the ones without a dot in their names","Failed","Passed","Expected strings to be the same, but they were different." +"Core","ServerAdmin","InheritedFrom of access entries that Windows cannot resolve on a share.Should name an unknown parent for an inherited entry and no source for an explicit entry when the file is gone","Failed","Passed","Expected strings to be the same, but they were different." +"Core","ServerAdmin","InheritedFrom of audit entries that Windows cannot resolve on a share.Should name an unknown parent for an inherited audit entry and no source for an explicit entry when the file is gone","Failed","Passed","Expected strings to be the same, but they were different." +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Copy-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Copy-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Move-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Move-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Remove-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Remove-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSOwner should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSOwner should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSSecurityDescriptor should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSSecurityDescriptor should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Get-FileHash2 should stop at the verbose message for Select-Object -First 1 and change nothing else","Failed","Passed","Expected 0, but got 1." +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Get-FileHash2 should stop at the verbose message for throw and change nothing else","Failed","Passed","Expected 1, but got 2." +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSOwner should stop at the debug message for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSOwner should stop at the debug message for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSSecurityDescriptor should stop at the verbose message for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSSecurityDescriptor should stop at the verbose message for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Admin","Get-ChildItem2 -Filter on a share folder.Should find a file whose name contains brackets by that name with -Filter","Failed","Passed","Expected a collection with size 1, but got an empty collection." +"Desktop","Admin","Get-ChildItem2 -Filter on a share folder.Should reject a null -Filter","Failed","Passed","Expected an exception with message like '*'Filter'*' and FullyQualifiedErrorId 'ParameterArgumentValidationError,NTFSSecurity.GetChildItem2' to be thrown, but the message was 'Cannot process argument because the value of argument ""pattern"" is null. Change the value of argument ""pattern"" to a non-null value.' and the FullyQualifiedErrorId was 'ArgumentNull,NTFSSecurity.GetChildItem2'. from C:\NTFSSecurityLab\Tests\NTFSSecurity.Live.Tests.ps1:1333 char:11" +"Desktop","Admin","Get-ChildItem2 -Filter on a share folder.Should return every item for -Filter *.*, also the ones without a dot in their names","Failed","Passed","Expected strings to be the same, but they were different." +"Desktop","Admin","InheritedFrom of access entries that Windows cannot resolve on a share.Should name an unknown parent for an inherited entry and no source for an explicit entry when the file is gone","Failed","Passed","Expected strings to be the same, but they were different." +"Desktop","Admin","InheritedFrom of audit entries that Windows cannot resolve on a share.Should name an unknown parent for an inherited audit entry and no source for an explicit entry when the file is gone","Failed","Passed","Expected strings to be the same, but they were different." +"Desktop","Admin","Privileges when a later command takes the debug messages of the cmdlet on a share.Should disable the privilege when Select-Object -First ends the pipeline at the message after its enabling","Failed","Passed","Expected $null or empty, but got 'TakeOwnership'." +"Desktop","Admin","Privileges when a later command takes the debug messages of the cmdlet on a share.Should hold the four privileges that the cmdlets enable","Passed","Passed", +"Desktop","Admin","Privileges when a later command takes the debug messages of the cmdlet on a share.Should pass on what a later command throws at the message after the enabling and disable the privileges","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Delegate","A later command and the error of a folder that Get-ChildItem2 cannot read on a share.Should leave the loop for a break of a later command that takes the error of a nested folder","Failed","Passed","Expected $false, but got $true." +"Desktop","Delegate","A later command and the error of a folder that Get-ChildItem2 cannot read on a share.Should pass on what a later command throws when it takes the error of a nested folder","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Delegate","A later command and the error of a folder that Get-ChildItem2 cannot read on a share.Should start with folders that the account cannot list","Passed","Passed", +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Copy-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Copy-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Move-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Move-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Remove-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Remove-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSOwner should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSOwner should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSSecurityDescriptor should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSSecurityDescriptor should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Get-FileHash2 should stop at the verbose message for Select-Object -First 1 and change nothing else","Failed","Passed","Expected 0, but got 1." +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Get-FileHash2 should stop at the verbose message for throw and change nothing else","Failed","Passed","Expected 1, but got 2." +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSOwner should stop at the debug message for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSOwner should stop at the debug message for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSSecurityDescriptor should stop at the verbose message for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSSecurityDescriptor should stop at the verbose message for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Delegate","An item that the account owns and whose owner may not change its permissions on a share.Clear-NTFSAccess -DisableInheritance should take ownership, clear and protect the DACL, and report no RestoreOwnerError","Failed","Passed","Expected $null or empty, but got 'RestoreOwnerError,NTFSSecurity.ClearAccess: (5) Access is denied: [\\F1AFile2.a.forest1.net\NTFSSecurityLive\local-Desktop-20261009212312\Case10\Delegate\Owner\Clear.txt]'." +"Desktop","Delegate","An item that the account owns and whose owner may not change its permissions on a share.Set-NTFSSecurityDescriptor should write the cleared DACL and report no RestoreOwnerError","Failed","Passed","Expected $null or empty, but got 'RestoreOwnerError,NTFSSecurity.SetSecurityDescriptor: (5) Access is denied: [\\F1AFile2.a.forest1.net\NTFSSecurityLive\local-Desktop-20261009212312\Case10\Delegate\Owner\Descriptor.txt]'." +"Desktop","Delegate","Get-ChildItem2 -Filter on a share folder.Should find a file whose name contains brackets by that name with -Filter","Failed","Passed","Expected a collection with size 1, but got an empty collection." +"Desktop","Delegate","Get-ChildItem2 -Filter on a share folder.Should reject a null -Filter","Failed","Passed","Expected an exception with message like '*'Filter'*' and FullyQualifiedErrorId 'ParameterArgumentValidationError,NTFSSecurity.GetChildItem2' to be thrown, but the message was 'Cannot process argument because the value of argument ""pattern"" is null. Change the value of argument ""pattern"" to a non-null value.' and the FullyQualifiedErrorId was 'ArgumentNull,NTFSSecurity.GetChildItem2'. from C:\NTFSSecurityLab\Tests\NTFSSecurity.Live.Tests.ps1:1333 char:11" +"Desktop","Delegate","Get-ChildItem2 -Filter on a share folder.Should return every item for -Filter *.*, also the ones without a dot in their names","Failed","Passed","Expected strings to be the same, but they were different." +"Desktop","Delegate","InheritedFrom of access entries that Windows cannot resolve on a share.Should name an unknown parent for an inherited entry and no source for an explicit entry when the file is gone","Failed","Passed","Expected strings to be the same, but they were different." +"Desktop","Delegate","InheritedFrom of an item below a folder on a share whose permissions the account cannot read.Get-NTFSAccess should name an unknown parent for an inherited entry and no source for an explicit entry","Failed","Passed","Expected strings to be the same, but they were different." +"Desktop","Delegate","InheritedFrom of an item below a folder on a share whose permissions the account cannot read.Should start with a folder whose permissions the account cannot read, and an item that it can","Passed","Passed", +"Desktop","Delegate","Privileges when a later command takes the debug messages of the cmdlet on a share.Should disable the privilege when Select-Object -First ends the pipeline at the message after its enabling","Failed","Passed","Expected $null or empty, but got 'TakeOwnership'." +"Desktop","Delegate","Privileges when a later command takes the debug messages of the cmdlet on a share.Should hold the four privileges that the cmdlets enable","Passed","Passed", +"Desktop","Delegate","Privileges when a later command takes the debug messages of the cmdlet on a share.Should pass on what a later command throws at the message after the enabling and disable the privileges","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Server","Security descriptors on the file server after the runs on the client.Should have changed only the first item of Admin for each cmdlet that a later command stopped","Failed","Passed","Expected $true, because Remove-Item2 left the second item (Select), but got $false." +"Desktop","Server","Security descriptors on the file server after the runs on the client.Should have changed only the first item of Delegate for each cmdlet that a later command stopped","Failed","Passed","Expected $true, because Remove-Item2 left the second item (Select), but got $false." +"Desktop","Server","Security descriptors on the file server after the runs on the client.Should have changed only the first item of ServerAdmin for each cmdlet that a later command stopped","Failed","Passed","Expected $true, because Remove-Item2 left the second item (Select), but got $false." +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Copy-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Copy-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Move-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Move-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Remove-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Remove-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSOwner should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSOwner should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSSecurityDescriptor should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSSecurityDescriptor should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Get-FileHash2 should stop at the verbose message for Select-Object -First 1 and change nothing else","Failed","Passed","Expected 0, but got 1." +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Get-FileHash2 should stop at the verbose message for throw and change nothing else","Failed","Passed","Expected 1, but got 2." +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSOwner should stop at the debug message for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSOwner should stop at the debug message for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSSecurityDescriptor should stop at the verbose message for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSSecurityDescriptor should stop at the verbose message for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","ServerAdmin","Get-ChildItem2 -Filter on a share folder.Should find a file whose name contains brackets by that name with -Filter","Failed","Passed","Expected a collection with size 1, but got an empty collection." +"Desktop","ServerAdmin","Get-ChildItem2 -Filter on a share folder.Should reject a null -Filter","Failed","Passed","Expected an exception with message like '*'Filter'*' and FullyQualifiedErrorId 'ParameterArgumentValidationError,NTFSSecurity.GetChildItem2' to be thrown, but the message was 'Cannot process argument because the value of argument ""pattern"" is null. Change the value of argument ""pattern"" to a non-null value.' and the FullyQualifiedErrorId was 'ArgumentNull,NTFSSecurity.GetChildItem2'. from C:\NTFSSecurityLab\Tests\NTFSSecurity.Live.Tests.ps1:1333 char:11" +"Desktop","ServerAdmin","Get-ChildItem2 -Filter on a share folder.Should return every item for -Filter *.*, also the ones without a dot in their names","Failed","Passed","Expected strings to be the same, but they were different." +"Desktop","ServerAdmin","InheritedFrom of access entries that Windows cannot resolve on a share.Should name an unknown parent for an inherited entry and no source for an explicit entry when the file is gone","Failed","Passed","Expected strings to be the same, but they were different." +"Desktop","ServerAdmin","InheritedFrom of audit entries that Windows cannot resolve on a share.Should name an unknown parent for an inherited audit entry and no source for an explicit entry when the file is gone","Failed","Passed","Expected strings to be the same, but they were different." diff --git a/Tests/Lab/Acceptance-2026-10-09-quality-gate-paths.md b/Tests/Lab/Acceptance-2026-10-09-quality-gate-paths.md new file mode 100644 index 0000000..67a225e --- /dev/null +++ b/Tests/Lab/Acceptance-2026-10-09-quality-gate-paths.md @@ -0,0 +1,251 @@ +# Quality-gate paths follow-up acceptance, 2026-10-09 + +Live acceptance, in the lab, of the behavior that the fixes of +`ai/quality-gate-paths` change, as the handoff table of the +[path report](../Coverage/Quality-Gate-Paths-2026-10-09.md) asks. The branch +(28 commits on `f11ff41`, the head of #117; head `83149ee`, draft #118) is a +local candidate, tested from its extracted package with `-ModulePath`. It is +not a published package, and this record is not a claim that the quality gate +is complete. Architecture and cmdlet-design choices remain with the +maintainer (Decisions 16, 21, and 22). + +## Method + +New live tests, case 10 and one test of the Server role, check what each fix +changed. The same tests, controller, and lab ran against two builds, in new +processes for each edition: the candidate (`83149ee`) and the baseline +(`f11ff41`, the base of the branch). A test is evidence of a fix when it +passes on the candidate and fails on the baseline; a test that passes on both +is a control. + +## Candidate and artifact identity + +| | Candidate | Baseline | +| --- | --- | --- | +| Commit | `83149eedee0684bd0a0522865abd0bc6127f6bf5` | `f11ff412947b35d682878ac4a8121c949868fcb2` | +| `NTFSSecurity.dll` SHA-256 | `40D0C8A6B819F15AE69A21D4D510B3B3CFCE2D93294368046C707BD558E67C1F` | `96F087E2AA39D521018346CC9F0A23C8AE2EE2D8CB39AE0E9B7A9325CF47AB73` | +| `NTFSSecurity.5.0.0-rc7.nupkg` SHA-256 | `2AAE3403A2D1C3AEE5156F05441E46B85B855AF95B46A7B73D2F80435513D71D` | `06244B161F76F3A9DCCCFDE3D7D6C5D0D5FEB625127FBF1B298D935BCBD8A2E2` | +| `NTFSSecurity.zip` SHA-256 | `A5AFA241DCA5DF87080A9801BB336282BD424D70DA395F6456F2D74B7FC8076A` | `3DF287C9AC4A311E4093DE519DED046B94109F51B513ACBC1653EF483DB3A2C0` | + +- Each build is a Release build (.NET Framework 4.5.2) in an isolated worktree + of its commit, packaged by `.github/scripts/New-ModulePackage.ps1`. Both + carry the label `5.0.0-rc7` and one assembly version, so every run used a + new process. All 11 files of each tested module folder equal the extracted + `NTFSSecurity.zip` byte for byte (SHA-256). The first packaging attempt, at + 20:41 UTC, stopped in both builds at the check of the build script that + compares the package folder with the extracted zip ("The extracted ZIP + differs from the module folder"); the logs of that attempt are kept. I + changed the script (20:43) and built both again; the files that the lab + tested are those of the second attempt, and the cause of the first + mismatch wasn't recorded. +- Test source, identical in both runs (last written 20:56 and 20:54 UTC, + before the candidate run started at 21:02): `NTFSSecurity.Live.Tests.ps1` + (Git blob `67b85efeb45af67070538f241c203c4afa38b6f4`) and + `Invoke-NTFSSecurityLabTest.ps1` (blob + `0b46427bc32b0b15449e283a2a6cf67879937541`). Both are in the commit that + adds this record. + +## Tests added + +Case 10 adds 78 tests per edition to the 166 of the acceptance at `3442194` +(244 in all): 75 in the roles on the client and 3 for the state that the file +server finds. The fixture adds the folder `Case10` with delegated Full +Control, the folder `Locked` that Administrators own, and files that +Administrators own for the cases of `Set-NTFSOwner`. + +| Describe (roles) | Tests | Fail on baseline | Fail on candidate | Fix | +| --- | ---: | ---: | ---: | --- | +| An item that the account owns and whose owner may not change its permissions (Delegate) | 2 | 2 | 0 | `c7a0383` owner restore | +| InheritedFrom of access entries that Windows cannot resolve (3 roles) | 3 | 3 | 0 | `2909a1c` | +| InheritedFrom of audit entries that Windows cannot resolve (ServerAdmin, Admin) | 2 | 2 | 0 | `2909a1c` | +| InheritedFrom of an item below a folder whose permissions the account cannot read (Delegate) | 2 | 1 | 0 | `2909a1c` | +| A later command that ends the pipeline or throws, for the item cmdlets (3 roles; 16 each) | 48 | 48 | 0 | `c77ecbf`, `40bf6a8` | +| A later command and the error of a folder that Get-ChildItem2 cannot read (Delegate) | 3 | 2 | 0 | `c77ecbf` (break), `d44a200` (throw) | +| Get-ChildItem2 -Filter (3 roles; brackets, `*.*`, null) | 9 | 9 | 0 | `ee7c105`, `40bf6a8`, `ae3078f` | +| Privileges when a later command takes the debug messages (Delegate, Admin) | 6 | 4 | 0 | `d44a200` | +| State of the file server: only the first item changed (Server; one per role) | 3 | 3 | 0 | `c77ecbf`, `40bf6a8` | + +The tests that pass on the baseline are controls (a precondition, or the +privileges the cmdlets hold). `b14c90b` (public object APIs) has no lab +scenario; the package smoke below runs its unit tests. The fix of the leaked +native buffer has no observable guard. + +A first run of the new tests on the candidate in Windows PowerShell failed +five tests. All five were errors of the tests, not of the module: a native +`icacls` call that Pester's `Stop` turned into a terminating error, and +assertions that expected a descriptor to be written at a verbose stop, which +that stop prevents. The tests were corrected, and the runs below are complete +runs of the final test files. + +## Package smoke + +Before the lab run, the eight unit-test files that guard the fixes ran +against the extracted candidate package in a scratch tree, in the four +configurations of the report (650 cases each): elevated Desktop 643 passed, +elevated Core 642, basic Desktop 528, basic Core 527; none failed; 7, 8, 122, +and 123 were skipped by their own conditions, which the report's eligibility +check covers. + +## Lab and rollback evidence + +`WindowsAccessControlLab`: F1ADC1, F1BDC1, F2DC1, F3DC1, F1AFile1 (client), +and F1AFile2 (file server), all Windows Server 2025 (10.0.26100). At +20:41 UTC, authenticated WinRM, LDAP RootDSE, Kerberos tickets, member secure +channels, and clocks (skew at most 7 s) passed on all six machines. At 20:43 +UTC, before the first test run, no `NTFSSecurityLive` OU or `NtfsLive*` +account existed. The runs changed no VM, operating system, or network +setting. A process listing at the start showed no other controller of these +tests on the host; it wasn't kept as a log. + +Six checkpoints named `ntfs-qg-paths-83149ee-before-acceptance` were taken +at 20:45 to 20:46 UTC, one per machine; the Hyper-V listing that shows the +names is kept with the evidence. The policy of each machine is Production, +but Hyper-V reports the type Standard. As before, Production classification +is unverified, and no checkpoint was restored or deleted. Every machine now +carries seven checkpoints of the acceptances since 2026-10-08, F1AFile1 eight. + +## Live results + +Candidate run 21:02 to 21:19 UTC, baseline run 21:22 to 21:39 UTC, each in +Windows PowerShell 5.1 and PowerShell 7 against the extracted package. Both +editions gave the same counts in each build. + +| Build | Role | Passed | Failed | Skipped | +| --- | --- | ---: | ---: | ---: | +| Candidate | Delegate | 69 | 0 | 0 | +| Candidate | ServerAdmin | 34 | 0 | 0 | +| Candidate | Admin | 64 | 0 | 0 | +| Candidate | Server | 76 | 0 | 1 | +| Baseline | Delegate | 42 | 27 | 0 | +| Baseline | ServerAdmin | 13 | 21 | 0 | +| Baseline | Admin | 41 | 23 | 0 | +| Baseline | Server | 73 | 3 | 1 | + +Candidate, both editions: 486 passed, zero failed, two skipped; the skip is +the test that needs the module in the Server role, which doesn't import it. +Baseline, both editions: 338 passed, 148 failed, two skipped. Every role +exited 0 on the candidate. A joined verification of the result files (not of +the counts) found the same 488 tests in both builds, no duplicate, and every +one of the 148 baseline failures passed on the candidate. The 148 failures +are 74 tests in each edition, all among the 78 new tests of each edition +(case 10 and the state test); the four that pass on both builds are +preconditions. [The results file](Acceptance-2026-10-09-quality-gate-paths-Results.csv) +lists the 156 results (78 tests in two editions) with both outcomes and the +first line of the baseline message. + +What the baseline shows, from its messages: + +- Owner: `RestoreOwnerError ... (5) Access is denied` for the unchanged owner. +- `InheritedFrom`: a text of 13 characters instead of the 14 of + `unknown parent`. +- Later command: the `Downstream failure` of a `throw` never reached the + caller (the messages read `Expected like wildcard '*Downstream failure*' to + match $null`), and a `break` of a later command didn't leave the caller's + loop. For `Select-Object -First 1`, see the next section. +- `-Filter`: no result for a name with brackets; `*.*` returned only the + three names with a dot and dropped `NoExtension` and `NoExtensionFolder`; + `$null` gave `ArgumentNull` instead of the parameter validation error. +- Privileges: `TakeOwnership` still enabled after the pipeline stopped. + +### Baseline failures without a message + +Seven tests of each role, 21 per edition and 42 in all, fail on the baseline +with an empty message, and Pester prints no line for them: `Select-Object +-First 1` for the five item cmdlets, the verbose stop of +`Set-NTFSSecurityDescriptor`, and the debug stop of `Set-NTFSOwner`. This lab +run doesn't show what the baseline did in them. The State test of the Server +role shows it only for `Remove-Item2`: in each role, the second item was +removed after `Select-Object -First 1`. That test stops at its first failed +assertion, so it says nothing about the other cmdlets, and its assertions for +the debug and verbose stops check only that the second item is as it was, +which is also true when the client test never ran. + +To close the gap, the bodies of these tests ran afterwards on this host, in a +sandbox below TEMP, with the settings of the runner (Pester 5.7.1, +`ErrorActionPreference` Stop), one build in one edition per process +(`Acceptance\Probe-LaterCommand.ps1`; it isn't part of the acceptance, and +it didn't run on a share). The result is the same in Windows PowerShell 5.1 +and PowerShell 7: + +| Cmdlet | Baseline `f11ff41`, after `Select-Object -First 1` and after `throw` | Candidate `83149ee` | +| --- | --- | --- | +| `Remove-Item2` | both items removed | the second item stays | +| `Copy-Item2` | both items copied | only the first is copied | +| `Move-Item2` | both items moved | the second item stays | +| `Set-NTFSOwner` | both owners changed, also at the debug stop | the second owner stays Administrators | +| `Set-NTFSSecurityDescriptor` | both descriptors written | only the first is written | + +All 12 tests of the probe (seven stop rows, five `throw` rows) fail on the +baseline, the seven stop rows with no error record, as in the lab, and the +`throw` rows with the message of the lab; all 12 pass on the candidate. At the +verbose stop of `Set-NTFSSecurityDescriptor`, neither build writes a +descriptor, because the stop comes before the first write, so that failure on +the baseline isn't a change of state. + +## Cleanup and review + +Before the removal, the SIDs of the fixture were saved from the four domains +(10: seven in `a.forest1.net`, one each in `b.forest1.net`, `forest2.net`, +and `forest3.net`). The fixture was removed at 21:40 to 21:41 UTC with +`Invoke-NTFSSecurityLabTest.ps1 -RemoveFixture`. A separate read-only check +at 21:41 UTC, not the wrapper's marker, found in all four domains no +`NTFSSecurityLive` OU and no `NtfsLive*` account, and on F1AFile1 and +F1AFile2 no share, no `C:\NTFSSecurityLive` or `C:\NTFSSecurityLab`, no +`NtfsLiveLocal` group, no fixture member of Administrators, Access Control +Assistance Operators, or Remote Management Users, and no profile of the ten +SIDs. No checkpoint was restored. + +One independent, read-only, static review of the finished change (tests, +fixture, README, this record and its results file, and Decision 22) ran +before the first commit. The custom `security-reviewer` can't start because +its configured model is unavailable, so the built-in code-review agent did +it. Verdict: approve with Minor; no Blocker and no Major. It confirmed that +the new tests can't pass vacuously (every precondition is asserted, the data +rows are not empty, nothing is shared between rows), that the fixture stays +below the guarded folders and throws when Administrators don't own the +files, and that the counts, the hashes, the 156 results, and the cleanup +facts of this record match the evidence. Its findings, all corrected in the +commit that follows the first: the fix that this record credited for the +`break` row, the claims about the State test and the 42 messageless +failures (now the section above, with the diagnostic), this heading, the +count of results, the wording about the folders before the run, the +truncated messages in the results file, the README row of case 10, and the +migration hint of item 8 and the comparison with `Copy-Item` in Decision 22. +It could not run anything, so the run state and the lab-wide claims rest on +the logs; the diagnostic above and the checkpoint listing close two of its +open points. + +## Limits + +- `Get-NTFSAudit` below an unreadable parent folder can't be built here: an + account that may read the audit entries (it holds the Security privilege) + also reads the DACL of an Administrators-owned folder. The audit scenario + uses a file that was deleted after it was read, which reaches the same + `unknown parent` text. +- The run covers the candidate package from disk (`-ModulePath`), not the + published package, one lab, and Windows Server 2025 only. The other + operating systems of Decision 21, the acceptance of the published + prerelease, and the answer of a non-Windows file server (#34) stay with the + other gates. The stable version remains 4.2.6. +- The candidate and the baseline differ only by the 28 commits; the test and + controller files are the same. +- What the baseline did in the 42 failures without a message is shown by a + local diagnostic, not by this lab run. A State test split per cmdlet and + stop style would show it on the share too, and would need both lab runs + again. + +## Evidence + +The result files, logs, hashes, readiness, checkpoint, snapshot, and cleanup +logs of both runs are in the session artifact +`4b12e2f4-d4c7-4a5d-883a-ddb7421c4848\files\lab-qg-paths` (local, not in Git); +so are the Hyper-V listing of the checkpoint names +(`checkpoints-83149ee-names.csv`) and the outputs of the diagnostic +(`runs\diagnostic-mute`, and `runs\diagnostic-mute-first-run` from before the +probe listed owners and entries). The per-test results of case 10 are in +[the results file](Acceptance-2026-10-09-quality-gate-paths-Results.csv). The +scripts that build, package, run, and clean up in this acceptance contain +paths of the session folder and stay in the session artifact; the generic +ones that it used, `Validate-LabResults.ps1` (the check of the result files) +and `Probe-LaterCommand.ps1` (the diagnostic), are in the folder +[Acceptance](Acceptance). diff --git a/Tests/Lab/Acceptance-2026-10-10-os-matrix-Cells.csv b/Tests/Lab/Acceptance-2026-10-10-os-matrix-Cells.csv new file mode 100644 index 0000000..5c80638 --- /dev/null +++ b/Tests/Lab/Acceptance-2026-10-10-os-matrix-Cells.csv @@ -0,0 +1,129 @@ +"Run","Candidate","FileServer","FileServerOs","Client","ClientOs","Edition","Role","Account","ExitCode","Passed","Failed","Skipped" +"rc7c","83149ee","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7c","83149ee","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","34","0","0" +"rc7c","83149ee","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Admin","osmatrix\NtfsLiveAdmin","0","51","0","1" +"rc7c","83149ee","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Server","osmatrix\install","0","73","0","1" +"rc7c","83149ee","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7c","83149ee","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","34","0","0" +"rc7c","83149ee","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Admin","osmatrix\NtfsLiveAdmin","0","51","0","1" +"rc7c","83149ee","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Server","osmatrix\install","0","73","0","1" +"rc7c","83149ee","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7c","83149ee","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","34","0","0" +"rc7c","83149ee","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Admin","osmatrix\NtfsLiveAdmin","0","51","0","1" +"rc7c","83149ee","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Server","osmatrix\install","0","73","0","1" +"rc7c","83149ee","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7c","83149ee","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","34","0","0" +"rc7c","83149ee","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Admin","osmatrix\NtfsLiveAdmin","0","51","0","1" +"rc7c","83149ee","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Server","osmatrix\install","0","73","0","1" +"rc7c","83149ee","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7c","83149ee","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","34","0","0" +"rc7c","83149ee","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Admin","osmatrix\NtfsLiveAdmin","0","51","0","1" +"rc7c","83149ee","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Server","osmatrix\install","0","73","0","1" +"rc7c","83149ee","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7c","83149ee","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","34","0","0" +"rc7c","83149ee","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Admin","osmatrix\NtfsLiveAdmin","0","51","0","1" +"rc7c","83149ee","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Server","osmatrix\install","0","73","0","1" +"rc7e","83149ee","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7e","83149ee","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","34","0","0" +"rc7e","83149ee","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Admin","osmatrix\NtfsLiveAdmin","0","51","0","1" +"rc7e","83149ee","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Server","osmatrix\install","0","73","0","1" +"rc7e","83149ee","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7e","83149ee","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","34","0","0" +"rc7e","83149ee","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Admin","osmatrix\NtfsLiveAdmin","0","51","0","1" +"rc7e","83149ee","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Server","osmatrix\install","0","73","0","1" +"rc7e","83149ee","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7e","83149ee","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","34","0","0" +"rc7e","83149ee","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Admin","osmatrix\NtfsLiveAdmin","0","51","0","1" +"rc7e","83149ee","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Server","osmatrix\install","0","73","0","1" +"rc7e","83149ee","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7e","83149ee","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","34","0","0" +"rc7e","83149ee","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Admin","osmatrix\NtfsLiveAdmin","0","51","0","1" +"rc7e","83149ee","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Server","osmatrix\install","0","73","0","1" +"rc7e","83149ee","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7e","83149ee","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","34","0","0" +"rc7e","83149ee","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Admin","osmatrix\NtfsLiveAdmin","0","51","0","1" +"rc7e","83149ee","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Server","osmatrix\install","0","73","0","1" +"rc7e","83149ee","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7e","83149ee","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","34","0","0" +"rc7e","83149ee","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Admin","osmatrix\NtfsLiveAdmin","0","51","0","1" +"rc7e","83149ee","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Server","osmatrix\install","0","73","0","1" +"rc7f","fdd7a8b","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7f","fdd7a8b","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","36","0","0" +"rc7f","fdd7a8b","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Admin","osmatrix\NtfsLiveAdmin","0","51","0","1" +"rc7f","fdd7a8b","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Server","osmatrix\install","0","73","0","1" +"rc7f","fdd7a8b","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7f","fdd7a8b","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","36","0","0" +"rc7f","fdd7a8b","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Admin","osmatrix\NtfsLiveAdmin","0","51","0","1" +"rc7f","fdd7a8b","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Server","osmatrix\install","0","73","0","1" +"rc7f","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7f","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","36","0","0" +"rc7f","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Admin","osmatrix\NtfsLiveAdmin","1","50","1","1" +"rc7f","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Server","osmatrix\install","0","73","0","1" +"rc7f","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7f","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","36","0","0" +"rc7f","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Admin","osmatrix\NtfsLiveAdmin","1","50","1","1" +"rc7f","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Server","osmatrix\install","0","73","0","1" +"rc7g","fdd7a8b","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7g","fdd7a8b","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","36","0","0" +"rc7g","fdd7a8b","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Admin","osmatrix\NtfsLiveAdmin","0","51","0","1" +"rc7g","fdd7a8b","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Server","osmatrix\install","0","73","0","1" +"rc7g","fdd7a8b","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7g","fdd7a8b","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","36","0","0" +"rc7g","fdd7a8b","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Admin","osmatrix\NtfsLiveAdmin","0","51","0","1" +"rc7g","fdd7a8b","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Server","osmatrix\install","0","73","0","1" +"rc7h","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7h","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","36","0","0" +"rc7h","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Admin","osmatrix\NtfsLiveAdmin","1","50","1","1" +"rc7h","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Server","osmatrix\install","0","73","0","1" +"rc7h","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7h","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","36","0","0" +"rc7h","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Admin","osmatrix\NtfsLiveAdmin","1","50","1","1" +"rc7h","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Server","osmatrix\install","0","73","0","1" +"rc7i","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7i","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","36","0","0" +"rc7i","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Admin","osmatrix\NtfsLiveAdmin","1","50","1","1" +"rc7i","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Server","osmatrix\install","0","73","0","1" +"rc7i","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7i","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","36","0","0" +"rc7i","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Admin","osmatrix\NtfsLiveAdmin","0","51","0","1" +"rc7i","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Server","osmatrix\install","0","73","0","1" +"rc7j","962887a","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7j","962887a","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","ServerAdmin","osmatrix\NtfsLiveServerAdmin","1","34","2","0" +"rc7j","962887a","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Admin","osmatrix\NtfsLiveAdmin","1","49","2","1" +"rc7j","962887a","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Server","osmatrix\install","0","73","0","1" +"rc7j","962887a","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7j","962887a","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","ServerAdmin","osmatrix\NtfsLiveServerAdmin","1","34","2","0" +"rc7j","962887a","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Admin","osmatrix\NtfsLiveAdmin","1","49","2","1" +"rc7j","962887a","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Server","osmatrix\install","0","73","0","1" +"rc7k","83149ee","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7k","83149ee","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","ServerAdmin","osmatrix\NtfsLiveServerAdmin","1","34","2","0" +"rc7k","83149ee","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Admin","osmatrix\NtfsLiveAdmin","0","51","0","1" +"rc7k","83149ee","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Server","osmatrix\install","0","73","0","1" +"rc7k","83149ee","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7k","83149ee","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","ServerAdmin","osmatrix\NtfsLiveServerAdmin","1","34","2","0" +"rc7k","83149ee","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Admin","osmatrix\NtfsLiveAdmin","0","51","0","1" +"rc7k","83149ee","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Server","osmatrix\install","0","73","0","1" +"rc7l","fdd7a8b","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7l","fdd7a8b","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","36","0","0" +"rc7l","fdd7a8b","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Admin","osmatrix\NtfsLiveAdmin","0","51","0","1" +"rc7l","fdd7a8b","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Server","osmatrix\install","0","73","0","1" +"rc7l","fdd7a8b","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7l","fdd7a8b","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","36","0","0" +"rc7l","fdd7a8b","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Admin","osmatrix\NtfsLiveAdmin","0","51","0","1" +"rc7l","fdd7a8b","OSFile19","Windows Server 2019 Datacenter 10.0.17763.1217 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Server","osmatrix\install","0","73","0","1" +"rc7l","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7l","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","36","0","0" +"rc7l","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Admin","osmatrix\NtfsLiveAdmin","0","51","0","1" +"rc7l","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Server","osmatrix\install","0","73","0","1" +"rc7l","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7l","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","36","0","0" +"rc7l","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Admin","osmatrix\NtfsLiveAdmin","0","51","0","1" +"rc7l","fdd7a8b","OSFile22","Windows Server 2022 Datacenter 10.0.20348.4773 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Server","osmatrix\install","0","73","0","1" +"rc7l","fdd7a8b","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7l","fdd7a8b","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","36","0","0" +"rc7l","fdd7a8b","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Admin","osmatrix\NtfsLiveAdmin","0","51","0","1" +"rc7l","fdd7a8b","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Desktop","Server","osmatrix\install","0","73","0","1" +"rc7l","fdd7a8b","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Delegate","osmatrix\NtfsLiveDelegate","0","69","0","0" +"rc7l","fdd7a8b","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","ServerAdmin","osmatrix\NtfsLiveServerAdmin","0","36","0","0" +"rc7l","fdd7a8b","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Admin","osmatrix\NtfsLiveAdmin","0","51","0","1" +"rc7l","fdd7a8b","OSFile25","Windows Server 2025 Datacenter 10.0.26100.32690 (server)","OSWin11E","Windows 11 Enterprise Evaluation 10.0.22621.525 (client)","Core","Server","osmatrix\install","0","73","0","1" diff --git a/Tests/Lab/Acceptance-2026-10-10-os-matrix-Failures.csv b/Tests/Lab/Acceptance-2026-10-10-os-matrix-Failures.csv new file mode 100644 index 0000000..8ae7344 --- /dev/null +++ b/Tests/Lab/Acceptance-2026-10-10-os-matrix-Failures.csv @@ -0,0 +1,297 @@ +"Candidate","Machine","Mode","Edition","Test" +"83149ee","OSFile22","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for break after its first object and change nothing else" +"83149ee","OSFile22","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for continue after its first object and change nothing else" +"83149ee","OSFile22","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop after the first object for Select-Object -First 1 and change nothing else" +"83149ee","OSFile22","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (throw) of the later command and change nothing else" +"83149ee","OSFile22","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else" +"83149ee","OSFile22","Basic","Core","A path that does not exist.Get-NTFSEffectiveAccess should write a ReadFileError for it and continue with the next path" +"83149ee","OSFile22","Basic","Core","An item whose owner may not read its permissions.Get-NTFSEffectiveAccess should write a ReadSecurityError for it and continue with the next path" +"83149ee","OSFile22","Basic","Core","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each Path and return no access entry" +"83149ee","OSFile22","Basic","Core","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each SecurityDescriptor and return no access entry" +"83149ee","OSFile22","Basic","Core","Get-NTFSEffectiveAccess.Should compute the effective access of a security descriptor" +"83149ee","OSFile22","Basic","Core","Get-NTFSEffectiveAccess.Should leave out an account without access when -ExcludeNoneAccessEntries is used" +"83149ee","OSFile22","Basic","Core","Get-NTFSEffectiveAccess.Should return an account with access when -ExcludeNoneAccessEntries is used" +"83149ee","OSFile22","Basic","Core","Get-NTFSEffectiveAccess.Should use the current location when -Path is omitted" +"83149ee","OSFile22","Basic","Core","Get-NTFSEffectiveAccess.Should warn once that the Security privilege is missing" +"83149ee","OSFile22","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for . and warn no more than for localhost" +"83149ee","OSFile22","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for LOCALHOST and warn no more than for localhost" +"83149ee","OSFile22","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSFILE22 and warn no more than for localhost" +"83149ee","OSFile22","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSFile22.osmatrix.net and warn no more than for localhost" +"83149ee","OSFile22","Basic","Core","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn" +"83149ee","OSFile22","Basic","Core","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn for an empty -ServerName" +"83149ee","OSFile22","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for break after its first object and change nothing else" +"83149ee","OSFile22","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for continue after its first object and change nothing else" +"83149ee","OSFile22","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop after the first object for Select-Object -First 1 and change nothing else" +"83149ee","OSFile22","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (throw) of the later command and change nothing else" +"83149ee","OSFile22","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else" +"83149ee","OSFile22","Basic","Desktop","A path that does not exist.Get-NTFSEffectiveAccess should write a ReadFileError for it and continue with the next path" +"83149ee","OSFile22","Basic","Desktop","An item whose owner may not read its permissions.Get-NTFSEffectiveAccess should write a ReadSecurityError for it and continue with the next path" +"83149ee","OSFile22","Basic","Desktop","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each Path and return no access entry" +"83149ee","OSFile22","Basic","Desktop","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each SecurityDescriptor and return no access entry" +"83149ee","OSFile22","Basic","Desktop","Get-NTFSEffectiveAccess.Should compute the effective access of a security descriptor" +"83149ee","OSFile22","Basic","Desktop","Get-NTFSEffectiveAccess.Should leave out an account without access when -ExcludeNoneAccessEntries is used" +"83149ee","OSFile22","Basic","Desktop","Get-NTFSEffectiveAccess.Should return an account with access when -ExcludeNoneAccessEntries is used" +"83149ee","OSFile22","Basic","Desktop","Get-NTFSEffectiveAccess.Should use the current location when -Path is omitted" +"83149ee","OSFile22","Basic","Desktop","Get-NTFSEffectiveAccess.Should warn once that the Security privilege is missing" +"83149ee","OSFile22","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for . and warn no more than for localhost" +"83149ee","OSFile22","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for LOCALHOST and warn no more than for localhost" +"83149ee","OSFile22","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSFile22 and warn no more than for localhost" +"83149ee","OSFile22","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSFile22.osmatrix.net and warn no more than for localhost" +"83149ee","OSFile22","Basic","Desktop","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn" +"83149ee","OSFile22","Basic","Desktop","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn for an empty -ServerName" +"83149ee","OSFile22","Elevated","Core","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn for an empty -ServerName" +"83149ee","OSFile22","Elevated","Core","Get-NTFSInheritance.With a security descriptor.Should report the same state as for the path of a file without audit entries" +"83149ee","OSFile22","Elevated","Core","Get-NTFSInheritance.With a security descriptor.Should report the same state as for the path of a folder without audit entries" +"83149ee","OSFile22","Elevated","Core","Get-NTFSInheritance.With a security descriptor.Should report the same state as for the path of the item" +"83149ee","OSFile22","Elevated","Desktop","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn for an empty -ServerName" +"83149ee","OSFile22","Elevated","Desktop","Get-NTFSInheritance.With a security descriptor.Should report the same state as for the path of a file without audit entries" +"83149ee","OSFile22","Elevated","Desktop","Get-NTFSInheritance.With a security descriptor.Should report the same state as for the path of a folder without audit entries" +"83149ee","OSFile22","Elevated","Desktop","Get-NTFSInheritance.With a security descriptor.Should report the same state as for the path of the item" +"83149ee","OSFile25","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for break after its first object and change nothing else" +"83149ee","OSFile25","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for continue after its first object and change nothing else" +"83149ee","OSFile25","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop after the first object for Select-Object -First 1 and change nothing else" +"83149ee","OSFile25","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (throw) of the later command and change nothing else" +"83149ee","OSFile25","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else" +"83149ee","OSFile25","Basic","Core","A path that does not exist.Get-NTFSEffectiveAccess should write a ReadFileError for it and continue with the next path" +"83149ee","OSFile25","Basic","Core","An item whose owner may not read its permissions.Get-NTFSEffectiveAccess should write a ReadSecurityError for it and continue with the next path" +"83149ee","OSFile25","Basic","Core","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each Path and return no access entry" +"83149ee","OSFile25","Basic","Core","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each SecurityDescriptor and return no access entry" +"83149ee","OSFile25","Basic","Core","Get-NTFSEffectiveAccess.Should compute the effective access of a security descriptor" +"83149ee","OSFile25","Basic","Core","Get-NTFSEffectiveAccess.Should leave out an account without access when -ExcludeNoneAccessEntries is used" +"83149ee","OSFile25","Basic","Core","Get-NTFSEffectiveAccess.Should return an account with access when -ExcludeNoneAccessEntries is used" +"83149ee","OSFile25","Basic","Core","Get-NTFSEffectiveAccess.Should use the current location when -Path is omitted" +"83149ee","OSFile25","Basic","Core","Get-NTFSEffectiveAccess.Should warn once that the Security privilege is missing" +"83149ee","OSFile25","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for . and warn no more than for localhost" +"83149ee","OSFile25","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for LOCALHOST and warn no more than for localhost" +"83149ee","OSFile25","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSFILE25 and warn no more than for localhost" +"83149ee","OSFile25","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSFile25.osmatrix.net and warn no more than for localhost" +"83149ee","OSFile25","Basic","Core","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn" +"83149ee","OSFile25","Basic","Core","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn for an empty -ServerName" +"83149ee","OSFile25","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for break after its first object and change nothing else" +"83149ee","OSFile25","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for continue after its first object and change nothing else" +"83149ee","OSFile25","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop after the first object for Select-Object -First 1 and change nothing else" +"83149ee","OSFile25","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (throw) of the later command and change nothing else" +"83149ee","OSFile25","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else" +"83149ee","OSFile25","Basic","Desktop","A path that does not exist.Get-NTFSEffectiveAccess should write a ReadFileError for it and continue with the next path" +"83149ee","OSFile25","Basic","Desktop","An item whose owner may not read its permissions.Get-NTFSEffectiveAccess should write a ReadSecurityError for it and continue with the next path" +"83149ee","OSFile25","Basic","Desktop","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each Path and return no access entry" +"83149ee","OSFile25","Basic","Desktop","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each SecurityDescriptor and return no access entry" +"83149ee","OSFile25","Basic","Desktop","Get-NTFSEffectiveAccess.Should compute the effective access of a security descriptor" +"83149ee","OSFile25","Basic","Desktop","Get-NTFSEffectiveAccess.Should leave out an account without access when -ExcludeNoneAccessEntries is used" +"83149ee","OSFile25","Basic","Desktop","Get-NTFSEffectiveAccess.Should return an account with access when -ExcludeNoneAccessEntries is used" +"83149ee","OSFile25","Basic","Desktop","Get-NTFSEffectiveAccess.Should use the current location when -Path is omitted" +"83149ee","OSFile25","Basic","Desktop","Get-NTFSEffectiveAccess.Should warn once that the Security privilege is missing" +"83149ee","OSFile25","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for . and warn no more than for localhost" +"83149ee","OSFile25","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for LOCALHOST and warn no more than for localhost" +"83149ee","OSFile25","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSFile25 and warn no more than for localhost" +"83149ee","OSFile25","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSFile25.osmatrix.net and warn no more than for localhost" +"83149ee","OSFile25","Basic","Desktop","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn" +"83149ee","OSFile25","Basic","Desktop","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn for an empty -ServerName" +"83149ee","OSFile25","Elevated","Core","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn for an empty -ServerName" +"83149ee","OSFile25","Elevated","Core","Get-NTFSInheritance.With a security descriptor.Should report the same state as for the path of a file without audit entries" +"83149ee","OSFile25","Elevated","Core","Get-NTFSInheritance.With a security descriptor.Should report the same state as for the path of a folder without audit entries" +"83149ee","OSFile25","Elevated","Core","Get-NTFSInheritance.With a security descriptor.Should report the same state as for the path of the item" +"83149ee","OSFile25","Elevated","Desktop","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn for an empty -ServerName" +"83149ee","OSFile25","Elevated","Desktop","Get-NTFSInheritance.With a security descriptor.Should report the same state as for the path of a file without audit entries" +"83149ee","OSFile25","Elevated","Desktop","Get-NTFSInheritance.With a security descriptor.Should report the same state as for the path of a folder without audit entries" +"83149ee","OSFile25","Elevated","Desktop","Get-NTFSInheritance.With a security descriptor.Should report the same state as for the path of the item" +"962887a","OSFile19","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for break after its first object and change nothing else" +"962887a","OSFile19","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for continue after its first object and change nothing else" +"962887a","OSFile19","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop after the first object for Select-Object -First 1 and change nothing else" +"962887a","OSFile19","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (throw) of the later command and change nothing else" +"962887a","OSFile19","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else" +"962887a","OSFile19","Basic","Core","A path that does not exist.Get-NTFSEffectiveAccess should write a ReadFileError for it and continue with the next path" +"962887a","OSFile19","Basic","Core","An item whose owner may not read its permissions.Get-NTFSEffectiveAccess should write a ReadSecurityError for it and continue with the next path" +"962887a","OSFile19","Basic","Core","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each Path and return no access entry" +"962887a","OSFile19","Basic","Core","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each SecurityDescriptor and return no access entry" +"962887a","OSFile19","Basic","Core","Get-NTFSEffectiveAccess.Should compute the effective access of a security descriptor" +"962887a","OSFile19","Basic","Core","Get-NTFSEffectiveAccess.Should leave out an account without access when -ExcludeNoneAccessEntries is used" +"962887a","OSFile19","Basic","Core","Get-NTFSEffectiveAccess.Should return an account with access when -ExcludeNoneAccessEntries is used" +"962887a","OSFile19","Basic","Core","Get-NTFSEffectiveAccess.Should use the current location when -Path is omitted" +"962887a","OSFile19","Basic","Core","Get-NTFSEffectiveAccess.Should warn once that the Security privilege is missing" +"962887a","OSFile19","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for . and warn no more than for localhost" +"962887a","OSFile19","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for LOCALHOST and warn no more than for localhost" +"962887a","OSFile19","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSFILE19 and warn no more than for localhost" +"962887a","OSFile19","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSFile19.osmatrix.net and warn no more than for localhost" +"962887a","OSFile19","Basic","Core","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn" +"962887a","OSFile19","Basic","Core","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn for an empty -ServerName" +"962887a","OSFile19","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for break after its first object and change nothing else" +"962887a","OSFile19","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for continue after its first object and change nothing else" +"962887a","OSFile19","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop after the first object for Select-Object -First 1 and change nothing else" +"962887a","OSFile19","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (throw) of the later command and change nothing else" +"962887a","OSFile19","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else" +"962887a","OSFile19","Basic","Desktop","A path that does not exist.Get-NTFSEffectiveAccess should write a ReadFileError for it and continue with the next path" +"962887a","OSFile19","Basic","Desktop","An item whose owner may not read its permissions.Get-NTFSEffectiveAccess should write a ReadSecurityError for it and continue with the next path" +"962887a","OSFile19","Basic","Desktop","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each Path and return no access entry" +"962887a","OSFile19","Basic","Desktop","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each SecurityDescriptor and return no access entry" +"962887a","OSFile19","Basic","Desktop","Get-NTFSEffectiveAccess.Should compute the effective access of a security descriptor" +"962887a","OSFile19","Basic","Desktop","Get-NTFSEffectiveAccess.Should leave out an account without access when -ExcludeNoneAccessEntries is used" +"962887a","OSFile19","Basic","Desktop","Get-NTFSEffectiveAccess.Should return an account with access when -ExcludeNoneAccessEntries is used" +"962887a","OSFile19","Basic","Desktop","Get-NTFSEffectiveAccess.Should use the current location when -Path is omitted" +"962887a","OSFile19","Basic","Desktop","Get-NTFSEffectiveAccess.Should warn once that the Security privilege is missing" +"962887a","OSFile19","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for . and warn no more than for localhost" +"962887a","OSFile19","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for LOCALHOST and warn no more than for localhost" +"962887a","OSFile19","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSFile19 and warn no more than for localhost" +"962887a","OSFile19","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSFile19.osmatrix.net and warn no more than for localhost" +"962887a","OSFile19","Basic","Desktop","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn" +"962887a","OSFile19","Basic","Desktop","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn for an empty -ServerName" +"962887a","OSFile22","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for break after its first object and change nothing else" +"962887a","OSFile22","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for continue after its first object and change nothing else" +"962887a","OSFile22","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop after the first object for Select-Object -First 1 and change nothing else" +"962887a","OSFile22","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (throw) of the later command and change nothing else" +"962887a","OSFile22","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else" +"962887a","OSFile22","Basic","Core","A path that does not exist.Get-NTFSEffectiveAccess should write a ReadFileError for it and continue with the next path" +"962887a","OSFile22","Basic","Core","An item whose owner may not read its permissions.Get-NTFSEffectiveAccess should write a ReadSecurityError for it and continue with the next path" +"962887a","OSFile22","Basic","Core","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each Path and return no access entry" +"962887a","OSFile22","Basic","Core","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each SecurityDescriptor and return no access entry" +"962887a","OSFile22","Basic","Core","Get-NTFSEffectiveAccess.Should compute the effective access of a security descriptor" +"962887a","OSFile22","Basic","Core","Get-NTFSEffectiveAccess.Should leave out an account without access when -ExcludeNoneAccessEntries is used" +"962887a","OSFile22","Basic","Core","Get-NTFSEffectiveAccess.Should return an account with access when -ExcludeNoneAccessEntries is used" +"962887a","OSFile22","Basic","Core","Get-NTFSEffectiveAccess.Should use the current location when -Path is omitted" +"962887a","OSFile22","Basic","Core","Get-NTFSEffectiveAccess.Should warn once that the Security privilege is missing" +"962887a","OSFile22","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for . and warn no more than for localhost" +"962887a","OSFile22","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for LOCALHOST and warn no more than for localhost" +"962887a","OSFile22","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSFILE22 and warn no more than for localhost" +"962887a","OSFile22","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSFile22.osmatrix.net and warn no more than for localhost" +"962887a","OSFile22","Basic","Core","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn" +"962887a","OSFile22","Basic","Core","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn for an empty -ServerName" +"962887a","OSFile22","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for break after its first object and change nothing else" +"962887a","OSFile22","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for continue after its first object and change nothing else" +"962887a","OSFile22","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop after the first object for Select-Object -First 1 and change nothing else" +"962887a","OSFile22","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (throw) of the later command and change nothing else" +"962887a","OSFile22","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else" +"962887a","OSFile22","Basic","Desktop","A path that does not exist.Get-NTFSEffectiveAccess should write a ReadFileError for it and continue with the next path" +"962887a","OSFile22","Basic","Desktop","An item whose owner may not read its permissions.Get-NTFSEffectiveAccess should write a ReadSecurityError for it and continue with the next path" +"962887a","OSFile22","Basic","Desktop","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each Path and return no access entry" +"962887a","OSFile22","Basic","Desktop","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each SecurityDescriptor and return no access entry" +"962887a","OSFile22","Basic","Desktop","Get-NTFSEffectiveAccess.Should compute the effective access of a security descriptor" +"962887a","OSFile22","Basic","Desktop","Get-NTFSEffectiveAccess.Should leave out an account without access when -ExcludeNoneAccessEntries is used" +"962887a","OSFile22","Basic","Desktop","Get-NTFSEffectiveAccess.Should return an account with access when -ExcludeNoneAccessEntries is used" +"962887a","OSFile22","Basic","Desktop","Get-NTFSEffectiveAccess.Should use the current location when -Path is omitted" +"962887a","OSFile22","Basic","Desktop","Get-NTFSEffectiveAccess.Should warn once that the Security privilege is missing" +"962887a","OSFile22","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for . and warn no more than for localhost" +"962887a","OSFile22","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for LOCALHOST and warn no more than for localhost" +"962887a","OSFile22","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSFile22 and warn no more than for localhost" +"962887a","OSFile22","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSFile22.osmatrix.net and warn no more than for localhost" +"962887a","OSFile22","Basic","Desktop","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn" +"962887a","OSFile22","Basic","Desktop","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn for an empty -ServerName" +"962887a","OSFile25","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for break after its first object and change nothing else" +"962887a","OSFile25","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for continue after its first object and change nothing else" +"962887a","OSFile25","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop after the first object for Select-Object -First 1 and change nothing else" +"962887a","OSFile25","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (throw) of the later command and change nothing else" +"962887a","OSFile25","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else" +"962887a","OSFile25","Basic","Core","A path that does not exist.Get-NTFSEffectiveAccess should write a ReadFileError for it and continue with the next path" +"962887a","OSFile25","Basic","Core","An item whose owner may not read its permissions.Get-NTFSEffectiveAccess should write a ReadSecurityError for it and continue with the next path" +"962887a","OSFile25","Basic","Core","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each Path and return no access entry" +"962887a","OSFile25","Basic","Core","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each SecurityDescriptor and return no access entry" +"962887a","OSFile25","Basic","Core","Get-NTFSEffectiveAccess.Should compute the effective access of a security descriptor" +"962887a","OSFile25","Basic","Core","Get-NTFSEffectiveAccess.Should leave out an account without access when -ExcludeNoneAccessEntries is used" +"962887a","OSFile25","Basic","Core","Get-NTFSEffectiveAccess.Should return an account with access when -ExcludeNoneAccessEntries is used" +"962887a","OSFile25","Basic","Core","Get-NTFSEffectiveAccess.Should use the current location when -Path is omitted" +"962887a","OSFile25","Basic","Core","Get-NTFSEffectiveAccess.Should warn once that the Security privilege is missing" +"962887a","OSFile25","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for . and warn no more than for localhost" +"962887a","OSFile25","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for LOCALHOST and warn no more than for localhost" +"962887a","OSFile25","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSFILE25 and warn no more than for localhost" +"962887a","OSFile25","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSFile25.osmatrix.net and warn no more than for localhost" +"962887a","OSFile25","Basic","Core","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn" +"962887a","OSFile25","Basic","Core","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn for an empty -ServerName" +"962887a","OSFile25","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for break after its first object and change nothing else" +"962887a","OSFile25","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for continue after its first object and change nothing else" +"962887a","OSFile25","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop after the first object for Select-Object -First 1 and change nothing else" +"962887a","OSFile25","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (throw) of the later command and change nothing else" +"962887a","OSFile25","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else" +"962887a","OSFile25","Basic","Desktop","A path that does not exist.Get-NTFSEffectiveAccess should write a ReadFileError for it and continue with the next path" +"962887a","OSFile25","Basic","Desktop","An item whose owner may not read its permissions.Get-NTFSEffectiveAccess should write a ReadSecurityError for it and continue with the next path" +"962887a","OSFile25","Basic","Desktop","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each Path and return no access entry" +"962887a","OSFile25","Basic","Desktop","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each SecurityDescriptor and return no access entry" +"962887a","OSFile25","Basic","Desktop","Get-NTFSEffectiveAccess.Should compute the effective access of a security descriptor" +"962887a","OSFile25","Basic","Desktop","Get-NTFSEffectiveAccess.Should leave out an account without access when -ExcludeNoneAccessEntries is used" +"962887a","OSFile25","Basic","Desktop","Get-NTFSEffectiveAccess.Should return an account with access when -ExcludeNoneAccessEntries is used" +"962887a","OSFile25","Basic","Desktop","Get-NTFSEffectiveAccess.Should use the current location when -Path is omitted" +"962887a","OSFile25","Basic","Desktop","Get-NTFSEffectiveAccess.Should warn once that the Security privilege is missing" +"962887a","OSFile25","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for . and warn no more than for localhost" +"962887a","OSFile25","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for LOCALHOST and warn no more than for localhost" +"962887a","OSFile25","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSFile25 and warn no more than for localhost" +"962887a","OSFile25","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSFile25.osmatrix.net and warn no more than for localhost" +"962887a","OSFile25","Basic","Desktop","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn" +"962887a","OSFile25","Basic","Desktop","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn for an empty -ServerName" +"962887a","OSWin11","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for break after its first object and change nothing else" +"962887a","OSWin11","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for continue after its first object and change nothing else" +"962887a","OSWin11","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop after the first object for Select-Object -First 1 and change nothing else" +"962887a","OSWin11","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (throw) of the later command and change nothing else" +"962887a","OSWin11","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else" +"962887a","OSWin11","Basic","Core","A path that does not exist.Get-NTFSEffectiveAccess should write a ReadFileError for it and continue with the next path" +"962887a","OSWin11","Basic","Core","An item whose owner may not read its permissions.Get-NTFSEffectiveAccess should write a ReadSecurityError for it and continue with the next path" +"962887a","OSWin11","Basic","Core","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each Path and return no access entry" +"962887a","OSWin11","Basic","Core","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each SecurityDescriptor and return no access entry" +"962887a","OSWin11","Basic","Core","Get-NTFSEffectiveAccess.Should compute the effective access of a security descriptor" +"962887a","OSWin11","Basic","Core","Get-NTFSEffectiveAccess.Should leave out an account without access when -ExcludeNoneAccessEntries is used" +"962887a","OSWin11","Basic","Core","Get-NTFSEffectiveAccess.Should return an account with access when -ExcludeNoneAccessEntries is used" +"962887a","OSWin11","Basic","Core","Get-NTFSEffectiveAccess.Should use the current location when -Path is omitted" +"962887a","OSWin11","Basic","Core","Get-NTFSEffectiveAccess.Should warn once that the Security privilege is missing" +"962887a","OSWin11","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for . and warn no more than for localhost" +"962887a","OSWin11","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for LOCALHOST and warn no more than for localhost" +"962887a","OSWin11","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSWIN11 and warn no more than for localhost" +"962887a","OSWin11","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSWin11.osmatrix.net and warn no more than for localhost" +"962887a","OSWin11","Basic","Core","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn" +"962887a","OSWin11","Basic","Core","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn for an empty -ServerName" +"962887a","OSWin11","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for break after its first object and change nothing else" +"962887a","OSWin11","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for continue after its first object and change nothing else" +"962887a","OSWin11","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop after the first object for Select-Object -First 1 and change nothing else" +"962887a","OSWin11","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (throw) of the later command and change nothing else" +"962887a","OSWin11","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else" +"962887a","OSWin11","Basic","Desktop","A path that does not exist.Get-NTFSEffectiveAccess should write a ReadFileError for it and continue with the next path" +"962887a","OSWin11","Basic","Desktop","An item whose owner may not read its permissions.Get-NTFSEffectiveAccess should write a ReadSecurityError for it and continue with the next path" +"962887a","OSWin11","Basic","Desktop","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each Path and return no access entry" +"962887a","OSWin11","Basic","Desktop","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each SecurityDescriptor and return no access entry" +"962887a","OSWin11","Basic","Desktop","Get-NTFSEffectiveAccess.Should compute the effective access of a security descriptor" +"962887a","OSWin11","Basic","Desktop","Get-NTFSEffectiveAccess.Should leave out an account without access when -ExcludeNoneAccessEntries is used" +"962887a","OSWin11","Basic","Desktop","Get-NTFSEffectiveAccess.Should return an account with access when -ExcludeNoneAccessEntries is used" +"962887a","OSWin11","Basic","Desktop","Get-NTFSEffectiveAccess.Should use the current location when -Path is omitted" +"962887a","OSWin11","Basic","Desktop","Get-NTFSEffectiveAccess.Should warn once that the Security privilege is missing" +"962887a","OSWin11","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for . and warn no more than for localhost" +"962887a","OSWin11","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for LOCALHOST and warn no more than for localhost" +"962887a","OSWin11","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSWin11 and warn no more than for localhost" +"962887a","OSWin11","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSWin11.osmatrix.net and warn no more than for localhost" +"962887a","OSWin11","Basic","Desktop","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn" +"962887a","OSWin11","Basic","Desktop","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn for an empty -ServerName" +"962887a","OSWin11E","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for break after its first object and change nothing else" +"962887a","OSWin11E","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for continue after its first object and change nothing else" +"962887a","OSWin11E","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop after the first object for Select-Object -First 1 and change nothing else" +"962887a","OSWin11E","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (throw) of the later command and change nothing else" +"962887a","OSWin11E","Basic","Core","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else" +"962887a","OSWin11E","Basic","Core","A path that does not exist.Get-NTFSEffectiveAccess should write a ReadFileError for it and continue with the next path" +"962887a","OSWin11E","Basic","Core","An item whose owner may not read its permissions.Get-NTFSEffectiveAccess should write a ReadSecurityError for it and continue with the next path" +"962887a","OSWin11E","Basic","Core","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each Path and return no access entry" +"962887a","OSWin11E","Basic","Core","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each SecurityDescriptor and return no access entry" +"962887a","OSWin11E","Basic","Core","Get-NTFSEffectiveAccess.Should compute the effective access of a security descriptor" +"962887a","OSWin11E","Basic","Core","Get-NTFSEffectiveAccess.Should leave out an account without access when -ExcludeNoneAccessEntries is used" +"962887a","OSWin11E","Basic","Core","Get-NTFSEffectiveAccess.Should return an account with access when -ExcludeNoneAccessEntries is used" +"962887a","OSWin11E","Basic","Core","Get-NTFSEffectiveAccess.Should use the current location when -Path is omitted" +"962887a","OSWin11E","Basic","Core","Get-NTFSEffectiveAccess.Should warn once that the Security privilege is missing" +"962887a","OSWin11E","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for . and warn no more than for localhost" +"962887a","OSWin11E","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for LOCALHOST and warn no more than for localhost" +"962887a","OSWin11E","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSWIN11E and warn no more than for localhost" +"962887a","OSWin11E","Basic","Core","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSWin11E.osmatrix.net and warn no more than for localhost" +"962887a","OSWin11E","Basic","Core","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn" +"962887a","OSWin11E","Basic","Core","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn for an empty -ServerName" +"962887a","OSWin11E","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for break after its first object and change nothing else" +"962887a","OSWin11E","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should leave the loop for continue after its first object and change nothing else" +"962887a","OSWin11E","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop after the first object for Select-Object -First 1 and change nothing else" +"962887a","OSWin11E","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (throw) of the later command and change nothing else" +"962887a","OSWin11E","Basic","Desktop","A later command that ends the pipeline.Get-NTFSEffectiveAccess should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else" +"962887a","OSWin11E","Basic","Desktop","A path that does not exist.Get-NTFSEffectiveAccess should write a ReadFileError for it and continue with the next path" +"962887a","OSWin11E","Basic","Desktop","An item whose owner may not read its permissions.Get-NTFSEffectiveAccess should write a ReadSecurityError for it and continue with the next path" +"962887a","OSWin11E","Basic","Desktop","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each Path and return no access entry" +"962887a","OSWin11E","Basic","Desktop","Get-NTFSEffectiveAccess for an unresolved identity.Should report the native identity error for each SecurityDescriptor and return no access entry" +"962887a","OSWin11E","Basic","Desktop","Get-NTFSEffectiveAccess.Should compute the effective access of a security descriptor" +"962887a","OSWin11E","Basic","Desktop","Get-NTFSEffectiveAccess.Should leave out an account without access when -ExcludeNoneAccessEntries is used" +"962887a","OSWin11E","Basic","Desktop","Get-NTFSEffectiveAccess.Should return an account with access when -ExcludeNoneAccessEntries is used" +"962887a","OSWin11E","Basic","Desktop","Get-NTFSEffectiveAccess.Should use the current location when -Path is omitted" +"962887a","OSWin11E","Basic","Desktop","Get-NTFSEffectiveAccess.Should warn once that the Security privilege is missing" +"962887a","OSWin11E","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for . and warn no more than for localhost" +"962887a","OSWin11E","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for LOCALHOST and warn no more than for localhost" +"962887a","OSWin11E","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSWin11E and warn no more than for localhost" +"962887a","OSWin11E","Basic","Desktop","Get-NTFSEffectiveAccess.When -ServerName names this computer.Should return the result of localhost for OSWin11E.osmatrix.net and warn no more than for localhost" +"962887a","OSWin11E","Basic","Desktop","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn" +"962887a","OSWin11E","Basic","Desktop","Get-NTFSEffectiveAccess.When the computer of -ServerName cannot be reached.Should return the result of this computer and warn for an empty -ServerName" diff --git a/Tests/Lab/Acceptance-2026-10-10-os-matrix-FirstLab.csv b/Tests/Lab/Acceptance-2026-10-10-os-matrix-FirstLab.csv new file mode 100644 index 0000000..19a51c4 --- /dev/null +++ b/Tests/Lab/Acceptance-2026-10-10-os-matrix-FirstLab.csv @@ -0,0 +1,9 @@ +"Version","Edition","Role","Account","ExitCode","Passed","Failed","Skipped" +"local","Desktop","Delegate","A\NtfsLiveDelegate","0","69","0","0" +"local","Desktop","ServerAdmin","A\NtfsLiveServerAdmin","0","36","0","0" +"local","Desktop","Admin","A\NtfsLiveAdmin","0","64","0","0" +"local","Desktop","Server","A\install","0","76","0","1" +"local","Core","Delegate","A\NtfsLiveDelegate","0","69","0","0" +"local","Core","ServerAdmin","A\NtfsLiveServerAdmin","0","36","0","0" +"local","Core","Admin","A\NtfsLiveAdmin","0","64","0","0" +"local","Core","Server","A\install","0","76","0","1" diff --git a/Tests/Lab/Acceptance-2026-10-10-os-matrix-LocalSuite.csv b/Tests/Lab/Acceptance-2026-10-10-os-matrix-LocalSuite.csv new file mode 100644 index 0000000..c8ebe49 --- /dev/null +++ b/Tests/Lab/Acceptance-2026-10-10-os-matrix-LocalSuite.csv @@ -0,0 +1,57 @@ +"Candidate","Machine","Os","Mode","Edition","PowerShell","Result","Passed","Failed","Skipped","Total","Seconds" +"83149ee","OSFile22","Windows Server 2022 Datacenter 20348.4773","Basic","Core","7.6.3","Failed","760","20","231","1011","109" +"83149ee","OSFile22","Windows Server 2022 Datacenter 20348.4773","Basic","Desktop","5.1.20348.4294","Failed","762","20","229","1011","121" +"83149ee","OSFile22","Windows Server 2022 Datacenter 20348.4773","Elevated","Core","7.6.3","Failed","988","4","19","1011","230" +"83149ee","OSFile22","Windows Server 2022 Datacenter 20348.4773","Elevated","Desktop","5.1.20348.4294","Failed","990","4","17","1011","125" +"83149ee","OSFile25","Windows Server 2025 Datacenter 26100.32690","Basic","Core","7.6.3","Failed","760","20","231","1011","118" +"83149ee","OSFile25","Windows Server 2025 Datacenter 26100.32690","Basic","Desktop","5.1.26100.32684","Failed","762","20","229","1011","114" +"83149ee","OSFile25","Windows Server 2025 Datacenter 26100.32690","Elevated","Core","7.6.3","Failed","988","4","19","1011","238" +"83149ee","OSFile25","Windows Server 2025 Datacenter 26100.32690","Elevated","Desktop","5.1.26100.32684","Failed","990","4","17","1011","259" +"962887a","LOCAL","Windows Server 2025 Datacenter 26100.33438","Basic","Core","7.6.6","Passed","779","0","231","1010","20" +"962887a","LOCAL","Windows Server 2025 Datacenter 26100.33438","Basic","Desktop","5.1.26100.33438","Passed","781","0","229","1010","22" +"962887a","LOCAL","Windows Server 2025 Datacenter 26100.33438","Elevated","Core","7.6.6","Passed","991","0","19","1010","91" +"962887a","LOCAL","Windows Server 2025 Datacenter 26100.33438","Elevated","Desktop","5.1.26100.33438","Passed","993","0","17","1010","97" +"962887a","OSFile19","Windows Server 2019 Datacenter 17763.1217","Basic","Core","7.6.3","Failed","760","20","231","1011","111" +"962887a","OSFile19","Windows Server 2019 Datacenter 17763.1217","Basic","Desktop","5.1.17763.1007","Failed","762","20","229","1011","120" +"962887a","OSFile19","Windows Server 2019 Datacenter 17763.1217","Elevated","Core","7.6.3","Passed","992","0","19","1011","205" +"962887a","OSFile19","Windows Server 2019 Datacenter 17763.1217","Elevated","Desktop","5.1.17763.1007","Passed","994","0","17","1011","96" +"962887a","OSFile22","Windows Server 2022 Datacenter 20348.4773","Basic","Core","7.6.3","Failed","760","20","231","1011","120" +"962887a","OSFile22","Windows Server 2022 Datacenter 20348.4773","Basic","Desktop","5.1.20348.4294","Failed","762","20","229","1011","117" +"962887a","OSFile22","Windows Server 2022 Datacenter 20348.4773","Elevated","Core","7.6.3","Passed","992","0","19","1011","230" +"962887a","OSFile22","Windows Server 2022 Datacenter 20348.4773","Elevated","Desktop","5.1.20348.4294","Passed","994","0","17","1011","123" +"962887a","OSFile25","Windows Server 2025 Datacenter 26100.32690","Basic","Core","7.6.3","Failed","760","20","231","1011","108" +"962887a","OSFile25","Windows Server 2025 Datacenter 26100.32690","Basic","Desktop","5.1.26100.32684","Failed","762","20","229","1011","114" +"962887a","OSFile25","Windows Server 2025 Datacenter 26100.32690","Elevated","Core","7.6.3","Passed","992","0","19","1011","226" +"962887a","OSFile25","Windows Server 2025 Datacenter 26100.32690","Elevated","Desktop","5.1.26100.32684","Passed","994","0","17","1011","256" +"962887a","OSWin11","Windows 10 Pro 28000.1836","Basic","Core","7.6.3","Failed","760","20","231","1011","30" +"962887a","OSWin11","Windows 10 Pro 28000.1836","Basic","Desktop","5.1.28000.1830","Failed","762","20","229","1011","120" +"962887a","OSWin11","Windows 10 Pro 28000.1836","Elevated","Core","7.6.3","Passed","992","0","19","1011","104" +"962887a","OSWin11","Windows 10 Pro 28000.1836","Elevated","Desktop","5.1.28000.1830","Passed","994","0","17","1011","170" +"962887a","OSWin11E","Windows 10 Enterprise Evaluation 22621.525","Basic","Core","7.6.3","Failed","760","20","231","1011","216" +"962887a","OSWin11E","Windows 10 Enterprise Evaluation 22621.525","Basic","Desktop","5.1.22621.169","Failed","762","20","229","1011","347" +"962887a","OSWin11E","Windows 10 Enterprise Evaluation 22621.525","Elevated","Core","7.6.3","Passed","992","0","19","1011","403" +"962887a","OSWin11E","Windows 10 Enterprise Evaluation 22621.525","Elevated","Desktop","5.1.22621.169","Passed","994","0","17","1011","431" +"fdd7a8b","LOCAL","Windows Server 2025 Datacenter 26100.33438","Basic","Core","7.6.6","Passed","779","0","231","1010","20" +"fdd7a8b","LOCAL","Windows Server 2025 Datacenter 26100.33438","Basic","Desktop","5.1.26100.33438","Passed","781","0","229","1010","23" +"fdd7a8b","LOCAL","Windows Server 2025 Datacenter 26100.33438","Elevated","Core","7.6.6","Passed","991","0","19","1010","92" +"fdd7a8b","LOCAL","Windows Server 2025 Datacenter 26100.33438","Elevated","Desktop","5.1.26100.33438","Passed","993","0","17","1010","104" +"fdd7a8b","OSFile19","Windows Server 2019 Datacenter 17763.1217","Basic","Core","7.6.3","Passed","780","0","231","1011","99" +"fdd7a8b","OSFile19","Windows Server 2019 Datacenter 17763.1217","Basic","Desktop","5.1.17763.1007","Passed","782","0","229","1011","108" +"fdd7a8b","OSFile19","Windows Server 2019 Datacenter 17763.1217","Elevated","Core","7.6.3","Passed","992","0","19","1011","197" +"fdd7a8b","OSFile19","Windows Server 2019 Datacenter 17763.1217","Elevated","Desktop","5.1.17763.1007","Passed","994","0","17","1011","101" +"fdd7a8b","OSFile22","Windows Server 2022 Datacenter 20348.4773","Basic","Core","7.6.3","Passed","780","0","231","1011","115" +"fdd7a8b","OSFile22","Windows Server 2022 Datacenter 20348.4773","Basic","Desktop","5.1.20348.4294","Passed","782","0","229","1011","130" +"fdd7a8b","OSFile22","Windows Server 2022 Datacenter 20348.4773","Elevated","Core","7.6.3","Passed","992","0","19","1011","213" +"fdd7a8b","OSFile22","Windows Server 2022 Datacenter 20348.4773","Elevated","Desktop","5.1.20348.4294","Passed","994","0","17","1011","127" +"fdd7a8b","OSFile25","Windows Server 2025 Datacenter 26100.32690","Basic","Core","7.6.3","Passed","780","0","231","1011","120" +"fdd7a8b","OSFile25","Windows Server 2025 Datacenter 26100.32690","Basic","Desktop","5.1.26100.32684","Passed","782","0","229","1011","112" +"fdd7a8b","OSFile25","Windows Server 2025 Datacenter 26100.32690","Elevated","Core","7.6.3","Passed","992","0","19","1011","219" +"fdd7a8b","OSFile25","Windows Server 2025 Datacenter 26100.32690","Elevated","Desktop","5.1.26100.32684","Passed","994","0","17","1011","257" +"fdd7a8b","OSWin11","Windows 10 Pro 28000.1836","Basic","Core","7.6.3","Passed","780","0","231","1011","37" +"fdd7a8b","OSWin11","Windows 10 Pro 28000.1836","Basic","Desktop","5.1.28000.1830","Passed","782","0","229","1011","126" +"fdd7a8b","OSWin11","Windows 10 Pro 28000.1836","Elevated","Core","7.6.3","Passed","992","0","19","1011","104" +"fdd7a8b","OSWin11","Windows 10 Pro 28000.1836","Elevated","Desktop","5.1.28000.1830","Passed","994","0","17","1011","180" +"fdd7a8b","OSWin11E","Windows 10 Enterprise Evaluation 22621.525","Basic","Core","7.6.3","Passed","780","0","231","1011","33" +"fdd7a8b","OSWin11E","Windows 10 Enterprise Evaluation 22621.525","Basic","Desktop","5.1.22621.169","Passed","782","0","229","1011","167" +"fdd7a8b","OSWin11E","Windows 10 Enterprise Evaluation 22621.525","Elevated","Core","7.6.3","Passed","992","0","19","1011","113" +"fdd7a8b","OSWin11E","Windows 10 Enterprise Evaluation 22621.525","Elevated","Desktop","5.1.22621.169","Passed","994","0","17","1011","132" diff --git a/Tests/Lab/Acceptance-2026-10-10-os-matrix-Timeline.csv b/Tests/Lab/Acceptance-2026-10-10-os-matrix-Timeline.csv new file mode 100644 index 0000000..8bbb946 --- /dev/null +++ b/Tests/Lab/Acceptance-2026-10-10-os-matrix-Timeline.csv @@ -0,0 +1,44 @@ +"Run","Candidate","FileServer","Edition","Subject","SubjectRid","SameNameAsPreviousCell","SameAccountAsPreviousCell","PreviousRemoval","AccountsCreated","AdminRoleStarted","MinutesRemovalToCreation","MinutesCreationToAdmin","MinutesRemovalToAdmin","T1ServerNameFileServer","T2DefaultServerName","T3UnreachableServerName","EffectiveAccessFailures" +"rc7c","83149ee","OSFile19","Desktop","osmatrix\NtfsLiveSubject","1110","False","False","","2026-10-09 23:43:43","2026-10-09 23:46:33","","2.8","","pass 276ms","pass 43ms","pass 12.1s","0" +"rc7c","83149ee","OSFile19","Core","osmatrix\NtfsLiveSubject","1110","False","False","","2026-10-09 23:43:43","2026-10-09 23:48:09","","4.4","","pass 165ms","pass 24ms","pass 11.06s","0" +"rc7c","83149ee","OSFile22","Desktop","osmatrix\NtfsLiveSubject","1110","True","True","","2026-10-09 23:49:38","2026-10-09 23:52:16","","2.6","","pass 439ms","pass 37ms","pass 11.47s","0" +"rc7c","83149ee","OSFile22","Core","osmatrix\NtfsLiveSubject","1110","True","True","","2026-10-09 23:49:38","2026-10-09 23:53:48","","4.2","","pass 164ms","pass 33ms","pass 11.76s","0" +"rc7c","83149ee","OSFile25","Desktop","osmatrix\NtfsLiveSubject","1110","True","True","","2026-10-09 23:55:37","2026-10-09 23:58:32","","2.9","","pass 423ms","pass 41ms","pass 11.95s","0" +"rc7c","83149ee","OSFile25","Core","osmatrix\NtfsLiveSubject","1110","True","True","","2026-10-09 23:55:37","2026-10-10 00:01:33","","5.9","","pass 129ms","pass 40ms","pass 11.58s","0" +"rc7e","83149ee","OSFile19","Desktop","osmatrix\NtfsLiveSubject","1130","True","False","2026-10-10 00:16:22","2026-10-10 00:20:59","2026-10-10 00:23:36","4.6","2.6","7.2","pass 250ms","pass 37ms","pass 11.55s","0" +"rc7e","83149ee","OSFile19","Core","osmatrix\NtfsLiveSubject","1130","True","False","2026-10-10 00:16:22","2026-10-10 00:20:59","2026-10-10 00:25:03","4.6","4.1","8.7","pass 147ms","pass 26ms","pass 11.16s","0" +"rc7e","83149ee","OSFile22","Desktop","osmatrix\NtfsLiveSubject","1130","True","True","","2026-10-10 00:26:33","2026-10-10 00:29:19","","2.8","","pass 445ms","pass 36ms","pass 12.11s","0" +"rc7e","83149ee","OSFile22","Core","osmatrix\NtfsLiveSubject","1130","True","True","","2026-10-10 00:26:33","2026-10-10 00:30:47","","4.2","","pass 158ms","pass 31ms","pass 12.09s","0" +"rc7e","83149ee","OSFile25","Desktop","osmatrix\NtfsLiveSubject","1141","True","False","2026-10-10 00:31:54","2026-10-10 00:32:55","2026-10-10 00:35:48","1.0","2.9","3.9","pass 256ms","pass 28ms","pass 12.11s","0" +"rc7e","83149ee","OSFile25","Core","osmatrix\NtfsLiveSubject","1141","True","False","2026-10-10 00:31:54","2026-10-10 00:32:55","2026-10-10 00:41:22","1.0","8.5","9.5","pass 137ms","pass 50ms","pass 11.22s","0" +"rc7f","fdd7a8b","OSFile19","Desktop","osmatrix\NtfsLiveSubject","1153","True","False","2026-10-10 00:44:09","2026-10-10 02:10:50","2026-10-10 02:13:46","86.7","2.9","89.6","pass 287ms","pass 44ms","pass 11.53s","0" +"rc7f","fdd7a8b","OSFile19","Core","osmatrix\NtfsLiveSubject","1153","True","False","2026-10-10 00:44:09","2026-10-10 02:10:50","2026-10-10 02:15:21","86.7","4.5","91.2","pass 143ms","pass 31ms","pass 11.26s","0" +"rc7f","fdd7a8b","OSFile22","Desktop","osmatrix\NtfsLiveSubject","1162","True","False","2026-10-10 02:16:36","2026-10-10 02:17:38","2026-10-10 02:20:32","1.0","2.9","3.9","pass 302ms","FAIL 553ms 0x100000","pass 11.26s","1" +"rc7f","fdd7a8b","OSFile22","Core","osmatrix\NtfsLiveSubject","1162","True","False","2026-10-10 02:16:36","2026-10-10 02:17:38","2026-10-10 02:22:01","1.0","4.4","5.4","pass 174ms","FAIL 80ms 0x100000","pass 11.72s","1" +"rc7g","fdd7a8b","OSFile25","Desktop","osmatrix\NtfsLiveSubject","1171","True","False","2026-10-10 02:23:08","2026-10-10 02:28:21","2026-10-10 02:31:16","5.2","2.9","8.1","pass 265ms","pass 35ms","pass 11.65s","0" +"rc7g","fdd7a8b","OSFile25","Core","osmatrix\NtfsLiveSubject","1171","True","False","2026-10-10 02:23:08","2026-10-10 02:28:21","2026-10-10 02:34:21","5.2","6.0","11.2","pass 161ms","pass 27ms","pass 12.04s","0" +"rc7h","fdd7a8b","OSFile22","Desktop","osmatrix\NtfsLiveSubject","1180","True","False","2026-10-10 02:35:34","2026-10-10 02:36:51","2026-10-10 02:39:41","1.3","2.8","4.1","pass 292ms","FAIL 626ms 0x100000","pass 11.23s","1" +"rc7h","fdd7a8b","OSFile22","Core","osmatrix\NtfsLiveSubject","1180","True","False","2026-10-10 02:35:34","2026-10-10 02:36:51","2026-10-10 02:41:11","1.3","4.3","5.6","pass 174ms","FAIL 80ms 0x100000","pass 11.3s","1" +"rc7i","fdd7a8b","OSFile22","Desktop","osmatrix\NtfsLiveSubject","1189","True","False","2026-10-10 02:42:17","2026-10-10 02:46:06","2026-10-10 02:49:01","3.8","2.9","6.7","FAIL 827ms 0x100000","pass 40ms","pass 11.02s","1" +"rc7i","fdd7a8b","OSFile22","Core","osmatrix\NtfsLiveSubject","1189","True","False","2026-10-10 02:42:17","2026-10-10 02:46:06","2026-10-10 02:50:32","3.8","4.4","8.3","pass 169ms","pass 34ms","pass 12.05s","0" +"rc7j","962887a","OSFile22","Desktop","osmatrix\NtfsLiveSubject","1198","True","False","2026-10-10 02:51:38","2026-10-10 02:52:37","2026-10-10 02:55:28","1.0","2.9","3.8","FAIL 884ms 0x100000","FAIL 36ms 0x100000","pass 11s","2" +"rc7j","962887a","OSFile22","Core","osmatrix\NtfsLiveSubject","1198","True","False","2026-10-10 02:51:38","2026-10-10 02:52:37","2026-10-10 02:56:55","1.0","4.3","5.3","FAIL 226ms 0x100000","FAIL 34ms 0x100000","pass 11.46s","2" +"rc7k","83149ee","OSFile22","Desktop","osmatrix\NtfsLiveSubject","1207","True","False","2026-10-10 02:58:00","2026-10-10 02:58:58","2026-10-10 03:01:50","1.0","2.9","3.8","pass 312ms","pass 36ms","pass 12.26s","0" +"rc7k","83149ee","OSFile22","Core","osmatrix\NtfsLiveSubject","1207","True","False","2026-10-10 02:58:00","2026-10-10 02:58:58","2026-10-10 03:03:20","1.0","4.4","5.3","pass 163ms","pass 25ms","pass 12.1s","0" +"rc7l","fdd7a8b","OSFile19","Desktop","osmatrix\NtfsLiveSubject0602","1279","False","False","2026-10-10 03:04:28","2026-10-10 03:45:30","2026-10-10 03:48:27","41.0","3.0","44.0","pass 300ms","pass 52ms","pass 11.59s","0" +"rc7l","fdd7a8b","OSFile19","Core","osmatrix\NtfsLiveSubject0602","1279","False","False","2026-10-10 03:04:28","2026-10-10 03:45:30","2026-10-10 03:49:59","41.0","4.5","45.5","pass 148ms","pass 56ms","pass 11.03s","0" +"rc7l","fdd7a8b","OSFile22","Desktop","osmatrix\NtfsLiveSubject6688","1290","False","False","2026-10-10 03:51:09","2026-10-10 03:52:12","2026-10-10 03:55:03","1.1","2.9","3.9","pass 298ms","pass 42ms","pass 11.35s","0" +"rc7l","fdd7a8b","OSFile22","Core","osmatrix\NtfsLiveSubject6688","1290","False","False","2026-10-10 03:51:09","2026-10-10 03:52:12","2026-10-10 03:56:29","1.1","4.3","5.3","pass 141ms","pass 34ms","pass 11.24s","0" +"rc7l","fdd7a8b","OSFile25","Desktop","osmatrix\NtfsLiveSubject4884","1298","False","False","2026-10-10 03:57:33","2026-10-10 03:58:35","2026-10-10 04:01:28","1.0","2.9","3.9","pass 303ms","pass 46ms","pass 11.29s","0" +"rc7l","fdd7a8b","OSFile25","Core","osmatrix\NtfsLiveSubject4884","1298","False","False","2026-10-10 03:57:33","2026-10-10 03:58:35","2026-10-10 04:04:32","1.0","6.0","7.0","pass 152ms","pass 27ms","pass 12.12s","0" +"ab0","fdd7a8b","OSFile22","Desktop","osmatrix\NtfsLiveSubject","1318","False","False","2026-10-10 04:06:55","2026-10-10 04:58:03","2026-10-10 05:00:57","51.1","2.9","54.0","pass 289ms","pass 42ms","pass 11.51s","0" +"ab1","83149ee","OSFile22","Desktop","osmatrix\NtfsLiveSubject","1326","True","False","2026-10-10 05:02:19","2026-10-10 05:03:18","2026-10-10 05:06:09","1.0","2.9","3.8","FAIL 817ms 0x100000","FAIL 34ms 0x100000","pass 11.17s","2" +"ab2","fdd7a8b","OSFile22","Desktop","osmatrix\NtfsLiveSubject","1334","True","False","2026-10-10 05:07:28","2026-10-10 05:08:28","2026-10-10 05:11:12","1.0","2.7","3.7","pass 297ms","pass 55ms","pass 12.2s","0" +"ab3","fdd7a8b","OSFile22","Desktop","osmatrix\NtfsLiveSubject","1342","True","False","2026-10-10 05:12:32","2026-10-10 05:13:31","2026-10-10 05:16:23","1.0","2.9","3.9","FAIL 825ms 0x100000","FAIL 36ms 0x100000","pass 12.18s","2" +"ab4","83149ee","OSFile22","Desktop","osmatrix\NtfsLiveSubject","1350","True","False","2026-10-10 05:17:43","2026-10-10 05:18:42","2026-10-10 05:21:34","1.0","2.9","3.9","pass 283ms","pass 47ms","pass 12.25s","0" +"ab5","83149ee","OSFile22","Desktop","osmatrix\NtfsLiveSubject","1358","True","False","2026-10-10 05:23:04","2026-10-10 05:24:05","2026-10-10 05:26:57","1.0","2.9","3.9","FAIL 804ms 0x100000","FAIL 35ms 0x100000","pass 11.71s","2" +"ab6","fdd7a8b","OSFile22","Desktop","osmatrix\NtfsLiveSubject","1366","True","False","2026-10-10 05:28:16","2026-10-10 05:29:14","2026-10-10 05:32:04","1.0","2.8","3.8","pass 289ms","pass 51ms","pass 11.13s","0" +"ab7","83149ee","OSFile22","Desktop","osmatrix\NtfsLiveSubject8013","1374","False","False","2026-10-10 05:33:22","2026-10-10 05:37:15","2026-10-10 05:40:11","3.9","2.9","6.8","pass 280ms","pass 45ms","pass 12.2s","0" +"ab8","fdd7a8b","OSFile22","Desktop","osmatrix\NtfsLiveSubject0900","1382","False","False","2026-10-10 05:41:34","2026-10-10 05:42:38","2026-10-10 05:45:32","1.1","2.9","4.0","pass 300ms","pass 40ms","pass 11.89s","0" +"ab9","83149ee","OSFile22","Desktop","osmatrix\NtfsLiveSubject7705","1391","False","False","2026-10-10 05:46:54","2026-10-10 05:48:00","2026-10-10 05:50:54","1.1","2.9","4.0","pass 265ms","pass 40ms","pass 12.16s","0" +"ab10","fdd7a8b","OSFile22","Desktop","osmatrix\NtfsLiveSubject8799","1399","False","False","2026-10-10 05:52:14","2026-10-10 05:53:18","2026-10-10 05:56:12","1.1","2.9","4.0","pass 272ms","pass 35ms","pass 11.07s","0" diff --git a/Tests/Lab/Acceptance-2026-10-10-os-matrix.md b/Tests/Lab/Acceptance-2026-10-10-os-matrix.md new file mode 100644 index 0000000..4ccc366 --- /dev/null +++ b/Tests/Lab/Acceptance-2026-10-10-os-matrix.md @@ -0,0 +1,760 @@ +# Operating-system matrix acceptance, 2026-10-10 + +Live and local acceptance of NTFSSecurity candidates on more operating systems +than the first lab has (Decision 24, handoff 2 of the 5.0.0 quality gate): +Windows Server 2019, 2022, and 2025 as file servers and Windows 11 as client, in +Windows PowerShell 5.1 and PowerShell 7, elevated and as a basic user. The +candidates are local builds of `ai/quality-gate-lab-matrix`, tested from their +extracted packages with `-ModulePath`. None is a published package, so this +record isn't the acceptance of a release (see the limits at the end), and it +isn't a claim that the quality gate is complete. + +## Result + +- The module's own suite, 1,011 cases per configuration (1,010 on the host), ran + on five operating systems and on the host in four configurations each. The + final candidate (`fdd7a8b`) has no failure in any of the 24 runs; every + skipped test is also skipped on the host. +- The live controller ran for the final candidate in three cells of the matrix + (the Windows 11 client with each file server, both editions, every role) in + one sequence, after the fixture got a new account name for each new fixture: + 1,374 passed, 0 failed, 12 skipped (case 9 and the module test of the Server + role). An earlier run of the same cells with the old controller had failed in + the Windows Server 2022 cell. A replay showed that the baseline fails the same + way there, so the module doesn't decide the outcome: the failures depend on the + position of the cell (six replay runs can't rule out a small effect of the + module; see "The effective-access failures of the Admin role"). +- The final candidate also passed the live controller in the first lab, where + case 9 runs: 245 passed, 0 failed, 1 skipped in each edition (see "First lab, + final candidate (case 9)"). +- The matrix found three defects of the module, fixed in two commits on the + branch, and each was red on the machines where it shows before its fix and + green after it: `Get-NTFSInheritance -SecurityDescriptor` for an item without + audit entries and `Get-NTFSEffectiveAccess -ServerName ''` (`962887a`, two + fixes), and `Get-NTFSEffectiveAccess` for a user who isn't an administrator on + a computer in a domain (`fdd7a8b`). The first two showed on Windows Server 2022 + and 2025 and on Windows 11 26H1, the third on every machine of the domain. +- The controller had four defects of its own: three in cleanup and setup + (`7d47316`) and the reuse of the name of the account of case 3 (`1dec389`). + Cells that followed each other failed in the effective-access tests of the + Admin role when the account of case 3 was deleted and created again under the + same name: the remote authorization managers of the client and of the file + server returned no groups for the new account, for the baseline and for the + final candidate alike. A model with a lifetime of about ten minutes fits every run; the mechanism + in Windows isn't known. This looked like a regression of the module until the + baseline failed the same way in a replay of the same cells. +- Windows 11 26H1 (10.0.28000) can't keep a secure channel to the Windows + Server 2025 domain controller of this lab, so it runs the module's suite only. + The domain client is Windows 11 Enterprise Evaluation 22H2. +- Open: the published package in every cell and in the first lab (stage D of the + gate), and the maintainer's decisions listed at the end. The matrix lab has no + trusts, so case 9 runs only in the first lab. + +## Machines + +All machines are virtual machines on the Hyper-V host in the lab +`NtfsSecurityOsMatrixLab` (domain `osmatrix.net`, switch `192.168.12.0/24`), +which AutomatedLab deployed beside the other labs without touching them. The +.NET Framework release number is the one that the readiness check read. + +| Machine | Role | Operating system | Build | .NET Framework | Windows PowerShell | PowerShell 7 | +| --- | --- | --- | --- | ---: | --- | --- | +| OSDC1 | Root domain controller | Windows Server 2025 Datacenter | 10.0.26100.32690 | 533509 | 5.1.26100.32684 | 7.6.3 | +| OSFile19 | File server | Windows Server 2019 Datacenter | 10.0.17763.1217 | 461814 | 5.1.17763.1007 | 7.6.3 | +| OSFile22 | File server | Windows Server 2022 Datacenter | 10.0.20348.4773 | 528449 | 5.1.20348.4294 | 7.6.3 | +| OSFile25 | File server | Windows Server 2025 Datacenter | 10.0.26100.32690 | 533509 | 5.1.26100.32684 | 7.6.3 | +| OSWin11E | Client | Windows 11 Enterprise Evaluation 22H2 | 10.0.22621.525 | 533320 | 5.1.22621.169 | 7.6.3 | +| OSWin11 | Suite only | Windows 11 Pro 26H1 | 10.0.28000.1836 | 533510 | 5.1.28000.1830 | 7.6.3 | +| Host | Reference for the suite | Windows Server 2025 Datacenter | 10.0.26100.33438 | 533509 | 5.1.26100.33438 | 7.6.6 | + +Windows 11 reports `Windows 10` as the product name in the registry; the builds +are Windows 11. The VMs have no internet, so PowerShell 7.6.3 and Pester 5.7.1 +came from the host. Every machine passed a readiness check before a cell: WinRM +with the lab account, LDAP, Kerberos, the secure channel, the clocks, the tools, +and the Pester version. + +## Candidates and artifact identity + +Each candidate is a Release build (.NET Framework 4.5.2) in an isolated worktree +of its commit, packaged by `.github/scripts/New-ModulePackage.ps1`. All 11 files +of every tested module folder equal the extracted `NTFSSecurity.zip` byte for +byte. The builds aren't byte-reproducible: `PrivilegeControl.dll` and +`ProcessPrivileges.dll` differ between the candidates although no source of +theirs changed, so the hashes belong to one build each. + +| | Baseline `83149ee` | Candidate `962887a` | Final `fdd7a8b` | +| --- | --- | --- | --- | +| What it is | Head of #118 | Two fixes in the module | Three fixes in the module | +| `NTFSSecurity.dll` | `40D0C8A6B819F15A…` | `9AC1169F91687495…` | `B0631389E68C8244…` | +| `Security2.dll` | `804D199335CA0D6A…` | `FF314FACCF01676A…` | `A4D744579E8FF3BE…` | +| `NTFSSecurity.5.0.0-rc7.nupkg` | `2AAE3403A2D1C3AE…` | `ACFA247BCD5AD33D…` | `1A112B8CBBE27F9D…` | +| `NTFSSecurity.zip` | `A5AFA241DCA5DF87…` | `CD836E39C6B22EB3…` | `3DF48E5C590A9E38…` | + +The full SHA-256 values and the hashes of every result file are in the local +evidence (see the end). The tests of the suite are the files of the working +tree at the commit of the run. The live tests (Git blob +`efe36e5073b9b10742ca7de242ddcbe90d8eda62`) are those of `fdd7a8b` in every run +from `rc7f` to `rc7l` and in the first-lab run `fl1`; the replay `ab0` to `ab10` +ran the same file with one diagnostic test added (blob +`72c12fe09e4005e048a8aed0fa02b9a922c58f34`, see "The effective-access failures +of the Admin role"). The controller of the cells `rc7f` to `rc7k` and of the +replay cells `ab0` to `ab6` is the blob `683aee91ec8805d77a33b2d368acaf876724fa32` +(`fdd7a8b`). The cells of `rc7l` and the replay cells `ab7` to `ab10` ran with the +blob `9917cac5820ed20ed2eb5592eff06894677f9874` (`1dec389`), and the first-lab run +`fl1` with the blob `d269e0fe6ba5f72894dd2dcba5dca7f2dc56624f` (the head of the +branch then, which differs from `1dec389` by a comment). The earlier cells of the +baseline ran with the controller blobs `0b46427b…` and `d485b1d0…` (`rc7d` with +`4cac0d0b…`) and the live tests `67b85efe…`, before the cleanup fixes. + +## Method + +- **Module's own suite.** `Acceptance\Run-MatrixLocalSuite.ps1` copies the 18 + behavior test files and the candidate to a machine and runs them there in a + new Windows PowerShell process and a new PowerShell 7 process, elevated and as + a basic user. The basic user is the token that + `.github\scripts\Invoke-TestsAsBasicUser.ps1` makes (SAFER level Normal User), + so the tests that need a missing privilege skip in the elevated mode and run + in this one. The processes run as scheduled tasks with a batch logon at the + highest run level: a process that starts from a remoting session has every + privilege enabled and no credentials of its own, which eight tests don't + expect. The host runs the same stage as the reference. A skipped test counts + as a difference when only one side skips it; the skipped lists are compared as + multisets of test names. +- **Live controller.** `Acceptance\Run-MatrixSequence.ps1` runs, for each file + server with the client `OSWin11E`: the readiness of every machine, the + unmodified controller of the repository in both editions, the validation of + every role from the result files (`Validate-LabResults.ps1`, never from the + marker `DONE`), a snapshot of the fixture SIDs, the removal of the fixture, + and an independent check of the end state (`Test-MatrixCleanup.ps1`). +- **Probe.** `Acceptance\Probe-EffectiveAccess.ps1` asks + `Get-NTFSEffectiveAccess` the same questions under four tokens on one machine + and writes the result and the failing call of each: the elevated lab account, + the SAFER token of a basic user, a local standard user, and a standard user of + the domain. It creates the two standard users with passwords that exist only + in memory and removes them, their profiles, and their group membership again. +- **Account probe.** `Acceptance\Probe-AccountRecreation.ps1` deletes an account + and creates it again with the same name in a loop. It shows the token that + Kerberos S4U logons give on the domain controller, the client, and the file + server, and what `Get-NTFSEffectiveAccess` of each module under test returns + from the client (see "The effective-access failures of the Admin role"). Its + accounts, folder, and files are named `NtfsProbe*`, which `Test-MatrixCleanup.ps1` + reports if they stay. +- **Replay.** The cells that failed were run again back to back, one edition, one + file server, with the baseline and the final candidate alternating: after a + restart of the client, one `Acceptance\Run-MatrixSequence.ps1 -Edition Desktop + -FileServer OSFile22` per cell with a different `-ModulePath`, from frozen + copies of the kit and the controller so that no edit could change a run in + progress. The live tests of the replay had one test added that is not in the + repository and prints the state of the subject account after the three + effective-access tests. The kit has the tools that read the + result: `Acceptance\Export-CellTimeline.ps1` (the timeline of the Admin role of + every cell and edition: the module, the account, the times, and the three + tests) and + `Acceptance\Test-StaleAuthzModel.ps1` (the model of the failures, replayed + against that timeline). + +## Results + +### Live controller + +The final candidate (`fdd7a8b`) in the three cells of the matrix, with the +Windows 11 client `OSWin11E`, the controller of `1dec389` (Git blob +`9917cac5820ed20ed2eb5592eff06894677f9874`) and the live tests of blob +`efe36e5073b9b10742ca7de242ddcbe90d8eda62`: run `rc7l`, one sequence, 03:45 to +04:07 UTC. Every role was checked from the result files +(`Validate-LabResults.ps1` printed `LIVE_RESULT_VERIFIED`). Passed / failed / +skipped: + +| File server | Edition | Delegate | ServerAdmin | Admin | Server | +| --- | --- | --- | --- | --- | --- | +| OSFile19 (Windows Server 2019) | Windows PowerShell | 69 / 0 / 0 | 36 / 0 / 0 | 51 / 0 / 1 | 73 / 0 / 1 | +| OSFile19 (Windows Server 2019) | PowerShell 7 | 69 / 0 / 0 | 36 / 0 / 0 | 51 / 0 / 1 | 73 / 0 / 1 | +| OSFile22 (Windows Server 2022) | Windows PowerShell | 69 / 0 / 0 | 36 / 0 / 0 | 51 / 0 / 1 | 73 / 0 / 1 | +| OSFile22 (Windows Server 2022) | PowerShell 7 | 69 / 0 / 0 | 36 / 0 / 0 | 51 / 0 / 1 | 73 / 0 / 1 | +| OSFile25 (Windows Server 2025) | Windows PowerShell | 69 / 0 / 0 | 36 / 0 / 0 | 51 / 0 / 1 | 73 / 0 / 1 | +| OSFile25 (Windows Server 2025) | PowerShell 7 | 69 / 0 / 0 | 36 / 0 / 0 | 51 / 0 / 1 | 73 / 0 / 1 | + +That is 1,374 passed, 0 failed, and 12 skipped for the three cells. The skipped +tests are the same in every cell: case 9 (the Admin role skips "Get-NTFSOrphanedAccess +should not report the entries of the accounts" of other domains and forests, +because the matrix lab has no foreign domain) and the Server role's test of the +module version (that role runs without the module). Each cell had a new +fixture: the subject of case 3 was `NtfsLiveSubject0602` in the cell of +OSFile19 and `NtfsLiveSubject6688` in the cell of OSFile22. + +All runs of the controller, as the table of cells +([Cells.csv](Acceptance-2026-10-10-os-matrix-Cells.csv)) lists them. The runs +before `rc7l` used the controller with the fixed name of the subject: + +| Run | Candidate | Cells | Result per edition and cell | +| --- | --- | --- | --- | +| `rc7c`, `rc7e` | Baseline `83149ee`, tests before the new cases | OSFile19, 22, 25 | 227 passed, 0 failed, 2 skipped in every cell. In `rc7c`, the failed cleanup of the first cell left the accounts in place through all three cells. `rc7d` between them created accounts and removed them 36 seconds later, because its setup failed ("Failed to compare two elements in the array", fixed in `7d47316`) before any test ran. In `rc7e`, the first cell created new accounts 4.6 minutes after that removal, the second reused them, and the third created new accounts 1.0 minute after the previous removal | +| `rc7f` | Final `fdd7a8b` | OSFile19, OSFile22 | OSFile19: 229 / 0 / 2. OSFile22: 228 / 1 / 2, the effective-access test of the Admin role | +| `rc7g` | Final | OSFile25 | 229 / 0 / 2 | +| `rc7h` | Final | OSFile22 | 228 / 1 / 2, the same test | +| `rc7i` | Final | OSFile22 | Windows PowerShell 228 / 1 / 2 (Admin), PowerShell 7 229 / 0 / 2 | +| `rc7j` | `962887a` (without the third fix) | OSFile22 | 225 / 4 / 2: the two new tests of the ServerAdmin role (red without the fix, "Access is denied" for `localhost` and for the name of the client) and two tests of the Admin role | +| `rc7k` | Baseline `83149ee`, with the final tests | OSFile22 | 227 / 2 / 2: the two new tests of the ServerAdmin role; the Admin role passed | + +The failures of the Admin role in `rc7f`, `rc7h`, `rc7i`, and `rc7j` don't depend +on the module: the baseline fails the same way in a replay of the cells (see "The +effective-access failures of the Admin role"). The two +failures of the ServerAdmin role in `rc7j` and `rc7k` are the red state of the +new live tests, as intended; they pass in `rc7f`, `rc7g`, `rc7h`, `rc7i`, and +`rc7l`. The end-state check after each cell of `rc7l` found the fixture gone +(no organizational unit, account, share, folder, local group, membership, or +profile) and reported only the staging folders of the earlier suite runs, which +`Test-MatrixCleanup.ps1` didn't check before (see the limits). + +### First lab, final candidate (case 9) + +Case 9, the accounts of other domains and forests, needs the trusts of the +first lab, so the cells of the matrix skip it. The final candidate (`fdd7a8b`, +from the same extracted module folder as in the matrix) ran through the +controller of the repository (blob `d269e0fe6ba5f72894dd2dcba5dca7f2dc56624f`) in +`WindowsAccessControlLab`, both editions, on 2026-10-10 from 06:14 to 06:31 UTC +(run `fl1`): the domain controller `F1ADC1`, the file server `F1AFile2`, and the +client `F1AFile1` (all Windows Server 2025 Datacenter 10.0.26100.32690, domain +`a.forest1.net`), with the foreign domain controllers `F1BDC1`, `F2DC1`, and +`F3DC1`. `Validate-LabResults.ps1` printed `LIVE_RESULT_VERIFIED`. Passed / +failed / skipped, the same in both editions +([FirstLab.csv](Acceptance-2026-10-10-os-matrix-FirstLab.csv)): + +| Role | Passed / failed / skipped | +| --- | --- | +| Delegate | 69 / 0 / 0 | +| ServerAdmin | 36 / 0 / 0 | +| Admin | 64 / 0 / 0 | +| Server | 76 / 0 / 1 | + +That is 245 passed, 0 failed, and 1 skipped per edition; the skipped test is the +test of the module version in the Server role, which runs without the module. A +cell of the matrix has 229 passed and 2 skipped. The 16 tests more that passed +here are the tests of case 9: 15 that a matrix cell doesn't have (12 in the +Admin role and 3 in the Server role: the entries of `NtfsLiveForeign` of the +three foreign domains by `Get-NTFSAccess`, `Add-NTFSAccess`, `Remove-NTFSAccess`, +and `Get-NTFSEffectiveAccess`, and on the file server), and the test of +`Get-NTFSOrphanedAccess` that the matrix cells skip. Every test of a matrix +cell is in this run too (a comparison of the test names of `rc7l` OSFile25 and +this run found none that only the cell has). The fixture was removed with +`-RemoveFixture`, and the independent check (`Test-MatrixCleanup.ps1`, with the +10 SIDs that it recorded before: the accounts of the lab domain and +`NtfsLiveForeign` in each of the three foreign domains) found the four domains +and both machines clean: no organizational unit, account, share, folder, local +group, membership, or profile of the fixture, and none of the residue that the +check counted then (scheduled tasks, stage items, probe users); its verdict was +CLEAN. The check has counted the profiles and the log-group entries of the +account probe since the review that followed, and a `Verify` with that version +(07:29 UTC, the same SIDs) found none on the two machines either. This +is one run of one candidate. The baseline didn't run in the first lab on this +occasion, so the record says nothing about the red state of the new tests there. + +### The module's own suite, final candidate + +Passed / failed / skipped. Every configuration has 1,011 cases on the machines +of the domain and 1,010 on the host, which has no DNS domain and so doesn't run +the case for the fully qualified name of its computer. + +| Machine | Elevated, Windows PowerShell | Elevated, PowerShell 7 | Basic user, Windows PowerShell | Basic user, PowerShell 7 | +| --- | --- | --- | --- | --- | +| OSFile19 (Server 2019) | 994 / 0 / 17 | 992 / 0 / 19 | 782 / 0 / 229 | 780 / 0 / 231 | +| OSFile22 (Server 2022) | 994 / 0 / 17 | 992 / 0 / 19 | 782 / 0 / 229 | 780 / 0 / 231 | +| OSFile25 (Server 2025) | 994 / 0 / 17 | 992 / 0 / 19 | 782 / 0 / 229 | 780 / 0 / 231 | +| OSWin11E (Windows 11 22H2, the client of the cells) | 994 / 0 / 17 | 992 / 0 / 19 | 782 / 0 / 229 | 780 / 0 / 231 | +| OSWin11 (Windows 11 26H1) | 994 / 0 / 17 | 992 / 0 / 19 | 782 / 0 / 229 | 780 / 0 / 231 | +| Host (reference) | 993 / 0 / 17 | 991 / 0 / 19 | 781 / 0 / 229 | 779 / 0 / 231 | + +On every machine, the skipped tests are the same as on the host, name for name, +in every configuration. They are tests that need the other privilege level or +the other edition and that run in another configuration, as the earlier +analysis of the skipped rows found (all 578 skipped rows of the suite at +`5a5d58b` ran in two other configurations). The two disabled-audit-inheritance +tests that `962887a` added are skipped without the Security privilege, so the +basic configurations skip two cases more than before (229 instead of 227). + +### What the fixes changed + +The same tests, the same machines, and three builds. The baseline and the +candidate `962887a` were run with the tests of the final commit, so the guards +of the later fixes were present and red. + +| Candidate | Elevated | Basic user | +| --- | --- | --- | +| Baseline `83149ee` (Server 2022 and 2025) | 4 failures in every configuration | 20 failures in every configuration | +| `962887a` (all five machines of the domain) | 0 failures | 20 failures in every configuration | +| Final `fdd7a8b` (all five machines of the domain) | 0 failures | 0 failures | + +The four elevated failures of the baseline are the same on Server 2022 and +2025, in both editions: + +1. `Get-NTFSInheritance` with a security descriptor, "Should report the same + state as for the path of the item" (existing test), +2. the same for "a file without audit entries" and +3. "a folder without audit entries" (two new guards), +4. `Get-NTFSEffectiveAccess`, "Should return the result of this computer and + warn for an empty -ServerName" (existing test). + +The 20 failures in the basic-user mode are the same set on every machine of the +domain and in both editions, once the name of the computer in two test names is +set aside. All of them call `Get-NTFSEffectiveAccess` without a reachable +remote authorization manager: 11 in its own tests, 5 in the tests of a later +command that ends the pipeline, 2 for an unresolved identity, 1 for a path that +doesn't exist, and 1 for an item whose owner may not read its permissions. Each +fails with "Could not get effective permissions from machine 'localhost'. The +error is 'Access is denied'". The host and the CI runners aren't in a domain and +passed them all along. The failing names of every run are in +[the failures table](Acceptance-2026-10-10-os-matrix-Failures.csv). + +## Defects found + +### In the module + +1. **`Get-NTFSInheritance -SecurityDescriptor` for an item without audit + entries** reported the audit inheritance as disabled, where `-Path` reported + it as enabled. On Windows Server 2022 and 2025 and on Windows 11 26H1, .NET + reports the SACL of such an item as protected from inheritance when it reads + all sections together, and as not protected when it reads the SACL alone; + the descriptor kept the first state. The baseline showed it on Windows + Server 2022 and 2025, and an earlier run with the same two existing tests + showed it on Windows 11 26H1; the host (build 33438) reads both ways alike. + The baseline wasn't run on Server 2019 and Windows 11 22H2. `Write()` stores + the sections that were read, so a descriptor with the wrong flag would also + have stored the SACL as protected. Fixed in `962887a`: the descriptor takes + the audit section from a separate read, as it already did for the access + section. +2. **`Get-NTFSEffectiveAccess -ServerName ''`** wrote "Access is denied" on the + same machines, because Windows takes an empty name for this computer and the + remote interface then refuses the check; on the host it fails as + unreachable. Fixed in `962887a`: an empty name names no computer, so the + cmdlet warns and returns the result of this computer on every machine. +3. **`Get-NTFSEffectiveAccess` for a user who isn't an administrator**, on a + computer in a domain, wrote "Access is denied" and returned nothing for + every account, also for the default `-ServerName localhost`. See the probe + below. The function that fails, `GetEffectivePermissions_AuthzInitializeContextFromSid`, + is identical in the published 5.0.0-rc6 (compared with `git diff` against + the tag; the cmdlet wasn't run there), and the defect shows in the tests only + on a domain-joined machine as a basic user. Fixed in `fdd7a8b`. + +### The probe + +The remote interface of the authorization manager of a computer answers only +its administrators and the members of its group Access Control Assistance +Operators. A computer in a domain offers the interface to every caller; a +computer outside a domain doesn't, so the cmdlet already used the local manager +there. The probe, on Windows Server 2019, 2022, and 2025, with the module of +`962887a` (before the third fix): + +| Token | Name of this computer (the default, `localhost`, computer name, FQDN) | `-ServerName ''` (the local manager) | Another computer | +| --- | --- | --- | --- | +| Lab account, elevated | Result for every account | Result for every account | Result for every domain account | +| Lab account, filtered (SAFER Normal User) | Access denied for every account | Result for every account, also the Administrator and Domain Users of the domain | Result for domain accounts: network authentication carries the groups of the account | +| Local standard user | Access denied | Result, except for the domain Administrator, a user account of the domain | Access denied: a local account has no domain credentials | +| Standard domain user | Access denied for every account | Result for every account, also the domain Administrator | Access denied, as the cmdlet page and the live test of the delegated account describe | + +The accounts were the user itself, Everyone, the local Administrator, and the +Administrator and Domain Users of the domain. The cmdlet now uses the local +manager for a name of this computer when the remote one refuses the user. The +second column shows that this answers for every caller of these four kinds, +except for a local user who asks about a user account of the domain; that stays +an "Access is denied" from Windows. For another computer, the denial stays an +error. A new live test runs the case as the administrator of the file server, +who isn't an administrator of the client, and compares the rights with the S4U +oracle that the other roles use. + +### In the environment + +- The base images of Windows Server 2019 and Windows 11 22H2 had an empty EFI + system partition: the `bcdboot` of the Server 2025 host fails with exit code + 193 on their boot files, and AutomatedLab ignores the exit code, so the VM + doesn't boot (Hyper-V event 18603). `Repair-OsMatrixBoot.ps1` runs the + `bcdboot` of the image itself on the VM's own disk. +- Windows 11 26H1 (28000.1836) joins the domain but loses the secure channel to + the domain controller (26100.32690): the client asks `NetrLogonGetCapabilities` + for query level 2, the controller answers `0xC0000022`, and the client denies + the channel (`NlConfirmRequestedCapabilities: denying access ... 0xc0000022`). + A rejoin can't fix a protocol mismatch, so this machine isn't a domain client. +- The Windows 11 Enterprise Evaluation image shuts itself down an hour after + each start (`wlms.exe`: "The license period for this installation of Windows + has expired"; status 0xC004F009). It recorded its install time on a clock that + ran about seven hours ahead, the clock was then corrected, and the evaluation + licensing took the step back as the end of the grace period. One of the two + documented rearms didn't clear it. After an unplanned shutdown its machine + account password no longer matched the domain's (`0xC000018D` for domain + logons, `nltest /sc_verify` reports `ERROR_INVALID_PASSWORD`); + `Test-ComputerSecureChannel -Repair` with the lab account repaired it. Runs on + this machine have to stay under an hour from its start. +- A profile of an account that a scheduled task used stayed loaded on one + server, so its folder couldn't be removed until the machine restarted. + +### In the harness + +- A line that a native command writes to stderr is a terminating error in + Windows PowerShell 5.1 under `$ErrorActionPreference = 'Stop'` when `2>&1` + redirects it. The first "The directory is not empty" of PowerShell 7 ended the + fixture removal on Server 2019 before the retry. +- `Get-LocalGroupMember` fails with "Failed to compare two elements in the + array" when a group holds an orphaned SID, which stopped the setup of the next + run. The setup now adds members and ignores `MemberExistsException`. +- A profile that is gone in the meantime failed the cleanup of the client. + +All three are fixed in the controller (`7d47316`). + +### The effective-access failures of the Admin role + +In the cells of the Windows Server 2022 file server, and only there, the Admin +role failed two effective-access tests of case 3 in `rc7f`, `rc7h`, `rc7i`, and +`rc7j` (`rc7j` ran the candidate `962887a`; `rc7i` failed only in Windows +PowerShell): `Get-NTFSEffectiveAccess` returned no access (Synchronize only, +`0x100000`) for the subject of case 3, where the tests expect the rights +through the nested domain groups (`0x1200A9`, and `0x1201BF` with the local +group of the file server), either with the default `-ServerName` (the +authorization manager of the client) or with the name of the file server, or +both. The baseline had passed the same position in `rc7e` and `rc7k`, and the +audit read of `962887a` is the only change of the module on the path of the +cmdlet before `fdd7a8b`, so the module was the first suspect. It isn't the +cause, as the replay below shows. In `rc7c`, the failed cleanup of the first cell +had left the accounts in place through all three cells; in the later sequences +the fixture was removed after most cells and created again for the next one, with +the same names and new SIDs. + +**The replay.** The controller of `db04ef2` (the controller of `rc7f` to +`rc7k`, blob `683aee91ec8805d77a33b2d368acaf876724fa32`, the fixed name of the +account), Windows PowerShell only, the file server OSFile22, seven cells (`ab0` +to `ab6`, 04:57 to 05:33 UTC) back to back after a restart of the client, the +module alternating between the baseline `83149ee` and the final +candidate `fdd7a8b`. The live tests were the blob `efe36e5073b9b10742ca7de242ddcbe90d8eda62` +with one test added for this replay (the file then has the blob +`72c12fe09e4005e048a8aed0fa02b9a922c58f34`), which isn't committed: after the +three effective-access tests of the Admin role it prints, in the same second, the +state of the subject account (see below); it runs after them, so it can't change +their results. `ab0` is the warm-up and has a new fixture. + +| Cell | Module | Admin role at (UTC) | Minutes since the previous removal | Test 1, name of the file server | Test 2, default server name | +| --- | --- | --- | ---: | --- | --- | +| `ab0` | final | 05:00:57 | 54.0 | pass | pass | +| `ab1` | baseline | 05:06:09 | 3.8 | FAIL `0x100000` | FAIL `0x100000` | +| `ab2` | final | 05:11:12 | 3.7 | pass | pass | +| `ab3` | final | 05:16:23 | 3.9 | FAIL `0x100000` | FAIL `0x100000` | +| `ab4` | baseline | 05:21:34 | 3.9 | pass | pass | +| `ab5` | baseline | 05:26:57 | 3.9 | FAIL `0x100000` | FAIL `0x100000` | +| `ab6` | final | 05:32:04 | 3.8 | pass | pass | + +In every cell from `ab1` on, the accounts were created 1.0 minute after the +removal of the previous fixture, and the Admin role ran 3.7 to 3.9 minutes +after it. The cells differ in the module, in the outcome, and in one more +variable: the age of the entry that an earlier cell left for the same account +name, counted from that cell's Admin role (5.2 to 5.4 minutes in the failing +cells, 10.25 to 10.5 minutes in the passing ones). Not counting the warm-up +`ab0`, the baseline fails two of its three cells and the final candidate one of +its three, and the failing and the passing cells alternate. If the module +decided, the baseline wouldn't fail. + +**What is wrong in a failing cell.** The test that runs right after the three +tests printed the same in `ab1`, `ab3`, and `ab5`: the name `osmatrix\NtfsLiveSubject` +resolves to the current SID; a Kerberos S4U logon of `NtfsLiveSubject@osmatrix.net` +on the client returns the current SID with nine groups, among them `NtfsLiveInner` +and `NtfsLiveOuter` (this logon is the oracle of the controller); `Get-NTFSEffectiveAccess` +with the unreachable server name, which falls back to the local authorization +manager, returns `0x1200A9`; and every call that asks a remote authorization +manager, the one of the client by the default `-ServerName` and the one of the +file server by its name, returns `0x100000`, by name and by SID alike. In the +passing cells all five calls were right. So 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. The module makes the +same Authz calls for both kinds of manager; only the manager differs. + +**A model that fits.** The pattern is the one of a cache. The model: a remote +authorization manager computes the groups of an account at the first request +for the account name and answers from that result for L minutes, also when the +account was deleted and created again under the same name in the meantime. The +file server and the client have one entry each for a name, and use doesn't +renew it. `Test-StaleAuthzModel.ps1` replays the Admin roles of the timeline of +all cells ([Timeline.csv](Acceptance-2026-10-10-os-matrix-Timeline.csv): `rc7c` +to `rc7l` and `ab0` to `ab10`, 43 runs in 27 cells, and `rc7d`, which stopped +before its tests) against the model (`-StepMinutes 0.05`, so every bound is known +to within 0.05 minute). With L from 9.35 to 10.25 minutes the model predicts the +result of the first test (the file server) of all 43 runs, and with L from 9.95 +to 10.25 minutes that of the second (the client): 43 of 43 for each, with 6 and 9 +failures. One L from 9.95 to 10.25 minutes serves both tests (86 of 86). That +includes the cells where the two tests differ (`rc7f`, `rc7h`: the entry of the +client was stale, the one of the file server had expired), the cells of the +baseline that passed (`rc7e`, `rc7k`), and the cells that passed with an account +name that was new. A random assignment of the observed outcomes to the runs (the +same number of failures) never fits that well: none of 5,000 assignments reaches +43 of 43 for any L, and the best of them reaches 41 for the first test and 39 +for the second (`-Permutations 5000`, fixed seed). I fitted the model after +`ab3` and wrote down its predictions before they ran (in the night log of the +session, outside the repository, at 05:20 UTC): `ab4` passes, `ab5` fails, `ab6` +passes. All three held, and `ab5` is the baseline failing; if the module decided, +`ab5` would have passed and `ab6` would have failed. `ab6` is the weakest of the +three: its entry was 10.5 minutes old, a little above the lifetimes that fit. + +**The probes of the night.** Three probes (the second is +`Probe-AccountRecreation.ps1` of the kit) deleted and created the accounts again +within seconds. In that regime, the Kerberos S4U logon itself returned the old +account on the domain controller, the client, and the file server for more than +seven and less than fifteen minutes, and both modules returned `0x100000` for +every call. In the cells, with one minute between the deletion and the new +creation, the Kerberos logon is right (the oracle of the controller never failed, +and the replay prints it). Both are state that Windows keeps for a name beyond +the deletion of the account; the cells show the variant of the remote +authorization managers. + +The first loop probe, with the baseline and the final candidate: + +| Round | Name resolves to | S4U token of the account on the client | Baseline and final candidate, by name and by SID, with the default `-ServerName` and with the file server | +| --- | --- | --- | --- | +| 1 (new names) | the current SID | holds the outer group | `0x1200A9`, both modules, all four calls | +| 2 to 6 | the SID of the previous round in the first process of a round, the current SID in the second | lacks the new outer group | `0x100000`, both modules, all four calls | + +The probe of the kit, `Probe-AccountRecreation.ps1`, which also logs the user on +with Kerberos S4U on the domain controller, the client, and the file server, gave +the same picture in four rounds with the baseline and the final candidate (04:19 +UTC): in round 1, all three machines returned the current account and both +modules `0x1200A9` for every call; in rounds 2 to 4, all three returned the old +account, without the new outer group, and both modules `0x100000` for every call. +The own ticket cache of the computers (logon session `0x3e7`) held no ticket for +the account, and a purge of it changed nothing. + +A third probe created five sets of accounts, logged each user on with S4U on the +three machines, deleted and created them again with the same names within a +second, and asked once per set after a delay (the sets after the first were +asked after a `klist purge` on the client, so the rows of the client for them +aren't independent): + +| Question | Domain controller | File server | Client | +| --- | --- | --- | --- | +| At once | old account | old account | old account; Authz by SID `0x100000` | +| After `klist purge` on the client | old account | old account | current account (the token of the session); Authz by SID still `0x100000` | +| After `nltest /sc_reset`, a DNS flush on the client, and a restart of the Kerberos service of the domain controller | old account | old account | unchanged | +| 60 seconds after the accounts were created again | old account | old account | Authz by SID `0x100000` | +| 180 seconds | old account | old account | Authz by SID `0x100000` | +| 420 seconds | old account | old account | Authz by SID `0x100000` | +| 900 seconds | current account | current account | Authz by SID `0x1200A9`, also with the name of the file server | + +`WindowsIdentity` with the user principal name, which the module doesn't call, +returns the old account in this regime, so the module isn't involved in it +either. + +**What the evidence supports.** The failures of the Admin role depend on the +position of the cell relative to the previous fixture with the same account +name, and the module doesn't decide the outcome: not counting the warm-up, the +baseline fails in two of its three replay cells and the final candidate in one +of its three, and one model with one parameter predicts all 43 runs, including +three that it predicted before they ran. In a failing cell the remote +authorization managers of the client and of the file server are the wrong layer: +the name resolution, a Kerberos logon of the account, and the local +authorization manager are right in the same second, and the module makes the +same Authz calls for both kinds of manager. The replay gives no reason to change +the module for it. + +**What it doesn't establish.** How Windows does it: which component keeps the +state, and why for about ten minutes. L is estimated from 43 runs on one client +and three file servers with a cell every five minutes or so, so a different +spacing of the cells could tell more. The window of L that fits the client test +is 0.3 minute wide, and its bounds come from two runs (`rc7h`, whose Core run is +9.92 minutes after the entry of `rc7g`, and `ab2`, 10.25 minutes after `ab0`). +The times of the model are those of the start of the Admin role, some seconds +before the first request, and the offset may differ between the editions, so the +bounds of L are uncertain by about that much. The model describes the +observations that it was fitted to, and the three predictions are the only ones +that it didn't see. The replay rules out a module effect that decides the +outcome (every position that the model predicts to fail failed for the +baseline, the final candidate, and the baseline again, and every position that +it predicts to pass passed for the final candidate, the baseline, and the final +candidate), but six runs can't rule out a small or a random effect of the +module. The replay ran one edition against one file server. Whether a user can +meet it, an administrator who deletes an account, creates it again under the same +name, and asks within ten minutes for its effective access on a remote computer, +wasn't tried outside the lab. The cmdlet can't detect it: the answer of a manager +that has no groups for the account looks like the answer for an account without +access. + +**The change of the controller.** A new fixture gets a new name for the account +of case 3 (`NtfsLiveSubject` and four digits, `1dec389`), and a fixture that +exists keeps its account. No cache has to be flushed, and the module isn't +changed by this. With it, the Windows Server 2022 cell passed in `rc7l`, where the +cells of the old controller had failed in `rc7f`, `rc7h`, `rc7i`, and `rc7j`, and +so did the other two cells of that sequence. + +The controller of `1dec389` (blob `9917cac5820ed20ed2eb5592eff06894677f9874`) +then ran four more cells of the replay, `ab7` to `ab10`: baseline, final, +baseline, final, in Windows PowerShell against OSFile22, back to back after a +restart of the client (05:37 to 05:58 UTC), with the same diagnostic test. Each +cell created a fixture with a new name for the account of case 3. I wrote the +prediction down before the Admin role of `ab7` ran (night log, about 05:40 UTC): +all four pass. For a controller that reuses the name, the model with L = 10.1 +minutes predicts failures in `ab7` (the entry of `ab6` would have been 8.1 +minutes old) and in `ab9` (5.4 minutes after `ab8`): + +| Cell | Module | Account of case 3 | Admin role at (UTC) | Test 1 | Test 2 | Test 3, local manager | The model, had the name been reused (`-AsIfSameSubject`) | +| --- | --- | --- | --- | --- | --- | --- | --- | +| `ab7` | baseline | `NtfsLiveSubject8013` | 05:40:11 | pass | pass | pass | Test 1 FAIL; Test 2 FAIL, unless the restart of the client at 05:34 cleared its entry | +| `ab8` | final | `NtfsLiveSubject0900` | 05:45:32 | pass | pass | pass | pass | +| `ab9` | baseline | `NtfsLiveSubject7705` | 05:50:54 | pass | pass | pass | both tests FAIL | +| `ab10` | final | `NtfsLiveSubject8799` | 05:56:12 | pass | pass | pass | pass | + +The model doesn't know about restarts, and the client restarted ten times between +23:37 and 05:34 UTC, nine of them after the first cell had started (Hyper-V worker +log, UTC: 23:37, 00:38, 01:43, 01:58, 02:43, 03:23, +03:42, 04:07, 04:55, and 05:34; the file servers and the domain controller +didn't restart between the first and the last run, except OSFile22 at 01:36). +If the entry of a remote manager lives in the memory of the computer, a restart +clears it. For the fit this changes no prediction: of the entries that the model +keeps, only one lives across a restart and is read by a later run (the entry +that `rc7e` made at 00:35:48 on OSFile25, read by its Core run after the restart +at 00:38), and that run has the same account, so it passes either way. For the +counterfactual it matters once, in the table: the restart at 05:34 came between +`ab6` and `ab7`, so the client test of `ab7` is a prediction only if the state +survives a restart, while the file-server test (OSFile22 didn't restart) is one +in any case. + +In each cell the diagnostic test printed the right rights for all five calls +(`0x1200A9` for the client, `0x1201BF` for the file server). In the timeline, the +old controller failed in 7 of its 20 cells, all on OSFile22 (`rc7f`, `rc7h`, +`rc7i`, `rc7j`, `ab1`, `ab3`, `ab5`); the new one failed in none of its 7 (`rc7l` +and `ab7` to `ab10`), where the model for a reused name predicts failures in 3 +(the OSFile22 cell of `rc7l`, `ab7`, `ab9`). The cells aren't paired runs and +seven cells are few, so this doesn't prove that the new names are the reason; it +shows that the failures are absent where the model says that a reused name +fails, which the reuse of the name explains and the module doesn't. + +## Limits and open items + +- Every run is a validation of a local build (`-ModulePath`). The acceptance of + a release is the run with `-Version` of the exact prerelease from the + PowerShell Gallery in every cell, which handoff 3 sequences after the maintainer + decides which fixes belong to 5.0.0-rc7. The same cells have to be repeated for + a changed binary. The `-Version` path of `Run-MatrixSequence.ps1` ran once as a + dry run with the published 5.0.0-rc6 on OSFile19 in Windows PowerShell (07:39 to + 07:45 UTC, kit at `664ef3a`): 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, such as the later-command + tests, `Get-ChildItem2 -Filter`, `InheritedFrom`, and the two new ServerAdmin + tests), and the cleanup verdict was CLEAN. That tests the mechanics only and + accepts nothing. +- Case 9 (accounts of other domains and forests) needs trusts that the matrix + lab doesn't have; it runs only in `WindowsAccessControlLab`, where the + baseline passed it and the final candidate passed it in run `fl1` (see "First + lab, final candidate (case 9)"). The published package has to run there too. +- The file servers are Windows. A server of another kind is the subject of + Decision 23. +- Windows 11 26H1 has no domain cell until the domain controller or the + mismatch changes. The Windows 11 client of the cells is the 22H2 evaluation + build, which has to be started shortly before a run (see above). +- `GetEffectiveAccess` ignores what the initialization of the resource manager + throws (an outer `catch { }` that is older than this work). An operating + system that refused another computer at that step, not at the context as every + machine of the matrix did, would give a result without rights and a warning + instead of the documented error; and a failure of the local fallback for a name + of this computer would give a result without rights and neither a warning nor an + error, because the flag that suppresses the warning is set before the fallback + runs (also older than this work). The help and the CHANGELOG say that the error + stays for another computer, which holds for the denial at the creation of the + context. This is unverified on every machine of the matrix and outside the + fixes (Decision 16 asks for reproducible defects only); recording the + initialization exceptions in `authzException` would close it. +- The built-in `security-review` agent (the custom `security-reviewer` couldn't + start: its model isn't offered, and I didn't override it) read `83149ee..664ef3a` + and found no exploitable vulnerability in the changes of the module: the + decision "this name is this computer" is an exact, case-insensitive match + (true for `.`, `localhost`, the machine name, the host name, and the name with + the DNS domain; false for null, empty, whitespace, a trailing dot, an IP + address, UNC forms, an embedded NUL, and a name of 100,000 characters, all of + which keep the remote path and its denial), the fallback runs in the caller's + own process and token, and the separate audit read uses the privilege handling + of the combined read. It reported two LOW items. The first is the item above. + The second is that the kit creates staging folders directly under `C:\` + (`C:\NTFSSecurityLab`, `C:\NtfsMatrixLocal`, `C:\NtfsMatrixProbe`, + `C:\NtfsProbeModules`) without an ACL, so they inherit Authenticated Users: + Modify, while scripts and the module's DLL in them run elevated or as the role + accounts: a principal that can run code on a lab VM during a run could replace + them. The labs are isolated and the role accounts must read the tests and write + results there, so protecting the folders is a design change of the controller + that needs a new acceptance; I left it for the maintainer. The reviewer also + noted that the lab password crosses remoting and `Register-ScheduledTask + -Password` (module or script-block logging on a machine would record it), that + the controller reaches the client with CredSSP to an IP address, where the + logon inside CredSSP is NTLM-only and the server isn't authenticated (the + policy comes from AutomatedLab), and that the help says that a user who isn't an + administrator gets the result on a domain computer without saying that a local + standard user who asks about a domain account still gets "Access is denied" (the + probe table above) or that the answer now comes from the caller's own token and + manager. It could not check the ACL of `C:\` on the lab VMs, the behavior of the + fallback as a non-administrator on a domain computer, or whether the local + manager equals the remote one for every token. +- The scripts of the kit were read by a reviewer who ran none of them; module + logging or script-block logging on a machine would record the lab password + that `Register-ScheduledTask -Password` needs. +- The mechanism isn't known. The replay shows that the remote authorization + managers answer for an account name from state that outlives the account, and + the model puts the lifetime at about ten minutes (9.95 to 10.25 minutes for + both tests, from 43 runs), but I didn't find which component keeps it, why that + long, or whether it is constant: all runs have the same timing, and it was + fitted to them. The replay ran one edition (Windows PowerShell, Desktop) + against one file server (OSFile22). The probes of the first regime (accounts + deleted and created again within seconds), in which the Kerberos S4U logon + returned the old account for more than seven and less than fifteen minutes, + were measured once, with no repetition, and five remedies (`klist purge`, + `nltest /sc_reset`, a DNS flush, a restart of the Kerberos service of the + domain controller, and waiting) were tried: only waiting helped. A script of + the kit that creates accounts again under one name would meet the state; the + controller doesn't any more. +- The end-state check of the matrix reported the staging folders of the suite + runs (`C:\NtfsMatrixLocal`) as residue in the three cells of `rc7l`, which + made their verdict DIRTY, although the fixture was gone. The suite runner now + removes its stage after it has copied the results back. The check counts the + items in the stage folders, each of the folders `C:\NtfsProbeRecreation` and + `C:\NtfsProbeModules` that exists, the scheduled tasks of the matrix, the local + `NtfsProbe*` users, their profiles and profile folders (`C:\Users\NtfsProbe*`), + their entries in Performance Log Users, and the `NtfsProbe*` objects of the + directory, and `-Mode Repair` removes what it finds. `Verify` and `Repair` treat + every unresolved `S-1-5-21-…` member of Performance Log Users as the probe's + (the probe is the only writer of that group in these labs, and its own cleanup + uses the same pattern); on a machine where something else leaves such members, + the check would report them and `Repair` would remove them. A `Verify` on the + two machines of the first lab (07:29 UTC) found none. The check ran with real + residue on the five machines three times: at 06:03 UTC with the script that + `9344ff7` committed at 06:08 UTC, at + 06:44 UTC with the handling of profiles and of the entries in Performance Log + Users that the follow-up review asked for, and at 06:51 UTC after the second run + had shown that the check missed the entry of a local user (`net localgroup` + lists a local user by its bare name, and the pattern wanted a domain prefix). A + first attempt at 05:58 UTC died while it made the residue, without a log (the + cause is unknown; decision log D37), so the run at 06:03 started in a lab where + that attempt might have made some items; its first `Verify` listed exactly the + expected ones. + The last run made residue of every kind at once: a stage item and a local user + `NtfsProbeDummy` on OSFile19; a local user with a profile and an entry in + Performance Log Users on OSFile19; a local user with a profile that was deleted + afterwards (an orphaned profile) on OSFile22; `C:\NtfsProbeRecreation` on + OSFile22; a domain account that was made a member of Performance Log Users on + OSFile22 and then deleted in the directory (`net localgroup` still showed it by + its cached name); a scheduled task `NtfsMatrix-dummy` on OSFile25; + `C:\NtfsProbeModules` on OSWin11E; and a disabled directory user + `NtfsProbeDummy`. `Verify` counted every item (on OSFile19 `probe users=2 probe + profiles=1 probe group members=1`, on OSFile22 `probe profiles=1 probe group + members=1`, and so on), and the verdict expression of `Run-MatrixSequence.ps1`, + read from the script with the parser and not copied, gave DIRTY. `Repair` + removed every item, and a second `Verify` gave CLEAN. Not tried: a profile that + stays loaded (the retries of `Repair` never needed a second attempt), and an + entry that `net localgroup` shows as a bare SID, so the branch for a SID and + `Remove-LocalGroupMember` with a SID didn't meet real residue (the cached name + of the deleted domain account was removed by name). +- Decision 24 is the agent's decision under the maintainer's delegation and stays + `proposed`. So do Decisions 22 and 23. + +## Evidence + +The raw logs, result files, probe outputs, and the packages are local, outside +Git, in the session files of the run; they aren't part of this commit. The +tables of this record are in the files next to it: + +- [the suite results of the three candidates](Acceptance-2026-10-10-os-matrix-LocalSuite.csv), +- [the failing tests of every suite run](Acceptance-2026-10-10-os-matrix-Failures.csv), +- [the controller cells of `rc7c` to `rc7l`](Acceptance-2026-10-10-os-matrix-Cells.csv), +- [the timeline of the Admin role of every cell and edition, `rc7c` to `rc7l` and the replay `ab0` to `ab10`, with the three effective-access tests](Acceptance-2026-10-10-os-matrix-Timeline.csv), +- [the counts of the first-lab run `fl1`](Acceptance-2026-10-10-os-matrix-FirstLab.csv). + +The scripts that produced them are in [Acceptance](Acceptance), and the +decision is `.memory-bank\decisions\0024-os-matrix-lab.md`. diff --git a/Tests/Lab/Acceptance/Add-OsMatrixMachine.ps1 b/Tests/Lab/Acceptance/Add-OsMatrixMachine.ps1 new file mode 100644 index 0000000..de4de2c --- /dev/null +++ b/Tests/Lab/Acceptance/Add-OsMatrixMachine.ps1 @@ -0,0 +1,125 @@ +[CmdletBinding()] +param ( + [Parameter(Mandatory)] [string] $LogPath, + [Parameter(Mandatory)] [ValidatePattern('^[A-Za-z][A-Za-z0-9-]{0,14}$')] [string] $Name, + [Parameter(Mandatory)] [string] $OperatingSystem, + [Parameter(Mandatory)] [string] $IpAddress, + [string] $LabName = 'NtfsSecurityOsMatrixLab', + [ValidateRange(2, 16)] [int] $MemoryGB = 4, + [ValidateRange(1, 8)] [int] $Processors = 2, + [ValidateRange(5, 240)] [int] $StartTimeoutMinutes = 40, + [string] $BackupRoot = 'C:\ProgramData\AutomatedLab\Backups' +) + +# Adds one machine to the already deployed matrix lab (Decision 24) and creates only that machine. AutomatedLab 5.61 has no supported way to +# extend a deployed lab: Add-LabMachineDefinition refuses while a lab is imported or exported, and Install-Lab creates every machine of the +# lab again. This script copies the lab metadata first (the copy is readable by administrators only, because the files hold the lab +# credentials), reloads the definition with Import-LabDefinition (never Import-Lab), adds the machine, exports the definition, and then runs +# the same steps Install-Lab runs for a single machine: base image, hosts entries, virtual machine, start. The other machines are neither +# created, started, nor changed. Windows PowerShell 5.1 on the host; run it elevated. +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' +$stamp = '[{0:yyyy-MM-dd HH:mm:ss}Z]' +function Write-Step { param ([string] $Message) ($stamp -f [DateTime]::UtcNow) + ' ' + $Message | Add-Content -LiteralPath $LogPath } + +$principal = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent() +if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { throw 'This script must run in an elevated PowerShell session.' } + +($stamp -f [DateTime]::UtcNow) + " START add-os-matrix-machine lab=$LabName name=$Name os='$OperatingSystem' ip=$IpAddress" | Set-Content -LiteralPath $LogPath +$lockPath = $null +try { + Import-Module -Name AutomatedLab -ErrorAction Stop + if ((Get-Lab -List) -notcontains $LabName) { throw "The lab '$LabName' does not exist." } + if (Get-VM -Name $Name -ErrorAction SilentlyContinue) { throw "A virtual machine named '$Name' exists already." } + $hostsText = Get-Content -LiteralPath (Join-Path -Path $env:SystemRoot -ChildPath 'System32\drivers\etc\hosts') -Raw + if ($hostsText -match ('(?im)^\s*[^#\s]+\s+{0}(\.|\s|$)' -f [regex]::Escape($Name)) -or $hostsText -match ('(?im)^\s*{0}\s' -f [regex]::Escape($IpAddress))) { + throw "The hosts file mentions '$Name' or $IpAddress already." + } + + $labFolder = Join-Path -Path (Get-LabConfigurationItem -Name LabAppDataRoot) -ChildPath "Labs\$LabName" + $backup = Join-Path -Path $BackupRoot -ChildPath ('{0}-{1:yyyyMMdd-HHmmss}' -f $LabName, [DateTime]::UtcNow) + $null = New-Item -ItemType Directory -Path $backup -Force + $null = & icacls.exe $backup /inheritance:r /grant:r '*S-1-5-32-544:(OI)(CI)F' '*S-1-5-18:(OI)(CI)F' + if ($LASTEXITCODE -ne 0) { throw "icacls failed on the backup folder (exit code $LASTEXITCODE)." } + Copy-Item -LiteralPath $labFolder -Destination $backup -Recurse + Write-Step "lab metadata copied to $backup" + + Import-LabDefinition -Name $LabName + $definition = Get-LabDefinition + $before = @(Get-LabMachineDefinition | ForEach-Object -Process { $_.Name }) + $domainName = $definition.Domains[0].Name + $rootDc = Get-LabMachineDefinition | Where-Object -FilterScript { 'RootDC' -in $_.Roles.Name } | Select-Object -First 1 + $dcAddress = ($rootDc.NetworkAdapters | Select-Object -First 1).Ipv4Address.IpAddress.AddressAsString + $dcPrefix = ($dcAddress -split '\.')[0..2] -join '.' + $newPrefix = ($IpAddress -split '\.')[0..2] -join '.' + if ($dcPrefix -ne $newPrefix) { throw "$IpAddress isn't in the /24 of the domain controller ($dcAddress)." } + Write-Step ("definition loaded: domain {0}; machines {1}; installation account {2}" -f $domainName, ($before -join ','), $definition.DefaultInstallationCredential.UserName) + + $parameters = @{ + Name = $Name; DomainName = $domainName; OperatingSystem = $OperatingSystem; Memory = ($MemoryGB * 1GB) + Processors = $Processors; Network = $LabName; IpAddress = $IpAddress + } + if ($OperatingSystem -like 'Windows 11*') { + $parameters.HypervProperties = @{ EnableSecureBoot = 'On'; SecureBootTemplate = 'MicrosoftWindows'; EnableTpm = 'true' } + } + + Add-LabMachineDefinition @parameters + Export-LabDefinition -Force -ExportDefaultUnattendedXml + Import-Lab -Name $LabName -NoValidation -NoDisplay + $after = @(Get-LabVM -IncludeLinux | ForEach-Object -Process { $_.Name }) + $difference = @(Compare-Object -ReferenceObject ($before + $Name) -DifferenceObject $after) + if ($difference.Count -gt 0) { throw "The exported lab doesn't hold exactly the old machines plus $Name. Restore the metadata from $backup." } + Write-Step 'definition extended and exported' + + $lockCandidate = Get-LabConfigurationItem -Name DiskDeploymentInProgressPath + if (Test-Path -LiteralPath $lockCandidate) { throw "Another lab disk deployment seems to be in progress ($lockCandidate)." } + $null = New-Item -Path $lockCandidate -ItemType File -Value $LabName + # Only a lock that this script created is removed in the finally block below. + $lockPath = $lockCandidate + New-LabBaseImages + Write-Step 'base images ready' + + $machine = Get-LabVM -ComputerName $Name + $address = ($machine.NetworkAdapters | Select-Object -First 1).Ipv4Address.IpAddress.AddressAsString + $null = Add-HostEntry -HostName $machine.Name -IpAddress $address -Section $LabName + $null = Add-HostEntry -HostName $machine.FQDN -IpAddress $address -Section $LabName + New-LabVM -Name $Name + Set-LabDefinition -Machines (Get-Lab).Machines + Export-LabDefinition -Force -ExportDefaultUnattendedXml -Silent + Write-Step 'virtual machine created and definition exported' + Remove-Item -LiteralPath $lockPath -Force -ErrorAction SilentlyContinue + $lockPath = $null + + Start-LabVM -ComputerName $Name -ProgressIndicator 30 -TimeoutInMinutes $StartTimeoutMinutes -Wait + Write-Step 'machine started and reachable with the lab credentials' + + $userName = (Get-Lab).DefaultInstallationCredential.UserName + Invoke-LabCommand -ActivityName 'Setting PasswordNeverExpires for local deployment accounts' -ComputerName $Name -NoDisplay -Variable (Get-Variable -Name userName) -ScriptBlock { + Get-CimInstance -Query "Select * from Win32_UserAccount where name = '$userName' and localaccount='true'" | Set-CimInstance -Property @{ PasswordExpires = $false } + } + + $evidence = Invoke-LabCommand -ComputerName $Name -ActivityName 'Readiness of the new member' -NoDisplay -PassThru -ErrorAction Stop -ArgumentList $domainName -ScriptBlock { + param ($Domain) + $current = Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' + [pscustomobject]@{ + Build = '{0}.{1}' -f $current.CurrentBuildNumber, $current.UBR + Product = $current.ProductName + Domain = (Get-CimInstance -ClassName Win32_ComputerSystem).Domain + SecureChannel = [bool] (Test-ComputerSecureChannel) + Verify = (@(& nltest.exe "/sc_verify:$Domain" 2>&1) -join ' | ') + } + } + Write-Step ('new member: build {0} ({1}); domain {2}; secure channel {3}; nltest: {4}' -f $evidence.Build, $evidence.Product, $evidence.Domain, $evidence.SecureChannel, $evidence.Verify) + if (-not $evidence.SecureChannel) { throw "The secure channel of $Name is broken." } + + Write-Step 'add-os-matrix-machine-DONE' + exit 0 +} +catch { + Write-Step ('add-os-matrix-machine-FAILED: {0}' -f $_) + $_ | Format-List -Property * -Force | Out-String | Add-Content -LiteralPath $LogPath + exit 1 +} +finally { + if ($lockPath) { Remove-Item -LiteralPath $lockPath -Force -ErrorAction SilentlyContinue } +} diff --git a/Tests/Lab/Acceptance/Complete-OsMatrixLab.ps1 b/Tests/Lab/Acceptance/Complete-OsMatrixLab.ps1 new file mode 100644 index 0000000..14c75e6 --- /dev/null +++ b/Tests/Lab/Acceptance/Complete-OsMatrixLab.ps1 @@ -0,0 +1,91 @@ +[CmdletBinding()] +param ( + [Parameter(Mandatory)] [string] $LogPath, + [string] $LabName = 'NtfsSecurityOsMatrixLab', + [string[]] $Member = @('OSDC1', 'OSFile19', 'OSFile22', 'OSFile25', 'OSWin11E'), + [string[]] $LocalCredentialMember = @(), + [string] $PesterModulePath = 'V:\Git\WindowsAccessControl\output\RequiredModules\Pester\5.7.1', + [string] $PowerShell7Msi = 'V:\LabSources\SoftwarePackages\PowerShell-7.6.3-win-x64.msi' +) + +# Finishes machines of the matrix lab after a deployment that stopped in AutomatedLab's file server step (a job that never completed +# although its remote side was idle) or after Add-OsMatrixMachine.ps1: detaches the installation ISO from the file servers, installs +# PowerShell 7 from the MSI of the host, and copies Pester 5.7.1 into the module folders of both editions. It uses no AutomatedLab job +# (no -AsJob), only synchronous remoting. A member in -LocalCredentialMember is reached with the local installation account through a +# session, for a machine whose secure channel to the domain controller fails. Windows PowerShell 5.1 on the host; run it elevated. +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' +# -File passes an array as one string, so a list may arrive as 'A,B'. +$Member = @($Member | ForEach-Object -Process { $_ -split ',' } | Where-Object -FilterScript { $_ }) +$LocalCredentialMember = @($LocalCredentialMember | ForEach-Object -Process { $_ -split ',' } | Where-Object -FilterScript { $_ }) +$stamp = '[{0:yyyy-MM-dd HH:mm:ss}Z]' +function Write-Step { param ([string] $Message) ($stamp -f [DateTime]::UtcNow) + ' ' + $Message | Add-Content -LiteralPath $LogPath } + +$installBlock = { + param ($Msi) + $msiPath = Join-Path -Path 'C:\Windows\Temp' -ChildPath $Msi + $process = Start-Process -FilePath 'msiexec.exe' -ArgumentList @('/i', ('"{0}"' -f $msiPath), '/quiet', '/norestart', 'ADD_PATH=1', '/l*v', 'C:\Windows\Temp\pwsh-install.log') -Wait -PassThru + $pwsh = Join-Path -Path $env:ProgramFiles -ChildPath 'PowerShell\7\pwsh.exe' + [pscustomobject]@{ ExitCode = $process.ExitCode; Pwsh = $(if (Test-Path -LiteralPath $pwsh) { (Get-Item -LiteralPath $pwsh).VersionInfo.ProductVersion } else { 'missing' }) } +} +$createBlock = { param ($Path) $null = New-Item -Path $Path -ItemType Directory -Force } +$checkBlock = { param ($Path) '{0}: {1}' -f $env:COMPUTERNAME, (Test-Path -LiteralPath (Join-Path -Path $Path -ChildPath '5.7.1\Pester.psd1')) } + +($stamp -f [DateTime]::UtcNow) + " START complete-os-matrix-lab lab=$LabName members=$($Member -join ',') localCredential=$($LocalCredentialMember -join ',')" | Set-Content -LiteralPath $LogPath +try { + foreach ($path in $PesterModulePath, $PowerShell7Msi) { if (-not (Test-Path -LiteralPath $path)) { throw "Missing payload: $path" } } + Import-Module -Name AutomatedLab -ErrorAction Stop + Import-Lab -Name $LabName -NoValidation -NoDisplay + $fileServers = @($Member | Where-Object -FilterScript { $_ -like 'OSFile*' -and $_ -notin $LocalCredentialMember }) + if ($fileServers) { + Dismount-LabIsoImage -ComputerName $fileServers -SupressOutput + Write-Step "installation ISO detached from $($fileServers -join ',')" + } + + $msiName = Split-Path -Path $PowerShell7Msi -Leaf + $domainMembers = @($Member | Where-Object -FilterScript { $_ -notin $LocalCredentialMember }) + foreach ($name in $Member) { + if ($name -in $LocalCredentialMember) { + $session = New-LabPSSession -ComputerName $name -UseLocalCredential + try { + Copy-Item -LiteralPath $PowerShell7Msi -Destination 'C:\Windows\Temp\' -ToSession $session -Force + $outcome = Invoke-Command -Session $session -ScriptBlock $installBlock -ArgumentList $msiName + foreach ($modulesRoot in 'C:\Program Files\WindowsPowerShell\Modules', 'C:\Program Files\PowerShell\Modules') { + $destination = Join-Path -Path $modulesRoot -ChildPath 'Pester' + Invoke-Command -Session $session -ScriptBlock $createBlock -ArgumentList $destination + Copy-Item -LiteralPath $PesterModulePath -Destination $destination -ToSession $session -Recurse -Force + Write-Step ("Pester 5.7.1 in {0}: {1}" -f $modulesRoot, (Invoke-Command -Session $session -ScriptBlock $checkBlock -ArgumentList $destination)) + } + } + finally { + Remove-PSSession -Session $session -ErrorAction SilentlyContinue + } + } + else { + Copy-LabFileItem -Path $PowerShell7Msi -ComputerName $name -DestinationFolderPath 'C:\Windows\Temp' + $outcome = Invoke-LabCommand -ComputerName $name -ActivityName "Install PowerShell 7 on $name" -NoDisplay -PassThru -ErrorAction Stop -ArgumentList $msiName -ScriptBlock $installBlock + } + + Write-Step ("PowerShell 7 on {0}: msiexec exit code {1}; pwsh {2}" -f $name, $outcome.ExitCode, $outcome.Pwsh) + if ($outcome.ExitCode -notin 0, 3010 -or $outcome.Pwsh -eq 'missing') { throw "PowerShell 7 isn't installed on $name (exit code $($outcome.ExitCode))." } + } + + if ($domainMembers) { + foreach ($modulesRoot in 'C:\Program Files\WindowsPowerShell\Modules', 'C:\Program Files\PowerShell\Modules') { + $destination = Join-Path -Path $modulesRoot -ChildPath 'Pester' + Invoke-LabCommand -ComputerName $domainMembers -ActivityName 'Create the Pester module directory' -NoDisplay -ErrorAction Stop -ArgumentList $destination -ScriptBlock $createBlock + Copy-LabFileItem -Path $PesterModulePath -ComputerName $domainMembers -DestinationFolderPath $destination -Recurse + $found = Invoke-LabCommand -ComputerName $domainMembers -ActivityName 'Check Pester' -NoDisplay -PassThru -ErrorAction Stop -ArgumentList $destination -ScriptBlock $checkBlock + Write-Step ("Pester 5.7.1 in {0}: {1}" -f $modulesRoot, (@($found) -join '; ')) + if (@($found | Where-Object -FilterScript { $_ -notmatch ': True$' }).Count -gt 0) { throw "Pester 5.7.1 isn't in $destination on every member." } + } + } + + Write-Step 'complete-os-matrix-lab-DONE' + exit 0 +} +catch { + Write-Step ("complete-os-matrix-lab-FAILED: {0}" -f $_) + $_ | Format-List -Property * -Force | Out-String | Add-Content -LiteralPath $LogPath + exit 1 +} diff --git a/Tests/Lab/Acceptance/Deploy-OsMatrixLab.ps1 b/Tests/Lab/Acceptance/Deploy-OsMatrixLab.ps1 new file mode 100644 index 0000000..3d7c159 --- /dev/null +++ b/Tests/Lab/Acceptance/Deploy-OsMatrixLab.ps1 @@ -0,0 +1,105 @@ +[CmdletBinding()] +param ( + [Parameter(Mandatory)] [string] $LogPath, + [string] $LabName = 'NtfsSecurityOsMatrixLab', + [string] $DomainName = 'osmatrix.net', + [string] $VmPath = 'V:\AutomatedLab-VMs', + [string] $AddressSpace = '192.168.12.0/24', + [string] $PesterModulePath = 'V:\Git\WindowsAccessControl\output\RequiredModules\Pester\5.7.1', + [string] $PowerShell7Msi = 'V:\LabSources\SoftwarePackages\PowerShell-7.6.3-win-x64.msi' +) + +# Deploys an isolated AutomatedLab lab for the NTFSSecurity operating-system matrix (Decision 24): one domain controller, three file +# servers (Server 2019, 2022, 2025), and a Windows 11 client, in a domain and on a switch of their own. It touches none of the +# existing labs, machines, switches, or domains, never calls Remove-Lab, and refuses to run when the lab or a machine name exists. +# The installation password is generated here, kept in memory, and stored only where AutomatedLab stores it for every lab. +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' +$stamp = '[{0:yyyy-MM-dd HH:mm:ss}Z]' +function Write-Step { param ([string] $Message) ($stamp -f [DateTime]::UtcNow) + ' ' + $Message | Add-Content -LiteralPath $LogPath } + +$principal = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent() +if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { throw 'This script must run in an elevated PowerShell session.' } + +($stamp -f [DateTime]::UtcNow) + " START deploy-os-matrix-lab lab=$LabName" | Set-Content -LiteralPath $LogPath +try { + Import-Module -Name AutomatedLab -ErrorAction Stop + foreach ($path in $PesterModulePath, $PowerShell7Msi) { if (-not (Test-Path -LiteralPath $path)) { throw "Missing payload: $path" } } + + $machines = @( + @{ Name = 'OSDC1'; Os = 'Windows Server 2025 Datacenter (Desktop Experience)'; Roles = @('RootDC'); Memory = 4GB; Address = '192.168.12.10' } + @{ Name = 'OSFile25'; Os = 'Windows Server 2025 Datacenter (Desktop Experience)'; Roles = @('FileServer'); Memory = 3GB; Address = '192.168.12.25' } + @{ Name = 'OSFile22'; Os = 'Windows Server 2022 Datacenter (Desktop Experience)'; Roles = @('FileServer'); Memory = 3GB; Address = '192.168.12.22' } + @{ Name = 'OSFile19'; Os = 'Windows Server 2019 Datacenter (Desktop Experience)'; Roles = @('FileServer'); Memory = 3GB; Address = '192.168.12.19' } + @{ Name = 'OSWin11'; Os = 'Windows 11 Pro'; Roles = @(); Memory = 4GB; Address = '192.168.12.11' } + ) + + # Collision checks from AutomatedLab metadata and from Hyper-V; the existing labs are only read. + $existingNames = New-Object System.Collections.Generic.List[string] + $labs = @(Get-Lab -List) + if ($labs -contains $LabName) { throw "The lab '$LabName' exists already. Refusing to redefine it." } + foreach ($existing in $labs) { + Import-Lab -Name $existing -NoValidation -NoDisplay -ErrorAction Stop + foreach ($vm in Get-LabVM -IncludeLinux) { $existingNames.Add($vm.Name) } + } + foreach ($vm in Get-VM) { $existingNames.Add($vm.Name) } + $collisions = @($machines.Name | Where-Object { $_ -in $existingNames }) + if ($collisions) { throw "Machine name collision: $($collisions -join ', ')" } + if (Get-VMSwitch -Name $LabName -ErrorAction SilentlyContinue) { throw "A virtual switch named '$LabName' exists already." } + $usedAddresses = @(Get-NetIPAddress -AddressFamily IPv4 | ForEach-Object { $_.IPAddress }) + if ($usedAddresses | Where-Object { $_ -like '192.168.12.*' }) { throw 'The address space 192.168.12.0/24 is in use on the host.' } + Write-Step ('preflight ok; existing labs: {0}; existing machine names: {1}' -f ($labs -join ', '), $existingNames.Count) + + $characters = ([char[]](48..57) + [char[]](65..90) + [char[]](97..122) + '!', '#', '%', '+', '-', '=') + # A cryptographic generator, without the bias of a remainder: this is the installation and domain administrator password of the lab. + $generator = [Security.Cryptography.RandomNumberGenerator]::Create() + $limit = 256 - (256 % $characters.Count) + $buffer = New-Object -TypeName 'byte[]' -ArgumentList 1 + $chosen = New-Object -TypeName 'System.Text.StringBuilder' + while ($chosen.Length -lt 24) { + $generator.GetBytes($buffer) + if ($buffer[0] -lt $limit) { $null = $chosen.Append($characters[$buffer[0] % $characters.Count]) } + } + + $password = 'Aa1!' + $chosen.ToString() + + New-LabDefinition -Name $LabName -DefaultVirtualizationEngine HyperV -VmPath $VmPath + Add-LabVirtualNetworkDefinition -Name $LabName -AddressSpace $AddressSpace + Add-LabDomainDefinition -Name $DomainName -AdminUser 'install' -AdminPassword $password + Set-LabInstallationCredential -Username 'install' -Password $password + foreach ($definition in $machines) { + $parameters = @{ + Name = $definition.Name; DomainName = $DomainName; OperatingSystem = $definition.Os; Memory = $definition.Memory + Processors = 2; Network = $LabName; IpAddress = $definition.Address + } + if ($definition.Roles.Count -gt 0) { $parameters.Roles = $definition.Roles } + if ($definition.Os -like 'Windows 11*') { + $parameters.HypervProperties = @{ EnableSecureBoot = 'On'; SecureBootTemplate = 'MicrosoftWindows'; EnableTpm = 'true' } + } + Add-LabMachineDefinition @parameters + } + Write-Step 'lab defined; installing network switches and base images' + + Install-Lab -NetworkSwitches -BaseImages + Write-Step 'network switches and base images done' + Install-Lab + Write-Step 'machines, domain, and roles done' + + $labMachines = Get-LabVM + Install-LabSoftwarePackage -ComputerName $labMachines -Path $PowerShell7Msi -CommandLine '/quiet /norestart ADD_PATH=1' -Timeout 30 + Write-Step 'PowerShell 7 installed' + foreach ($modulesRoot in 'C:\Program Files\WindowsPowerShell\Modules', 'C:\Program Files\PowerShell\Modules') { + $destination = Join-Path $modulesRoot 'Pester' + Invoke-LabCommand -ComputerName $labMachines -ActivityName 'Create the Pester module directory' -ScriptBlock { param ($Path) $null = New-Item -Path $Path -ItemType Directory -Force } -ArgumentList $destination -NoDisplay + Copy-LabFileItem -Path $PesterModulePath -ComputerName $labMachines -DestinationFolderPath $destination -Recurse + } + Write-Step 'Pester 5.7.1 copied' + Show-LabDeploymentSummary -Summary + Write-Step "deploy-os-matrix-lab-DONE" + exit 0 +} +catch { + Write-Step ("deploy-os-matrix-lab-FAILED: {0}" -f $_) + $_ | Format-List -Property * -Force | Out-String | Add-Content -LiteralPath $LogPath + exit 1 +} diff --git a/Tests/Lab/Acceptance/Export-CellTimeline.ps1 b/Tests/Lab/Acceptance/Export-CellTimeline.ps1 new file mode 100644 index 0000000..844fe78 --- /dev/null +++ b/Tests/Lab/Acceptance/Export-CellTimeline.ps1 @@ -0,0 +1,131 @@ +[CmdletBinding()] +param ( + [Parameter(Mandatory)] [string] $MatrixRoot, + [Parameter(Mandatory)] [string[]] $Label, + [Parameter(Mandatory)] [string] $OutputPath +) + +# The timeline of the cells of the operating-system matrix (Decision 24): for each cell and edition in which the Admin role ran, in the order in which the +# cells ran, the module under test, the account of case 3 (its name and its relative ID, which tells two accounts of one name apart within a domain), +# whether the previous cell had the same name and the same account, when the previous fixture was removed, when the accounts were created, when the +# Admin role started, the minutes between them, and the three effective-access tests of case 3 in the Admin role (result, milliseconds, and the rights +# that a failing test received). A failing effective-access test of the Admin role is easy to blame on the module or on the environment; this table +# puts it beside the module, the position of the cell in the sequence, and the age of the accounts. Pass every label of a series, also a run that +# stopped before its tests (it writes no row, but it created and removed the accounts, which the next cell reports as the previous removal). The +# times come from the logs of Run-MatrixSequence.ps1 and of the controller (UTC). It reads files only; Windows PowerShell 5.1 or PowerShell 7. +$ErrorActionPreference = 'Stop' +# -File passes an array as one string, so a list may arrive as 'A,B'. +$Label = @($Label | ForEach-Object -Process { $_ -split ',' } | Where-Object -FilterScript { $_ }) +function Get-LogTime { + param ([string[]] $Lines, [string] $Pattern) + $line = $Lines | Where-Object -FilterScript { $_ -match $Pattern } | Select-Object -First 1 + if ($line -and $line -match '^\[(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d)Z?\]') { + [DateTime]::ParseExact($Matches[1], 'yyyy-MM-dd HH:mm:ss', [Globalization.CultureInfo]::InvariantCulture) + } +} + +$cells = New-Object -TypeName 'System.Collections.Generic.List[object]' +foreach ($name in $Label) { + $sequenceLog = Join-Path -Path $MatrixRoot -ChildPath ('{0}-sequence.log' -f $name) + if (-not (Test-Path -LiteralPath $sequenceLog)) { continue } + $candidate = if ((Get-Content -LiteralPath $sequenceLog -TotalCount 1) -match 'candidate-(\w+)') { $Matches[1] } else { '' } + foreach ($folder in (Get-ChildItem -LiteralPath $MatrixRoot -Directory -Filter ('{0}-*' -f $name))) { + $runLog = Join-Path -Path $folder.FullName -ChildPath 'run.log' + if (-not (Test-Path -LiteralPath $runLog)) { continue } + $run = @(Get-Content -LiteralPath $runLog) + $removeLog = Join-Path -Path $folder.FullName -ChildPath 'cleanup-2-remove.log' + $removed = if (Test-Path -LiteralPath $removeLog) { Get-LogTime -Lines @(Get-Content -LiteralPath $removeLog) -Pattern 'Removed the live tests' } + $configuration = Get-ChildItem -LiteralPath (Join-Path -Path $folder.FullName -ChildPath 'Results') -Recurse -Filter 'local-*.json' -ErrorAction SilentlyContinue | + Where-Object -FilterScript { $_.Name -match '-\d{14}\.json$' } | Select-Object -First 1 + $subjectName = '' + $subjectRid = '' + if ($configuration) { + $subject = (Get-Content -LiteralPath $configuration.FullName -Raw | ConvertFrom-Json).Accounts.Subject + $subjectName = $subject.Name + $subjectRid = ($subject.Sid -split '-')[-1] + } + else { + # A cell that stopped before its tests has no result file, but it created and removed the accounts, so the snapshot of its fixture names them. + $snapshotLog = Join-Path -Path $folder.FullName -ChildPath 'cleanup-1-snapshot.log' + $snapshot = if (Test-Path -LiteralPath $snapshotLog) { Get-Content -LiteralPath $snapshotLog -Raw } + if ($snapshot -match '(?m)^(\w+)\.\S+\s+OU NTFSSecurityLive.*\b(NtfsLiveSubject\w*)=S-[\d-]+-(\d+)') { + $subjectName = '{0}\{1}' -f $Matches[1], $Matches[2] + $subjectRid = $Matches[3] + } + } + + $cells.Add([pscustomobject]@{ + Run = $name + Candidate = $candidate + FileServer = $folder.Name.Substring($name.Length + 1) + Folder = $folder.FullName + Lines = $run + Subject = $subjectName + SubjectRid = $subjectRid + Started = Get-LogTime -Lines $run -Pattern 'START live tests' + Created = Get-LogTime -Lines $run -Pattern 'Preparing the accounts' + Removed = $removed + }) + } +} + +$rows = New-Object -TypeName 'System.Collections.Generic.List[object]' +$previous = $null +foreach ($cell in ($cells | Sort-Object -Property Started)) { + foreach ($edition in 'Desktop', 'Core') { + $adminStart = Get-LogTime -Lines $cell.Lines -Pattern "local-$edition-\d+: role Admin$" + if (-not $adminStart) { continue } + $tests = @{ T1 = ''; T2 = ''; T3 = '' } + $failures = 0 + $adminLog = Get-ChildItem -LiteralPath (Join-Path -Path $cell.Folder -ChildPath 'Results') -Recurse -Filter "local-$edition-*-Admin.log" | Select-Object -First 1 + if ($adminLog) { + $text = @(Get-Content -LiteralPath $adminLog.FullName) + $start = ($text | Select-String -Pattern 'Describing Get-NTFSEffectiveAccess for a domain account on a share folder' | Select-Object -First 1).LineNumber + $seen = 0 + for ($index = $start; $start -and $index -lt $text.Count -and $seen -lt 3; $index++) { + if ($text[$index] -notmatch '^\s+\[([+-])\] (.+?) (\d+(?:\.\d+)?m?s) \(') { continue } + $outcome = $Matches[1]; $title = $Matches[2]; $duration = $Matches[3] + $received = '' + if ($outcome -eq '-') { + $failures++ + for ($next = $index + 1; $next -lt [Math]::Min($index + 12, $text.Count); $next++) { + if ($text[$next] -match "But was:\s+'(0x\w+)'") { $received = ' ' + $Matches[1]; break } + if ($text[$next] -match 'Expected \$null or empty') { $received = ' errors'; break } + } + } + + $key = if ($title -match 'with -ServerName, without') { 'T1' } elseif ($title -match 'without -ServerName') { 'T2' } else { 'T3' } + $tests[$key] = '{0} {1}{2}' -f $(if ($outcome -eq '+') { 'pass' } else { 'FAIL' }), $duration, $received + $seen++ + } + } + + $hasPrevious = $null -ne $previous -and $null -ne $previous.Removed + $sameName = $null -ne $previous -and $cell.Subject -and $previous.Subject -eq $cell.Subject + $rows.Add([pscustomobject][ordered]@{ + Run = $cell.Run + Candidate = $cell.Candidate + FileServer = $cell.FileServer + Edition = $edition + Subject = $cell.Subject + SubjectRid = $cell.SubjectRid + SameNameAsPreviousCell = [bool] $sameName + SameAccountAsPreviousCell = [bool] ($sameName -and $previous.SubjectRid -eq $cell.SubjectRid) + PreviousRemoval = $(if ($hasPrevious) { '{0:yyyy-MM-dd HH:mm:ss}' -f $previous.Removed }) + AccountsCreated = '{0:yyyy-MM-dd HH:mm:ss}' -f $cell.Created + AdminRoleStarted = '{0:yyyy-MM-dd HH:mm:ss}' -f $adminStart + MinutesRemovalToCreation = $(if ($hasPrevious) { '{0:N1}' -f ($cell.Created - $previous.Removed).TotalMinutes }) + MinutesCreationToAdmin = '{0:N1}' -f ($adminStart - $cell.Created).TotalMinutes + MinutesRemovalToAdmin = $(if ($hasPrevious) { '{0:N1}' -f ($adminStart - $previous.Removed).TotalMinutes }) + T1ServerNameFileServer = $tests.T1 + T2DefaultServerName = $tests.T2 + T3UnreachableServerName = $tests.T3 + EffectiveAccessFailures = $failures + }) + } + + $previous = [pscustomobject]@{ Removed = $cell.Removed; Subject = $cell.Subject; SubjectRid = $cell.SubjectRid } +} + +$rows | Export-Csv -LiteralPath $OutputPath -NoTypeInformation -Encoding ASCII +'{0} rows for {1} cells written to {2}' -f $rows.Count, $cells.Count, $OutputPath diff --git a/Tests/Lab/Acceptance/Export-MatrixResults.ps1 b/Tests/Lab/Acceptance/Export-MatrixResults.ps1 new file mode 100644 index 0000000..2bf98eb --- /dev/null +++ b/Tests/Lab/Acceptance/Export-MatrixResults.ps1 @@ -0,0 +1,101 @@ +[CmdletBinding()] +param ( + [Parameter(Mandatory)] [string] $OutputPrefix, + [string] $MatrixRoot, + [string] $Label, + [string[]] $LocalSuiteFolder = @(), + [string] $ReferenceMachine = 'LOCAL' +) + +# Turns the raw results of the operating-system matrix (Decision 24) into the tables of the acceptance record, in Windows PowerShell 5.1. +# -MatrixRoot and -Label name the sequences of Run-MatrixSequence.ps1 (folders