Browse Source

Merge pull request #118 from raandree/ai/quality-gate-paths

test: close the remaining C# paths of the quality gate (handoff 1)
pull/121/head
Raimund Andrée 1 day ago
committed by GitHub
parent
commit
03bef2ca1e
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 83
      .memory-bank/activeContext.md
  2. 37
      .memory-bank/progress.md
  3. 34
      .memory-bank/systemPatterns.md
  4. 47
      .memory-bank/techContext.md
  5. 55
      CHANGELOG.md
  6. 16
      Docs/Cmdlets/Get-ChildItem2.md
  7. 4
      Docs/Cmdlets/Get-NTFSAccess.md
  8. 4
      Docs/Cmdlets/Get-NTFSAudit.md
  9. 18
      Docs/FAQ.md
  10. 141
      NTFSSecurity/BaseCmdlets.cs
  11. 5
      NTFSSecurity/ItemCmdlets/CopyItem2.cs
  12. 23
      NTFSSecurity/ItemCmdlets/GetChildItem2.cs
  13. 16
      NTFSSecurity/ItemCmdlets/GetDiskSpace.cs
  14. 5
      NTFSSecurity/ItemCmdlets/MoveItem2.cs
  15. 5
      NTFSSecurity/ItemCmdlets/RemoveItem2.cs
  16. 6
      NTFSSecurity/MiscCmdlets/GetFileHash2.cs
  17. 5
      NTFSSecurity/OwnerCmdlets/SetOwner.cs
  18. 10
      NTFSSecurity/SecurityDescriptorCmdlets/GetSecurityDescriptor.cs
  19. 20
      NTFSSecurity/SecurityDescriptorCmdlets/SetSecurityDescriptor.cs
  20. 5
      NTFSSecurity/SimpleAccessCmdlets/SimpleAccessCmdlets.cs
  21. 20
      NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml
  22. 2
      ProcessPrivileges/PrivilegeAndAttributes.cs
  23. 3
      Security2/FileSystem/FileSystemAccessRule2 Class/FileSystemAccessRule2.GetFileSystemAccessRules.cs
  24. 1
      Security2/FileSystem/FileSystemAccessRule2 Class/FileSystemAccessRule2.cs
  25. 3
      Security2/FileSystem/FileSystemAuditRule2 Class/FileSystemAuditRule2.GetFileSystemAuditRules.cs
  26. 1
      Security2/FileSystem/FileSystemAuditRule2 Class/FileSystemAuditRule2.cs
  27. 5
      Security2/FileSystem/SimpleFileSystemAuditRule.cs
  28. 11
      Security2/Win32/Lib.cs
  29. 198
      Tests/Access.Tests.ps1
  30. 193
      Tests/Audit.Tests.ps1
  31. 37
      Tests/Coverage/Quality-Gate-Paths-2026-10-09-Cmdlets.csv
  32. 796
      Tests/Coverage/Quality-Gate-Paths-2026-10-09-Explanations.md
  33. 232
      Tests/Coverage/Quality-Gate-Paths-2026-10-09-Methods.csv
  34. 65
      Tests/Coverage/Quality-Gate-Paths-2026-10-09-ParameterSets.csv
  35. 41
      Tests/Coverage/Quality-Gate-Paths-2026-10-09-RedGreen-Logs.csv
  36. 75
      Tests/Coverage/Quality-Gate-Paths-2026-10-09-RedGreen.csv
  37. 649
      Tests/Coverage/Quality-Gate-Paths-2026-10-09.md
  38. 70
      Tests/DriveRoot.Tests.ps1
  39. 19
      Tests/FileHash.Tests.ps1
  40. 124
      Tests/Inheritance.Tests.ps1
  41. 339
      Tests/ItemCmdlets.Tests.ps1
  42. 5
      Tests/Lab/README.md
  43. 73
      Tests/Links.Tests.ps1
  44. 903
      Tests/ObjectApis.Tests.ps1
  45. 14
      Tests/Owner.Tests.ps1
  46. 39
      Tests/PathErrors.Tests.ps1
  47. 638
      Tests/PipelineControl.Tests.ps1
  48. 401
      Tests/Privileges.Tests.ps1
  49. 59
      Tests/SecurityDescriptor.Tests.ps1
  50. 74
      Tests/TestHelpers.Tests.ps1
  51. 170
      Tests/TestHelpers.psm1

83
.memory-bank/activeContext.md

@ -9,11 +9,19 @@ source: current task evidence
## Current focus
Quality-gate follow-up is implemented and validated locally on
`ai/quality-gate-coverage`, based on `d25647d` of #116. Code/test baseline
`3442194`, lab regression/acceptance `7594e0c`; final records follow.
No remote mutation. Architecture/cmdlet-design choices remain deferred;
Decision 22 is still proposed. Stable 5.0.0 is not ready (Decision 21).
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
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.
## Evidence
@ -29,11 +37,41 @@ Decision 22 is still proposed. Stable 5.0.0 is not ready (Decision 21).
- Hidden omission was reproduced in all four configurations before the
fix. No parameter/design change. Publication tests are wholly mocked;
exact ordinal SHA-512 identity is required, no real upload occurred.
- Final uninstrumented suite: 914 each, zero failed. Passed/skipped:
elevated Desktop 890/24, Core 860/54; basic Desktop 749/165, Core 719/195.
Frozen aggregate: 2,641/3,559 sequence (74.21%), 974/1,933 branches
(50.39%); NTFSSecurity assembly 84.28%. All skipped templates have
executed counterparts; mutations restored exactly before green builds.
- At `3442194` (start of Handoff 1): 914 cases per configuration, 2,641/3,559
sequence points (74.21%), 974/1,933 branches (50.39%), 918 unvisited
points. All skipped templates had executed counterparts.
- Handoff 1 result at `5a5d58b` (frozen Release, four configurations, CI
wrappers, then AltCover): 1,310 cases per configuration, zero failures;
passed/skipped: elevated Desktop 1,286/24, Core 1,255/55; basic Desktop
1,076/234, Core 1,045/265. Coverage 3,192/3,634 sequence points (87.84%),
1,273/1,978 branches (64.36%); 231 methods with 442 points stay unvisited,
all classified: 223 explained, 8 open. Skip eligibility was checked by row
from a second full run per configuration: all 578 skipped rows (137
distinct tests) are executed in two other configurations.
- Mutation rounds on the frozen tree at `5a5d58b`: 26 mutations in four
rounds; 25 are detected by their guard in every configuration where it
runs, and M25 (the check by type, an equivalent mutant) survives as
expected. At `d61dffa` three had escaped (the verbose and debug rows ran
under the CI runner's `Stop`, and an Init test could not fail for its
branch), which led to `f4a16e1`. A first round at `630926f` had let one
mutation escape, which led to the `-Filter` bracket defect.
- Red/green matrix (measured after the last measurement, because the first
red runs were not kept): the final tests of the eight files that guard the
fixes (650 cases per configuration) over the production code of ten states
of the branch, built in Release and run with the focused runner (it sets
`Stop` like the CI wrappers) in the four configurations. 76 rows (73 in the
basic configurations) fail at the base `f11ff41`, each is green at the step
of its fix, none breaks later, and `d44a200` has none (the control). Defect
9 (the native buffer) has no guard. The matrix does not show that a test was
written before its fix: each fix commit carries both, and the red runs of
that time were not kept.
- Review: custom `security-reviewer` could not start (model unavailable); the
built-in read-only `code-review` agent made nine static passes: no Blocker
or Major; its Minor findings led to the `*.*`, help, test, later-command,
error-stream, and privilege fixes. Report:
`Tests/Coverage/Quality-Gate-Paths-2026-10-09.md` with an appendix of
explanations and CSV rows; raw evidence is in the session folder
`4b12e2f4-d4c7-4a5d-883a-ddb7421c4848\files\qg-paths`.
- Live packaged candidate, 09:20 to 09:51 UTC: 330 passed, zero failed,
two expected Server-module skips. Published rc6: 326 passed, four
expected failures (Hidden and rc7 warning text in each edition), two
@ -43,9 +81,8 @@ Decision 22 is still proposed. Stable 5.0.0 is not ready (Decision 21).
both editions. Cleanup wrapper's broad Error.Count was not acceptance
proof. Independent probes verified all fixture objects/members/profiles
gone from six machines. Raw failing markers and corrected evidence kept.
- One read-only independent code review approved, high confidence, no
significant findings or confirmed exploit. Custom reviewer could not
start (model unavailable); built-in code-review performed the one pass.
- Review of the lab candidate: one read-only independent code review
approved, high confidence, no significant findings or confirmed exploit.
- 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.
@ -56,10 +93,16 @@ Decision 22 is still proposed. Stable 5.0.0 is not ready (Decision 21).
## Next step
1. Maintainer pushes/reviews this follow-up; retain separate commits and
stacked-PR merge order (15). #116's Decision 22 review remains required.
2. Integrate and pass CI, then publish/test the next candidate package.
3. Close the remaining-path inventory (918 points, 562 for finer review),
decide/provision the OS matrix, obtain or explicitly accept #34 feedback.
4. Only then release 5.0.0 through documented CI steps; never claim the
current coverage percentage alone meets the quality gate.
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.

37
.memory-bank/progress.md

@ -12,8 +12,9 @@ source: repository and validation evidence
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 on `ai/quality-gate-coverage`; Phase 2 is not complete while the
remaining-path inventory is open. Stable Gallery version: 4.2.6.
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.
After 5.0.0, archive in favor of WindowsAccessControl (Decision 18).
## Recent milestones
@ -62,6 +63,17 @@ After 5.0.0, archive in favor of WindowsAccessControl (Decision 18).
Lab guards/acceptance committed in `7594e0c`. One independent code review
approved with no significant finding (custom model unavailable; built-in
fallback). All 11 tested files match the ZIP. OS/path gates stay open.
- 2026-10-09: Handoff 1 on `ai/quality-gate-paths` (28 local commits, no
push): suite 914 to 1,310 per configuration, zero failures; coverage
3,192/3,634 sequence points (87.84%), 1,273/1,978 branches; all 231
unvisited methods classified (223 explained, 8 open). Eleven defects
fixed, ten with a guard that is red before the fix and green after it (a
red/green matrix over ten states of the branch: 76 rows red at the base,
none after the last fix), among them a later command's exception that
cmdlets swallowed (a `throw` made `Remove-Item2` remove the next item; also
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`.
## Stable capabilities
@ -97,17 +109,16 @@ After 5.0.0, archive in favor of WindowsAccessControl (Decision 18).
6. Optional maintainer cleanup: obsolete AppVeyor/Read the Docs access,
wiki editing restrictions, `test/transfer`, and old lab checkpoints
when no longer needed. No remote changes or snapshot restores here.
7. Fresh coverage inventory at `3442194`: 918 unvisited sequence points.
Of these, 244 are in classes unused by cmdlets and 112 in parameter
getters; 562 remain for finer review/testing, including unused overloads,
defensive/native failures, and environment-specific branches. High-value
local gaps closed: folders/Force/DeleteError, RestoreOwnerError, all
scopes, file/folder inheritance, enumeration/depth/link skipping,
descriptor write failures, and forced file replacement. Remaining
candidates: audit ownership-retry failures, SD inheritance edge cases,
effective-access unresolved identity, recursive denial/error surfaces,
output-object comparisons/formatting. A conditional ACE display remains
a .NET representation limit, not evidence of unconditional permissions.
7. Remaining-path inventory at the final frozen commit of Handoff 1:
442 unvisited sequence points in 231 methods, all classified
(`Tests/Coverage`): 223 explained from source with evidence, 8 open
(`FileSecurity` conversions, `RemoveAll` account filters). Other open
decisions: lazy path overloads, abandoned `PrivilegeEnabler`, dot patterns
of `Get-ChildItem2 -Filter`, 17 owner-restore handlers that do not pass on
what a later command raises (a rare combination), unused classes. An audit
write's ownership retry cannot run on a local volume and is covered only
by the lab. A conditional ACE display remains a .NET representation limit,
not evidence of unconditional permissions.
8. Publication recovery is implemented locally in `95b827e`, with 14 offline
tests and exact artifact SHA-512 verification. Original upload errors
remain errors for missing/different/unverifiable outcomes. Not deployed

34
.memory-bank/systemPatterns.md

@ -64,6 +64,25 @@ Read only task-relevant records; the index controls routing.
- Pipeline getters never throw; per-item errors name input and allow continuation.
- Folder moves never use CopyAllowed; preserve cross-volume source folders.
- Apply implied Hidden/Force before deciding to emit, including the first item.
- A catch-all for the failures of one item must pass on what a later command
raises through a Write call. A downstream throw is an ordinary exception,
so a type check finds only the end of the pipeline, break, and continue.
BaseCmdlet notes the exception that its WriteObject, WriteError,
WriteVerbose, and WriteDebug raised (WriteWarning is not noted: no catch-all
encloses it); `IsFromLaterCommand` recognizes it, and the type check
`PipelineControl.IsEnd` backs it up for other calls into PowerShell. A write
inside a helper, such as the owner restore of `InvokeAsOwner`, is an
accepted gap: its handler reports the item's own error, which raises too.
Prefer writing outside the try. `Tests/PipelineControl.Tests.ps1` has rows
for every cmdlet: add a row for a new one, and keep the typed-throw rows
(they fail if PowerShell stops wrapping a thrown exception).
- Record an enabled privilege before the next write, which a later command
can answer with an exception: Dispose disables only what is recorded.
- `Get-ChildItem2 -Filter` has two matchers: the AlphaFS enumeration, whose
dot rules also differ from those of `Get-ChildItem` (probe: `Report.*` and
`Rep*.` in both editions), and a PowerShell wildcard on the name, where only
`*` and `?` are special. `*.*` is treated as `*`; the other dot patterns are
pinned by `ItemCmdlets.Tests` and are an open maintainer decision.
### Tests and documentation
@ -73,9 +92,24 @@ Read only task-relevant records; the index controls routing.
- Assert persisted state, errors/targets, continuation, and no failed
PassThru output. Prove new characterization guards with bounded mutations;
restore source exactly and rebuild before green validation or packaging.
Apply the mutations of one round together only when no guard can fail
because of another mutation; otherwise split the rounds.
- A test that arranges a retry asserts its precondition (the plain write is
denied), or it can pass without reaching the retry.
- A test variable must not take the name of an automatic variable such as
`$foreach`: Pester runs the block inside a foreach, and the value is lost.
- The CI scripts set `$ErrorActionPreference = 'Stop'`; focused runs do the
same, and a test that needs a non-terminating error (for example to take it
through `2>&1`) names `-ErrorAction Continue`.
- Fixture DACLs use .NET SetAccessControl, not Set-Acl's unintended SACL writes.
- Scope/descendant expectations are independent of the production converter.
- Drive-root tests map a sandbox folder with `subst` through
`New-TestDriveMapping` (elevated only; the basic token cannot). Tests that
need a letter without a volume take the lowest free one.
- Desktop platyPS: generate help, rebuild, round-trip unchanged, check links.
platyPS 0.14.2 turns a pair of asterisks in a paragraph into emphasis, also
inside backticks, and the help drops them: write the words, put patterns in
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.

47
.memory-bank/techContext.md

@ -115,19 +115,42 @@ source: repository and executable evidence
- Report sequence points, not unique source lines. Four-run baselines:
rc5 2,020/3,476 (58.1%), branches 711/1,873 (38.0%);
rc6 2,412/3,540 (68.14%), branches 850/1,918 (44.32%);
follow-up `3442194` 2,641/3,559 (74.21%), 974/1,933 (50.39%).
NTFSSecurity assembly: 1,769/2,099 (84.28%). Different code changes
denominators; never present these as same-source incremental percentages.
- Final suite: 914 per configuration, zero failures. Passed/skipped:
elevated Desktop 890/24, Core 860/54; basic Desktop 749/165, Core 719/195.
follow-up `3442194` 2,641/3,559 (74.21%), 974/1,933 (50.39%);
Handoff 1 `5a5d58b` 3,192/3,634 (87.84%), 1,273/1,978 (64.36%). Different
code changes denominators; never present these as same-source incremental
percentages. The branch summary counts 820 compiler-generated points (185
visited); report the explicit branch points as well (1,088/1,158, 93.96%).
- Suite at `5a5d58b`: 1,310 per configuration, zero failures. Passed/skipped:
elevated Desktop 1,286/24, Core 1,255/55; basic Desktop 1,076/234,
Core 1,045/265.
- NUnit skipped ForEach names retain placeholders and parameter tuples,
executed names expand them. Strip trailing data tuples and match templates;
raw-name intersection or positional alignment is invalid across editions.
139 skipped templates have eligible executed counterparts. Inspect input
eligibility when an individual data row has a condition of its own.
- Remaining inventory: 918 points, including 244 in cmdlet-unused classes,
112 parameter-getter points, and 562 awaiting finer classification/testing.
Preserve raw XML, eligibility CSV, logs, commit identity, and build hashes.
executed names expand them; raw-name intersection and positional alignment
are invalid. Skip eligibility is checked by row: run the suite once per
configuration with Pester PassThru (`Get-DiscoveryRows2.ps1 -Run` in the
session evidence) and match skipped with executed rows by file, line,
path, name, and data. Discovery alone misses tests that skip themselves
while they run, and `ConvertTo-Json` of rich data rows never finishes:
write primitives and type names.
- Remaining inventory: 442 points in 231 methods, classified by rule with
evidence (probe, IL scan, source reading); see `Tests/Coverage`. Preserve
raw XML, row CSVs, logs, commit identity, and build hashes. The frozen
Build rewrites the hash file each time: save the hashes of the measured
assemblies (AltCover `__Saved` copies) before any mutation build.
- Bounded mutations: one script per round on the frozen worktree, with
guards that no other mutation of the round can trip (an escape can be an
overlap or an equivalent mutant: check before changing a test). Restore
the source exactly and rebuild.
- Red/green matrix, to show afterwards that a guard fails without its fix:
build each state of the branch (base, then each fix commit) in its own
Release worktree, lay the final `Tests` over it (`git checkout <final> --
Tests`), run the guarding files with the focused runner (it sets
`$ErrorActionPreference` to `Stop` like the CI wrappers) in all four
configurations, and count failed rows per name with their multiplicity (a
block whose `BeforeAll` fails lists its data rows under one unexpanded
template name). The last state is the control and must have no failure.
Keep the logs and a manifest with their hashes, and hash each build: the
first red runs of Handoff 1 were deleted and could not be reproduced, and
the frozen runner rewrites its hash file at each build.
## Lab acceptance

55
CHANGELOG.md

@ -104,6 +104,51 @@ The format is based on
### Fixed
- Retain the supplied path in the public access- and audit-rule constructors
so their `FullName`, `Name`, and simplified audit conversions identify
the item
- Reduce `ReadData` to `Read` in simplified audit entries, and compare them
with audit entries rather than access entries, preserving equality with
themselves and with equivalent simplified audit objects
- Compare boxed privilege output values by their privilege and attributes;
the object overload rejected privilege values and recursively compared
an attributes enum instead
- Fix `Clear-NTFSAccess -DisableInheritance` and `Set-NTFSSecurityDescriptor`,
which reported a `RestoreOwnerError` for an owner that had not changed:
after they took ownership of an item that the user owned already and left a
DACL without the right to set an owner, they failed to set the same owner
back
- Fix `InheritedFrom` of `Get-NTFSAccess` and `Get-NTFSAudit` for an entry
whose folder Windows cannot name, such as for an item that was deleted
after it was read or a folder above it that the user cannot read: the text
read `unknown paren`, and the explicit entries showed it as well. An
inherited entry now shows `unknown parent`, and an explicit entry no source;
the failed lookup no longer leaks its native buffer
- Fix `Remove-Item2`, `Copy-Item2`, `Move-Item2`, `Set-NTFSOwner`,
`Set-NTFSSecurityDescriptor`, `Get-NTFSSecurityDescriptor`,
`Get-NTFSSimpleAccess`, `Get-FileHash2`, `Get-DiskSpace`, and
`Get-ChildItem2` below the first folder, which went on with the next item
when a later command ended the pipeline: a `break` or `continue`,
`Select-Object -First`, or a `throw` was handled as a failure of the item,
so that `Remove-Item2 -PassThru | Select-Object -First 1` removed every
item, and the caller never saw the `throw`. The same held when the later
command took a stream instead of the objects: the verbose messages of
`Get-FileHash2` and `Set-NTFSSecurityDescriptor`, the debug messages of
`Set-NTFSOwner`, and the errors of `Get-ChildItem2` for a folder that it
cannot read, for example with `4>&1` or `2>&1`. They now stop and write no
error, and the error of the later command reaches the caller
- Fix the cmdlets that enable the privileges for the duration of their
command, which left a privilege enabled in the session and hid the
exception of a later command when that command took the debug message
after the enabling, for example with `5>&1 | Select-Object -First 2`; they
now disable the privilege and pass the exception on
- Fix `Get-ChildItem2 -Filter`, which read a bracket as the start of a
character class, so that it did not return a file with brackets in its name,
such as `Report[1].txt`, for that name; only `*` and `?` are wildcards. A
null `-Filter` is rejected as a parameter error
- Fix `Get-ChildItem2 -Filter *.*`, which returned only the items with a dot
in their names and dropped the other files and folders, most folders among
them, instead of every item as `Get-ChildItem` does
- Fix `Get-Help`, which showed only the syntax: ship the help file
`en-US\NTFSSecurity.dll-Help.xml` generated from the cmdlet documentation,
including the links that `Get-Help -Online` opens, instead of the outdated
@ -291,10 +336,12 @@ The format is based on
for such a path, as in PowerShell 7, and writes the reason as a debug
message
- Fix the cmdlets that enable the Backup, Restore, Take Ownership, and
Security privileges, which left them enabled in the session when a later
command, such as `Select-Object -First`, or a terminating error stopped
the pipeline early; they now disable them also then
- Fix the cmdlets that enable the privileges, which stopped with the error
Security privileges for the duration of their command, which left them
enabled in the session when a later command, such as `Select-Object -First`,
or a terminating error stopped the pipeline early; they now disable them
also then. `Enable-Privileges` keeps them enabled by design
- Fix the cmdlets that enable the privileges for the duration of their
command, which stopped with the error
"Priviledge already disabled" and left the other privileges enabled when
another command in the pipeline, such as `Disable-Privileges`, had
disabled one of them; a privilege that they can't disable now gives a

16
Docs/Cmdlets/Get-ChildItem2.md

@ -65,6 +65,14 @@ PS C:\> dir2 -Path C:\Data -Attributes Hidden, System
Uses the `dir2` alias and returns the items of `C:\Data` that have the hidden or the system attribute, like `Get-ChildItem -Attributes Hidden, System`.
### Example 5: Return every item, with or without a dot in its name
```PowerShell
PS C:\> Get-ChildItem2 -Path C:\Data -Filter *.*
```
Returns every item of `C:\Data`, also the files and folders whose names have no dot, as `Get-ChildItem` does for this filter.
## PARAMETERS
### -Attributes
@ -134,7 +142,7 @@ Accept wildcard characters: False
### -Filter
Specifies a name pattern that an item must match to be returned. The pattern supports the `*` and `?` wildcard characters, and the match ignores case. The default value is `*`, which returns every item. The pattern is applied to the name of each item, not to its path, and during a recursive listing it restricts only the returned items; the cmdlet still descends into every subfolder.
Specifies a name pattern that an item must match to be returned. The pattern supports the asterisk and the question mark as wildcard characters, an asterisk for any number of characters and a question mark for exactly one, and the match ignores case. Any other character stands for itself; a bracket is an ordinary character, so `Report[1].txt` returns the file of that name. As for `Get-ChildItem`, a pattern of an asterisk, a dot, and an asterisk returns every item, also an item without a dot in its name. The default value is `*`, which returns every item. The pattern is applied to the name of each item, not to its path, and during a recursive listing it restricts only the returned items; the cmdlet still descends into every subfolder.
```yaml
Type: String
@ -309,6 +317,12 @@ A folder that cannot be read produces a non-terminating error with the ID `DirUn
Before 5.0.0, a `-Path` value that points to a file stopped the cmdlet with an `InvalidCastException`, `-Attributes` returned only the items that had all the listed attributes, and an empty `-Attributes` value returned every item, also the hidden ones. Earlier builds, including the 5.0.0 prereleases, could also omit the first hidden item with `-Hidden` unless `-Force` was explicitly supplied.
Before 5.0.0, `-Filter` read a bracket as the start of a character class, so a file with brackets in its name, such as `Report[1].txt`, was not returned for its name, and a pattern of an asterisk, a dot, and an asterisk dropped the items without a dot in their names, most folders among them.
The names are matched twice, by the enumeration of the AlphaFS library and by the cmdlet, which reads a dot as an ordinary character, and their rules for a dot can differ from those of `Get-ChildItem`: a pattern such as `Report.*` does not return the file `Report`, which has no dot, a pattern that ends in a dot returns nothing, and an empty value returns nothing. Only the pattern of an asterisk, a dot, and an asterisk is treated as a single asterisk.
Before 5.0.0, a `break`, a `continue`, or a `throw` in a later command of the pipeline did not end the cmdlet for an item below the first folder, also when the later command took the error of a folder that the cmdlet cannot read, for example with `2>&1`.
## RELATED LINKS
[Get-Item2](Get-Item2.md)

4
Docs/Cmdlets/Get-NTFSAccess.md

@ -33,7 +33,7 @@ In the `Path` parameter set the cmdlet reads the item from disk; relative paths
By default both explicit and inherited entries are returned. `-ExcludeInherited` limits the result to the entries defined on the item itself, `-ExcludeExplicit` limits it to the entries the item inherits from its parents, and combining both returns nothing. `-Account` filters the result to a single account; an entry matches when the account resolves to the same SID.
When the module setting `GetInheritedFrom` is `$true`, which is the default in the `PrivateData` section of NTFSSecurity.psd1, the `InheritedFrom` property of every inherited entry contains the path of the folder the entry originates from. The default table view shows the account, the rights, the scope of the ACE in the wording of the Windows security dialog, the access type, and the inheritance information; setting `ShowAccountSid` to `$true` adds the SID to the account column.
When the module setting `GetInheritedFrom` is `$true`, which is the default in the `PrivateData` section of NTFSSecurity.psd1, the `InheritedFrom` property of every inherited entry contains the path of the folder the entry originates from, or `unknown parent` when Windows cannot name that folder, for example because the user cannot read a folder above the item. The default table view shows the account, the rights, the scope of the ACE in the wording of the Windows security dialog, the access type, and the inheritance information; setting `ShowAccountSid` to `$true` adds the SID to the account column.
## EXAMPLES
@ -188,6 +188,8 @@ Before 5.0.0, after a path whose ACL could not be read, the cmdlet returned the
Before 5.0.0-rc6, with `-ExcludeExplicit`, each inherited entry showed the `InheritedFrom` path of another entry, and for a security descriptor with audit entries, such as one that `Get-NTFSSecurityDescriptor` reads in an elevated session, the cmdlet stopped with an `ArgumentOutOfRangeException`.
Before 5.0.0, when Windows could not name the folder of an inherited entry, `InheritedFrom` read `unknown paren`, and the explicit entries of the item showed it as well.
For the root of a drive, such as `C:\`, or of a volume, such as `\\?\Volume{GUID}\`, the cmdlets that read and change security use the root folder of the volume, like Explorer, `icacls`, and `Get-Acl`. Before 5.0.0, they read and changed the security descriptor of the drive itself, a device object with other entries.
## RELATED LINKS

4
Docs/Cmdlets/Get-NTFSAudit.md

@ -33,7 +33,7 @@ In the `Path` parameter set the cmdlet reads the security descriptor of every it
By default the cmdlet returns explicit and inherited entries. Use `-ExcludeInherited` to return only the entries that are set on the item itself, and `-ExcludeExplicit` to return only the entries that the item inherits from a parent folder. `-Account` filters the result to a single account; the comparison is made on the security identifier (SID), so an account name and its SID select the same entries.
The `InheritedFrom` property is filled only when the module setting `GetInheritedFrom` is `$true`, which is the default in the `PrivateData` section of `NTFSSecurity.psd1`.
The `InheritedFrom` property is filled only when the module setting `GetInheritedFrom` is `$true`, which is the default in the `PrivateData` section of `NTFSSecurity.psd1`. It contains `unknown parent` for an inherited entry when Windows cannot name the folder that the entry comes from.
## EXAMPLES
@ -187,6 +187,8 @@ Before 5.0.0, the cmdlet returned no entries and no error without the Security p
Before 5.0.0-rc6, with `-ExcludeExplicit`, each inherited entry showed the `InheritedFrom` path of another entry.
Before 5.0.0, when Windows could not name the folder of an inherited entry, `InheritedFrom` read `unknown paren`, and the explicit entries of the item showed it as well.
## RELATED LINKS
[Add-NTFSAudit](Add-NTFSAudit.md)

18
Docs/FAQ.md

@ -93,3 +93,21 @@ Compare-Object -ReferenceObject (Get-NTFSAccess -Path C:\Data\A) -DifferenceObje
The same works for the entries of `Get-NTFSAudit`, with `AuditFlags` in
place of `AccessControlType`. See
[Get-NTFSAccess](Cmdlets/Get-NTFSAccess.md).
## How do the public object APIs compare and convert entries?
The public `FileSystemAccessRule2` and `FileSystemAuditRule2` constructors
that take a .NET rule and a string path retain that path in `FullName` and
its last component in `Name`. They do not read or change the item. This is
useful when an application constructs a rule before replaying it through
the public rule helpers.
`ToSimpleFileSystemAuditRule2()` retains the path and account and reduces
`ReadData` to `Read`, like the simplified access-rule helper. Simplified
audit objects compare only with other simplified audit objects; they are
not equal to access objects. This does not change the reference-based
comparison of the full access and audit entries described above.
The values returned by `Get-Privileges` compare by `Privilege` and
`PrivilegeAttributes`. Typed and boxed .NET comparisons agree, and an
unrelated object is not equal to a privilege value.

141
NTFSSecurity/BaseCmdlets.cs

@ -8,11 +8,133 @@ using System.Collections;
namespace NTFSSecurity
{
/// <summary>
/// Recognizes what a later command in the pipeline raises to end the pipeline or the loop around it: the end of the
/// pipeline, for example for Select-Object -First, and a break or continue in a script block. These exceptions pass
/// through a cmdlet while it writes to a stream. A catch-all for the failures of an item must pass them on: reported
/// as the error of that item, they would end nothing, and the cmdlet would go on with the next item. BaseCmdlet
/// notes the exception that each of its Write methods but WriteWarning raises, which includes everything that a
/// later command can throw; this check by type is a second line of defense for calls into PowerShell that are not
/// noted, such as ShouldProcess in the try block of Remove-Item2. See
/// BaseCmdlet.IsFromLaterCommand.
/// </summary>
internal static class PipelineControl
{
/// <summary>
/// Whether the exception ends the pipeline or the loop around it. PowerShell doesn't make the exceptions of break
/// and continue public, so they are recognized by the name of their base type.
/// </summary>
internal static bool IsEnd(Exception exception)
{
if (exception is PipelineStoppedException)
{
return true;
}
for (var type = exception.GetType(); type != null; type = type.BaseType)
{
if (type.FullName == "System.Management.Automation.FlowControlException")
{
return true;
}
}
return false;
}
}
public class BaseCmdlet : PSCmdlet
{
protected List<string> paths = new List<string>();
protected List<FileSystemSecurity2> securityDescriptors = new List<FileSystemSecurity2>();
// The exception that a Write method of this cmdlet raised last. A Write method runs the later commands of the
// pipeline and so raises what they raise: a throw in a script block, an error with -ErrorAction Stop, the end of the
// pipeline, a break or a continue. None of it is a failure of the item that the cmdlet processes. A catch-all for
// those failures must pass it on (IsFromLaterCommand), or the cmdlet reports it as the error of that item, goes on
// with the next one, and the caller never sees the exception. WriteWarning is the one Write method that isn't
// noted, because no catch-all of the module encloses it.
private Exception laterCommandException;
/// <summary>Writes the object to the pipeline and notes what a later command raises, see IsFromLaterCommand.</summary>
public new void WriteObject(object sendToPipeline)
{
try
{
base.WriteObject(sendToPipeline);
}
catch (Exception ex)
{
laterCommandException = ex;
throw;
}
}
/// <summary>Writes the object to the pipeline and notes what a later command raises, see IsFromLaterCommand.</summary>
public new void WriteObject(object sendToPipeline, bool enumerateCollection)
{
try
{
base.WriteObject(sendToPipeline, enumerateCollection);
}
catch (Exception ex)
{
laterCommandException = ex;
throw;
}
}
// The error, verbose, and debug streams, which a later command can take too, for example Select-Object -First with 2>&1.
/// <summary>Writes the error and notes what a later command raises, see IsFromLaterCommand.</summary>
public new void WriteError(ErrorRecord errorRecord)
{
try
{
base.WriteError(errorRecord);
}
catch (Exception ex)
{
laterCommandException = ex;
throw;
}
}
/// <summary>Writes a verbose message and notes what a later command raises, see IsFromLaterCommand.</summary>
public new void WriteVerbose(string text)
{
try
{
base.WriteVerbose(text);
}
catch (Exception ex)
{
laterCommandException = ex;
throw;
}
}
/// <summary>Writes a debug message and notes what a later command raises, see IsFromLaterCommand.</summary>
public new void WriteDebug(string text)
{
try
{
base.WriteDebug(text);
}
catch (Exception ex)
{
laterCommandException = ex;
throw;
}
}
/// <summary>
/// Whether the exception comes from a later command of the pipeline, not from the item that the cmdlet processes.
/// </summary>
protected bool IsFromLaterCommand(Exception exception)
{
return ReferenceEquals(exception, laterCommandException) || PipelineControl.IsEnd(exception);
}
protected override void BeginProcessing()
{
base.BeginProcessing();
@ -192,6 +314,8 @@ namespace NTFSSecurity
/// <summary>
/// Takes ownership of the item, runs the action, and restores the previous owner on every exit path.
/// A failure to restore the owner is written as a RestoreOwnerError and doesn't hide an error of the action.
/// An owner that the action did not change, because the current user owned the item already, isn't set again:
/// an action such as clearing the DACL can leave nobody the right to do so.
/// </summary>
/// <param name="item">The file or folder to take ownership of.</param>
/// <param name="path">The path the user specified, used as the error target.</param>
@ -199,14 +323,17 @@ namespace NTFSSecurity
protected void InvokeAsOwner(Alphaleonis.Win32.Filesystem.FileSystemInfo item, string path, Action action)
{
var previousOwner = FileSystemOwner.GetOwner(item).Owner;
IdentityReference2 currentUser = System.Security.Principal.WindowsIdentity.GetCurrent().User;
FileSystemOwner.SetOwner(item, System.Security.Principal.WindowsIdentity.GetCurrent().User);
FileSystemOwner.SetOwner(item, currentUser);
try
{
action();
}
finally
{
if (previousOwner != currentUser)
{
try
{
@ -218,6 +345,7 @@ namespace NTFSSecurity
}
}
}
}
#endregion
}
@ -314,9 +442,10 @@ namespace NTFSSecurity
WriteDebug(string.Format("The privilege {0} is disabled...", privilege));
//activate it
privControl.EnablePrivilege(privilege);
WriteDebug(string.Format("..enabled"));
//remember the privilege so that we can automatically disable it after the cmdlet finished processing
//remember the privilege so that we can automatically disable it after the cmdlet finished processing; before
//the next message, which a later command can answer with an exception: Dispose disables only what is noted
enabledPrivileges.Add(privilege.ToString());
WriteDebug(string.Format("..enabled"));
privileges = privControl.GetPrivileges();
}
@ -340,6 +469,12 @@ namespace NTFSSecurity
}
catch(Exception ex)
{
// Not a failure to enable the privilege: a later command that took one of the debug messages raised it.
if (IsFromLaterCommand(ex))
{
throw;
}
WriteDebug(string.Format("Could not enable privilege {0}. The error was: {1}", privilege, ex.Message));
return false;
}

5
NTFSSecurity/ItemCmdlets/CopyItem2.cs

@ -149,6 +149,11 @@ namespace NTFSSecurity
}
catch (Exception ex)
{
if (IsFromLaterCommand(ex))
{
throw;
}
WriteError(new ErrorRecord(ex, "CopyError", ErrorCategory.NotSpecified, resolvedPath));
}
}

23
NTFSSecurity/ItemCmdlets/GetChildItem2.cs

@ -45,6 +45,7 @@ namespace NTFSSecurity
}
[Parameter(Position = 2)]
[ValidateNotNull]
public string Filter
{
get { return filter; }
@ -145,7 +146,12 @@ namespace NTFSSecurity
paths = new List<string>() { GetCurrentLocation() };
}
wildcard = new WildcardPattern(filter, WildcardOptions.Compiled | WildcardOptions.IgnoreCase);
// Only * and ? are wildcards, like in the pattern that the enumeration matches; a bracket or a backtick stands
// for itself. Before 5.0.0, [1] was read as a character class, so a file with brackets in its name was not
// returned for its name. The enumeration returns every item for *.* as Windows does, so the comparison does
// too; with the dot as an ordinary character, it would drop the items without a dot, most folders.
var pattern = filter == "*.*" ? "*" : filter;
wildcard = new WildcardPattern(pattern.Replace("`", "``").Replace("[", "`[").Replace("]", "`]"), WildcardOptions.Compiled | WildcardOptions.IgnoreCase);
modeMethodInfo = typeof(FileSystemCodeMembers).GetMethod("Mode");
@ -244,8 +250,15 @@ namespace NTFSSecurity
{
throw ex;
}
catch (Exception)
catch (Exception ex)
{
// Not what a later command raises, which this catch would hide; the verbose message is for a
// folder that can't be listed.
if (IsFromLaterCommand(ex))
{
throw;
}
WriteVerbose(string.Format("Cannot access folder '{0}' for recursive operation", di));
}
}
@ -260,11 +273,11 @@ namespace NTFSSecurity
}
catch (Exception ex)
{
//System.Management.Automation.BreakException or System.Management.Automation.ContinueException cannot be caught due to its protection level in PowerShell v2
if (ex.GetType().FullName == "System.Management.Automation.BreakException" | ex.GetType().FullName == "System.Management.Automation.ContinueException")
if (IsFromLaterCommand(ex))
{
throw ex;
throw;
}
WriteError(new ErrorRecord(ex, "DirUnspecifiedError", ErrorCategory.NotSpecified, di.FullName));
}
}

16
NTFSSecurity/ItemCmdlets/GetDiskSpace.cs

@ -1,4 +1,5 @@
using Alphaleonis.Win32.Filesystem;
using System;
using System.Linq;
using System.Management.Automation;
@ -35,17 +36,22 @@ namespace NTFSSecurity
foreach (var letter in driveLetter)
{
var diskSpaceInfo = new DiskSpaceInfo(letter);
var hasSpace = false;
try
{
diskSpaceInfo.Refresh();
if (diskSpaceInfo.TotalNumberOfBytes > 0)
{
this.WriteObject(diskSpaceInfo);
hasSpace = diskSpaceInfo.TotalNumberOfBytes > 0;
}
}
catch
catch (Exception)
{
this.WriteWarning(string.Format("Could not get drive details for '{0}'", letter));
continue;
}
// Outside the try: what a later command raises while it takes the object is not a failure of the drive.
if (hasSpace)
{
this.WriteObject(diskSpaceInfo);
}
}
}

5
NTFSSecurity/ItemCmdlets/MoveItem2.cs

@ -160,6 +160,11 @@ namespace NTFSSecurity
}
catch (Exception ex)
{
if (IsFromLaterCommand(ex))
{
throw;
}
WriteError(new ErrorRecord(ex, "MoveError", ErrorCategory.NotSpecified, resolvedPath));
}
}

5
NTFSSecurity/ItemCmdlets/RemoveItem2.cs

@ -102,6 +102,11 @@ namespace NTFSSecurity
}
catch (Exception ex)
{
if (IsFromLaterCommand(ex))
{
throw;
}
WriteError(new ErrorRecord(ex, "DeleteError", ErrorCategory.NotSpecified, path));
}
}

6
NTFSSecurity/MiscCmdlets/GetFileHash2.cs

@ -78,6 +78,12 @@ namespace NTFSSecurity
}
catch (Exception ex)
{
// Not what a later command raises, for example when it takes the verbose message.
if (IsFromLaterCommand(ex))
{
throw;
}
WriteError(new ErrorRecord(ex, "ReadFileError", ErrorCategory.OpenError, path));
continue;
}

5
NTFSSecurity/OwnerCmdlets/SetOwner.cs

@ -87,6 +87,11 @@ namespace NTFSSecurity
}
catch (Exception ex)
{
if (IsFromLaterCommand(ex))
{
throw;
}
WriteError(new ErrorRecord(ex, "SetOwnerError", ErrorCategory.WriteError, path));
continue;
}

10
NTFSSecurity/SecurityDescriptorCmdlets/GetSecurityDescriptor.cs

@ -64,12 +64,22 @@ namespace NTFSSecurity
}
catch (Exception ex2)
{
if (IsFromLaterCommand(ex2))
{
throw;
}
WriteError(new ErrorRecord(ex2, "ReadSecurityError", ErrorCategory.WriteError, path));
continue;
}
}
catch (Exception ex)
{
if (IsFromLaterCommand(ex))
{
throw;
}
WriteError(new ErrorRecord(ex, "ReadSecurityError", ErrorCategory.OpenError, path));
}
}

20
NTFSSecurity/SecurityDescriptorCmdlets/SetSecurityDescriptor.cs

@ -67,6 +67,12 @@ namespace NTFSSecurity
}
catch (Exception ex)
{
// Not what a later command raises, for example when it takes the verbose message.
if (IsFromLaterCommand(ex))
{
throw;
}
WriteError(new ErrorRecord(ex, "WriteSdError", ErrorCategory.WriteError, sd.Item));
continue;
}
@ -81,6 +87,11 @@ namespace NTFSSecurity
}
catch (Exception ex)
{
if (IsFromLaterCommand(ex))
{
throw;
}
WriteError(new ErrorRecord(ex, "ReadSecurityError", ErrorCategory.ReadError, sd.Item));
}
}
@ -88,13 +99,16 @@ namespace NTFSSecurity
}
// Like InvokeAsOwner, takes ownership for the write and sets the previous owner back on every exit path, but not
// after a successful write of a descriptor that sets the owner itself, which would undo that owner.
// after a successful write of a descriptor that sets the owner itself, which would undo that owner, and not when the
// current user owned the item already, so that nothing changed and a descriptor that leaves nobody the right to set
// an owner can't make it fail.
private void WriteChangesAsOwner(FileSystemSecurity2 sd)
{
var setsOwner = (sd.ChangedSections & AccessControlSections.Owner) == AccessControlSections.Owner;
var previousOwner = FileSystemOwner.GetOwner(sd.Item).Owner;
IdentityReference2 currentUser = System.Security.Principal.WindowsIdentity.GetCurrent().User;
FileSystemOwner.SetOwner(sd.Item, System.Security.Principal.WindowsIdentity.GetCurrent().User);
FileSystemOwner.SetOwner(sd.Item, currentUser);
var written = false;
try
@ -104,7 +118,7 @@ namespace NTFSSecurity
}
finally
{
if (!(written && setsOwner))
if (!(written && setsOwner) && previousOwner != currentUser)
{
try
{

5
NTFSSecurity/SimpleAccessCmdlets/SimpleAccessCmdlets.cs

@ -111,6 +111,11 @@ namespace NTFSSecurity
}
catch (Exception ex)
{
if (IsFromLaterCommand(ex))
{
throw;
}
WriteError(new ErrorRecord(ex, "ReadError", ErrorCategory.OpenError, p));
}
}

20
NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml

@ -3520,7 +3520,7 @@ PS C:\&gt; Disable-Privileges</dev:code>
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="False" position="2" aliases="none">
<maml:name>Filter</maml:name>
<maml:description>
<maml:para>Specifies a name pattern that an item must match to be returned. The pattern supports the ` ` and `?` wildcard characters, and the match ignores case. The default value is ` `, which returns every item. The pattern is applied to the name of each item, not to its path, and during a recursive listing it restricts only the returned items; the cmdlet still descends into every subfolder.</maml:para>
<maml:para>Specifies a name pattern that an item must match to be returned. The pattern supports the asterisk and the question mark as wildcard characters, an asterisk for any number of characters and a question mark for exactly one, and the match ignores case. Any other character stands for itself; a bracket is an ordinary character, so `Report[1].txt` returns the file of that name. As for `Get-ChildItem`, a pattern of an asterisk, a dot, and an asterisk returns every item, also an item without a dot in its name. The default value is `*`, which returns every item. The pattern is applied to the name of each item, not to its path, and during a recursive listing it restricts only the returned items; the cmdlet still descends into every subfolder.</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">String</command:parameterValue>
<dev:type>
@ -3724,7 +3724,7 @@ PS C:\&gt; Disable-Privileges</dev:code>
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="False" position="2" aliases="none">
<maml:name>Filter</maml:name>
<maml:description>
<maml:para>Specifies a name pattern that an item must match to be returned. The pattern supports the ` ` and `?` wildcard characters, and the match ignores case. The default value is ` `, which returns every item. The pattern is applied to the name of each item, not to its path, and during a recursive listing it restricts only the returned items; the cmdlet still descends into every subfolder.</maml:para>
<maml:para>Specifies a name pattern that an item must match to be returned. The pattern supports the asterisk and the question mark as wildcard characters, an asterisk for any number of characters and a question mark for exactly one, and the match ignores case. Any other character stands for itself; a bracket is an ordinary character, so `Report[1].txt` returns the file of that name. As for `Get-ChildItem`, a pattern of an asterisk, a dot, and an asterisk returns every item, also an item without a dot in its name. The default value is `*`, which returns every item. The pattern is applied to the name of each item, not to its path, and during a recursive listing it restricts only the returned items; the cmdlet still descends into every subfolder.</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">String</command:parameterValue>
<dev:type>
@ -3866,6 +3866,9 @@ PS C:\&gt; Disable-Privileges</dev:code>
<maml:para>The `PrivateData` section of the module manifest `NTFSSecurity.psd1` contains two settings that this cmdlet reads when it starts. `GetFileSystemModeProperty` adds the calculated `Mode` property to every item. `IdentifyHardLinks` adds the `HardLinkCount` property to every file, which requires an extra call into the file system for each file and therefore slows down large listings noticeably. Set either value to `$false` in the manifest and import the module again if you prefer the faster enumeration over the additional properties.</maml:para>
<maml:para>A folder that cannot be read produces a non-terminating error with the ID `DirUnauthorizedAccessError` for an access denial or `DirUnspecifiedError` for any other failure, and a path that does not exist produces the error `FileNotFound`. In each case the cmdlet continues with the next path. Failures that occur while `-Recurse` collects the subfolders of a folder are reported as verbose messages only, not as errors.</maml:para>
<maml:para>Before 5.0.0, a `-Path` value that points to a file stopped the cmdlet with an `InvalidCastException`, `-Attributes` returned only the items that had all the listed attributes, and an empty `-Attributes` value returned every item, also the hidden ones. Earlier builds, including the 5.0.0 prereleases, could also omit the first hidden item with `-Hidden` unless `-Force` was explicitly supplied.</maml:para>
<maml:para>Before 5.0.0, `-Filter` read a bracket as the start of a character class, so a file with brackets in its name, such as `Report[1].txt`, was not returned for its name, and a pattern of an asterisk, a dot, and an asterisk dropped the items without a dot in their names, most folders among them.</maml:para>
<maml:para>The names are matched twice, by the enumeration of the AlphaFS library and by the cmdlet, which reads a dot as an ordinary character, and their rules for a dot can differ from those of `Get-ChildItem`: a pattern such as `Report.*` does not return the file `Report`, which has no dot, a pattern that ends in a dot returns nothing, and an empty value returns nothing. Only the pattern of an asterisk, a dot, and an asterisk is treated as a single asterisk.</maml:para>
<maml:para>Before 5.0.0, a `break`, a `continue`, or a `throw` in a later command of the pipeline did not end the cmdlet for an item below the first folder, also when the later command took the error of a folder that the cmdlet cannot read, for example with `2&gt;&amp;1`.</maml:para>
</maml:alert>
</maml:alertSet>
<command:examples>
@ -3897,6 +3900,13 @@ PS C:\&gt; Disable-Privileges</dev:code>
<maml:para>Uses the `dir2` alias and returns the items of `C:\Data` that have the hidden or the system attribute, like `Get-ChildItem -Attributes Hidden, System`.</maml:para>
</dev:remarks>
</command:example>
<command:example>
<maml:title>Example 5: Return every item, with or without a dot in its name</maml:title>
<dev:code>PS C:\&gt; Get-ChildItem2 -Path C:\Data -Filter *.*</dev:code>
<dev:remarks>
<maml:para>Returns every item of `C:\Data`, also the files and folders whose names have no dot, as `Get-ChildItem` does for this filter.</maml:para>
</dev:remarks>
</command:example>
</command:examples>
<command:relatedLinks>
<maml:navigationLink>
@ -4380,7 +4390,7 @@ PS C:\&gt; Disable-Privileges</dev:code>
<maml:para>Reads the discretionary access control list (DACL) of a file or a folder and writes one `Security2.FileSystemAccessRule2` object for every access control entry (ACE) it contains. Each object carries the account, the rights, the access type, the inheritance and propagation flags, whether the ACE is inherited, and the path of the item it was read from.</maml:para>
<maml:para>In the `Path` parameter set the cmdlet reads the item from disk; relative paths are resolved against the current location, and when `-Path` is omitted the current location is used. In the `SD` parameter set it reads the ACEs from a `Security2.FileSystemSecurity2` object returned by `Get-NTFSSecurityDescriptor`, which also reflects changes that have not been written back yet.</maml:para>
<maml:para>By default both explicit and inherited entries are returned. `-ExcludeInherited` limits the result to the entries defined on the item itself, `-ExcludeExplicit` limits it to the entries the item inherits from its parents, and combining both returns nothing. `-Account` filters the result to a single account; an entry matches when the account resolves to the same SID.</maml:para>
<maml:para>When the module setting `GetInheritedFrom` is `$true`, which is the default in the `PrivateData` section of NTFSSecurity.psd1, the `InheritedFrom` property of every inherited entry contains the path of the folder the entry originates from. The default table view shows the account, the rights, the scope of the ACE in the wording of the Windows security dialog, the access type, and the inheritance information; setting `ShowAccountSid` to `$true` adds the SID to the account column.</maml:para>
<maml:para>When the module setting `GetInheritedFrom` is `$true`, which is the default in the `PrivateData` section of NTFSSecurity.psd1, the `InheritedFrom` property of every inherited entry contains the path of the folder the entry originates from, or `unknown parent` when Windows cannot name that folder, for example because the user cannot read a folder above the item. The default table view shows the account, the rights, the scope of the ACE in the wording of the Windows security dialog, the access type, and the inheritance information; setting `ShowAccountSid` to `$true` adds the SID to the account column.</maml:para>
</maml:description>
<command:syntax>
<command:syntaxItem>
@ -4589,6 +4599,7 @@ PS C:\&gt; Disable-Privileges</dev:code>
<maml:para>Entries whose account cannot be translated into a name are returned with their SID. Use `Get-NTFSOrphanedAccess` to list only those entries.</maml:para>
<maml:para>Before 5.0.0, after a path whose ACL could not be read, the cmdlet returned the entries of the previous item again.</maml:para>
<maml:para>Before 5.0.0-rc6, with `-ExcludeExplicit`, each inherited entry showed the `InheritedFrom` path of another entry, and for a security descriptor with audit entries, such as one that `Get-NTFSSecurityDescriptor` reads in an elevated session, the cmdlet stopped with an `ArgumentOutOfRangeException`.</maml:para>
<maml:para>Before 5.0.0, when Windows could not name the folder of an inherited entry, `InheritedFrom` read `unknown paren`, and the explicit entries of the item showed it as well.</maml:para>
<maml:para>For the root of a drive, such as `C:`, or of a volume, such as `\?\Volume{GUID}`, the cmdlets that read and change security use the root folder of the volume, like Explorer, `icacls`, and `Get-Acl`. Before 5.0.0, they read and changed the security descriptor of the drive itself, a device object with other entries.</maml:para>
</maml:alert>
</maml:alertSet>
@ -4666,7 +4677,7 @@ PS C:\&gt; Disable-Privileges</dev:code>
<maml:para>The `Get-NTFSAudit` cmdlet returns the audit entries that are stored in the system access control list (SACL) of a file or folder. Each entry is a `Security2.FileSystemAuditRule2` object that reports the audited account, the audited access rights, the audit flags (`Success`, `Failure`, or both), the inheritance and propagation flags, whether the entry is inherited, and the item it is inherited from. The access rights are the same values that `Add-NTFSAccess` and `Add-NTFSAudit` use; for what each right permits, see Concepts (../Concepts.md).</maml:para>
<maml:para>In the `Path` parameter set the cmdlet reads the security descriptor of every item in `-Path`. Relative paths are resolved against the current location, and when you omit `-Path` the cmdlet uses the current location. The parameter accepts pipeline input by value and by property name through its `FullName` alias, so the output of `Get-ChildItem`, `Get-ChildItem2`, and `Get-Item2` binds to it. In the `SD` parameter set the cmdlet reads the audit entries from an in-memory `Security2.FileSystemSecurity2` object that `Get-NTFSSecurityDescriptor` returned instead of reading the item again.</maml:para>
<maml:para>By default the cmdlet returns explicit and inherited entries. Use `-ExcludeInherited` to return only the entries that are set on the item itself, and `-ExcludeExplicit` to return only the entries that the item inherits from a parent folder. `-Account` filters the result to a single account; the comparison is made on the security identifier (SID), so an account name and its SID select the same entries.</maml:para>
<maml:para>The `InheritedFrom` property is filled only when the module setting `GetInheritedFrom` is `$true`, which is the default in the `PrivateData` section of `NTFSSecurity.psd1`.</maml:para>
<maml:para>The `InheritedFrom` property is filled only when the module setting `GetInheritedFrom` is `$true`, which is the default in the `PrivateData` section of `NTFSSecurity.psd1`. It contains `unknown parent` for an inherited entry when Windows cannot name the folder that the entry comes from.</maml:para>
</maml:description>
<command:syntax>
<command:syntaxItem>
@ -4873,6 +4884,7 @@ PS C:\&gt; Disable-Privileges</dev:code>
<maml:para>If reading the audit entries is denied, the cmdlet writes a `ReadSecurityError` with the category `PermissionDenied`. It doesn't take ownership of the item, because ownership grants no access to the SACL.</maml:para>
<maml:para>Before 5.0.0, the cmdlet returned no entries and no error without the Security privilege, and after a path whose security descriptor could not be read, it returned the entries of the previous item again. The `InheritanceEnabled` property of the entries also reported whether the access entries were inherited instead of the audit entries.</maml:para>
<maml:para>Before 5.0.0-rc6, with `-ExcludeExplicit`, each inherited entry showed the `InheritedFrom` path of another entry.</maml:para>
<maml:para>Before 5.0.0, when Windows could not name the folder of an inherited entry, `InheritedFrom` read `unknown paren`, and the explicit entries of the item showed it as well.</maml:para>
</maml:alert>
</maml:alertSet>
<command:examples>

2
ProcessPrivileges/PrivilegeAndAttributes.cs

@ -83,7 +83,7 @@ namespace ProcessPrivileges
/// <returns>Value indicating whether this instance and a specified object are equal.</returns>
public override bool Equals(object obj)
{
return obj is PrivilegeAttributes ? this.Equals((PrivilegeAttributes)obj) : false;
return obj is PrivilegeAndAttributes ? this.Equals((PrivilegeAndAttributes)obj) : false;
}
/// <summary>Indicates whether this instance and another instance are equal.</summary>

3
Security2/FileSystem/FileSystemAccessRule2 Class/FileSystemAccessRule2.GetFileSystemAccessRules.cs

@ -43,7 +43,8 @@ namespace Security2
var ace2 = new FileSystemAccessRule2(ace) { FullName = sd.Item.FullName, InheritanceEnabled = !sd.SecurityDescriptor.AreAccessRulesProtected };
if (getInheritedFrom && inheritedFrom.Count > 0)
{
ace2.inheritedFrom = string.IsNullOrEmpty(source) ? "" : source.Substring(0, source.Length - 1);
// Windows names a folder with a trailing backslash; the text for an unknown parent has none.
ace2.inheritedFrom = string.IsNullOrEmpty(source) ? "" : source.TrimEnd('\\');
}
aceList.Add(ace2);

1
Security2/FileSystem/FileSystemAccessRule2 Class/FileSystemAccessRule2.cs

@ -37,6 +37,7 @@ namespace Security2
public FileSystemAccessRule2(FileSystemAccessRule fileSystemAccessRule, string path)
{
this.fileSystemAccessRule = fileSystemAccessRule;
this.fullName = path;
}
public static implicit operator FileSystemAccessRule(FileSystemAccessRule2 ace2)

3
Security2/FileSystem/FileSystemAuditRule2 Class/FileSystemAuditRule2.GetFileSystemAuditRules.cs

@ -43,7 +43,8 @@ namespace Security2
var ace2 = new FileSystemAuditRule2(ace) { FullName = sd.Item.FullName, InheritanceEnabled = !sd.SecurityDescriptor.AreAuditRulesProtected };
if (getInheritedFrom && inheritedFrom.Count > 0)
{
ace2.inheritedFrom = string.IsNullOrEmpty(source) ? "" : source.Substring(0, source.Length - 1);
// Windows names a folder with a trailing backslash; the text for an unknown parent has none.
ace2.inheritedFrom = string.IsNullOrEmpty(source) ? "" : source.TrimEnd('\\');
}
aceList.Add(ace2);

1
Security2/FileSystem/FileSystemAuditRule2 Class/FileSystemAuditRule2.cs

@ -37,6 +37,7 @@ namespace Security2
public FileSystemAuditRule2(FileSystemAuditRule fileSystemAuditRule, string path)
{
this.fileSystemAuditRule = fileSystemAuditRule;
this.fullName = path;
}
#region Conversion

5
Security2/FileSystem/SimpleFileSystemAuditRule.cs

@ -42,6 +42,9 @@ namespace Security2
if ((accessRights & FileSystemRights2.Read) == FileSystemRights2.Read)
{ result |= SimpleFileSystemAccessRights.Read; }
if ((accessRights & FileSystemRights2.ReadData) == FileSystemRights2.ReadData)
{ result |= SimpleFileSystemAccessRights.Read; }
if ((accessRights & FileSystemRights2.CreateFiles) == FileSystemRights2.CreateFiles)
{ result |= SimpleFileSystemAccessRights.Write; }
@ -109,7 +112,7 @@ namespace Security2
public override bool Equals(object obj)
{
var compareObject = obj as SimpleFileSystemAccessRule;
var compareObject = obj as SimpleFileSystemAuditRule;
if (compareObject == null)
{

11
Security2/Win32/Lib.cs

@ -45,10 +45,12 @@ namespace Security2
}
catch
{
// Windows can't name the folders, for example because the item is gone or a folder above it can't
// be read. An explicit entry has no source in any case.
inheritedFrom = new List<string>();
for (int i = 0; i < aceCount; i++)
{
inheritedFrom.Add("unknown parent");
inheritedFrom.Add(acl[i].IsInherited ? "unknown parent" : string.Empty);
}
}
}
@ -69,6 +71,8 @@ namespace Security2
var pInheritInfo = Marshal.AllocHGlobal(aceCount * Marshal.SizeOf(typeof(PINHERITED_FROM)));
try
{
returnValue = GetInheritanceSource(
path,
ResourceType.FileObject,
@ -97,7 +101,12 @@ namespace Security2
}
FreeInheritedFromArray(pInheritInfo, (ushort)aceCount, IntPtr.Zero);
}
finally
{
// Also after a failed call, which the fallback of GetInheritedFrom now expects.
Marshal.FreeHGlobal(pInheritInfo);
}
return inheritedFrom;
}

198
Tests/Access.Tests.ps1

@ -155,6 +155,20 @@ Describe 'Get-NTFSEffectiveAccess' {
"because the computer 'ntfssecurity-test.invalid' can't be reached for a remote access check. " +
'For more accurate results, calculate effective access rights on that computer.')
}
# An empty name names no computer, so it names this one no more than any other name that can't be reached.
It 'Should return the result of this computer and warn for an empty -ServerName' {
$expected = Get-NTFSEffectiveAccess -Path $effectiveFile -WarningAction SilentlyContinue -ErrorAction Stop
$result = @(Get-NTFSEffectiveAccess -Path $effectiveFile -ServerName '' -WarningVariable accessWarnings -WarningAction SilentlyContinue -ErrorVariable accessErrors -ErrorAction SilentlyContinue)
$accessErrors | Should -BeNullOrEmpty
$result | Should -HaveCount 1
$result[0].AccessRights | Should -Be $expected.AccessRights
$accessWarnings.Message | Should -Contain ("The effective rights can only be computed based on group membership on this computer, " +
"because the computer '' can't be reached for a remote access check. " +
'For more accurate results, calculate effective access rights on that computer.')
}
}
# Not every computer offers the remote interface of the authorization manager; the cmdlet then calculates the result
@ -388,6 +402,19 @@ Describe 'Get-NTFSSimpleAccess' {
@($result | Where-Object -Property FullName -EQ -Value $root) | Should -HaveCount $rootAlone.Count
}
# With -IncludeRootFolder, the default, the cmdlet reports the parent folder of the first path first. A drive root
# has none, so it reports the root itself, once. The test reads the entries of the drive root only.
It 'Should report no parent folder in front of a drive root by default' {
$root = [IO.Path]::GetPathRoot($child)
$rootAlone = @(Get-NTFSSimpleAccess -Path $root -IncludeRootFolder:$false -ErrorAction Stop)
$result = @(Get-NTFSSimpleAccess -Path $root -ErrorVariable simpleErrors -ErrorAction SilentlyContinue)
$simpleErrors | Should -BeNullOrEmpty
$result | Should -HaveCount $rootAlone.Count
$result | ForEach-Object -Process { $_.FullName | Should -Be $root }
}
# Windows doesn't distinguish paths by case. Before 5.0.0-rc7, the cmdlet didn't recognize the parent folder of a
# folder whose path differed from it in case, and left the folder out.
It 'Should compare a folder with its parent folder also when their paths differ in case' {
@ -501,6 +528,29 @@ Describe 'Remove-NTFSAccess' {
}
}
# .NET refuses to build a deny entry without rights, also to find the entry to remove. The cmdlet reports the
# exception for the item and goes on.
Context 'With -AccessRights None for a deny entry' {
It 'Should write a RemoveAceError for each item, change nothing, and return nothing with -PassThru' {
$first = New-TestSandboxItem -Sandbox $sandbox -Name 'RemoveDenyNoneFirst'
$second = New-TestSandboxItem -Sandbox $sandbox -Name 'RemoveDenyNoneSecond'
$before = @((Get-Acl -LiteralPath $first).Sddl, (Get-Acl -LiteralPath $second).Sddl)
$result = @(Remove-NTFSAccess -Path $first, $second -Account 'Everyone' -AccessRights None -AccessType Deny -PassThru -ErrorVariable removeErrors -ErrorAction SilentlyContinue)
$result | Should -BeNullOrEmpty
$removeErrors | Should -HaveCount 2
for ($index = 0; $index -lt 2; $index++) {
$removeErrors[$index].FullyQualifiedErrorId | Should -BeLike 'RemoveAceError,*'
$removeErrors[$index].CategoryInfo.Category | Should -Be 'WriteError'
$removeErrors[$index].TargetObject | Should -BeExactly @($first, $second)[$index]
$removeErrors[$index].Exception | Should -BeOfType [System.ArgumentException]
}
(Get-Acl -LiteralPath $first).Sddl | Should -BeExactly $before[0]
(Get-Acl -LiteralPath $second).Sddl | Should -BeExactly $before[1]
}
}
Context 'When the item has an owner that the user cannot assign' {
BeforeAll {
$privateData['EnablePrivileges'] = $false
@ -591,6 +641,24 @@ Describe 'Remove-NTFSAccess' {
$removeErrors | Should -BeNullOrEmpty
Get-GuestsRule -Path $folder | Should -BeNullOrEmpty
}
# The entry of another account with exactly the rights to remove is not an exact match for the entry of the
# account, so the rights that the entry of the account keeps still have their Synchronize.
It 'Should take only the requested generic right from the entry of the account when another account has an exact entry' {
$users = [System.Security.Principal.SecurityIdentifier]'S-1-5-32-545'
$folder = New-GenericRightFolder -Entry '(A;OICIIO;0x10100000;;;BU)(A;OICIIO;0x90100000;;;BG)'
Remove-NTFSAccess -Path $folder -Account 'S-1-5-32-546' -AccessRights GenericAll -InheritanceFlags ContainerInherit, ObjectInherit -PropagationFlags InheritOnly -ErrorVariable removeErrors -ErrorAction SilentlyContinue
$removeErrors | Should -BeNullOrEmpty
$guestsRule = @(Get-GuestsRule -Path $folder)
$guestsRule | Should -HaveCount 1
[int] $guestsRule[0].FileSystemRights | Should -Be 0x80100000
$usersRule = @((Get-Acl -LiteralPath $folder).GetAccessRules($true, $false, [System.Security.Principal.SecurityIdentifier]) |
Where-Object -Property IdentityReference -EQ -Value $users)
$usersRule | Should -HaveCount 1
[int] $usersRule[0].FileSystemRights | Should -Be 0x10100000
}
}
Context 'With -RemoveSpecific' {
BeforeEach {
@ -662,6 +730,28 @@ Describe 'Add-NTFSAccess' {
}
}
# .NET refuses to build a deny entry without rights. The cmdlet reports the exception for the item and goes on.
Context 'With -AccessRights None for a deny entry' {
It 'Should write an AddAceError for each item, change nothing, and return nothing with -PassThru' {
$first = New-TestSandboxItem -Sandbox $sandbox -Name 'DenyNoneFirst'
$second = New-TestSandboxItem -Sandbox $sandbox -Name 'DenyNoneSecond'
$before = @((Get-Acl -LiteralPath $first).Sddl, (Get-Acl -LiteralPath $second).Sddl)
$result = @(Add-NTFSAccess -Path $first, $second -Account 'Everyone' -AccessRights None -AccessType Deny -PassThru -ErrorVariable addErrors -ErrorAction SilentlyContinue)
$result | Should -BeNullOrEmpty
$addErrors | Should -HaveCount 2
for ($index = 0; $index -lt 2; $index++) {
$addErrors[$index].FullyQualifiedErrorId | Should -BeLike 'AddAceError,*'
$addErrors[$index].CategoryInfo.Category | Should -Be 'WriteError'
$addErrors[$index].TargetObject | Should -BeExactly @($first, $second)[$index]
$addErrors[$index].Exception | Should -BeOfType [System.ArgumentException]
}
(Get-Acl -LiteralPath $first).Sddl | Should -BeExactly $before[0]
(Get-Acl -LiteralPath $second).Sddl | Should -BeExactly $before[1]
}
}
Context 'When the item has an owner that the user cannot assign' {
BeforeAll {
$privateData['EnablePrivileges'] = $false
@ -811,6 +901,24 @@ Describe 'Security descriptor parameter sets' {
Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' }
$rule.InheritanceFlags | Should -Be ([System.Security.AccessControl.InheritanceFlags]::None)
}
# A deny entry has no Synchronize right to add or remove, unlike an allow entry.
It 'Remove-NTFSAccess should remove a deny entry from the descriptor and leave the item unchanged' {
$item = New-TestSandboxItem -Sandbox $sandbox -Name 'DenyInMemory'
$sd = Get-NTFSSecurityDescriptor -Path $item
Add-NTFSAccess -SecurityDescriptor $sd -Account 'Everyone' -AccessRights ReadData -AccessType Deny
$entries = @($sd.SecurityDescriptor.GetAccessRules($true, $false, [System.Security.Principal.SecurityIdentifier]) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' })
$entries | Should -HaveCount 1
$entries[0].AccessControlType | Should -Be 'Deny'
Remove-NTFSAccess -SecurityDescriptor $sd -Account 'Everyone' -AccessRights ReadData -AccessType Deny -ErrorAction Stop
@($sd.SecurityDescriptor.GetAccessRules($true, $false, [System.Security.Principal.SecurityIdentifier]) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' }) | Should -BeNullOrEmpty
@((Get-Acl -LiteralPath $item).GetAccessRules($true, $false, [System.Security.Principal.SecurityIdentifier]) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' }) | Should -BeNullOrEmpty
}
}
Describe 'Clear-NTFSAccess' {
@ -940,6 +1048,46 @@ Describe 'InheritedFrom of access entries' {
$inherited[0].InheritedFrom | Should -Be $parent
}
# The module setting GetInheritedFrom turns off the lookup of the sources, which costs a call for each item.
It 'Should leave InheritedFrom empty when the module setting GetInheritedFrom is off' {
$saved = $privateData['GetInheritedFrom']
$privateData['GetInheritedFrom'] = $false
try {
$result = @(Get-NTFSAccess -Path $inheritedFromFile -ErrorAction Stop)
}
finally {
$privateData['GetInheritedFrom'] = $saved
}
$inherited = @($result | Where-Object -FilterScript { $_.IsInherited })
$inherited | Should -Not -BeNullOrEmpty
$inherited | ForEach-Object -Process { $_.InheritedFrom | Should -BeNullOrEmpty }
}
# Windows can't name the folders when the item is gone, for example deleted by another process after its security
# descriptor was read, or when a folder above it can't be read. The entries still come back. Before 5.0.0, the
# text lost its last character, and an explicit entry, which has no source, got it as well.
It 'Should name an unknown parent for an inherited entry and no source for an explicit entry when Windows cannot resolve the folders' {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'InheritedFromGone' -Directory
$file = Join-Path -Path $folder -ChildPath 'Gone.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $file
Set-Content -LiteralPath $file -Value 'Gone'
Add-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData -ErrorAction Stop
$sd = Get-NTFSSecurityDescriptor -Path $file -ErrorAction Stop
Remove-Item -LiteralPath $file -Force
$entries = @([Security2.FileSystemAccessRule2]::GetFileSystemAccessRules($sd, $true, $true, $true))
$inherited = @($entries | Where-Object -FilterScript { $_.IsInherited })
$inherited | Should -Not -BeNullOrEmpty
foreach ($entry in $inherited) {
$entry.InheritedFrom | Should -BeExactly 'unknown parent'
}
$explicit = @($entries | Where-Object -FilterScript { -not $_.IsInherited })
$explicit | Should -HaveCount 1
$explicit[0].InheritedFrom | Should -BeNullOrEmpty
}
It 'Should read a security descriptor with audit entries and name the same folders' -Skip:(-not $holdsSecurityPrivilege) {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'InheritedFromAudit'
Add-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData
@ -954,4 +1102,54 @@ Describe 'InheritedFrom of access entries' {
$entry.InheritedFrom | Should -Be $expectedSource["$($entry.Account.Sid)"]
}
}
# A NULL DACL gives everyone every access. It has no entries, so Windows names no source, and .NET reports one
# entry for Everyone nevertheless. The sources are looked up by the index of an entry, which this entry exceeds.
It 'Should return the one entry that .NET reports for a NULL DACL, without a source' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'InheritedFromNullDacl'
Set-TestNullDacl -Sandbox $sandbox -Path $file
$privateData['GetInheritedFrom'] | Should -BeTrue
$entries = @(Get-NTFSAccess -Path $file -ErrorAction Stop)
$entries | Should -HaveCount 1
"$($entries[0].Account.Sid)" | Should -Be 'S-1-1-0'
$entries[0].IsInherited | Should -BeFalse
$entries[0].InheritedFrom | Should -BeNullOrEmpty
}
}
Describe 'Get-NTFSEffectiveAccess for an unresolved identity' {
It 'Should report the native identity error for each <Source> and return no access entry' -ForEach @(
@{ Source = 'Path' }
@{ Source = 'SecurityDescriptor' }
) {
$first = New-TestSandboxItem -Sandbox $sandbox -Name 'UnresolvedFirst'
$next = New-TestSandboxItem -Sandbox $sandbox -Name 'UnresolvedNext'
$identity = [Security2.IdentityReference2] 'S-1-5-21-1-2-3-1001'
$identity.AccountName | Should -BeNullOrEmpty
$identity.LastError | Should -Not -BeNullOrEmpty
$before = @((Get-Acl -LiteralPath $first).Sddl, (Get-Acl -LiteralPath $next).Sddl)
$parameters = @{ Account = $identity; WarningAction = 'SilentlyContinue'; ErrorAction = 'SilentlyContinue' }
if ($Source -eq 'Path') {
$parameters.Path = @($first, $next)
}
else {
$parameters.SecurityDescriptor = @(Get-NTFSSecurityDescriptor -Path $first, $next)
}
$result = @(Get-NTFSEffectiveAccess @parameters -ErrorVariable accessErrors)
$result | Should -BeNullOrEmpty
$accessErrors | Should -HaveCount 2
for ($index = 0; $index -lt 2; $index++) {
$accessErrors[$index].FullyQualifiedErrorId | Should -BeLike 'GetEffectiveAccessError,*'
$accessErrors[$index].CategoryInfo.Category | Should -Be 'ReadError'
$accessErrors[$index].TargetObject.FullName | Should -BeExactly @($first, $next)[$index]
$cause = $accessErrors[$index].Exception.GetBaseException()
$cause | Should -BeOfType [System.ComponentModel.Win32Exception]
$cause.NativeErrorCode | Should -Be 1332
}
(Get-Acl -LiteralPath $first).Sddl | Should -BeExactly $before[0]
(Get-Acl -LiteralPath $next).Sddl | Should -BeExactly $before[1]
}
}

193
Tests/Audit.Tests.ps1

@ -137,6 +137,28 @@ Describe 'Add-NTFSAudit' {
}
}
# .NET refuses to build an audit entry without rights. The cmdlet reports the exception for the item and goes on.
Context 'With -AccessRights None' -Skip:(-not $canReadAudit) {
It 'Should write an AddAceError for each item, change nothing, and return nothing with -PassThru' {
$first = New-TestSandboxItem -Sandbox $sandbox -Name 'AuditNoneFirst'
$second = New-TestSandboxItem -Sandbox $sandbox -Name 'AuditNoneSecond'
$before = @((Get-Acl -LiteralPath $first -Audit).Sddl, (Get-Acl -LiteralPath $second -Audit).Sddl)
$result = @(Add-NTFSAudit -Path $first, $second -Account 'Everyone' -AccessRights None -AuditFlags Success -PassThru -ErrorVariable auditErrors -ErrorAction SilentlyContinue)
$result | Should -BeNullOrEmpty
$auditErrors | Should -HaveCount 2
for ($index = 0; $index -lt 2; $index++) {
$auditErrors[$index].FullyQualifiedErrorId | Should -BeLike 'AddAceError,*'
$auditErrors[$index].CategoryInfo.Category | Should -Be 'WriteError'
$auditErrors[$index].TargetObject | Should -BeExactly @($first, $second)[$index]
$auditErrors[$index].Exception | Should -BeOfType [System.ArgumentException]
}
(Get-Acl -LiteralPath $first -Audit).Sddl | Should -BeExactly $before[0]
(Get-Acl -LiteralPath $second -Audit).Sddl | Should -BeExactly $before[1]
}
}
Context 'When the item has an owner that the user cannot assign' {
BeforeAll {
$privateData['EnablePrivileges'] = $false
@ -511,4 +533,175 @@ Describe 'InheritedFrom of audit entries' {
$inherited | Should -HaveCount 1
$inherited[0].InheritedFrom | Should -Be $folder
}
# Windows names the folders of audit entries only for a caller whose Security privilege is enabled, and the cmdlets
# enable it. A caller of the library that doesn't gets the entries without sources. Before 5.0.0, the text lost its
# last character, and an explicit entry, which has no source, got it as well.
It 'Should name an unknown parent for an inherited entry and no source for an explicit entry when the privilege is disabled' -Skip:(-not $canReadAudit) {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'InheritedFromDisabled' -Directory
Add-NTFSAudit -Path $folder -Account 'S-1-1-0' -AccessRights ReadData -InheritanceFlags 'ContainerInherit, ObjectInherit' -PropagationFlags None
$file = Join-Path -Path $folder -ChildPath 'File.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $file
Set-Content -LiteralPath $file -Value 'File'
Add-NTFSAudit -Path $file -Account 'S-1-5-32-546' -AccessRights Delete -InheritanceFlags None -PropagationFlags None
$sd = Get-NTFSSecurityDescriptor -Path $file -ErrorAction Stop
Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
$entries = @([Security2.FileSystemAuditRule2]::GetFileSystemAuditRules($sd, $true, $true, $true))
$inherited = @($entries | Where-Object -FilterScript { $_.IsInherited })
$inherited | Should -HaveCount 1
$inherited[0].InheritedFrom | Should -BeExactly 'unknown parent'
$explicit = @($entries | Where-Object -FilterScript { -not $_.IsInherited })
$explicit | Should -HaveCount 1
$explicit[0].InheritedFrom | Should -BeNullOrEmpty
}
# The module setting GetInheritedFrom turns off the lookup of the sources, which costs a call for each item.
It 'Should leave InheritedFrom empty when the module setting GetInheritedFrom is off' -Skip:(-not $canReadAudit) {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'InheritedFromOff' -Directory
Add-NTFSAudit -Path $folder -Account 'S-1-1-0' -AccessRights ReadData -InheritanceFlags 'ContainerInherit, ObjectInherit' -PropagationFlags None
$file = Join-Path -Path $folder -ChildPath 'File.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $file
Set-Content -LiteralPath $file -Value 'File'
$saved = $privateData['GetInheritedFrom']
$privateData['GetInheritedFrom'] = $false
try {
$result = @(Get-NTFSAudit -Path $file -ErrorAction Stop)
}
finally {
$privateData['GetInheritedFrom'] = $saved
}
$inherited = @($result | Where-Object -FilterScript { $_.IsInherited })
$inherited | Should -HaveCount 1
$inherited[0].InheritedFrom | Should -BeNullOrEmpty
}
}
Describe 'Audit changes with the Security privilege disabled' {
BeforeAll {
$holdsSecurityForOperations = Test-PrivilegeHeld -Name 'SeSecurityPrivilege'
}
BeforeEach {
$savedEnablePrivileges = $privateData['EnablePrivileges']
$securityWasEnabled = (Get-Privileges | Where-Object -Property Privilege -EQ -Value 'Security').PrivilegeState -eq 'Enabled'
}
AfterEach {
$privateData['EnablePrivileges'] = $savedEnablePrivileges
if ($securityWasEnabled) {
$null = [ProcessPrivileges.ProcessExtensions]::EnablePrivilege(
[Diagnostics.Process]::GetCurrentProcess(), [ProcessPrivileges.Privilege]::Security
)
}
else {
$null = [ProcessPrivileges.ProcessExtensions]::DisablePrivilege(
[Diagnostics.Process]::GetCurrentProcess(), [ProcessPrivileges.Privilege]::Security
)
}
}
It '<Command> should use a held privilege or report a missing one and continue to the next path' -ForEach @(
@{ Command = 'Add-NTFSAudit'; ErrorId = 'AddAceError'; Parameters = @{ Account = 'S-1-1-0'; AccessRights = 'ReadData'; PassThru = $true } }
@{ Command = 'Remove-NTFSAudit'; ErrorId = 'RemoveAceError'; Parameters = @{ Account = 'S-1-1-0'; AccessRights = 'Delete'; PassThru = $true } }
@{ Command = 'Clear-NTFSAudit'; ErrorId = 'ClearAclError'; Parameters = @{ DisableInheritance = $true } }
@{ Command = 'Enable-NTFSAuditInheritance'; ErrorId = 'ModifySdError'; Parameters = @{ PassThru = $true; RemoveExplicitAuditRules = $true } }
@{ Command = 'Disable-NTFSAuditInheritance'; ErrorId = 'ModifySdError'; Parameters = @{ PassThru = $true; RemoveInheritedAuditRules = $true } }
) {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'DisabledSecurity'
$missing = Join-Path -Path $sandbox -ChildPath ('MissingAudit-{0}' -f [guid]::NewGuid().ToString('N'))
Assert-TestSandboxPath -Sandbox $sandbox -Path $path, $missing
if ($holdsSecurityForOperations) {
$privateData['EnablePrivileges'] = $true
Add-NTFSAudit -Path $path -Account 'S-1-1-0' -AccessRights Delete -AuditFlags Success -AppliesTo ThisFolderOnly
$saclBefore = (Get-NTFSSecurityDescriptor -Path $path).SecurityDescriptor.GetSecurityDescriptorSddlForm('Audit')
}
$before = (Get-Acl -LiteralPath $path).Sddl
$privateData['EnablePrivileges'] = $false
$null = [ProcessPrivileges.ProcessExtensions]::DisablePrivilege(
[Diagnostics.Process]::GetCurrentProcess(), [ProcessPrivileges.Privilege]::Security
)
(Get-Privileges | Where-Object -Property Privilege -EQ -Value 'Security').PrivilegeState | Should -Not -Be 'Enabled'
$result = @(& $Command -Path $path, $missing @Parameters -ErrorVariable auditErrors -ErrorAction SilentlyContinue)
(Get-Acl -LiteralPath $path).Sddl | Should -BeExactly $before
if ($holdsSecurityForOperations) {
# AlphaFS temporarily enables a held Security privilege for SACL access, even with automatic privileges off.
(Get-Privileges | Where-Object -Property Privilege -EQ -Value 'Security').PrivilegeState | Should -Be 'Disabled'
$auditErrors | Should -HaveCount 1
$auditErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadFileError,*'
$auditErrors[0].CategoryInfo.Category | Should -Be 'OpenError'
$auditErrors[0].TargetObject | Should -BeExactly $missing
$written = Get-NTFSSecurityDescriptor -Path $path
$rules = @($written.SecurityDescriptor.GetAuditRules(
$true, $false, [System.Security.Principal.SecurityIdentifier]
))
switch ($Command) {
'Add-NTFSAudit' {
$result | Should -Not -BeNullOrEmpty
$result | ForEach-Object { $_.FullName | Should -BeExactly $path }
@($rules | Where-Object {
$_.IdentityReference.Value -eq 'S-1-1-0' -and $_.FileSystemRights.HasFlag(
[System.Security.AccessControl.FileSystemRights]::ReadData
)
}).Count | Should -BeGreaterThan 0
}
'Disable-NTFSAuditInheritance' {
$result | Should -HaveCount 1
$result[0].AuditInheritanceEnabled | Should -BeFalse
$rules | Should -HaveCount 1
$rules[0].FileSystemRights | Should -Be ([System.Security.AccessControl.FileSystemRights]::Delete)
}
'Enable-NTFSAuditInheritance' {
$result | Should -HaveCount 1
$result[0].AuditInheritanceEnabled | Should -BeTrue
$rules | Should -BeNullOrEmpty
}
default {
$result | Should -BeNullOrEmpty
$rules | Should -BeNullOrEmpty
}
}
$written.SecurityDescriptor.GetSecurityDescriptorSddlForm('Audit') | Should -Not -BeExactly $saclBefore
}
else {
$result | Should -BeNullOrEmpty
$auditErrors | Should -HaveCount 2
$auditErrors[0].FullyQualifiedErrorId | Should -BeLike "$ErrorId,*"
$auditErrors[0].CategoryInfo.Category | Should -Be 'WriteError'
$auditErrors[0].TargetObject | Should -BeExactly $path
$auditErrors[1].FullyQualifiedErrorId | Should -BeLike 'ReadFileError,*'
$auditErrors[1].CategoryInfo.Category | Should -Be 'OpenError'
$auditErrors[1].TargetObject | Should -BeExactly $missing
}
}
}
Describe 'Clear-NTFSAudit descriptor inheritance' {
It 'Should clear and protect the descriptor SACL without writing the <Type>' -Skip:(-not $canReadAudit) -ForEach @(
@{ Type = 'file' }
@{ Type = 'folder' }
) {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'ClearAuditDescriptor' -Directory:($Type -eq 'folder')
Assert-TestSandboxPath -Sandbox $sandbox -Path $path
Add-NTFSAudit -Path $path -Account 'S-1-1-0' -AccessRights Delete -AuditFlags Success -AppliesTo ThisFolderOnly
$before = Get-NTFSSecurityDescriptor -Path $path
$auditBefore = $before.SecurityDescriptor.GetSecurityDescriptorSddlForm('Audit')
$daclBefore = (Get-Acl -LiteralPath $path).Sddl
$sd = Get-NTFSSecurityDescriptor -Path $path
Clear-NTFSAudit -SecurityDescriptor $sd -DisableInheritance -ErrorAction Stop
$sd.SecurityDescriptor.AreAuditRulesProtected | Should -BeTrue
@($sd.SecurityDescriptor.GetAuditRules($true, $true, $sidType)) | Should -BeNullOrEmpty
(Get-NTFSSecurityDescriptor -Path $path).SecurityDescriptor.GetSecurityDescriptorSddlForm('Audit') |
Should -BeExactly $auditBefore
Set-NTFSSecurityDescriptor -SecurityDescriptor $sd -ErrorAction Stop
(Get-NTFSInheritance -Path $path).AuditInheritanceEnabled | Should -BeFalse
@(Get-NTFSAudit -Path $path) | Should -BeNullOrEmpty
(Get-Acl -LiteralPath $path).Sddl | Should -BeExactly $daclBefore
}
}

37
Tests/Coverage/Quality-Gate-Paths-2026-10-09-Cmdlets.csv

@ -0,0 +1,37 @@
"Cmdlet","Class","SequenceVisited","SequenceTotal","SequencePercent","BranchVisited","BranchTotal"
"Add-NTFSAccess","AddAccess","63","72","87.5","13","37"
"Add-NTFSAudit","AddAudit","56","74","75.7","15","39"
"Clear-NTFSAccess","ClearAccess","44","50","88","14","22"
"Clear-NTFSAudit","ClearAudit","37","51","72.5","13","24"
"Copy-Item2","CopyItem2","61","65","93.8","21","31"
"Disable-NTFSAccessInheritance","DisableAccessInheritance","42","49","85.7","11","21"
"Disable-NTFSAuditInheritance","DisableAuditInheritance","36","49","73.5","11","21"
"Disable-Privileges","DisablePrivileges","15","16","93.8","5","11"
"Enable-NTFSAccessInheritance","EnableAccessInheritance","42","49","85.7","11","21"
"Enable-NTFSAuditInheritance","EnableAuditInheritance","36","49","73.5","11","21"
"Enable-Privileges","EnablePrivileges","17","20","85","9","14"
"Get-ChildItem2","GetChildItem2","132","145","91","73","102"
"Get-DiskSpace","GetDiskSpace","23","24","95.8","6","11"
"Get-FileHash2","GetFileHash2","50","58","86.2","11","19"
"Get-Item2","GetItem2","21","22","95.5","6","9"
"Get-NTFSAccess","GetAccess","52","59","88.1","13","26"
"Get-NTFSAudit","GetAudit","53","56","94.6","15","28"
"Get-NTFSEffectiveAccess","GetEffectiveAccess","61","76","80.3","23","36"
"Get-NTFSHardLink","GetHardLink","34","37","91.9","10","13"
"Get-NTFSInheritance","GetInheritance","38","47","80.9","13","19"
"Get-NTFSOrphanedAccess","GetOrphanedAccess","39","46","84.8","10","15"
"Get-NTFSOrphanedAudit","GetOrphanedAudit","38","41","92.7","12","16"
"Get-NTFSOwner","GetOwner","32","35","91.4","7","12"
"Get-NTFSSecurityDescriptor","GetSecurityDescriptor","28","36","77.8","8","13"
"Get-NTFSSimpleAccess","GetSimpleAccess","60","61","98.4","24","35"
"Get-Privileges","GetPrivileges","5","5","100","0","2"
"Move-Item2","MoveItem2","65","69","94.2","23","33"
"New-NTFSHardLink","NewHardLink","57","58","98.3","19","26"
"New-NTFSSymbolicLink","NewSymbolicLink","38","39","97.4","12","19"
"Remove-Item2","RemoveItem2","44","48","91.7","13","23"
"Remove-NTFSAccess","RemoveAccess","74","77","96.1","15","41"
"Remove-NTFSAudit","RemoveAudit","60","79","75.9","17","43"
"Set-NTFSInheritance","SetInheritance","83","88","94.3","34","46"
"Set-NTFSOwner","SetOwner","40","43","93","13","22"
"Set-NTFSSecurityDescriptor","SetSecurityDescriptor","54","55","98.2","16","21"
"Test-Path2","TestPath2","35","41","85.4","14","20"
1 Cmdlet Class SequenceVisited SequenceTotal SequencePercent BranchVisited BranchTotal
2 Add-NTFSAccess AddAccess 63 72 87.5 13 37
3 Add-NTFSAudit AddAudit 56 74 75.7 15 39
4 Clear-NTFSAccess ClearAccess 44 50 88 14 22
5 Clear-NTFSAudit ClearAudit 37 51 72.5 13 24
6 Copy-Item2 CopyItem2 61 65 93.8 21 31
7 Disable-NTFSAccessInheritance DisableAccessInheritance 42 49 85.7 11 21
8 Disable-NTFSAuditInheritance DisableAuditInheritance 36 49 73.5 11 21
9 Disable-Privileges DisablePrivileges 15 16 93.8 5 11
10 Enable-NTFSAccessInheritance EnableAccessInheritance 42 49 85.7 11 21
11 Enable-NTFSAuditInheritance EnableAuditInheritance 36 49 73.5 11 21
12 Enable-Privileges EnablePrivileges 17 20 85 9 14
13 Get-ChildItem2 GetChildItem2 132 145 91 73 102
14 Get-DiskSpace GetDiskSpace 23 24 95.8 6 11
15 Get-FileHash2 GetFileHash2 50 58 86.2 11 19
16 Get-Item2 GetItem2 21 22 95.5 6 9
17 Get-NTFSAccess GetAccess 52 59 88.1 13 26
18 Get-NTFSAudit GetAudit 53 56 94.6 15 28
19 Get-NTFSEffectiveAccess GetEffectiveAccess 61 76 80.3 23 36
20 Get-NTFSHardLink GetHardLink 34 37 91.9 10 13
21 Get-NTFSInheritance GetInheritance 38 47 80.9 13 19
22 Get-NTFSOrphanedAccess GetOrphanedAccess 39 46 84.8 10 15
23 Get-NTFSOrphanedAudit GetOrphanedAudit 38 41 92.7 12 16
24 Get-NTFSOwner GetOwner 32 35 91.4 7 12
25 Get-NTFSSecurityDescriptor GetSecurityDescriptor 28 36 77.8 8 13
26 Get-NTFSSimpleAccess GetSimpleAccess 60 61 98.4 24 35
27 Get-Privileges GetPrivileges 5 5 100 0 2
28 Move-Item2 MoveItem2 65 69 94.2 23 33
29 New-NTFSHardLink NewHardLink 57 58 98.3 19 26
30 New-NTFSSymbolicLink NewSymbolicLink 38 39 97.4 12 19
31 Remove-Item2 RemoveItem2 44 48 91.7 13 23
32 Remove-NTFSAccess RemoveAccess 74 77 96.1 15 41
33 Remove-NTFSAudit RemoveAudit 60 79 75.9 17 43
34 Set-NTFSInheritance SetInheritance 83 88 94.3 34 46
35 Set-NTFSOwner SetOwner 40 43 93 13 22
36 Set-NTFSSecurityDescriptor SetSecurityDescriptor 54 55 98.2 16 21
37 Test-Path2 TestPath2 35 41 85.4 14 20

796
Tests/Coverage/Quality-Gate-Paths-2026-10-09-Explanations.md

@ -0,0 +1,796 @@
# Explanations of the unvisited code, by rule
Generated from the classification of the unvisited methods in the aggregated
AltCover report of source `5a5d58b`.
[Quality-Gate-Paths-2026-10-09.md](./Quality-Gate-Paths-2026-10-09.md)
describes the method and the result; the rows of every method are in the CSV
file next to it. An explanation closes a path only as far as its evidence
goes: the evidence line of each rule says whether an executed probe, a static
scan of the compiled code, or reading the source supports it.
## By category
| Category | Disposition | Methods | Unvisited sequence points | Unvisited explicit branch points |
| --- | --- | ---: | ---: | ---: |
| unused by cmdlets | Explained | 60 | 173 | 34 |
| parameter/API surface | Explained | 103 | 103 | 0 |
| environment-specific | Explained | 27 | 81 | 18 |
| defensive | Explained | 33 | 77 | 16 |
| unused by cmdlets | Open | 8 | 8 | 2 |
| **Total** | | **231** | **442** | **70** |
## By rule
### ACCESS-GENERIC-CATCH
8 method(s), 32 unvisited sequence point(s), 3 unvisited explicit
branch point(s). Category: defensive. Disposition: Explained.
Classes: ClearAccess, DisableAccessInheritance, EnableAccessInheritance,
GetAccess, GetInheritance, GetOrphanedAccess, GetSecurityDescriptor,
OwnerCmdlets.GetOwner.
Evidence: an executed probe.
Why: The unvisited points are the last catch (Exception) of the per-item loop,
taken when the DACL API fails with anything but access denied after the item
was found. A read or write of the DACL on local NTFS either succeeds or fails
with access denied (covered), and the probes found no other failure for these
cmdlets: an unresolvable SID is accepted, a locked file does not matter
because the security APIs ignore share modes, and a dangling junction is read
as the link itself. One input does trigger that catch, a deny entry without
rights, which .NET refuses with an ArgumentException; Add-NTFSAccess,
Remove-NTFSAccess and Add-NTFSAudit take it and are tested (an Allow entry
without rights is accepted, because the module adds Synchronize to it). A
cmdlet that has no rights parameter, such as the read and inheritance cmdlets,
has no such input. A second trigger exists for the cmdlets that add entries:
an ACL that is full. In both editions the 1,818th entry that was added to a
security descriptor in memory, with -SecurityDescriptor, raised an
OverflowException, "Length of the access control list exceed the allowed
maximum" (probe p37: unresolvable SIDs with ReadData; 1,816 entries were
written and read back without an error, and one more added with -Path
succeeded, so the overflow itself was not run with -Path). By reading the
source, the -Path loop takes that exception in the same handler as the
zero-mask case, whereas the -SecurityDescriptor sets have no handler around
the add, so there the exception ends the cmdlet. It was not turned into a test
of its own. A volume without ACL support or a corrupt descriptor cannot be
created safely on the shared host.
Residual risk: Low: one WriteError(exception, id, category, path) and
continue, the shape that the access-denied and ReadFileError siblings of the
same loops assert and that the zero-mask tests assert for the cmdlets that add
and remove entries. The catches of the cmdlets that only read or change
inheritance have no known trigger. A catch that wraps the write of the result
is entered also when a later command raises something while it takes the
object; every catch-all whose try block writes directly passes that exception
on (a scan of the source finds no exception; it cannot see a write inside a
helper such as the owner restore of InvokeAsOwner, an accepted limitation),
and the PipelineControl tests run that part for every cmdlet.
Related tests: Access.Tests and Audit.Tests: a deny or audit entry without
rights for Add-NTFSAccess, Remove-NTFSAccess and Add-NTFSAudit (AddAceError
and RemoveAceError, ArgumentException, WriteError, target, continuation,
nothing written). PathErrors: ReadFileError, ReadSecurityError, denied write
and ownership retry, each with continuation. PipelineControl.Tests: a break, a
continue, Select-Object -First, a throw, and an error with -ErrorAction Stop
after the first object.
### ALLOCATED-MEMORY-FINALIZER
2 method(s), 3 unvisited sequence point(s), 1 unvisited explicit
branch point(s). Category: defensive. Disposition: Explained.
Classes: AllocatedMemory.
Evidence: reading the source.
Why: AllocatedMemory is internal and used only in a using block of
GetTokenPrivileges, which disposes it and suppresses the finalizer. The
finalizer and the branch for a pointer that was released already run only for
an instance that nobody disposed.
Residual risk: None found.
Related tests: Privileges.Tests: the token handle tests list the privileges
through it.
### AUDIT-OWNER-RETRY
10 method(s), 47 unvisited sequence point(s), 2 unvisited explicit
branch point(s). Category: environment-specific. Disposition: Explained.
Classes: AddAudit, AddAudit/<>c__DisplayClass35_0, ClearAudit,
ClearAudit/<>c__DisplayClass11_0, DisableAuditInheritance,
DisableAuditInheritance/<>c__DisplayClass15_0, EnableAuditInheritance,
EnableAuditInheritance/<>c__DisplayClass15_0, RemoveAudit,
RemoveAudit/<>c__DisplayClass39_0.
Evidence: an executed probe.
Why: The unvisited points are the catch (UnauthorizedAccessException) of an
audit write, its InvokeAsOwner retry and the closure of that retry. Local NTFS
never raises that exception for an audit change: as a basic user all seven
audit cmdlets get an IOException, error 1314 (A required privilege is not
held), in both editions (probe); with the Security privilege nothing in a DACL
can deny the privilege-only right, and a loopback administrative share ended
in no exception for all five either (probe, elevated, EnablePrivileges on and
off). The retry is the InvokeAsOwner code that the access cmdlets share, so
its success, failure and RestoreOwnerError paths run there; only the audit
closure bodies, which repeat the first attempt, are unexecuted.
Residual risk: Low to medium: an audit write that a file server answers with
access denied runs lines that no instrumented test reaches. The live lab suite
runs the audit cmdlets over SMB outside the instrumented run.
Related tests: PathErrors (denied write and ownership retry for the access
cmdlets), FileHash and SecurityDescriptor tests (ownership retry), Audit.Tests
(privilege missing or disabled).
### AUDIT-READ-DENIED
2 method(s), 6 unvisited sequence point(s), 0 unvisited explicit
branch point(s). Category: environment-specific. Disposition: Explained.
Classes: AuditCmdlets.GetOrphanedAudit, GetAudit.
Evidence: an executed probe.
Why: The unvisited points are the catch (UnauthorizedAccessException) that
writes a PermissionDenied ReadSecurityError for an audit read. Without the
Security privilege Windows answers error 1314, which AlphaFS raises as an
IOException (probe as a basic user, both editions) and the generic catch of
the same loop reports as an OpenError; with the privilege, nothing denies the
read locally. An access-denied answer needs a remote file server.
Residual risk: Low: the same WriteError statement with another category.
Related tests: Audit.Tests: error without the Security privilege, descriptor
read without audit entries.
### CHILDITEM2-NON-FOLDER
1 method(s), 0 unvisited sequence point(s), 1 unvisited explicit
branch point(s). Category: defensive. Disposition: Explained.
Classes: GetChildItem2.
Evidence: reading the source.
Why: The else of the test for a DirectoryInfo at the start of WriteFileSystem:
both callers pass one, ProcessRecord after it returned for a file, and the
recursion with the folders that EnumerateDirectories returned.
Residual risk: None found.
Related tests: ItemCmdlets.Tests: files, folders, recursion, filters, depth.
### CMDLET-GETTER
103 method(s), 103 unvisited sequence point(s), 0 unvisited explicit
branch point(s). Category: parameter/API surface. Disposition: Explained.
Classes: AddAccess, AddAudit, ClearAccess, ClearAudit, CopyItem2,
DisableAccessInheritance, DisableAuditInheritance, DisablePrivileges,
EnableAccessInheritance, EnableAuditInheritance, EnablePrivileges,
GetChildItem2, GetDiskSpace, GetEffectiveAccess, GetFileHash2, GetInheritance,
GetItem2, GetSecurityDescriptor, GetSimpleAccess, MoveItem2, NewHardLink,
NewSymbolicLink, RemoveAccess, RemoveAudit, RemoveItem2, SetInheritance,
SetOwner, SetSecurityDescriptor.
Evidence: reading the source.
Why: A one-line getter of a cmdlet parameter. PowerShell binds a parameter
through its setter and the cmdlet code reads the private field, so no caller
invokes the getter. It has no logic, so a test that reads it back repeats the
field assignment and proves no behavior.
Residual risk: None found.
Related tests: Every parameter-set test binds the setters; Help.Tests and
OutputTypes.Tests read the parameter metadata.
### CODEMEMBERS-NULL-BASE
1 method(s), 1 unvisited sequence point(s), 1 unvisited explicit
branch point(s). Category: defensive. Disposition: Explained.
Classes: FileSystemCodeMembers.
Evidence: an executed probe.
Why: The second null check tests the base object of the PSObject that the
first check let through. A PSObject cannot wrap null: new PSObject($null) and
PSObject.AsPSObject($null) throw PSArgumentNullException in both editions
(probe), and PowerShell hands a $null argument over as null, which the first
check covers.
Residual risk: None found.
Related tests: ItemCmdlets.Tests: the Mode of files, of a folder, and of no
object.
### DISKSPACE-EMPTY-VOLUME
1 method(s), 0 unvisited sequence point(s), 1 unvisited explicit
branch point(s). Category: environment-specific. Disposition: Explained.
Classes: GetDiskSpace.
Evidence: reading the source.
Why: The unvisited branch skips a volume that reports zero bytes, such as a
card reader or an optical drive without media. The host has none, and a test
cannot attach one without changing the shared machine.
Residual risk: Low: the volume is skipped silently.
Related tests: ItemCmdlets.Tests: volumes with a size, drive letter without a
volume.
### EFFECTIVE-ACCESS-CATCH
2 method(s), 10 unvisited sequence point(s), 0 unvisited explicit
branch point(s). Category: defensive. Disposition: Explained.
Classes: GetEffectiveAccess, GetEffectiveAccess/<>c__DisplayClass20_0.
Evidence: reading the source.
Why: EffectiveAccess.GetEffectiveAccess hides every failure of its Authz calls
in a result with OperationFailed, which the cmdlet turns into
GetEffectiveAccessError. The catch (Exception) blocks and the InvokeAsOwner
retry therefore see an exception only from what runs outside those calls: the
read of the descriptor of the item (EffectiveAccess.cs, the FileSystemInfo
overload) or the conversion of the account. The only local failure of the
descriptor read is access denied, which the UnauthorizedAccessException branch
handles and the tests cover; no other local trigger was found, and the
unresolved identity, which the conversion accepts, ends in OperationFailed.
Residual risk: Low: a failure of the descriptor read other than access denied,
such as an I/O error of the volume, would run the unvisited catch, which
writes one ReadEffectivePermissionError and continues like its tested
siblings.
Related tests: Access.Tests: unresolved identity for a path and a descriptor,
remote fallback, privilege warnings.
### ENABLEPRIVILEGES-INIT
1 method(s), 2 unvisited sequence point(s), 0 unvisited explicit
branch point(s). Category: defensive. Disposition: Explained.
Classes: EnablePrivileges.
Evidence: reading the source.
Why: The branches that test whether the caller is a script named
NTFSSecurity.Init.ps1 are run by Privileges.Tests, which writes a script of
that name and of another name and runs each in a child process. The tests
check the state of the Backup privilege and the verbose message that only the
cmdlet writes, because with the setting $true the module enables the
privileges itself before the cmdlet runs: from the Init script the cmdlet
enables them for EnablePrivileges $true and does nothing for $false, from
another script it enables them for both. The script that the module ships
under that name does not call Enable-Privileges (it adds the types). What
stays unvisited is the catch that rethrows a ParseException for a malformed
EnablePrivileges value: the base BeginProcessing casts the same value first
and fails earlier, and the cmdlet takes no pipeline input, so only a consumer
of the debug or verbose stream could change the setting between the two calls.
Residual risk: Low: the catch rethrows with a message that names the setting;
it was not run.
Related tests: Privileges.Tests: Enable-Privileges in the script
NTFSSecurity.Init.ps1 (three cases), Enable-Privileges and Disable-Privileges.
### FILESECURITY-CONVERSION
2 method(s), 2 unvisited sequence point(s), 0 unvisited explicit
branch point(s). Category: unused by cmdlets. Disposition: Open.
Classes: FileSystemSecurity2.
Evidence: an executed probe.
Why: The implicit conversions from FileSecurity and DirectorySecurity
construct FileSystemSecurity2 from FileInfo("") and DirectoryInfo(""), so they
always throw ArgumentException (probe: "Path is a zero-length string"). A test
of the intended behavior would fail, and a test of the actual behavior would
cement a defect in an API that no cmdlet uses.
Residual risk: Library users who convert a .NET descriptor get an exception.
Fix or remove is a maintainer decision.
Related tests: None, on purpose.
### FSSEC2-CONSTRUCTOR-FALLBACK
1 method(s), 2 unvisited sequence point(s), 0 unvisited explicit
branch point(s). Category: environment-specific. Disposition: Explained.
Classes: FileSystemSecurity2.
Evidence: reading the source.
Why: The unvisited points are the last fallback of the constructor with one
argument: the DACL read alone after the DACL read together with the owner and
the group failed. The first fallback, without the SACL when the Security
privilege is missing, runs in the basic configurations. The owner and the
group need the same READ_CONTROL right as the DACL, so the third read succeeds
only where a volume or a server answers them differently.
Residual risk: Low: the descriptor that the fallback returns holds the DACL
only, and the cmdlets that need more report it.
Related tests: SecurityDescriptor.Tests and Audit.Tests: a descriptor read
without the Security privilege.
### FSSEC2-DRIVE-LETTER-CHARS
1 method(s), 0 unvisited sequence point(s), 1 unvisited explicit
branch point(s). Category: defensive. Disposition: Explained.
Classes: FileSystemSecurity2.
Evidence: reading the source.
Why: The unvisited branch is a first character that is no letter in front of
the colon. AlphaFS normalizes the full name, so a name of two characters that
ends in a colon always begins with a drive letter, in upper or lower case
(both run); a digit or another character cannot form such a name.
Residual risk: None found.
Related tests: DriveRoot.Tests: the system drive and a mapped drive;
ObjectApis.Tests: a lowercase drive letter.
### FSSEC2-HASHCODE-NULL
1 method(s), 0 unvisited sequence point(s), 1 unvisited explicit
branch point(s). Category: defensive. Disposition: Explained.
Classes: FileSystemSecurity2.
Evidence: reading the source.
Why: The zero is for a descriptor without a wrapped .NET descriptor. Both
constructors set it, because GetSecurity returns a descriptor or throws, and
the conversions from FileSecurity and DirectorySecurity throw before an object
exists. Only a derived class that sets the field to null reaches it.
Residual risk: None found.
Related tests: SecurityDescriptor.Tests: equality, hash code, and use as a
key.
### GENERATED-RESOURCES
6 method(s), 12 unvisited sequence point(s), 2 unvisited explicit
branch point(s). Category: unused by cmdlets. Disposition: Explained.
Classes: Properties.Resources.
Evidence: reading the source.
Why: The designer-generated resource class of two icons that no code
references.
Residual risk: None found.
Related tests: None needed.
### HARDLINK-DENIED
1 method(s), 3 unvisited sequence point(s), 0 unvisited explicit
branch point(s). Category: environment-specific. Disposition: Explained.
Classes: GetHardLink.
Evidence: an executed probe.
Why: The unvisited points are the catch (UnauthorizedAccessException) of the
hard link enumeration. No local input was found that raises it: with deny
entries for ReadAttributes, ReadData and ReadPermissions for OWNER RIGHTS and
for the user, and automatic privileges off, Get-NTFSHardLink still listed the
names (probe p32, elevated, both editions), and Links.Tests asserts that
listing while the data of the file cannot be read (Get-Content fails). Whether
a denial of ReadAttributes or ReadPermissions alone could change that was not
shown: an elevated session read the permissions despite the deny entries, so
the test asserts only the refused data. The IOException of a network share,
(50), is covered.
Residual risk: Low: one WriteError with the PermissionDenied category. If a
Windows version or a file server refuses the listing, the new test fails and
this catch is reached.
Related tests: Links.Tests: the names of a file whose read rights are denied,
error for a file on a network share, for a folder and for a missing path.
### HASH-POLICY-AND-RETRY
2 method(s), 7 unvisited sequence point(s), 0 unvisited explicit
branch point(s). Category: environment-specific. Disposition: Explained.
Classes: GetFileHash2, GetFileHash2/<>c__DisplayClass9_0.
Evidence: an executed probe.
Why: Two groups. The second catch around the algorithm check handles an
algorithm that a FIPS policy refuses; the policy of the host cannot be
switched in a test. The closing points after InvokeAsOwner run only when the
hash succeeds after taking ownership: ownership grants READ_CONTROL and
WRITE_DAC but never data read, so the retry can only fail, which two tests
assert. Probes looked for a way around that: with the owner changed to the
user the read still ended in access denied (error 5, plus a RestoreOwnerError
1307 for an owner that the user cannot set back), and an OWNER RIGHTS deny is
no way in either, because Windows drops that entry when the owner changes.
Residual risk: Low: the success path after the retry has no local trigger. A
file server that grants the read to the owner only would run it.
Related tests: FileHash.Tests: unavailable algorithms, unreadable file,
restore of the previous owner, failed restore.
### IDENTITY-NULL-REFERENCE
1 method(s), 0 unvisited sequence point(s), 1 unvisited explicit
branch point(s). Category: defensive. Disposition: Explained.
Classes: IdentityReference2.
Evidence: reading the source.
Why: The unvisited branch is an IdentityReference that is neither an NTAccount
nor a SecurityIdentifier, which only null is: the .NET class has no other
public subclass. The object then has no SID and every member fails with a
NullReferenceException. No cmdlet passes null, and a test would cement that
failure.
Residual risk: Low: a library caller that passes null gets an unusable object.
Related tests: ObjectApis.Tests: identity constructors and their errors.
### INHERITED-FROM-EMPTY-LIST
1 method(s), 0 unvisited sequence point(s), 1 unvisited explicit
branch point(s). Category: defensive. Disposition: Explained.
Classes: FileSystemAuditRule2.
Evidence: an executed probe.
Why: The access twin is tested: a NULL DACL, which Windows reads as an ACL
with no entries and .NET reports as one entry for Everyone, runs the branch
where the list of sources is empty (Access.Tests). The unvisited branch that
remains is the second operand of getInheritedFrom && inheritedFrom.Count > 0
in the audit twin. GetInheritedFrom returns one source for each entry of the
SACL that it reads, also for the fallback text of an unknown parent, and an
empty list only for a descriptor without a SACL. The loop that holds the
condition runs over the audit entries of the same SACL, so an empty list means
that the loop has no entry and does not run, as for every item without a SACL
(Audit.Tests: the item has no SACL). The setting that turns the lookup off and
the fallback cover the other outcomes. A probe shows that an integrity label
in the SACL of a folder is not part of the SACL that the module reads, so the
sources of the audit entries stay aligned with the entries (probe, both
editions).
Residual risk: Low: the two lists are aligned by position, which assumes that
the .NET API returns a rule for every entry of the ACL that Windows names a
source for; an entry type that it skips would shift the sources. No such entry
was found on NTFS, and none was tested.
Related tests: Access.Tests: InheritedFrom with the setting on and off, for an
unknown parent and for a NULL DACL; Audit.Tests: InheritedFrom of audit
entries and an item without a SACL; ObjectApis.Tests: an empty DACL.
### NATIVE-HANDLES
16 method(s), 34 unvisited sequence point(s), 6 unvisited explicit
branch point(s). Category: unused by cmdlets. Disposition: Explained.
Classes: IntPtrExtensions, SafeAuthzRMHandle, SafeHGlobalHandle,
SafeTokenHandle.
Evidence: reading the source.
Why: Native memory and handle wrappers. The overloads that the Authz and
descriptor code use run; the unvisited ones have no caller: further
AllocHGlobal overloads, InvalidHandle getters, ReleaseHandle for handles that
the module never creates, and Increment. They wrap Marshal.AllocHGlobal and
FreeHGlobal, so a test would repeat the BCL.
Residual risk: Low.
Related tests: Effective access tests run the used overloads.
### PRIVILEGE-DISABLE-FAILURE
4 method(s), 10 unvisited sequence point(s), 2 unvisited explicit
branch point(s). Category: defensive. Disposition: Explained.
Classes: BaseCmdletWithPrivControl.
Evidence: reading the source.
Why: These are the failure branches of disabling a privilege. DisablePrivilege
reads the current state first and does nothing unless the privilege is
Enabled, so a privilege that another command disabled, or that the token does
not hold, never reaches them. They run only when AdjustTokenPrivileges fails
for an enabled privilege, for example for an invalid token handle; a test
would have to corrupt the process token.
Residual risk: Low: a failed cleanup is a warning that names the privilege.
The deferred review "failed privilege-disable retry" stays not reproduced.
Related tests: Privileges.Tests: another command disables a privilege, early
pipeline stop, token holds only some privileges.
### PRIVILEGE-ENABLER-RACE
1 method(s), 0 unvisited sequence point(s), 1 unvisited explicit
branch point(s). Category: defensive. Disposition: Explained.
Classes: PrivilegeEnabler.
Evidence: reading the source.
Why: The third operand of the condition, that the adjustment returned
PrivilegeModified, is false only when another thread enabled the privilege
between the state check and the adjustment of the same call.
Residual risk: None found.
Related tests: Privileges.Tests: the first and the second enabler, a privilege
that was enabled before, and a privilege that the token does not hold.
### PRIVILEGECONTROL-DEAD-ELSE
2 method(s), 2 unvisited sequence point(s), 2 unvisited explicit
branch point(s). Category: defensive. Disposition: Explained.
Classes: PrivilegeControl.
Evidence: reading the source.
Why: The unvisited points are the last else of each method, taken when the
third read of the privilege state finds none of Disabled, Removed, or Enabled.
PrivilegeState has exactly these three values and GetPrivilegeState returns
one of them, so the else can run only when another thread changes the
privilege between the reads of one call.
Residual risk: None found: the message would be Unknown Error, for a state
that cannot exist.
Related tests: Privileges.Tests: enabling and disabling a held privilege,
repeating either, and a privilege that the token does not hold.
### READ-RETRY-CLOSURE
4 method(s), 8 unvisited sequence point(s), 0 unvisited explicit
branch point(s). Category: defensive. Disposition: Explained.
Classes: GetAccess/<>c__DisplayClass20_0, GetInheritance/<>c__DisplayClass7_0,
GetOrphanedAccess/<>c__DisplayClass1_0,
GetSecurityDescriptor/<>c__DisplayClass4_0.
Evidence: reading the source.
Why: The closure re-reads the item inside InvokeAsOwner. InvokeAsOwner reads
the owner first, which needs the same READ_CONTROL right as the read that
failed, so the retry stops before the closure runs; the documentation and
PathErrors assert the resulting ReadSecurityError. A DACL that denies reading
the DACL but not the owner cannot be built.
Residual risk: None found: the closure repeats the first attempt.
Related tests: PathErrors: An item whose owner may not read its permissions.
### REGISTRY-MODEL
34 method(s), 105 unvisited sequence point(s), 20 unvisited explicit
branch point(s). Category: unused by cmdlets. Disposition: Explained.
Classes: RegistryAccessRule2, RegistryEffectivePermissionEntry,
RegistryInheritanceInfo, RegistryKeyOpenException,
RegistryKeySetSecurityException, Win32RegistrySecurity.
Evidence: a static scan of the compiled code.
Why: The registry ACL object model of the original project. No cmdlet accepts
a registry path and no other class of the four assemblies calls it (static IL
scan). SetRegistryOwner takes ownership of registry keys, which no test may do
on the shared host, and the model is not part of the documented module
surface. Decisions 21 and 22 leave these classes to the maintainer.
Residual risk: Untested and undocumented library surface; a script that calls
it has no test behind it. Keep or remove is a maintainer decision.
Related tests: None; see the decision.
### RELATIVE-PATH-EMPTY
1 method(s), 1 unvisited sequence point(s), 1 unvisited explicit
branch point(s). Category: defensive. Disposition: Explained.
Classes: BaseCmdlet.
Evidence: an executed probe.
Why: The unvisited points are the first branch, which replaces an empty path
with the current location. Every caller passes a validated value: each -Path
and -Target parameter of the cmdlets carries ValidateNotNullOrEmpty, which
rejects an empty value and an empty element of an array in both editions
(probe), and the mandatory -Destination of Copy-Item2 and Move-Item2 rejects
an empty string. A cmdlet without -Path passes the current location itself.
Residual risk: None found: only a class derived from the cmdlet base class
could pass an empty path, and it would get the current location.
Related tests: ItemCmdlets.Tests: Get-Item2 resolves ., .., and relative
names.
### REMOVEALL-ACCOUNT-FILTER
6 method(s), 6 unvisited sequence point(s), 2 unvisited explicit
branch point(s). Category: unused by cmdlets. Disposition: Open.
Classes: FileSystemAccessRule2, FileSystemAccessRule2/<>c__DisplayClass36_0,
FileSystemAccessRule2/<>c__DisplayClass36_1, FileSystemAuditRule2,
FileSystemAuditRule2/<>c__DisplayClass7_0,
FileSystemAuditRule2/<>c__DisplayClass7_1.
Evidence: reading the source.
Why: The methods run: the cmdlets call RemoveFileSystemAccessRuleAll and
RemoveFileSystemAuditRuleAll without an account list. Only the branch for a
non-null list is unvisited, and no cmdlet reaches it. That branch discards the
result of the LINQ Where that should filter the rules, so the account list has
no effect and every explicit entry is removed (the deferred #113 finding); its
predicate, Count() > 1, would also match no account that appears once, so
using the result as it stands would remove nothing.
Residual risk: Library users who pass accounts lose all entries. A test of the
intended behavior would fail; the defect stays with the maintainer (Decision
16 and #113).
Related tests: None for the account list, on purpose; the cmdlets that call
the methods without one are tested.
### TESTPATH-DEAD-CATCH
1 method(s), 6 unvisited sequence point(s), 0 unvisited explicit
branch point(s). Category: defensive. Disposition: Explained.
Classes: TestPath2.
Evidence: an executed probe.
Why: The catch (FileNotFoundException) is dead: TryGetFileSystemInfo2 returns
false for a missing item and never throws it. The catch
(NotSupportedException) was not reproduced with a colon, wildcard, device,
long or trailing-dot path in either edition (probes); the ArgumentException of
Windows PowerShell for illegal characters is covered.
Residual risk: Low: both branches write $false or an error for a path that
cannot exist.
Related tests: ItemCmdlets.Tests: Test-Path2 with illegal characters and the
debug message.
### TOKEN-NATIVE-FAILURE
6 method(s), 11 unvisited sequence point(s), 10 unvisited explicit
branch point(s). Category: environment-specific. Disposition: Explained.
Classes: AccessTokenHandle, Privileges, ProcessHandle.
Evidence: an executed probe.
Why: The unvisited points are the branches that throw a Win32Exception when a
native call fails, and the branches for a size query that succeeds, which it
never does. They are OpenProcessToken in the constructor of AccessTokenHandle
(internal; the token of an exited process still opens, probe), CloseHandle in
ReleaseHandle (ProcessHandle is only created with ownsHandle false, so Windows
never releases it, and AccessTokenHandle releases a handle that it opened
itself), LookupPrivilegeValue in GetLuid (the names come from a fixed
dictionary), LookupPrivilegeName in GetPrivilegeName (the LUIDs come from the
token), and the second GetTokenInformation call after the size query. A test
would have to corrupt the token handle of the process or run on a Windows
version that lacks a privilege. The failures that a caller can cause, a handle
without the right to query or to adjust privileges, run in the Privileges
tests.
Residual risk: Low: each branch throws the Win32Exception of the failed call,
the shape that the covered branches assert.
Related tests: Privileges.Tests: a handle that may only query cannot enable a
privilege, and a handle that may only adjust cannot be queried.
### UNUSED-CMDLET-HELPER
2 method(s), 8 unvisited sequence point(s), 4 unvisited explicit
branch point(s). Category: unused by cmdlets. Disposition: Explained.
Classes: BaseCmdlet.
Evidence: a static scan of the compiled code.
Why: A protected helper or an empty override without a caller in the four
assemblies (static IL scan): the System.IO variant of
BaseCmdlet.GetFileSystemInfo, which every cmdlet replaces with
GetFileSystemInfo2, and the empty BaseCmdlet.ProcessRecord that every cmdlet
overrides. Neither can be called from a test without a running cmdlet that
derives from the class, and they stay until the maintainer decides about
unused classes (Decisions 21 and 22). The scan misses generic instantiations:
it called the Extensions.ForEach and GetParent helpers unused although cmdlets
call them, so they are tested directly now (ObjectApis.Tests) and no longer
listed here.
Residual risk: Low: not reachable from a cmdlet.
Related tests: None needed; ObjectApis.Tests runs the public Extensions
helpers.
### WIN32-AUTHZ-FAILURE
4 method(s), 5 unvisited sequence point(s), 5 unvisited explicit
branch point(s). Category: environment-specific. Disposition: Explained.
Classes: Win32.
Evidence: an executed probe.
Why: The unvisited points are the failures of the Authz calls other than the
two answers that the module expects of an unreachable computer, RPC server
unavailable and endpoint not registered, and the failure of the local resource
manager that follows. Every server name that a test can give, an empty one,
one with a space, backslashes, a colon, a bracket, 300 characters, malformed
and well-formed addresses, ends in one of the two expected answers (probe),
and the local resource manager does not fail. A test would need a remote
computer that answers with another error.
Residual risk: Low: the cmdlet reports the exception as
GetEffectiveAccessError, the shape that the covered unresolved-identity case
asserts.
Related tests: Access.Tests: an unresolved identity, a computer that cannot be
reached, names of this computer, an empty name.
### WIN32-LOCAL-NAME-LOOKUP
1 method(s), 2 unvisited sequence point(s), 0 unvisited explicit
branch point(s). Category: defensive. Disposition: Explained.
Classes: Win32.
Evidence: reading the source.
Why: The unvisited points are the catch of a NetworkInformationException from
the lookup of the host and domain name of this computer, which returns false.
The lookup reads the local TCP/IP parameters, which this host has, and a test
cannot remove them.
Residual risk: None found: the name is then treated as another computer, with
the warning.
Related tests: Access.Tests: names of this computer, a computer that cannot be
reached, an empty name.
### WIN32-RAW-DESCRIPTOR
2 method(s), 14 unvisited sequence point(s), 2 unvisited explicit
branch point(s). Category: unused by cmdlets. Disposition: Explained.
Classes: Win32.
Evidence: a static scan of the compiled code.
Why: Win32 is an internal class. GetRawSecurityDescriptor is private and has
no caller, and the only caller of GetByteSecurityDescriptor is
GetRawSecurityDescriptor (static IL scan). They read a descriptor from a
handle, which the module does through AlphaFS.
Residual risk: None found: dead code. Removing it is a maintainer decision
(Decisions 21 and 22).
Related tests: None needed.

232
Tests/Coverage/Quality-Gate-Paths-2026-10-09-Methods.csv

@ -0,0 +1,232 @@
"Assembly","Class","Method","Source","UnvisitedLines","UnvisitedSequence","UnvisitedBranches","ReachableFromCmdletInIL","CmdletCallPath","RuleId","Category","Disposition"
"NTFSSecurity","NTFSSecurity.ClearAccess","ProcessRecord()","NTFSSecurity\AccessCmdlets\ClearAccess.cs","92,94,95","3","0","True","ClearAccess::ProcessRecord","ACCESS-GENERIC-CATCH","defensive","Explained"
"NTFSSecurity","NTFSSecurity.DisableAccessInheritance","ProcessRecord()","NTFSSecurity\InheritanceCmdlets\DisableAccessInheritance.cs","97,99,100","3","0","True","DisableAccessInheritance::ProcessRecord","ACCESS-GENERIC-CATCH","defensive","Explained"
"NTFSSecurity","NTFSSecurity.EnableAccessInheritance","ProcessRecord()","NTFSSecurity\InheritanceCmdlets\EnableAccessInheritance.cs","97,99,100","3","0","True","EnableAccessInheritance::ProcessRecord","ACCESS-GENERIC-CATCH","defensive","Explained"
"NTFSSecurity","NTFSSecurity.GetAccess","ProcessRecord()","NTFSSecurity\AccessCmdlets\GetAccess.cs","112,118,119,121,122","5","0","True","GetAccess::ProcessRecord","ACCESS-GENERIC-CATCH","defensive","Explained"
"NTFSSecurity","NTFSSecurity.GetInheritance","ProcessRecord()","NTFSSecurity\InheritanceCmdlets\GetInheritance.cs","80,86,87,89,90,107","5","1","True","GetInheritance::ProcessRecord","ACCESS-GENERIC-CATCH","defensive","Explained"
"NTFSSecurity","NTFSSecurity.GetOrphanedAccess","ProcessRecord()","NTFSSecurity\AccessCmdlets\GetOrphanedAccess.cs","55,62,63,65,66","5","0","True","GetOrphanedAccess::ProcessRecord","ACCESS-GENERIC-CATCH","defensive","Explained"
"NTFSSecurity","NTFSSecurity.GetSecurityDescriptor","ProcessRecord()","NTFSSecurity\SecurityDescriptorCmdlets\GetSecurityDescriptor.cs","64,67,69,75,78,83,84","5","2","True","GetSecurityDescriptor::ProcessRecord","ACCESS-GENERIC-CATCH","defensive","Explained"
"NTFSSecurity","NTFSSecurity.OwnerCmdlets.GetOwner","ProcessRecord()","NTFSSecurity\OwnerCmdlets\GetOwner.cs","72,74,75","3","0","True","GetOwner::ProcessRecord","ACCESS-GENERIC-CATCH","defensive","Explained"
"ProcessPrivileges","ProcessPrivileges.AllocatedMemory","Finalize()","ProcessPrivileges\AllocatedMemory.cs","28,29","3","0","False","","ALLOCATED-MEMORY-FINALIZER","defensive","Explained"
"ProcessPrivileges","ProcessPrivileges.AllocatedMemory","InternalDispose()","ProcessPrivileges\AllocatedMemory.cs","47","0","1","False","","ALLOCATED-MEMORY-FINALIZER","defensive","Explained"
"NTFSSecurity","NTFSSecurity.AddAudit","ProcessRecord()","NTFSSecurity\AuditCmdlets\AddAudit.cs","137,141,145,146,148,149,151","7","0","True","AddAudit::ProcessRecord","AUDIT-OWNER-RETRY","environment-specific","Explained"
"NTFSSecurity","NTFSSecurity.AddAudit/<>c__DisplayClass35_0","<ProcessRecord>b__0()","NTFSSecurity\AuditCmdlets\AddAudit.cs","143,144","2","0","True","AddAudit::ProcessRecord > AddAudit/<>c__DisplayClass35_0::<ProcessRecord>b__0","AUDIT-OWNER-RETRY","environment-specific","Explained"
"NTFSSecurity","NTFSSecurity.ClearAudit","ProcessRecord()","NTFSSecurity\AuditCmdlets\ClearAudit.cs","76,80,86,87,89,90,91","7","0","True","ClearAudit::ProcessRecord","AUDIT-OWNER-RETRY","environment-specific","Explained"
"NTFSSecurity","NTFSSecurity.ClearAudit/<>c__DisplayClass11_0","<ProcessRecord>b__0()","NTFSSecurity\AuditCmdlets\ClearAudit.cs","82,83,84,85","4","2","True","ClearAudit::ProcessRecord > ClearAudit/<>c__DisplayClass11_0::<ProcessRecord>b__0","AUDIT-OWNER-RETRY","environment-specific","Explained"
"NTFSSecurity","NTFSSecurity.DisableAuditInheritance","ProcessRecord()","NTFSSecurity\InheritanceCmdlets\DisableAuditInheritance.cs","88,92,96,97,99,100,102","7","0","True","DisableAuditInheritance::ProcessRecord","AUDIT-OWNER-RETRY","environment-specific","Explained"
"NTFSSecurity","NTFSSecurity.DisableAuditInheritance/<>c__DisplayClass15_0","<ProcessRecord>b__0()","NTFSSecurity\InheritanceCmdlets\DisableAuditInheritance.cs","94,95","2","0","True","DisableAuditInheritance::ProcessRecord > DisableAuditInheritance/<>c__DisplayClass15_0::<ProcessRecord>b__0","AUDIT-OWNER-RETRY","environment-specific","Explained"
"NTFSSecurity","NTFSSecurity.EnableAuditInheritance","ProcessRecord()","NTFSSecurity\InheritanceCmdlets\EnableAuditInheritance.cs","87,91,95,96,98,99,101","7","0","True","EnableAuditInheritance::ProcessRecord","AUDIT-OWNER-RETRY","environment-specific","Explained"
"NTFSSecurity","NTFSSecurity.EnableAuditInheritance/<>c__DisplayClass15_0","<ProcessRecord>b__0()","NTFSSecurity\InheritanceCmdlets\EnableAuditInheritance.cs","93,94","2","0","True","EnableAuditInheritance::ProcessRecord > EnableAuditInheritance/<>c__DisplayClass15_0::<ProcessRecord>b__0","AUDIT-OWNER-RETRY","environment-specific","Explained"
"NTFSSecurity","NTFSSecurity.RemoveAudit","ProcessRecord()","NTFSSecurity\AuditCmdlets\RemoveAudit.cs","153,157,161,162,164,165,167","7","0","True","RemoveAudit::ProcessRecord","AUDIT-OWNER-RETRY","environment-specific","Explained"
"NTFSSecurity","NTFSSecurity.RemoveAudit/<>c__DisplayClass39_0","<ProcessRecord>b__0()","NTFSSecurity\AuditCmdlets\RemoveAudit.cs","159,160","2","0","True","RemoveAudit::ProcessRecord > RemoveAudit/<>c__DisplayClass39_0::<ProcessRecord>b__0","AUDIT-OWNER-RETRY","environment-specific","Explained"
"NTFSSecurity","NTFSSecurity.AuditCmdlets.GetOrphanedAudit","ProcessRecord()","NTFSSecurity\AuditCmdlets\Get-OrphanedAudit.cs","56,58,59","3","0","True","GetOrphanedAudit::ProcessRecord","AUDIT-READ-DENIED","environment-specific","Explained"
"NTFSSecurity","NTFSSecurity.GetAudit","ProcessRecord()","NTFSSecurity\AuditCmdlets\GetAudit.cs","104,107,108","3","0","True","GetAudit::ProcessRecord","AUDIT-READ-DENIED","environment-specific","Explained"
"NTFSSecurity","NTFSSecurity.GetChildItem2","WriteFileSystem(FileSystemInfo,Int32)","NTFSSecurity\ItemCmdlets\GetChildItem2.cs","205","0","1","True","GetChildItem2::ProcessRecord > GetChildItem2::WriteFileSystem","CHILDITEM2-NON-FOLDER","defensive","Explained"
"NTFSSecurity","NTFSSecurity.AddAccess","get_AccessRights()","NTFSSecurity\AccessCmdlets\AddAccess.cs","62","1","0","True","AddAccess::get_AccessRights","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.AddAccess","get_AccessType()","NTFSSecurity\AccessCmdlets\AddAccess.cs","70","1","0","True","AddAccess::get_AccessType","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.AddAccess","get_Account()","NTFSSecurity\AccessCmdlets\AddAccess.cs","54","1","0","True","AddAccess::get_Account","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.AddAccess","get_AppliesTo()","NTFSSecurity\AccessCmdlets\AddAccess.cs","95","1","0","True","AddAccess::get_AppliesTo","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.AddAccess","get_InheritanceFlags()","NTFSSecurity\AccessCmdlets\AddAccess.cs","78","1","0","True","AddAccess::get_InheritanceFlags","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.AddAccess","get_PassThru()","NTFSSecurity\AccessCmdlets\AddAccess.cs","102","1","0","True","AddAccess::get_PassThru","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.AddAccess","get_Path()","NTFSSecurity\AccessCmdlets\AddAccess.cs","29","1","0","True","AddAccess::get_Path","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.AddAccess","get_PropagationFlags()","NTFSSecurity\AccessCmdlets\AddAccess.cs","86","1","0","True","AddAccess::get_PropagationFlags","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.AddAccess","get_SecurityDescriptor()","NTFSSecurity\AccessCmdlets\AddAccess.cs","42","1","0","True","AddAccess::get_SecurityDescriptor","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.AddAudit","get_AccessRights()","NTFSSecurity\AuditCmdlets\AddAudit.cs","62","1","0","True","AddAudit::get_AccessRights","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.AddAudit","get_Account()","NTFSSecurity\AuditCmdlets\AddAudit.cs","54","1","0","True","AddAudit::get_Account","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.AddAudit","get_AppliesTo()","NTFSSecurity\AuditCmdlets\AddAudit.cs","94","1","0","True","AddAudit::get_AppliesTo","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.AddAudit","get_AuditFlags()","NTFSSecurity\AuditCmdlets\AddAudit.cs","69","1","0","True","AddAudit::get_AuditFlags","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.AddAudit","get_InheritanceFlags()","NTFSSecurity\AuditCmdlets\AddAudit.cs","77","1","0","True","AddAudit::get_InheritanceFlags","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.AddAudit","get_PassThru()","NTFSSecurity\AuditCmdlets\AddAudit.cs","101","1","0","True","AddAudit::get_PassThru","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.AddAudit","get_Path()","NTFSSecurity\AuditCmdlets\AddAudit.cs","29","1","0","True","AddAudit::get_Path","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.AddAudit","get_PropagationFlags()","NTFSSecurity\AuditCmdlets\AddAudit.cs","85","1","0","True","AddAudit::get_PropagationFlags","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.AddAudit","get_SecurityDescriptor()","NTFSSecurity\AuditCmdlets\AddAudit.cs","42","1","0","True","AddAudit::get_SecurityDescriptor","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.ClearAccess","get_DisableInheritance()","NTFSSecurity\AccessCmdlets\ClearAccess.cs","42","1","0","True","ClearAccess::get_DisableInheritance","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.ClearAccess","get_Path()","NTFSSecurity\AccessCmdlets\ClearAccess.cs","19","1","0","True","ClearAccess::get_Path","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.ClearAccess","get_SecurityDescriptor()","NTFSSecurity\AccessCmdlets\ClearAccess.cs","31","1","0","True","ClearAccess::get_SecurityDescriptor","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.ClearAudit","get_DisableInheritance()","NTFSSecurity\AuditCmdlets\ClearAudit.cs","42","1","0","True","ClearAudit::get_DisableInheritance","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.ClearAudit","get_Path()","NTFSSecurity\AuditCmdlets\ClearAudit.cs","19","1","0","True","ClearAudit::get_Path","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.ClearAudit","get_SecurityDescriptor()","NTFSSecurity\AuditCmdlets\ClearAudit.cs","31","1","0","True","ClearAudit::get_SecurityDescriptor","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.CopyItem2","get_Destination()","NTFSSecurity\ItemCmdlets\CopyItem2.cs","33","1","0","True","CopyItem2::get_Destination","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.CopyItem2","get_Force()","NTFSSecurity\ItemCmdlets\CopyItem2.cs","40","1","0","True","CopyItem2::get_Force","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.CopyItem2","get_PassThru()","NTFSSecurity\ItemCmdlets\CopyItem2.cs","47","1","0","True","CopyItem2::get_PassThru","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.CopyItem2","get_Path()","NTFSSecurity\ItemCmdlets\CopyItem2.cs","21","1","0","True","CopyItem2::get_Path","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.DisableAccessInheritance","get_PassThru()","NTFSSecurity\InheritanceCmdlets\DisableAccessInheritance.cs","51","1","0","True","DisableAccessInheritance::get_PassThru","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.DisableAccessInheritance","get_Path()","NTFSSecurity\InheritanceCmdlets\DisableAccessInheritance.cs","21","1","0","True","DisableAccessInheritance::get_Path","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.DisableAccessInheritance","get_RemoveInheritedAccessRules()","NTFSSecurity\InheritanceCmdlets\DisableAccessInheritance.cs","44","1","0","True","DisableAccessInheritance::get_RemoveInheritedAccessRules","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.DisableAccessInheritance","get_SecurityDescriptor()","NTFSSecurity\InheritanceCmdlets\DisableAccessInheritance.cs","33","1","0","True","DisableAccessInheritance::get_SecurityDescriptor","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.DisableAuditInheritance","get_PassThru()","NTFSSecurity\InheritanceCmdlets\DisableAuditInheritance.cs","57","1","0","True","DisableAuditInheritance::get_PassThru","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.DisableAuditInheritance","get_Path()","NTFSSecurity\InheritanceCmdlets\DisableAuditInheritance.cs","22","1","0","True","DisableAuditInheritance::get_Path","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.DisableAuditInheritance","get_RemoveInheritedAuditRules()","NTFSSecurity\InheritanceCmdlets\DisableAuditInheritance.cs","50","1","0","True","DisableAuditInheritance::get_RemoveInheritedAuditRules","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.DisableAuditInheritance","get_SecurityDescriptor()","NTFSSecurity\InheritanceCmdlets\DisableAuditInheritance.cs","34","1","0","True","DisableAuditInheritance::get_SecurityDescriptor","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.DisablePrivileges","get_PassThru()","NTFSSecurity\OtherCmdlets.cs","83","1","0","True","DisablePrivileges::get_PassThru","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.EnableAccessInheritance","get_PassThru()","NTFSSecurity\InheritanceCmdlets\EnableAccessInheritance.cs","44","1","0","True","EnableAccessInheritance::get_PassThru","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.EnableAccessInheritance","get_Path()","NTFSSecurity\InheritanceCmdlets\EnableAccessInheritance.cs","21","1","0","True","EnableAccessInheritance::get_Path","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.EnableAccessInheritance","get_RemoveExplicitAccessRules()","NTFSSecurity\InheritanceCmdlets\EnableAccessInheritance.cs","51","1","0","True","EnableAccessInheritance::get_RemoveExplicitAccessRules","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.EnableAccessInheritance","get_SecurityDescriptor()","NTFSSecurity\InheritanceCmdlets\EnableAccessInheritance.cs","33","1","0","True","EnableAccessInheritance::get_SecurityDescriptor","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.EnableAuditInheritance","get_PassThru()","NTFSSecurity\InheritanceCmdlets\EnableAuditInheritance.cs","44","1","0","True","EnableAuditInheritance::get_PassThru","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.EnableAuditInheritance","get_Path()","NTFSSecurity\InheritanceCmdlets\EnableAuditInheritance.cs","21","1","0","True","EnableAuditInheritance::get_Path","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.EnableAuditInheritance","get_RemoveExplicitAuditRules()","NTFSSecurity\InheritanceCmdlets\EnableAuditInheritance.cs","56","1","0","True","EnableAuditInheritance::get_RemoveExplicitAuditRules","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.EnableAuditInheritance","get_SecurityDescriptor()","NTFSSecurity\InheritanceCmdlets\EnableAuditInheritance.cs","33","1","0","True","EnableAuditInheritance::get_SecurityDescriptor","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.EnablePrivileges","get_PassThru()","NTFSSecurity\OtherCmdlets.cs","21","1","0","True","EnablePrivileges::get_PassThru","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.GetChildItem2","get_Attributes()","NTFSSecurity\ItemCmdlets\GetChildItem2.cs","79","1","0","True","GetChildItem2::get_Attributes","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.GetChildItem2","get_Depth()","NTFSSecurity\ItemCmdlets\GetChildItem2.cs","128","1","0","True","GetChildItem2::get_Depth","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.GetChildItem2","get_Directory()","NTFSSecurity\ItemCmdlets\GetChildItem2.cs","65","1","0","True","GetChildItem2::get_Directory","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.GetChildItem2","get_File()","NTFSSecurity\ItemCmdlets\GetChildItem2.cs","72","1","0","True","GetChildItem2::get_File","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.GetChildItem2","get_Filter()","NTFSSecurity\ItemCmdlets\GetChildItem2.cs","51","1","0","True","GetChildItem2::get_Filter","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.GetChildItem2","get_Force()","NTFSSecurity\ItemCmdlets\GetChildItem2.cs","107","1","0","True","GetChildItem2::get_Force","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.GetChildItem2","get_Hidden()","NTFSSecurity\ItemCmdlets\GetChildItem2.cs","86","1","0","True","GetChildItem2::get_Hidden","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.GetChildItem2","get_Path()","NTFSSecurity\ItemCmdlets\GetChildItem2.cs","39","1","0","True","GetChildItem2::get_Path","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.GetChildItem2","get_ReadOnly()","NTFSSecurity\ItemCmdlets\GetChildItem2.cs","100","1","0","True","GetChildItem2::get_ReadOnly","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.GetChildItem2","get_Recurse()","NTFSSecurity\ItemCmdlets\GetChildItem2.cs","58","1","0","True","GetChildItem2::get_Recurse","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.GetChildItem2","get_SkipMountPoints()","NTFSSecurity\ItemCmdlets\GetChildItem2.cs","114","1","0","True","GetChildItem2::get_SkipMountPoints","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.GetChildItem2","get_SkipSymbolicLinks()","NTFSSecurity\ItemCmdlets\GetChildItem2.cs","121","1","0","True","GetChildItem2::get_SkipSymbolicLinks","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.GetChildItem2","get_System()","NTFSSecurity\ItemCmdlets\GetChildItem2.cs","93","1","0","True","GetChildItem2::get_System","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.GetDiskSpace","get_DriveLetter()","NTFSSecurity\ItemCmdlets\GetDiskSpace.cs","18","1","0","True","GetDiskSpace::get_DriveLetter","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.GetEffectiveAccess","get_Account()","NTFSSecurity\AccessCmdlets\GetEffectiveAccess.cs","51","1","0","True","GetEffectiveAccess::get_Account","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.GetEffectiveAccess","get_ExcludeNoneAccessEntries()","NTFSSecurity\AccessCmdlets\GetEffectiveAccess.cs","65","1","0","True","GetEffectiveAccess::get_ExcludeNoneAccessEntries","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.GetEffectiveAccess","get_Path()","NTFSSecurity\AccessCmdlets\GetEffectiveAccess.cs","26","1","0","True","GetEffectiveAccess::get_Path","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.GetEffectiveAccess","get_SecurityDescriptor()","NTFSSecurity\AccessCmdlets\GetEffectiveAccess.cs","38","1","0","True","GetEffectiveAccess::get_SecurityDescriptor","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.GetEffectiveAccess","get_ServerName()","NTFSSecurity\AccessCmdlets\GetEffectiveAccess.cs","58","1","0","True","GetEffectiveAccess::get_ServerName","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.GetFileHash2","get_Algorithm()","NTFSSecurity\MiscCmdlets\GetFileHash2.cs","33","1","0","True","GetFileHash2::get_Algorithm","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.GetInheritance","get_Path()","NTFSSecurity\InheritanceCmdlets\GetInheritance.cs","19","1","0","True","GetInheritance::get_Path","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.GetInheritance","get_SecurityDescriptor()","NTFSSecurity\InheritanceCmdlets\GetInheritance.cs","31","1","0","True","GetInheritance::get_SecurityDescriptor","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.GetItem2","get_Path()","NTFSSecurity\ItemCmdlets\GetItem2.cs","19","1","0","True","GetItem2::get_Path","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.GetSecurityDescriptor","get_Path()","NTFSSecurity\SecurityDescriptorCmdlets\GetSecurityDescriptor.cs","18","1","0","True","GetSecurityDescriptor::get_Path","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.GetSimpleAccess","get_IncludeRootFolder()","NTFSSecurity\SimpleAccessCmdlets\SimpleAccessCmdlets.cs","24","1","0","True","GetSimpleAccess::get_IncludeRootFolder","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.MoveItem2","get_Destination()","NTFSSecurity\ItemCmdlets\MoveItem2.cs","33","1","0","True","MoveItem2::get_Destination","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.MoveItem2","get_Force()","NTFSSecurity\ItemCmdlets\MoveItem2.cs","40","1","0","True","MoveItem2::get_Force","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.MoveItem2","get_PassThru()","NTFSSecurity\ItemCmdlets\MoveItem2.cs","47","1","0","True","MoveItem2::get_PassThru","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.MoveItem2","get_Path()","NTFSSecurity\ItemCmdlets\MoveItem2.cs","21","1","0","True","MoveItem2::get_Path","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.NewHardLink","get_PassThru()","NTFSSecurity\LinkCmdlets\NewHardLink.cs","47","1","0","True","NewHardLink::get_PassThru","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.NewSymbolicLink","get_PassThru()","NTFSSecurity\LinkCmdlets\NewSymbolicLink.cs","45","1","0","True","NewSymbolicLink::get_PassThru","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.RemoveAccess","get_AppliesTo()","NTFSSecurity\AccessCmdlets\RemoveAccess.cs","96","1","0","True","RemoveAccess::get_AppliesTo","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.RemoveAccess","get_PassThru()","NTFSSecurity\AccessCmdlets\RemoveAccess.cs","113","1","0","True","RemoveAccess::get_PassThru","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.RemoveAccess","get_RemoveSpecific()","NTFSSecurity\AccessCmdlets\RemoveAccess.cs","106","1","0","True","RemoveAccess::get_RemoveSpecific","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.RemoveAudit","get_AccessRights()","NTFSSecurity\AuditCmdlets\RemoveAudit.cs","63","1","0","True","RemoveAudit::get_AccessRights","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.RemoveAudit","get_Account()","NTFSSecurity\AuditCmdlets\RemoveAudit.cs","55","1","0","True","RemoveAudit::get_Account","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.RemoveAudit","get_AppliesTo()","NTFSSecurity\AuditCmdlets\RemoveAudit.cs","95","1","0","True","RemoveAudit::get_AppliesTo","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.RemoveAudit","get_AuditFlags()","NTFSSecurity\AuditCmdlets\RemoveAudit.cs","70","1","0","True","RemoveAudit::get_AuditFlags","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.RemoveAudit","get_InheritanceFlags()","NTFSSecurity\AuditCmdlets\RemoveAudit.cs","78","1","0","True","RemoveAudit::get_InheritanceFlags","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.RemoveAudit","get_PassThru()","NTFSSecurity\AuditCmdlets\RemoveAudit.cs","112","1","0","True","RemoveAudit::get_PassThru","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.RemoveAudit","get_Path()","NTFSSecurity\AuditCmdlets\RemoveAudit.cs","30","1","0","True","RemoveAudit::get_Path","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.RemoveAudit","get_PropagationFlags()","NTFSSecurity\AuditCmdlets\RemoveAudit.cs","86","1","0","True","RemoveAudit::get_PropagationFlags","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.RemoveAudit","get_RemoveSpecific()","NTFSSecurity\AuditCmdlets\RemoveAudit.cs","105","1","0","True","RemoveAudit::get_RemoveSpecific","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.RemoveAudit","get_SecurityDescriptor()","NTFSSecurity\AuditCmdlets\RemoveAudit.cs","43","1","0","True","RemoveAudit::get_SecurityDescriptor","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.RemoveItem2","get_Force()","NTFSSecurity\ItemCmdlets\RemoveItem2.cs","32","1","0","True","RemoveItem2::get_Force","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.RemoveItem2","get_PassThru()","NTFSSecurity\ItemCmdlets\RemoveItem2.cs","48","1","0","True","RemoveItem2::get_PassThru","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.RemoveItem2","get_Path()","NTFSSecurity\ItemCmdlets\RemoveItem2.cs","21","1","0","True","RemoveItem2::get_Path","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.RemoveItem2","get_Recurse()","NTFSSecurity\ItemCmdlets\RemoveItem2.cs","39","1","0","True","RemoveItem2::get_Recurse","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.SetInheritance","get_AccessInheritanceEnabled()","NTFSSecurity\InheritanceCmdlets\SetInheritance.cs","45","1","0","True","SetInheritance::get_AccessInheritanceEnabled","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.SetInheritance","get_AuditInheritanceEnabled()","NTFSSecurity\InheritanceCmdlets\SetInheritance.cs","52","1","0","True","SetInheritance::get_AuditInheritanceEnabled","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.SetInheritance","get_PassThru()","NTFSSecurity\InheritanceCmdlets\SetInheritance.cs","59","1","0","True","SetInheritance::get_PassThru","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.SetInheritance","get_Path()","NTFSSecurity\InheritanceCmdlets\SetInheritance.cs","22","1","0","True","SetInheritance::get_Path","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.SetInheritance","get_SecurityDescriptor()","NTFSSecurity\InheritanceCmdlets\SetInheritance.cs","34","1","0","True","SetInheritance::get_SecurityDescriptor","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.SetOwner","get_Account()","NTFSSecurity\OwnerCmdlets\SetOwner.cs","44","1","0","True","SetOwner::get_Account","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.SetOwner","get_PassThru()","NTFSSecurity\OwnerCmdlets\SetOwner.cs","51","1","0","True","SetOwner::get_PassThru","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.SetOwner","get_SecurityDescriptor()","NTFSSecurity\OwnerCmdlets\SetOwner.cs","33","1","0","True","SetOwner::get_SecurityDescriptor","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.SetSecurityDescriptor","get_PassThru()","NTFSSecurity\SecurityDescriptorCmdlets\SetSecurityDescriptor.cs","28","1","0","True","SetSecurityDescriptor::get_PassThru","CMDLET-GETTER","parameter/API surface","Explained"
"NTFSSecurity","NTFSSecurity.FileSystemCodeMembers","Mode(Management.Automation.PSObject)","NTFSSecurity\CodeMembers.cs","15,17","1","1","False","","CODEMEMBERS-NULL-BASE","defensive","Explained"
"NTFSSecurity","NTFSSecurity.GetDiskSpace","ProcessRecord()","NTFSSecurity\ItemCmdlets\GetDiskSpace.cs","52","0","1","True","GetDiskSpace::ProcessRecord","DISKSPACE-EMPTY-VOLUME","environment-specific","Explained"
"NTFSSecurity","NTFSSecurity.GetEffectiveAccess","ProcessRecord()","NTFSSecurity\AccessCmdlets\GetEffectiveAccess.cs","99,101,102,141,147,148,150,151","8","0","True","GetEffectiveAccess::ProcessRecord","EFFECTIVE-ACCESS-CATCH","defensive","Explained"
"NTFSSecurity","NTFSSecurity.GetEffectiveAccess/<>c__DisplayClass20_0","<ProcessRecord>b__0()","NTFSSecurity\AccessCmdlets\GetEffectiveAccess.cs","139,140","2","0","True","GetEffectiveAccess::ProcessRecord > GetEffectiveAccess/<>c__DisplayClass20_0::<ProcessRecord>b__0","EFFECTIVE-ACCESS-CATCH","defensive","Explained"
"NTFSSecurity","NTFSSecurity.EnablePrivileges","ProcessRecord()","NTFSSecurity\OtherCmdlets.cs","39,41","2","0","True","EnablePrivileges::ProcessRecord","ENABLEPRIVILEGES-INIT","defensive","Explained"
"Security2","Security2.FileSystemSecurity2","op_Implicit(DirectorySecurity)","Security2\FileSystem\FileSystemSecurity2.cs","254","1","0","False","","FILESECURITY-CONVERSION","unused by cmdlets","Open"
"Security2","Security2.FileSystemSecurity2","op_Implicit(FileSecurity)","Security2\FileSystem\FileSystemSecurity2.cs","245","1","0","False","","FILESECURITY-CONVERSION","unused by cmdlets","Open"
"Security2","Security2.FileSystemSecurity2",".ctor(FileSystemInfo)","Security2\FileSystem\FileSystemSecurity2.cs","62,63","2","0","True","GetSecurityDescriptor::ProcessRecord > FileSystemSecurity2::.ctor","FSSEC2-CONSTRUCTOR-FALLBACK","environment-specific","Explained"
"Security2","Security2.FileSystemSecurity2","TryGetDriveRoot(FileSystemInfo,String&)","Security2\FileSystem\FileSystemSecurity2.cs","122","0","1","True","DisableAccessInheritance::ProcessRecord > FileSystemInheritanceInfo::GetFileSystemInheritanceInfo > FileSystemSecurity2::GetSecurity > FileSystemSecurity2::TryGetDriveRoot","FSSEC2-DRIVE-LETTER-CHARS","defensive","Explained"
"Security2","Security2.FileSystemSecurity2","GetHashCode()","Security2\FileSystem\FileSystemSecurity2.cs","267","0","1","False","","FSSEC2-HASHCODE-NULL","defensive","Explained"
"NTFSSecurity","NTFSSecurity.Properties.Resources",".ctor()","NTFSSecurity\Properties\Resources.Designer.cs","32,33","2","0","False","","GENERATED-RESOURCES","unused by cmdlets","Explained"
"NTFSSecurity","NTFSSecurity.Properties.Resources","get_ContainerIcon()","NTFSSecurity\Properties\Resources.Designer.cs","68","1","0","False","","GENERATED-RESOURCES","unused by cmdlets","Explained"
"NTFSSecurity","NTFSSecurity.Properties.Resources","get_Culture()","NTFSSecurity\Properties\Resources.Designer.cs","56","1","0","False","","GENERATED-RESOURCES","unused by cmdlets","Explained"
"NTFSSecurity","NTFSSecurity.Properties.Resources","get_IconContainer()","NTFSSecurity\Properties\Resources.Designer.cs","77,78","2","0","False","","GENERATED-RESOURCES","unused by cmdlets","Explained"
"NTFSSecurity","NTFSSecurity.Properties.Resources","get_ResourceManager()","NTFSSecurity\Properties\Resources.Designer.cs","41,42,43,45","4","2","False","","GENERATED-RESOURCES","unused by cmdlets","Explained"
"NTFSSecurity","NTFSSecurity.Properties.Resources","set_Culture(Globalization.CultureInfo)","NTFSSecurity\Properties\Resources.Designer.cs","59,60","2","0","False","","GENERATED-RESOURCES","unused by cmdlets","Explained"
"NTFSSecurity","NTFSSecurity.GetHardLink","ProcessRecord()","NTFSSecurity\LinkCmdlets\GetHardLink.cs","77,79,80","3","0","True","GetHardLink::ProcessRecord","HARDLINK-DENIED","environment-specific","Explained"
"NTFSSecurity","NTFSSecurity.GetFileHash2","ProcessRecord()","NTFSSecurity\MiscCmdlets\GetFileHash2.cs","51,52,55,56,103,109","6","0","True","GetFileHash2::ProcessRecord","HASH-POLICY-AND-RETRY","environment-specific","Explained"
"NTFSSecurity","NTFSSecurity.GetFileHash2/<>c__DisplayClass9_0","<ProcessRecord>b__0()","NTFSSecurity\MiscCmdlets\GetFileHash2.cs","102","1","0","True","GetFileHash2::ProcessRecord > GetFileHash2/<>c__DisplayClass9_0::<ProcessRecord>b__0","HASH-POLICY-AND-RETRY","environment-specific","Explained"
"Security2","Security2.IdentityReference2",".ctor(Security.Principal.IdentityReference)","Security2\IdentityReference2.cs","49","0","1","True","AddAccess::ProcessRecord > BaseCmdlet::InvokeAsOwner > IdentityReference2::op_Implicit > IdentityReference2::.ctor","IDENTITY-NULL-REFERENCE","defensive","Explained"
"Security2","Security2.FileSystemAuditRule2","GetFileSystemAuditRules(FileSystemSecurity2,Boolean,Boolean,Boolean)","Security2\FileSystem\FileSystemAuditRule2 Class\FileSystemAuditRule2.GetFileSystemAuditRules.cs","44","0","1","True","AddAudit::ProcessRecord > FileSystemAuditRule2::GetFileSystemAuditRules","INHERITED-FROM-EMPTY-LIST","defensive","Explained"
"Security2","Security2.IntPtrExtensions","Increment(IntPtr)","Security2\Extensions.cs","15","1","0","False","","NATIVE-HANDLES","unused by cmdlets","Explained"
"Security2","Security2.SafeAuthzRMHandle",".ctor(IntPtr)","Security2\Win32\SafeHandleEx.cs","299,301,302","3","0","False","","NATIVE-HANDLES","unused by cmdlets","Explained"
"Security2","Security2.SafeAuthzRMHandle","get_InvalidHandle()","Security2\Win32\SafeHandleEx.cs","309","1","0","False","","NATIVE-HANDLES","unused by cmdlets","Explained"
"Security2","Security2.SafeHGlobalHandle",".ctor(IntPtr)","Security2\Win32\SafeHandleEx.cs","31,33,34","3","0","False","","NATIVE-HANDLES","unused by cmdlets","Explained"
"Security2","Security2.SafeHGlobalHandle","AddSubReference(IEnumerable`1<SafeHGlobalHandle>)","Security2\Win32\SafeHandleEx.cs","60,62,65,66","4","2","False","","NATIVE-HANDLES","unused by cmdlets","Explained"
"Security2","Security2.SafeHGlobalHandle","AllocHGlobal(ICollection`1<T>)","Security2\Win32\SafeHandleEx.cs","109","1","0","False","","NATIVE-HANDLES","unused by cmdlets","Explained"
"Security2","Security2.SafeHGlobalHandle","AllocHGlobal(Int32,IEnumerable`1<T>,Int32)","Security2\Win32\SafeHandleEx.cs","130,132,133,135,136,139","8","2","False","","NATIVE-HANDLES","unused by cmdlets","Explained"
"Security2","Security2.SafeHGlobalHandle","AllocHGlobal(Int32)","Security2\Win32\SafeHandleEx.cs","184,186","1","1","False","","NATIVE-HANDLES","unused by cmdlets","Explained"
"Security2","Security2.SafeHGlobalHandle","AllocHGlobal(IntPtr[])","Security2\Win32\SafeHandleEx.cs","77,79,81","3","0","False","","NATIVE-HANDLES","unused by cmdlets","Explained"
"Security2","Security2.SafeHGlobalHandle","AllocHGlobal(String)","Security2\Win32\SafeHandleEx.cs","150","1","0","False","","NATIVE-HANDLES","unused by cmdlets","Explained"
"Security2","Security2.SafeHGlobalHandle","Dispose()","Security2\Win32\SafeHandleEx.cs","169","0","1","False","","NATIVE-HANDLES","unused by cmdlets","Explained"
"Security2","Security2.SafeHGlobalHandle","get_InvalidHandle()","Security2\Win32\SafeHandleEx.cs","45","1","0","False","","NATIVE-HANDLES","unused by cmdlets","Explained"
"Security2","Security2.SafeTokenHandle",".ctor()","Security2\Win32\SafeHandleEx.cs","243","2","0","False","","NATIVE-HANDLES","unused by cmdlets","Explained"
"Security2","Security2.SafeTokenHandle",".ctor(IntPtr)","Security2\Win32\SafeHandleEx.cs","247,249,250","3","0","False","","NATIVE-HANDLES","unused by cmdlets","Explained"
"Security2","Security2.SafeTokenHandle","get_InvalidHandle()","Security2\Win32\SafeHandleEx.cs","256","1","0","False","","NATIVE-HANDLES","unused by cmdlets","Explained"
"Security2","Security2.SafeTokenHandle","ReleaseHandle()","Security2\Win32\SafeHandleEx.cs","266","1","0","False","","NATIVE-HANDLES","unused by cmdlets","Explained"
"NTFSSecurity","NTFSSecurity.BaseCmdletWithPrivControl","DisableEnabledPrivileges(Dictionary`2<String,Exception>)","NTFSSecurity\BaseCmdlets.cs","422,424,425","3","0","True","BaseCmdletWithPrivControl::Dispose > BaseCmdletWithPrivControl::DisableEnabledPrivileges","PRIVILEGE-DISABLE-FAILURE","defensive","Explained"
"NTFSSecurity","NTFSSecurity.BaseCmdletWithPrivControl","DisableFileSystemPrivileges()","NTFSSecurity\BaseCmdlets.cs","544,545","1","1","True","DisablePrivileges::ProcessRecord > BaseCmdletWithPrivControl::DisableFileSystemPrivileges","PRIVILEGE-DISABLE-FAILURE","defensive","Explained"
"NTFSSecurity","NTFSSecurity.BaseCmdletWithPrivControl","EndProcessing()","NTFSSecurity\BaseCmdlets.cs","389,391,392","3","1","True","BaseCmdletWithPrivControl::EndProcessing","PRIVILEGE-DISABLE-FAILURE","defensive","Explained"
"NTFSSecurity","NTFSSecurity.BaseCmdletWithPrivControl","TryDisablePrivilege(ProcessPrivileges.Privilege)","NTFSSecurity\BaseCmdlets.cs","490,492,493","3","0","True","DisablePrivileges::ProcessRecord > BaseCmdletWithPrivControl::DisableFileSystemPrivileges > BaseCmdletWithPrivControl::TryDisablePrivilege","PRIVILEGE-DISABLE-FAILURE","defensive","Explained"
"ProcessPrivileges","ProcessPrivileges.PrivilegeEnabler","EnablePrivilege(ProcessPrivileges.Privilege)","ProcessPrivileges\PrivilegeEnabler.cs","172","0","1","False","","PRIVILEGE-ENABLER-RACE","defensive","Explained"
"PrivilegeControl","Security2.PrivilegeControl","DisablePrivilege(ProcessPrivileges.Privilege)","PrivilegeControl\PrivilegeControl.cs","61,67","1","1","True","BaseCmdletWithPrivControl::Dispose > BaseCmdletWithPrivControl::DisableEnabledPrivileges > BaseCmdletWithPrivControl::DisablePrivilege > PrivilegeControl::DisablePrivilege","PRIVILEGECONTROL-DEAD-ELSE","defensive","Explained"
"PrivilegeControl","Security2.PrivilegeControl","EnablePrivilege(ProcessPrivileges.Privilege)","PrivilegeControl\PrivilegeControl.cs","40,46","1","1","True","BaseCmdletWithPrivControl::BeginProcessing > BaseCmdletWithPrivControl::EnableFileSystemPrivileges > BaseCmdletWithPrivControl::TryEnablePrivilege > BaseCmdletWithPrivControl::EnablePrivilege > PrivilegeControl::EnablePrivilege","PRIVILEGECONTROL-DEAD-ELSE","defensive","Explained"
"NTFSSecurity","NTFSSecurity.GetAccess/<>c__DisplayClass20_0","<ProcessRecord>b__0()","NTFSSecurity\AccessCmdlets\GetAccess.cs","110,111","2","0","True","GetAccess::ProcessRecord > GetAccess/<>c__DisplayClass20_0::<ProcessRecord>b__0","READ-RETRY-CLOSURE","defensive","Explained"
"NTFSSecurity","NTFSSecurity.GetInheritance/<>c__DisplayClass7_0","<ProcessRecord>b__0()","NTFSSecurity\InheritanceCmdlets\GetInheritance.cs","78,79","2","0","True","GetInheritance::ProcessRecord > GetInheritance/<>c__DisplayClass7_0::<ProcessRecord>b__0","READ-RETRY-CLOSURE","defensive","Explained"
"NTFSSecurity","NTFSSecurity.GetOrphanedAccess/<>c__DisplayClass1_0","<ProcessRecord>b__0()","NTFSSecurity\AccessCmdlets\GetOrphanedAccess.cs","53,54","2","0","True","GetOrphanedAccess::ProcessRecord > GetOrphanedAccess/<>c__DisplayClass1_0::<ProcessRecord>b__0","READ-RETRY-CLOSURE","defensive","Explained"
"NTFSSecurity","NTFSSecurity.GetSecurityDescriptor/<>c__DisplayClass4_0","<ProcessRecord>b__0()","NTFSSecurity\SecurityDescriptorCmdlets\GetSecurityDescriptor.cs","62,63","2","0","True","GetSecurityDescriptor::ProcessRecord > GetSecurityDescriptor/<>c__DisplayClass4_0::<ProcessRecord>b__0","READ-RETRY-CLOSURE","defensive","Explained"
"Security2","Security2.RegistryAccessRule2",".ctor(RegistryAccessRule)","Security2\Registry\RegistrySecurity.cs","140,142,143","3","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryAccessRule2","Dispose()","Security2\Registry\RegistrySecurity.cs","180","1","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryAccessRule2","Equals(Object)","Security2\Registry\RegistrySecurity.cs","166","1","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryAccessRule2","get_AccessControlType()","Security2\Registry\RegistrySecurity.cs","146","1","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryAccessRule2","get_IdentityReference()","Security2\Registry\RegistrySecurity.cs","148","1","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryAccessRule2","get_InheritanceFlags()","Security2\Registry\RegistrySecurity.cs","149","1","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryAccessRule2","get_IsInherited()","Security2\Registry\RegistrySecurity.cs","150","1","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryAccessRule2","get_Name()","Security2\Registry\RegistrySecurity.cs","117,119,121,122,126,131","6","4","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryAccessRule2","get_PropagationFlags()","Security2\Registry\RegistrySecurity.cs","151","1","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryAccessRule2","get_RegistryRights()","Security2\Registry\RegistrySecurity.cs","147","1","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryAccessRule2","GetHashCode()","Security2\Registry\RegistrySecurity.cs","170","1","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryAccessRule2","op_Implicit(RegistryAccessRule)","Security2\Registry\RegistrySecurity.cs","161","1","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryAccessRule2","op_Implicit(RegistryAccessRule2)","Security2\Registry\RegistrySecurity.cs","157","1","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryAccessRule2","ToString()","Security2\Registry\RegistrySecurity.cs","174","1","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryEffectivePermissionEntry",".ctor(IdentityReference2,UInt32,String)","Security2\Registry\RegistrySecurity.cs","252,254,255,256,257,259,261,265,266,268,271","13","4","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryEffectivePermissionEntry","get_AccessAsString()","Security2\Registry\RegistrySecurity.cs","250","1","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryEffectivePermissionEntry","get_AccessMask()","Security2\Registry\RegistrySecurity.cs","224","1","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryEffectivePermissionEntry","get_Account()","Security2\Registry\RegistrySecurity.cs","221","1","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryEffectivePermissionEntry","get_FullName()","Security2\Registry\RegistrySecurity.cs","227","1","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryEffectivePermissionEntry","get_Name()","Security2\Registry\RegistrySecurity.cs","230,232,234,235,239,244","6","4","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryInheritanceInfo","get_FullName()","Security2\Registry\RegistrySecurity.cs","191","1","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryInheritanceInfo","get_Name()","Security2\Registry\RegistrySecurity.cs","196,198,200,201,205,210","6","4","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryKeyOpenException",".ctor()","Security2\Registry\RegistrySecurity.cs","333,334","2","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryKeyOpenException",".ctor(Exception,Int64)","Security2\Registry\RegistrySecurity.cs","347,349,350","3","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryKeyOpenException",".ctor(Exception)","Security2\Registry\RegistrySecurity.cs","337,338","2","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryKeyOpenException",".ctor(Int64)","Security2\Registry\RegistrySecurity.cs","341,343,344","3","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryKeyOpenException","get_Win32ErrorCode()","Security2\Registry\RegistrySecurity.cs","330","1","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryKeySetSecurityException",".ctor()","Security2\Registry\RegistrySecurity.cs","359,360","2","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryKeySetSecurityException",".ctor(Exception,Int64)","Security2\Registry\RegistrySecurity.cs","373,375,376","3","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryKeySetSecurityException",".ctor(Exception)","Security2\Registry\RegistrySecurity.cs","363,364","2","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryKeySetSecurityException",".ctor(Int64)","Security2\Registry\RegistrySecurity.cs","367,369,370","3","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.RegistryKeySetSecurityException","get_Win32ErrorCode()","Security2\Registry\RegistrySecurity.cs","356","1","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.Win32RegistrySecurity","GetRegistryKey(REGISTRY_ROOT,String,RegistryRights)","Security2\Registry\RegistrySecurity.cs","429,431,433,435,437,440,442,444","8","0","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"Security2","Security2.Win32RegistrySecurity","SetRegistryOwner(REGISTRY_ROOT,String,Security.Principal.SecurityIdentifier)","Security2\Registry\RegistrySecurity.cs","448,450,451,452,454,458,459,461,463,464,466,470,473,474,478,479,481,483,484,486,490,494,495,496","24","4","False","","REGISTRY-MODEL","unused by cmdlets","Explained"
"NTFSSecurity","NTFSSecurity.BaseCmdlet","GetRelativePath(String)","NTFSSecurity\BaseCmdlets.cs","229,231","1","1","True","CopyItem2::BeginProcessing > BaseCmdlet::GetRelativePath","RELATIVE-PATH-EMPTY","defensive","Explained"
"Security2","Security2.FileSystemAccessRule2","RemoveFileSystemAccessRuleAll(FileSystemSecurity2,List`1<IdentityReference2>)","Security2\FileSystem\FileSystemAccessRule2 Class\FileSystemAccessRule2.RemoveFileSystemAccessRulesAll.cs","15,17","1","1","True","ClearAccess::ProcessRecord > FileSystemAccessRule2::RemoveFileSystemAccessRuleAll","REMOVEALL-ACCOUNT-FILTER","unused by cmdlets","Open"
"Security2","Security2.FileSystemAccessRule2/<>c__DisplayClass36_0","<RemoveFileSystemAccessRuleAll>b__0(FileSystemAccessRule)","Security2\FileSystem\FileSystemAccessRule2 Class\FileSystemAccessRule2.RemoveFileSystemAccessRulesAll.cs","17","1","0","True","ClearAccess::ProcessRecord > FileSystemAccessRule2::RemoveFileSystemAccessRuleAll > FileSystemAccessRule2/<>c__DisplayClass36_0::<RemoveFileSystemAccessRuleAll>b__0","REMOVEALL-ACCOUNT-FILTER","unused by cmdlets","Open"
"Security2","Security2.FileSystemAccessRule2/<>c__DisplayClass36_1","<RemoveFileSystemAccessRuleAll>b__1(IdentityReference2)","Security2\FileSystem\FileSystemAccessRule2 Class\FileSystemAccessRule2.RemoveFileSystemAccessRulesAll.cs","17","1","0","True","ClearAccess::ProcessRecord > FileSystemAccessRule2::RemoveFileSystemAccessRuleAll > FileSystemAccessRule2/<>c__DisplayClass36_0::<RemoveFileSystemAccessRuleAll>b__0 > FileSystemAccessRule2/<>c__DisplayClass36_1::<RemoveFileSystemAccessRuleAll>b__1","REMOVEALL-ACCOUNT-FILTER","unused by cmdlets","Open"
"Security2","Security2.FileSystemAuditRule2","RemoveFileSystemAuditRuleAll(FileSystemSecurity2,List`1<IdentityReference2>)","Security2\FileSystem\FileSystemAuditRule2 Class\FileSystemAuditRule2.RemoveFileSystemAuditRuleAll.cs","15,17","1","1","True","ClearAudit::ProcessRecord > FileSystemAuditRule2::RemoveFileSystemAuditRuleAll","REMOVEALL-ACCOUNT-FILTER","unused by cmdlets","Open"
"Security2","Security2.FileSystemAuditRule2/<>c__DisplayClass7_0","<RemoveFileSystemAuditRuleAll>b__0(FileSystemAuditRule)","Security2\FileSystem\FileSystemAuditRule2 Class\FileSystemAuditRule2.RemoveFileSystemAuditRuleAll.cs","17","1","0","True","ClearAudit::ProcessRecord > FileSystemAuditRule2::RemoveFileSystemAuditRuleAll > FileSystemAuditRule2/<>c__DisplayClass7_0::<RemoveFileSystemAuditRuleAll>b__0","REMOVEALL-ACCOUNT-FILTER","unused by cmdlets","Open"
"Security2","Security2.FileSystemAuditRule2/<>c__DisplayClass7_1","<RemoveFileSystemAuditRuleAll>b__1(IdentityReference2)","Security2\FileSystem\FileSystemAuditRule2 Class\FileSystemAuditRule2.RemoveFileSystemAuditRuleAll.cs","17","1","0","True","ClearAudit::ProcessRecord > FileSystemAuditRule2::RemoveFileSystemAuditRuleAll > FileSystemAuditRule2/<>c__DisplayClass7_0::<RemoveFileSystemAuditRuleAll>b__0 > FileSystemAuditRule2/<>c__DisplayClass7_1::<RemoveFileSystemAuditRuleAll>b__1","REMOVEALL-ACCOUNT-FILTER","unused by cmdlets","Open"
"NTFSSecurity","NTFSSecurity.TestPath2","ProcessRecord()","NTFSSecurity\PathCmdlets\TestPath2.cs","53,55,56,65,67,68","6","0","True","TestPath2::ProcessRecord","TESTPATH-DEAD-CATCH","defensive","Explained"
"ProcessPrivileges","ProcessPrivileges.AccessTokenHandle",".ctor(ProcessPrivileges.ProcessHandle,ProcessPrivileges.TokenAccessRights)","ProcessPrivileges\AccessTokenHandle.cs","21,23","1","1","True","DisablePrivileges::ProcessRecord > PrivilegeControl::GetPrivileges > ProcessExtensions::GetPrivileges > AccessTokenHandle::.ctor","TOKEN-NATIVE-FAILURE","environment-specific","Explained"
"ProcessPrivileges","ProcessPrivileges.AccessTokenHandle","ReleaseHandle()","ProcessPrivileges\AccessTokenHandle.cs","32,34","1","1","False","","TOKEN-NATIVE-FAILURE","environment-specific","Explained"
"ProcessPrivileges","ProcessPrivileges.Privileges","GetLuid(ProcessPrivileges.Privilege)","ProcessPrivileges\Privileges.cs","236,238","1","1","True","BaseCmdletWithPrivControl::Dispose > BaseCmdletWithPrivControl::DisableEnabledPrivileges > BaseCmdletWithPrivControl::DisablePrivilege > PrivilegeControl::DisablePrivilege > ProcessExtensions::GetPrivilegeState > ProcessExtensions::GetPrivilegeAttributes > Privileges::GetPrivilegeAttributes > Privileges::GetLuid","TOKEN-NATIVE-FAILURE","environment-specific","Explained"
"ProcessPrivileges","ProcessPrivileges.Privileges","GetPrivilegeName(ProcessPrivileges.Luid)","ProcessPrivileges\Privileges.cs","249,251,255,257,261,263","3","3","True","DisablePrivileges::ProcessRecord > PrivilegeControl::GetPrivileges > ProcessExtensions::GetPrivileges > Privileges::GetPrivileges > Privileges::GetPrivilegeName","TOKEN-NATIVE-FAILURE","environment-specific","Explained"
"ProcessPrivileges","ProcessPrivileges.Privileges","GetTokenPrivileges(ProcessPrivileges.AccessTokenHandle)","ProcessPrivileges\Privileges.cs","273,280,294,301","2","2","True","DisablePrivileges::ProcessRecord > PrivilegeControl::GetPrivileges > ProcessExtensions::GetPrivileges > Privileges::GetPrivileges > Privileges::GetTokenPrivileges","TOKEN-NATIVE-FAILURE","environment-specific","Explained"
"ProcessPrivileges","ProcessPrivileges.ProcessHandle","ReleaseHandle()","ProcessPrivileges\ProcessHandle.cs","27,29,32","3","2","False","","TOKEN-NATIVE-FAILURE","environment-specific","Explained"
"NTFSSecurity","NTFSSecurity.BaseCmdlet","GetFileSystemInfo(String)","NTFSSecurity\BaseCmdlets.cs","166,168,170,172,174,178","6","4","False","","UNUSED-CMDLET-HELPER","unused by cmdlets","Explained"
"NTFSSecurity","NTFSSecurity.BaseCmdlet","ProcessRecord()","NTFSSecurity\BaseCmdlets.cs","145,146","2","0","True","BaseCmdlet::ProcessRecord","UNUSED-CMDLET-HELPER","unused by cmdlets","Explained"
"Security2","Security2.Win32","GetEffectiveAccess(ObjectSecurity,IdentityReference2,String,Boolean&,Exception&)","Security2\Win32\Lib.cs","135,136","2","0","True","GetEffectiveAccess::ProcessRecord > EffectiveAccess::GetEffectiveAccess > Win32::GetEffectiveAccess","WIN32-AUTHZ-FAILURE","environment-specific","Explained"
"Security2","Security2.Win32","GetEffectivePermissions_AuthzAccessCheck(ObjectSecurity)","Security2\Win32\Lib.cs","281","0","1","True","GetEffectiveAccess::ProcessRecord > EffectiveAccess::GetEffectiveAccess > Win32::GetEffectiveAccess > Win32::GetEffectivePermissions_AuthzAccessCheck","WIN32-AUTHZ-FAILURE","environment-specific","Explained"
"Security2","Security2.Win32","GetEffectivePermissions_AuthzInitializeContextFromSid(IdentityReference2)","Security2\Win32\Lib.cs","245","0","1","True","GetEffectiveAccess::ProcessRecord > EffectiveAccess::GetEffectiveAccess > Win32::GetEffectiveAccess > Win32::GetEffectivePermissions_AuthzInitializeContextFromSid","WIN32-AUTHZ-FAILURE","environment-specific","Explained"
"Security2","Security2.Win32","GetEffectivePermissions_AuthzInitializeResourceManager(String,Boolean&)","Security2\Win32\Lib.cs","188,194,196,210,218,223","3","3","True","GetEffectiveAccess::ProcessRecord > EffectiveAccess::GetEffectiveAccess > Win32::GetEffectiveAccess > Win32::GetEffectivePermissions_AuthzInitializeResourceManager","WIN32-AUTHZ-FAILURE","environment-specific","Explained"
"Security2","Security2.Win32","IsLocalComputer(String)","Security2\Win32\Lib.cs","170,172","2","0","True","GetEffectiveAccess::ProcessRecord > EffectiveAccess::GetEffectiveAccess > Win32::GetEffectiveAccess > Win32::GetEffectivePermissions_AuthzInitializeResourceManager > Win32::IsLocalComputer","WIN32-LOCAL-NAME-LOOKUP","defensive","Explained"
"Security2","Security2.Win32","GetByteSecurityDescriptor(Microsoft.Win32.SafeHandles.SafeFileHandle,SecurityInformationClass)","Security2\Win32\Lib.cs","311,312,315,323,325,328,330,331,332,335,336,337,339","13","2","False","","WIN32-RAW-DESCRIPTOR","unused by cmdlets","Explained"
"Security2","Security2.Win32","GetRawSecurityDescriptor(Microsoft.Win32.SafeHandles.SafeFileHandle,SecurityInformationClass)","Security2\Win32\Lib.cs","306","1","0","False","","WIN32-RAW-DESCRIPTOR","unused by cmdlets","Explained"
1 Assembly Class Method Source UnvisitedLines UnvisitedSequence UnvisitedBranches ReachableFromCmdletInIL CmdletCallPath RuleId Category Disposition
2 NTFSSecurity NTFSSecurity.ClearAccess ProcessRecord() NTFSSecurity\AccessCmdlets\ClearAccess.cs 92,94,95 3 0 True ClearAccess::ProcessRecord ACCESS-GENERIC-CATCH defensive Explained
3 NTFSSecurity NTFSSecurity.DisableAccessInheritance ProcessRecord() NTFSSecurity\InheritanceCmdlets\DisableAccessInheritance.cs 97,99,100 3 0 True DisableAccessInheritance::ProcessRecord ACCESS-GENERIC-CATCH defensive Explained
4 NTFSSecurity NTFSSecurity.EnableAccessInheritance ProcessRecord() NTFSSecurity\InheritanceCmdlets\EnableAccessInheritance.cs 97,99,100 3 0 True EnableAccessInheritance::ProcessRecord ACCESS-GENERIC-CATCH defensive Explained
5 NTFSSecurity NTFSSecurity.GetAccess ProcessRecord() NTFSSecurity\AccessCmdlets\GetAccess.cs 112,118,119,121,122 5 0 True GetAccess::ProcessRecord ACCESS-GENERIC-CATCH defensive Explained
6 NTFSSecurity NTFSSecurity.GetInheritance ProcessRecord() NTFSSecurity\InheritanceCmdlets\GetInheritance.cs 80,86,87,89,90,107 5 1 True GetInheritance::ProcessRecord ACCESS-GENERIC-CATCH defensive Explained
7 NTFSSecurity NTFSSecurity.GetOrphanedAccess ProcessRecord() NTFSSecurity\AccessCmdlets\GetOrphanedAccess.cs 55,62,63,65,66 5 0 True GetOrphanedAccess::ProcessRecord ACCESS-GENERIC-CATCH defensive Explained
8 NTFSSecurity NTFSSecurity.GetSecurityDescriptor ProcessRecord() NTFSSecurity\SecurityDescriptorCmdlets\GetSecurityDescriptor.cs 64,67,69,75,78,83,84 5 2 True GetSecurityDescriptor::ProcessRecord ACCESS-GENERIC-CATCH defensive Explained
9 NTFSSecurity NTFSSecurity.OwnerCmdlets.GetOwner ProcessRecord() NTFSSecurity\OwnerCmdlets\GetOwner.cs 72,74,75 3 0 True GetOwner::ProcessRecord ACCESS-GENERIC-CATCH defensive Explained
10 ProcessPrivileges ProcessPrivileges.AllocatedMemory Finalize() ProcessPrivileges\AllocatedMemory.cs 28,29 3 0 False ALLOCATED-MEMORY-FINALIZER defensive Explained
11 ProcessPrivileges ProcessPrivileges.AllocatedMemory InternalDispose() ProcessPrivileges\AllocatedMemory.cs 47 0 1 False ALLOCATED-MEMORY-FINALIZER defensive Explained
12 NTFSSecurity NTFSSecurity.AddAudit ProcessRecord() NTFSSecurity\AuditCmdlets\AddAudit.cs 137,141,145,146,148,149,151 7 0 True AddAudit::ProcessRecord AUDIT-OWNER-RETRY environment-specific Explained
13 NTFSSecurity NTFSSecurity.AddAudit/<>c__DisplayClass35_0 <ProcessRecord>b__0() NTFSSecurity\AuditCmdlets\AddAudit.cs 143,144 2 0 True AddAudit::ProcessRecord > AddAudit/<>c__DisplayClass35_0::<ProcessRecord>b__0 AUDIT-OWNER-RETRY environment-specific Explained
14 NTFSSecurity NTFSSecurity.ClearAudit ProcessRecord() NTFSSecurity\AuditCmdlets\ClearAudit.cs 76,80,86,87,89,90,91 7 0 True ClearAudit::ProcessRecord AUDIT-OWNER-RETRY environment-specific Explained
15 NTFSSecurity NTFSSecurity.ClearAudit/<>c__DisplayClass11_0 <ProcessRecord>b__0() NTFSSecurity\AuditCmdlets\ClearAudit.cs 82,83,84,85 4 2 True ClearAudit::ProcessRecord > ClearAudit/<>c__DisplayClass11_0::<ProcessRecord>b__0 AUDIT-OWNER-RETRY environment-specific Explained
16 NTFSSecurity NTFSSecurity.DisableAuditInheritance ProcessRecord() NTFSSecurity\InheritanceCmdlets\DisableAuditInheritance.cs 88,92,96,97,99,100,102 7 0 True DisableAuditInheritance::ProcessRecord AUDIT-OWNER-RETRY environment-specific Explained
17 NTFSSecurity NTFSSecurity.DisableAuditInheritance/<>c__DisplayClass15_0 <ProcessRecord>b__0() NTFSSecurity\InheritanceCmdlets\DisableAuditInheritance.cs 94,95 2 0 True DisableAuditInheritance::ProcessRecord > DisableAuditInheritance/<>c__DisplayClass15_0::<ProcessRecord>b__0 AUDIT-OWNER-RETRY environment-specific Explained
18 NTFSSecurity NTFSSecurity.EnableAuditInheritance ProcessRecord() NTFSSecurity\InheritanceCmdlets\EnableAuditInheritance.cs 87,91,95,96,98,99,101 7 0 True EnableAuditInheritance::ProcessRecord AUDIT-OWNER-RETRY environment-specific Explained
19 NTFSSecurity NTFSSecurity.EnableAuditInheritance/<>c__DisplayClass15_0 <ProcessRecord>b__0() NTFSSecurity\InheritanceCmdlets\EnableAuditInheritance.cs 93,94 2 0 True EnableAuditInheritance::ProcessRecord > EnableAuditInheritance/<>c__DisplayClass15_0::<ProcessRecord>b__0 AUDIT-OWNER-RETRY environment-specific Explained
20 NTFSSecurity NTFSSecurity.RemoveAudit ProcessRecord() NTFSSecurity\AuditCmdlets\RemoveAudit.cs 153,157,161,162,164,165,167 7 0 True RemoveAudit::ProcessRecord AUDIT-OWNER-RETRY environment-specific Explained
21 NTFSSecurity NTFSSecurity.RemoveAudit/<>c__DisplayClass39_0 <ProcessRecord>b__0() NTFSSecurity\AuditCmdlets\RemoveAudit.cs 159,160 2 0 True RemoveAudit::ProcessRecord > RemoveAudit/<>c__DisplayClass39_0::<ProcessRecord>b__0 AUDIT-OWNER-RETRY environment-specific Explained
22 NTFSSecurity NTFSSecurity.AuditCmdlets.GetOrphanedAudit ProcessRecord() NTFSSecurity\AuditCmdlets\Get-OrphanedAudit.cs 56,58,59 3 0 True GetOrphanedAudit::ProcessRecord AUDIT-READ-DENIED environment-specific Explained
23 NTFSSecurity NTFSSecurity.GetAudit ProcessRecord() NTFSSecurity\AuditCmdlets\GetAudit.cs 104,107,108 3 0 True GetAudit::ProcessRecord AUDIT-READ-DENIED environment-specific Explained
24 NTFSSecurity NTFSSecurity.GetChildItem2 WriteFileSystem(FileSystemInfo,Int32) NTFSSecurity\ItemCmdlets\GetChildItem2.cs 205 0 1 True GetChildItem2::ProcessRecord > GetChildItem2::WriteFileSystem CHILDITEM2-NON-FOLDER defensive Explained
25 NTFSSecurity NTFSSecurity.AddAccess get_AccessRights() NTFSSecurity\AccessCmdlets\AddAccess.cs 62 1 0 True AddAccess::get_AccessRights CMDLET-GETTER parameter/API surface Explained
26 NTFSSecurity NTFSSecurity.AddAccess get_AccessType() NTFSSecurity\AccessCmdlets\AddAccess.cs 70 1 0 True AddAccess::get_AccessType CMDLET-GETTER parameter/API surface Explained
27 NTFSSecurity NTFSSecurity.AddAccess get_Account() NTFSSecurity\AccessCmdlets\AddAccess.cs 54 1 0 True AddAccess::get_Account CMDLET-GETTER parameter/API surface Explained
28 NTFSSecurity NTFSSecurity.AddAccess get_AppliesTo() NTFSSecurity\AccessCmdlets\AddAccess.cs 95 1 0 True AddAccess::get_AppliesTo CMDLET-GETTER parameter/API surface Explained
29 NTFSSecurity NTFSSecurity.AddAccess get_InheritanceFlags() NTFSSecurity\AccessCmdlets\AddAccess.cs 78 1 0 True AddAccess::get_InheritanceFlags CMDLET-GETTER parameter/API surface Explained
30 NTFSSecurity NTFSSecurity.AddAccess get_PassThru() NTFSSecurity\AccessCmdlets\AddAccess.cs 102 1 0 True AddAccess::get_PassThru CMDLET-GETTER parameter/API surface Explained
31 NTFSSecurity NTFSSecurity.AddAccess get_Path() NTFSSecurity\AccessCmdlets\AddAccess.cs 29 1 0 True AddAccess::get_Path CMDLET-GETTER parameter/API surface Explained
32 NTFSSecurity NTFSSecurity.AddAccess get_PropagationFlags() NTFSSecurity\AccessCmdlets\AddAccess.cs 86 1 0 True AddAccess::get_PropagationFlags CMDLET-GETTER parameter/API surface Explained
33 NTFSSecurity NTFSSecurity.AddAccess get_SecurityDescriptor() NTFSSecurity\AccessCmdlets\AddAccess.cs 42 1 0 True AddAccess::get_SecurityDescriptor CMDLET-GETTER parameter/API surface Explained
34 NTFSSecurity NTFSSecurity.AddAudit get_AccessRights() NTFSSecurity\AuditCmdlets\AddAudit.cs 62 1 0 True AddAudit::get_AccessRights CMDLET-GETTER parameter/API surface Explained
35 NTFSSecurity NTFSSecurity.AddAudit get_Account() NTFSSecurity\AuditCmdlets\AddAudit.cs 54 1 0 True AddAudit::get_Account CMDLET-GETTER parameter/API surface Explained
36 NTFSSecurity NTFSSecurity.AddAudit get_AppliesTo() NTFSSecurity\AuditCmdlets\AddAudit.cs 94 1 0 True AddAudit::get_AppliesTo CMDLET-GETTER parameter/API surface Explained
37 NTFSSecurity NTFSSecurity.AddAudit get_AuditFlags() NTFSSecurity\AuditCmdlets\AddAudit.cs 69 1 0 True AddAudit::get_AuditFlags CMDLET-GETTER parameter/API surface Explained
38 NTFSSecurity NTFSSecurity.AddAudit get_InheritanceFlags() NTFSSecurity\AuditCmdlets\AddAudit.cs 77 1 0 True AddAudit::get_InheritanceFlags CMDLET-GETTER parameter/API surface Explained
39 NTFSSecurity NTFSSecurity.AddAudit get_PassThru() NTFSSecurity\AuditCmdlets\AddAudit.cs 101 1 0 True AddAudit::get_PassThru CMDLET-GETTER parameter/API surface Explained
40 NTFSSecurity NTFSSecurity.AddAudit get_Path() NTFSSecurity\AuditCmdlets\AddAudit.cs 29 1 0 True AddAudit::get_Path CMDLET-GETTER parameter/API surface Explained
41 NTFSSecurity NTFSSecurity.AddAudit get_PropagationFlags() NTFSSecurity\AuditCmdlets\AddAudit.cs 85 1 0 True AddAudit::get_PropagationFlags CMDLET-GETTER parameter/API surface Explained
42 NTFSSecurity NTFSSecurity.AddAudit get_SecurityDescriptor() NTFSSecurity\AuditCmdlets\AddAudit.cs 42 1 0 True AddAudit::get_SecurityDescriptor CMDLET-GETTER parameter/API surface Explained
43 NTFSSecurity NTFSSecurity.ClearAccess get_DisableInheritance() NTFSSecurity\AccessCmdlets\ClearAccess.cs 42 1 0 True ClearAccess::get_DisableInheritance CMDLET-GETTER parameter/API surface Explained
44 NTFSSecurity NTFSSecurity.ClearAccess get_Path() NTFSSecurity\AccessCmdlets\ClearAccess.cs 19 1 0 True ClearAccess::get_Path CMDLET-GETTER parameter/API surface Explained
45 NTFSSecurity NTFSSecurity.ClearAccess get_SecurityDescriptor() NTFSSecurity\AccessCmdlets\ClearAccess.cs 31 1 0 True ClearAccess::get_SecurityDescriptor CMDLET-GETTER parameter/API surface Explained
46 NTFSSecurity NTFSSecurity.ClearAudit get_DisableInheritance() NTFSSecurity\AuditCmdlets\ClearAudit.cs 42 1 0 True ClearAudit::get_DisableInheritance CMDLET-GETTER parameter/API surface Explained
47 NTFSSecurity NTFSSecurity.ClearAudit get_Path() NTFSSecurity\AuditCmdlets\ClearAudit.cs 19 1 0 True ClearAudit::get_Path CMDLET-GETTER parameter/API surface Explained
48 NTFSSecurity NTFSSecurity.ClearAudit get_SecurityDescriptor() NTFSSecurity\AuditCmdlets\ClearAudit.cs 31 1 0 True ClearAudit::get_SecurityDescriptor CMDLET-GETTER parameter/API surface Explained
49 NTFSSecurity NTFSSecurity.CopyItem2 get_Destination() NTFSSecurity\ItemCmdlets\CopyItem2.cs 33 1 0 True CopyItem2::get_Destination CMDLET-GETTER parameter/API surface Explained
50 NTFSSecurity NTFSSecurity.CopyItem2 get_Force() NTFSSecurity\ItemCmdlets\CopyItem2.cs 40 1 0 True CopyItem2::get_Force CMDLET-GETTER parameter/API surface Explained
51 NTFSSecurity NTFSSecurity.CopyItem2 get_PassThru() NTFSSecurity\ItemCmdlets\CopyItem2.cs 47 1 0 True CopyItem2::get_PassThru CMDLET-GETTER parameter/API surface Explained
52 NTFSSecurity NTFSSecurity.CopyItem2 get_Path() NTFSSecurity\ItemCmdlets\CopyItem2.cs 21 1 0 True CopyItem2::get_Path CMDLET-GETTER parameter/API surface Explained
53 NTFSSecurity NTFSSecurity.DisableAccessInheritance get_PassThru() NTFSSecurity\InheritanceCmdlets\DisableAccessInheritance.cs 51 1 0 True DisableAccessInheritance::get_PassThru CMDLET-GETTER parameter/API surface Explained
54 NTFSSecurity NTFSSecurity.DisableAccessInheritance get_Path() NTFSSecurity\InheritanceCmdlets\DisableAccessInheritance.cs 21 1 0 True DisableAccessInheritance::get_Path CMDLET-GETTER parameter/API surface Explained
55 NTFSSecurity NTFSSecurity.DisableAccessInheritance get_RemoveInheritedAccessRules() NTFSSecurity\InheritanceCmdlets\DisableAccessInheritance.cs 44 1 0 True DisableAccessInheritance::get_RemoveInheritedAccessRules CMDLET-GETTER parameter/API surface Explained
56 NTFSSecurity NTFSSecurity.DisableAccessInheritance get_SecurityDescriptor() NTFSSecurity\InheritanceCmdlets\DisableAccessInheritance.cs 33 1 0 True DisableAccessInheritance::get_SecurityDescriptor CMDLET-GETTER parameter/API surface Explained
57 NTFSSecurity NTFSSecurity.DisableAuditInheritance get_PassThru() NTFSSecurity\InheritanceCmdlets\DisableAuditInheritance.cs 57 1 0 True DisableAuditInheritance::get_PassThru CMDLET-GETTER parameter/API surface Explained
58 NTFSSecurity NTFSSecurity.DisableAuditInheritance get_Path() NTFSSecurity\InheritanceCmdlets\DisableAuditInheritance.cs 22 1 0 True DisableAuditInheritance::get_Path CMDLET-GETTER parameter/API surface Explained
59 NTFSSecurity NTFSSecurity.DisableAuditInheritance get_RemoveInheritedAuditRules() NTFSSecurity\InheritanceCmdlets\DisableAuditInheritance.cs 50 1 0 True DisableAuditInheritance::get_RemoveInheritedAuditRules CMDLET-GETTER parameter/API surface Explained
60 NTFSSecurity NTFSSecurity.DisableAuditInheritance get_SecurityDescriptor() NTFSSecurity\InheritanceCmdlets\DisableAuditInheritance.cs 34 1 0 True DisableAuditInheritance::get_SecurityDescriptor CMDLET-GETTER parameter/API surface Explained
61 NTFSSecurity NTFSSecurity.DisablePrivileges get_PassThru() NTFSSecurity\OtherCmdlets.cs 83 1 0 True DisablePrivileges::get_PassThru CMDLET-GETTER parameter/API surface Explained
62 NTFSSecurity NTFSSecurity.EnableAccessInheritance get_PassThru() NTFSSecurity\InheritanceCmdlets\EnableAccessInheritance.cs 44 1 0 True EnableAccessInheritance::get_PassThru CMDLET-GETTER parameter/API surface Explained
63 NTFSSecurity NTFSSecurity.EnableAccessInheritance get_Path() NTFSSecurity\InheritanceCmdlets\EnableAccessInheritance.cs 21 1 0 True EnableAccessInheritance::get_Path CMDLET-GETTER parameter/API surface Explained
64 NTFSSecurity NTFSSecurity.EnableAccessInheritance get_RemoveExplicitAccessRules() NTFSSecurity\InheritanceCmdlets\EnableAccessInheritance.cs 51 1 0 True EnableAccessInheritance::get_RemoveExplicitAccessRules CMDLET-GETTER parameter/API surface Explained
65 NTFSSecurity NTFSSecurity.EnableAccessInheritance get_SecurityDescriptor() NTFSSecurity\InheritanceCmdlets\EnableAccessInheritance.cs 33 1 0 True EnableAccessInheritance::get_SecurityDescriptor CMDLET-GETTER parameter/API surface Explained
66 NTFSSecurity NTFSSecurity.EnableAuditInheritance get_PassThru() NTFSSecurity\InheritanceCmdlets\EnableAuditInheritance.cs 44 1 0 True EnableAuditInheritance::get_PassThru CMDLET-GETTER parameter/API surface Explained
67 NTFSSecurity NTFSSecurity.EnableAuditInheritance get_Path() NTFSSecurity\InheritanceCmdlets\EnableAuditInheritance.cs 21 1 0 True EnableAuditInheritance::get_Path CMDLET-GETTER parameter/API surface Explained
68 NTFSSecurity NTFSSecurity.EnableAuditInheritance get_RemoveExplicitAuditRules() NTFSSecurity\InheritanceCmdlets\EnableAuditInheritance.cs 56 1 0 True EnableAuditInheritance::get_RemoveExplicitAuditRules CMDLET-GETTER parameter/API surface Explained
69 NTFSSecurity NTFSSecurity.EnableAuditInheritance get_SecurityDescriptor() NTFSSecurity\InheritanceCmdlets\EnableAuditInheritance.cs 33 1 0 True EnableAuditInheritance::get_SecurityDescriptor CMDLET-GETTER parameter/API surface Explained
70 NTFSSecurity NTFSSecurity.EnablePrivileges get_PassThru() NTFSSecurity\OtherCmdlets.cs 21 1 0 True EnablePrivileges::get_PassThru CMDLET-GETTER parameter/API surface Explained
71 NTFSSecurity NTFSSecurity.GetChildItem2 get_Attributes() NTFSSecurity\ItemCmdlets\GetChildItem2.cs 79 1 0 True GetChildItem2::get_Attributes CMDLET-GETTER parameter/API surface Explained
72 NTFSSecurity NTFSSecurity.GetChildItem2 get_Depth() NTFSSecurity\ItemCmdlets\GetChildItem2.cs 128 1 0 True GetChildItem2::get_Depth CMDLET-GETTER parameter/API surface Explained
73 NTFSSecurity NTFSSecurity.GetChildItem2 get_Directory() NTFSSecurity\ItemCmdlets\GetChildItem2.cs 65 1 0 True GetChildItem2::get_Directory CMDLET-GETTER parameter/API surface Explained
74 NTFSSecurity NTFSSecurity.GetChildItem2 get_File() NTFSSecurity\ItemCmdlets\GetChildItem2.cs 72 1 0 True GetChildItem2::get_File CMDLET-GETTER parameter/API surface Explained
75 NTFSSecurity NTFSSecurity.GetChildItem2 get_Filter() NTFSSecurity\ItemCmdlets\GetChildItem2.cs 51 1 0 True GetChildItem2::get_Filter CMDLET-GETTER parameter/API surface Explained
76 NTFSSecurity NTFSSecurity.GetChildItem2 get_Force() NTFSSecurity\ItemCmdlets\GetChildItem2.cs 107 1 0 True GetChildItem2::get_Force CMDLET-GETTER parameter/API surface Explained
77 NTFSSecurity NTFSSecurity.GetChildItem2 get_Hidden() NTFSSecurity\ItemCmdlets\GetChildItem2.cs 86 1 0 True GetChildItem2::get_Hidden CMDLET-GETTER parameter/API surface Explained
78 NTFSSecurity NTFSSecurity.GetChildItem2 get_Path() NTFSSecurity\ItemCmdlets\GetChildItem2.cs 39 1 0 True GetChildItem2::get_Path CMDLET-GETTER parameter/API surface Explained
79 NTFSSecurity NTFSSecurity.GetChildItem2 get_ReadOnly() NTFSSecurity\ItemCmdlets\GetChildItem2.cs 100 1 0 True GetChildItem2::get_ReadOnly CMDLET-GETTER parameter/API surface Explained
80 NTFSSecurity NTFSSecurity.GetChildItem2 get_Recurse() NTFSSecurity\ItemCmdlets\GetChildItem2.cs 58 1 0 True GetChildItem2::get_Recurse CMDLET-GETTER parameter/API surface Explained
81 NTFSSecurity NTFSSecurity.GetChildItem2 get_SkipMountPoints() NTFSSecurity\ItemCmdlets\GetChildItem2.cs 114 1 0 True GetChildItem2::get_SkipMountPoints CMDLET-GETTER parameter/API surface Explained
82 NTFSSecurity NTFSSecurity.GetChildItem2 get_SkipSymbolicLinks() NTFSSecurity\ItemCmdlets\GetChildItem2.cs 121 1 0 True GetChildItem2::get_SkipSymbolicLinks CMDLET-GETTER parameter/API surface Explained
83 NTFSSecurity NTFSSecurity.GetChildItem2 get_System() NTFSSecurity\ItemCmdlets\GetChildItem2.cs 93 1 0 True GetChildItem2::get_System CMDLET-GETTER parameter/API surface Explained
84 NTFSSecurity NTFSSecurity.GetDiskSpace get_DriveLetter() NTFSSecurity\ItemCmdlets\GetDiskSpace.cs 18 1 0 True GetDiskSpace::get_DriveLetter CMDLET-GETTER parameter/API surface Explained
85 NTFSSecurity NTFSSecurity.GetEffectiveAccess get_Account() NTFSSecurity\AccessCmdlets\GetEffectiveAccess.cs 51 1 0 True GetEffectiveAccess::get_Account CMDLET-GETTER parameter/API surface Explained
86 NTFSSecurity NTFSSecurity.GetEffectiveAccess get_ExcludeNoneAccessEntries() NTFSSecurity\AccessCmdlets\GetEffectiveAccess.cs 65 1 0 True GetEffectiveAccess::get_ExcludeNoneAccessEntries CMDLET-GETTER parameter/API surface Explained
87 NTFSSecurity NTFSSecurity.GetEffectiveAccess get_Path() NTFSSecurity\AccessCmdlets\GetEffectiveAccess.cs 26 1 0 True GetEffectiveAccess::get_Path CMDLET-GETTER parameter/API surface Explained
88 NTFSSecurity NTFSSecurity.GetEffectiveAccess get_SecurityDescriptor() NTFSSecurity\AccessCmdlets\GetEffectiveAccess.cs 38 1 0 True GetEffectiveAccess::get_SecurityDescriptor CMDLET-GETTER parameter/API surface Explained
89 NTFSSecurity NTFSSecurity.GetEffectiveAccess get_ServerName() NTFSSecurity\AccessCmdlets\GetEffectiveAccess.cs 58 1 0 True GetEffectiveAccess::get_ServerName CMDLET-GETTER parameter/API surface Explained
90 NTFSSecurity NTFSSecurity.GetFileHash2 get_Algorithm() NTFSSecurity\MiscCmdlets\GetFileHash2.cs 33 1 0 True GetFileHash2::get_Algorithm CMDLET-GETTER parameter/API surface Explained
91 NTFSSecurity NTFSSecurity.GetInheritance get_Path() NTFSSecurity\InheritanceCmdlets\GetInheritance.cs 19 1 0 True GetInheritance::get_Path CMDLET-GETTER parameter/API surface Explained
92 NTFSSecurity NTFSSecurity.GetInheritance get_SecurityDescriptor() NTFSSecurity\InheritanceCmdlets\GetInheritance.cs 31 1 0 True GetInheritance::get_SecurityDescriptor CMDLET-GETTER parameter/API surface Explained
93 NTFSSecurity NTFSSecurity.GetItem2 get_Path() NTFSSecurity\ItemCmdlets\GetItem2.cs 19 1 0 True GetItem2::get_Path CMDLET-GETTER parameter/API surface Explained
94 NTFSSecurity NTFSSecurity.GetSecurityDescriptor get_Path() NTFSSecurity\SecurityDescriptorCmdlets\GetSecurityDescriptor.cs 18 1 0 True GetSecurityDescriptor::get_Path CMDLET-GETTER parameter/API surface Explained
95 NTFSSecurity NTFSSecurity.GetSimpleAccess get_IncludeRootFolder() NTFSSecurity\SimpleAccessCmdlets\SimpleAccessCmdlets.cs 24 1 0 True GetSimpleAccess::get_IncludeRootFolder CMDLET-GETTER parameter/API surface Explained
96 NTFSSecurity NTFSSecurity.MoveItem2 get_Destination() NTFSSecurity\ItemCmdlets\MoveItem2.cs 33 1 0 True MoveItem2::get_Destination CMDLET-GETTER parameter/API surface Explained
97 NTFSSecurity NTFSSecurity.MoveItem2 get_Force() NTFSSecurity\ItemCmdlets\MoveItem2.cs 40 1 0 True MoveItem2::get_Force CMDLET-GETTER parameter/API surface Explained
98 NTFSSecurity NTFSSecurity.MoveItem2 get_PassThru() NTFSSecurity\ItemCmdlets\MoveItem2.cs 47 1 0 True MoveItem2::get_PassThru CMDLET-GETTER parameter/API surface Explained
99 NTFSSecurity NTFSSecurity.MoveItem2 get_Path() NTFSSecurity\ItemCmdlets\MoveItem2.cs 21 1 0 True MoveItem2::get_Path CMDLET-GETTER parameter/API surface Explained
100 NTFSSecurity NTFSSecurity.NewHardLink get_PassThru() NTFSSecurity\LinkCmdlets\NewHardLink.cs 47 1 0 True NewHardLink::get_PassThru CMDLET-GETTER parameter/API surface Explained
101 NTFSSecurity NTFSSecurity.NewSymbolicLink get_PassThru() NTFSSecurity\LinkCmdlets\NewSymbolicLink.cs 45 1 0 True NewSymbolicLink::get_PassThru CMDLET-GETTER parameter/API surface Explained
102 NTFSSecurity NTFSSecurity.RemoveAccess get_AppliesTo() NTFSSecurity\AccessCmdlets\RemoveAccess.cs 96 1 0 True RemoveAccess::get_AppliesTo CMDLET-GETTER parameter/API surface Explained
103 NTFSSecurity NTFSSecurity.RemoveAccess get_PassThru() NTFSSecurity\AccessCmdlets\RemoveAccess.cs 113 1 0 True RemoveAccess::get_PassThru CMDLET-GETTER parameter/API surface Explained
104 NTFSSecurity NTFSSecurity.RemoveAccess get_RemoveSpecific() NTFSSecurity\AccessCmdlets\RemoveAccess.cs 106 1 0 True RemoveAccess::get_RemoveSpecific CMDLET-GETTER parameter/API surface Explained
105 NTFSSecurity NTFSSecurity.RemoveAudit get_AccessRights() NTFSSecurity\AuditCmdlets\RemoveAudit.cs 63 1 0 True RemoveAudit::get_AccessRights CMDLET-GETTER parameter/API surface Explained
106 NTFSSecurity NTFSSecurity.RemoveAudit get_Account() NTFSSecurity\AuditCmdlets\RemoveAudit.cs 55 1 0 True RemoveAudit::get_Account CMDLET-GETTER parameter/API surface Explained
107 NTFSSecurity NTFSSecurity.RemoveAudit get_AppliesTo() NTFSSecurity\AuditCmdlets\RemoveAudit.cs 95 1 0 True RemoveAudit::get_AppliesTo CMDLET-GETTER parameter/API surface Explained
108 NTFSSecurity NTFSSecurity.RemoveAudit get_AuditFlags() NTFSSecurity\AuditCmdlets\RemoveAudit.cs 70 1 0 True RemoveAudit::get_AuditFlags CMDLET-GETTER parameter/API surface Explained
109 NTFSSecurity NTFSSecurity.RemoveAudit get_InheritanceFlags() NTFSSecurity\AuditCmdlets\RemoveAudit.cs 78 1 0 True RemoveAudit::get_InheritanceFlags CMDLET-GETTER parameter/API surface Explained
110 NTFSSecurity NTFSSecurity.RemoveAudit get_PassThru() NTFSSecurity\AuditCmdlets\RemoveAudit.cs 112 1 0 True RemoveAudit::get_PassThru CMDLET-GETTER parameter/API surface Explained
111 NTFSSecurity NTFSSecurity.RemoveAudit get_Path() NTFSSecurity\AuditCmdlets\RemoveAudit.cs 30 1 0 True RemoveAudit::get_Path CMDLET-GETTER parameter/API surface Explained
112 NTFSSecurity NTFSSecurity.RemoveAudit get_PropagationFlags() NTFSSecurity\AuditCmdlets\RemoveAudit.cs 86 1 0 True RemoveAudit::get_PropagationFlags CMDLET-GETTER parameter/API surface Explained
113 NTFSSecurity NTFSSecurity.RemoveAudit get_RemoveSpecific() NTFSSecurity\AuditCmdlets\RemoveAudit.cs 105 1 0 True RemoveAudit::get_RemoveSpecific CMDLET-GETTER parameter/API surface Explained
114 NTFSSecurity NTFSSecurity.RemoveAudit get_SecurityDescriptor() NTFSSecurity\AuditCmdlets\RemoveAudit.cs 43 1 0 True RemoveAudit::get_SecurityDescriptor CMDLET-GETTER parameter/API surface Explained
115 NTFSSecurity NTFSSecurity.RemoveItem2 get_Force() NTFSSecurity\ItemCmdlets\RemoveItem2.cs 32 1 0 True RemoveItem2::get_Force CMDLET-GETTER parameter/API surface Explained
116 NTFSSecurity NTFSSecurity.RemoveItem2 get_PassThru() NTFSSecurity\ItemCmdlets\RemoveItem2.cs 48 1 0 True RemoveItem2::get_PassThru CMDLET-GETTER parameter/API surface Explained
117 NTFSSecurity NTFSSecurity.RemoveItem2 get_Path() NTFSSecurity\ItemCmdlets\RemoveItem2.cs 21 1 0 True RemoveItem2::get_Path CMDLET-GETTER parameter/API surface Explained
118 NTFSSecurity NTFSSecurity.RemoveItem2 get_Recurse() NTFSSecurity\ItemCmdlets\RemoveItem2.cs 39 1 0 True RemoveItem2::get_Recurse CMDLET-GETTER parameter/API surface Explained
119 NTFSSecurity NTFSSecurity.SetInheritance get_AccessInheritanceEnabled() NTFSSecurity\InheritanceCmdlets\SetInheritance.cs 45 1 0 True SetInheritance::get_AccessInheritanceEnabled CMDLET-GETTER parameter/API surface Explained
120 NTFSSecurity NTFSSecurity.SetInheritance get_AuditInheritanceEnabled() NTFSSecurity\InheritanceCmdlets\SetInheritance.cs 52 1 0 True SetInheritance::get_AuditInheritanceEnabled CMDLET-GETTER parameter/API surface Explained
121 NTFSSecurity NTFSSecurity.SetInheritance get_PassThru() NTFSSecurity\InheritanceCmdlets\SetInheritance.cs 59 1 0 True SetInheritance::get_PassThru CMDLET-GETTER parameter/API surface Explained
122 NTFSSecurity NTFSSecurity.SetInheritance get_Path() NTFSSecurity\InheritanceCmdlets\SetInheritance.cs 22 1 0 True SetInheritance::get_Path CMDLET-GETTER parameter/API surface Explained
123 NTFSSecurity NTFSSecurity.SetInheritance get_SecurityDescriptor() NTFSSecurity\InheritanceCmdlets\SetInheritance.cs 34 1 0 True SetInheritance::get_SecurityDescriptor CMDLET-GETTER parameter/API surface Explained
124 NTFSSecurity NTFSSecurity.SetOwner get_Account() NTFSSecurity\OwnerCmdlets\SetOwner.cs 44 1 0 True SetOwner::get_Account CMDLET-GETTER parameter/API surface Explained
125 NTFSSecurity NTFSSecurity.SetOwner get_PassThru() NTFSSecurity\OwnerCmdlets\SetOwner.cs 51 1 0 True SetOwner::get_PassThru CMDLET-GETTER parameter/API surface Explained
126 NTFSSecurity NTFSSecurity.SetOwner get_SecurityDescriptor() NTFSSecurity\OwnerCmdlets\SetOwner.cs 33 1 0 True SetOwner::get_SecurityDescriptor CMDLET-GETTER parameter/API surface Explained
127 NTFSSecurity NTFSSecurity.SetSecurityDescriptor get_PassThru() NTFSSecurity\SecurityDescriptorCmdlets\SetSecurityDescriptor.cs 28 1 0 True SetSecurityDescriptor::get_PassThru CMDLET-GETTER parameter/API surface Explained
128 NTFSSecurity NTFSSecurity.FileSystemCodeMembers Mode(Management.Automation.PSObject) NTFSSecurity\CodeMembers.cs 15,17 1 1 False CODEMEMBERS-NULL-BASE defensive Explained
129 NTFSSecurity NTFSSecurity.GetDiskSpace ProcessRecord() NTFSSecurity\ItemCmdlets\GetDiskSpace.cs 52 0 1 True GetDiskSpace::ProcessRecord DISKSPACE-EMPTY-VOLUME environment-specific Explained
130 NTFSSecurity NTFSSecurity.GetEffectiveAccess ProcessRecord() NTFSSecurity\AccessCmdlets\GetEffectiveAccess.cs 99,101,102,141,147,148,150,151 8 0 True GetEffectiveAccess::ProcessRecord EFFECTIVE-ACCESS-CATCH defensive Explained
131 NTFSSecurity NTFSSecurity.GetEffectiveAccess/<>c__DisplayClass20_0 <ProcessRecord>b__0() NTFSSecurity\AccessCmdlets\GetEffectiveAccess.cs 139,140 2 0 True GetEffectiveAccess::ProcessRecord > GetEffectiveAccess/<>c__DisplayClass20_0::<ProcessRecord>b__0 EFFECTIVE-ACCESS-CATCH defensive Explained
132 NTFSSecurity NTFSSecurity.EnablePrivileges ProcessRecord() NTFSSecurity\OtherCmdlets.cs 39,41 2 0 True EnablePrivileges::ProcessRecord ENABLEPRIVILEGES-INIT defensive Explained
133 Security2 Security2.FileSystemSecurity2 op_Implicit(DirectorySecurity) Security2\FileSystem\FileSystemSecurity2.cs 254 1 0 False FILESECURITY-CONVERSION unused by cmdlets Open
134 Security2 Security2.FileSystemSecurity2 op_Implicit(FileSecurity) Security2\FileSystem\FileSystemSecurity2.cs 245 1 0 False FILESECURITY-CONVERSION unused by cmdlets Open
135 Security2 Security2.FileSystemSecurity2 .ctor(FileSystemInfo) Security2\FileSystem\FileSystemSecurity2.cs 62,63 2 0 True GetSecurityDescriptor::ProcessRecord > FileSystemSecurity2::.ctor FSSEC2-CONSTRUCTOR-FALLBACK environment-specific Explained
136 Security2 Security2.FileSystemSecurity2 TryGetDriveRoot(FileSystemInfo,String&) Security2\FileSystem\FileSystemSecurity2.cs 122 0 1 True DisableAccessInheritance::ProcessRecord > FileSystemInheritanceInfo::GetFileSystemInheritanceInfo > FileSystemSecurity2::GetSecurity > FileSystemSecurity2::TryGetDriveRoot FSSEC2-DRIVE-LETTER-CHARS defensive Explained
137 Security2 Security2.FileSystemSecurity2 GetHashCode() Security2\FileSystem\FileSystemSecurity2.cs 267 0 1 False FSSEC2-HASHCODE-NULL defensive Explained
138 NTFSSecurity NTFSSecurity.Properties.Resources .ctor() NTFSSecurity\Properties\Resources.Designer.cs 32,33 2 0 False GENERATED-RESOURCES unused by cmdlets Explained
139 NTFSSecurity NTFSSecurity.Properties.Resources get_ContainerIcon() NTFSSecurity\Properties\Resources.Designer.cs 68 1 0 False GENERATED-RESOURCES unused by cmdlets Explained
140 NTFSSecurity NTFSSecurity.Properties.Resources get_Culture() NTFSSecurity\Properties\Resources.Designer.cs 56 1 0 False GENERATED-RESOURCES unused by cmdlets Explained
141 NTFSSecurity NTFSSecurity.Properties.Resources get_IconContainer() NTFSSecurity\Properties\Resources.Designer.cs 77,78 2 0 False GENERATED-RESOURCES unused by cmdlets Explained
142 NTFSSecurity NTFSSecurity.Properties.Resources get_ResourceManager() NTFSSecurity\Properties\Resources.Designer.cs 41,42,43,45 4 2 False GENERATED-RESOURCES unused by cmdlets Explained
143 NTFSSecurity NTFSSecurity.Properties.Resources set_Culture(Globalization.CultureInfo) NTFSSecurity\Properties\Resources.Designer.cs 59,60 2 0 False GENERATED-RESOURCES unused by cmdlets Explained
144 NTFSSecurity NTFSSecurity.GetHardLink ProcessRecord() NTFSSecurity\LinkCmdlets\GetHardLink.cs 77,79,80 3 0 True GetHardLink::ProcessRecord HARDLINK-DENIED environment-specific Explained
145 NTFSSecurity NTFSSecurity.GetFileHash2 ProcessRecord() NTFSSecurity\MiscCmdlets\GetFileHash2.cs 51,52,55,56,103,109 6 0 True GetFileHash2::ProcessRecord HASH-POLICY-AND-RETRY environment-specific Explained
146 NTFSSecurity NTFSSecurity.GetFileHash2/<>c__DisplayClass9_0 <ProcessRecord>b__0() NTFSSecurity\MiscCmdlets\GetFileHash2.cs 102 1 0 True GetFileHash2::ProcessRecord > GetFileHash2/<>c__DisplayClass9_0::<ProcessRecord>b__0 HASH-POLICY-AND-RETRY environment-specific Explained
147 Security2 Security2.IdentityReference2 .ctor(Security.Principal.IdentityReference) Security2\IdentityReference2.cs 49 0 1 True AddAccess::ProcessRecord > BaseCmdlet::InvokeAsOwner > IdentityReference2::op_Implicit > IdentityReference2::.ctor IDENTITY-NULL-REFERENCE defensive Explained
148 Security2 Security2.FileSystemAuditRule2 GetFileSystemAuditRules(FileSystemSecurity2,Boolean,Boolean,Boolean) Security2\FileSystem\FileSystemAuditRule2 Class\FileSystemAuditRule2.GetFileSystemAuditRules.cs 44 0 1 True AddAudit::ProcessRecord > FileSystemAuditRule2::GetFileSystemAuditRules INHERITED-FROM-EMPTY-LIST defensive Explained
149 Security2 Security2.IntPtrExtensions Increment(IntPtr) Security2\Extensions.cs 15 1 0 False NATIVE-HANDLES unused by cmdlets Explained
150 Security2 Security2.SafeAuthzRMHandle .ctor(IntPtr) Security2\Win32\SafeHandleEx.cs 299,301,302 3 0 False NATIVE-HANDLES unused by cmdlets Explained
151 Security2 Security2.SafeAuthzRMHandle get_InvalidHandle() Security2\Win32\SafeHandleEx.cs 309 1 0 False NATIVE-HANDLES unused by cmdlets Explained
152 Security2 Security2.SafeHGlobalHandle .ctor(IntPtr) Security2\Win32\SafeHandleEx.cs 31,33,34 3 0 False NATIVE-HANDLES unused by cmdlets Explained
153 Security2 Security2.SafeHGlobalHandle AddSubReference(IEnumerable`1<SafeHGlobalHandle>) Security2\Win32\SafeHandleEx.cs 60,62,65,66 4 2 False NATIVE-HANDLES unused by cmdlets Explained
154 Security2 Security2.SafeHGlobalHandle AllocHGlobal(ICollection`1<T>) Security2\Win32\SafeHandleEx.cs 109 1 0 False NATIVE-HANDLES unused by cmdlets Explained
155 Security2 Security2.SafeHGlobalHandle AllocHGlobal(Int32,IEnumerable`1<T>,Int32) Security2\Win32\SafeHandleEx.cs 130,132,133,135,136,139 8 2 False NATIVE-HANDLES unused by cmdlets Explained
156 Security2 Security2.SafeHGlobalHandle AllocHGlobal(Int32) Security2\Win32\SafeHandleEx.cs 184,186 1 1 False NATIVE-HANDLES unused by cmdlets Explained
157 Security2 Security2.SafeHGlobalHandle AllocHGlobal(IntPtr[]) Security2\Win32\SafeHandleEx.cs 77,79,81 3 0 False NATIVE-HANDLES unused by cmdlets Explained
158 Security2 Security2.SafeHGlobalHandle AllocHGlobal(String) Security2\Win32\SafeHandleEx.cs 150 1 0 False NATIVE-HANDLES unused by cmdlets Explained
159 Security2 Security2.SafeHGlobalHandle Dispose() Security2\Win32\SafeHandleEx.cs 169 0 1 False NATIVE-HANDLES unused by cmdlets Explained
160 Security2 Security2.SafeHGlobalHandle get_InvalidHandle() Security2\Win32\SafeHandleEx.cs 45 1 0 False NATIVE-HANDLES unused by cmdlets Explained
161 Security2 Security2.SafeTokenHandle .ctor() Security2\Win32\SafeHandleEx.cs 243 2 0 False NATIVE-HANDLES unused by cmdlets Explained
162 Security2 Security2.SafeTokenHandle .ctor(IntPtr) Security2\Win32\SafeHandleEx.cs 247,249,250 3 0 False NATIVE-HANDLES unused by cmdlets Explained
163 Security2 Security2.SafeTokenHandle get_InvalidHandle() Security2\Win32\SafeHandleEx.cs 256 1 0 False NATIVE-HANDLES unused by cmdlets Explained
164 Security2 Security2.SafeTokenHandle ReleaseHandle() Security2\Win32\SafeHandleEx.cs 266 1 0 False NATIVE-HANDLES unused by cmdlets Explained
165 NTFSSecurity NTFSSecurity.BaseCmdletWithPrivControl DisableEnabledPrivileges(Dictionary`2<String,Exception>) NTFSSecurity\BaseCmdlets.cs 422,424,425 3 0 True BaseCmdletWithPrivControl::Dispose > BaseCmdletWithPrivControl::DisableEnabledPrivileges PRIVILEGE-DISABLE-FAILURE defensive Explained
166 NTFSSecurity NTFSSecurity.BaseCmdletWithPrivControl DisableFileSystemPrivileges() NTFSSecurity\BaseCmdlets.cs 544,545 1 1 True DisablePrivileges::ProcessRecord > BaseCmdletWithPrivControl::DisableFileSystemPrivileges PRIVILEGE-DISABLE-FAILURE defensive Explained
167 NTFSSecurity NTFSSecurity.BaseCmdletWithPrivControl EndProcessing() NTFSSecurity\BaseCmdlets.cs 389,391,392 3 1 True BaseCmdletWithPrivControl::EndProcessing PRIVILEGE-DISABLE-FAILURE defensive Explained
168 NTFSSecurity NTFSSecurity.BaseCmdletWithPrivControl TryDisablePrivilege(ProcessPrivileges.Privilege) NTFSSecurity\BaseCmdlets.cs 490,492,493 3 0 True DisablePrivileges::ProcessRecord > BaseCmdletWithPrivControl::DisableFileSystemPrivileges > BaseCmdletWithPrivControl::TryDisablePrivilege PRIVILEGE-DISABLE-FAILURE defensive Explained
169 ProcessPrivileges ProcessPrivileges.PrivilegeEnabler EnablePrivilege(ProcessPrivileges.Privilege) ProcessPrivileges\PrivilegeEnabler.cs 172 0 1 False PRIVILEGE-ENABLER-RACE defensive Explained
170 PrivilegeControl Security2.PrivilegeControl DisablePrivilege(ProcessPrivileges.Privilege) PrivilegeControl\PrivilegeControl.cs 61,67 1 1 True BaseCmdletWithPrivControl::Dispose > BaseCmdletWithPrivControl::DisableEnabledPrivileges > BaseCmdletWithPrivControl::DisablePrivilege > PrivilegeControl::DisablePrivilege PRIVILEGECONTROL-DEAD-ELSE defensive Explained
171 PrivilegeControl Security2.PrivilegeControl EnablePrivilege(ProcessPrivileges.Privilege) PrivilegeControl\PrivilegeControl.cs 40,46 1 1 True BaseCmdletWithPrivControl::BeginProcessing > BaseCmdletWithPrivControl::EnableFileSystemPrivileges > BaseCmdletWithPrivControl::TryEnablePrivilege > BaseCmdletWithPrivControl::EnablePrivilege > PrivilegeControl::EnablePrivilege PRIVILEGECONTROL-DEAD-ELSE defensive Explained
172 NTFSSecurity NTFSSecurity.GetAccess/<>c__DisplayClass20_0 <ProcessRecord>b__0() NTFSSecurity\AccessCmdlets\GetAccess.cs 110,111 2 0 True GetAccess::ProcessRecord > GetAccess/<>c__DisplayClass20_0::<ProcessRecord>b__0 READ-RETRY-CLOSURE defensive Explained
173 NTFSSecurity NTFSSecurity.GetInheritance/<>c__DisplayClass7_0 <ProcessRecord>b__0() NTFSSecurity\InheritanceCmdlets\GetInheritance.cs 78,79 2 0 True GetInheritance::ProcessRecord > GetInheritance/<>c__DisplayClass7_0::<ProcessRecord>b__0 READ-RETRY-CLOSURE defensive Explained
174 NTFSSecurity NTFSSecurity.GetOrphanedAccess/<>c__DisplayClass1_0 <ProcessRecord>b__0() NTFSSecurity\AccessCmdlets\GetOrphanedAccess.cs 53,54 2 0 True GetOrphanedAccess::ProcessRecord > GetOrphanedAccess/<>c__DisplayClass1_0::<ProcessRecord>b__0 READ-RETRY-CLOSURE defensive Explained
175 NTFSSecurity NTFSSecurity.GetSecurityDescriptor/<>c__DisplayClass4_0 <ProcessRecord>b__0() NTFSSecurity\SecurityDescriptorCmdlets\GetSecurityDescriptor.cs 62,63 2 0 True GetSecurityDescriptor::ProcessRecord > GetSecurityDescriptor/<>c__DisplayClass4_0::<ProcessRecord>b__0 READ-RETRY-CLOSURE defensive Explained
176 Security2 Security2.RegistryAccessRule2 .ctor(RegistryAccessRule) Security2\Registry\RegistrySecurity.cs 140,142,143 3 0 False REGISTRY-MODEL unused by cmdlets Explained
177 Security2 Security2.RegistryAccessRule2 Dispose() Security2\Registry\RegistrySecurity.cs 180 1 0 False REGISTRY-MODEL unused by cmdlets Explained
178 Security2 Security2.RegistryAccessRule2 Equals(Object) Security2\Registry\RegistrySecurity.cs 166 1 0 False REGISTRY-MODEL unused by cmdlets Explained
179 Security2 Security2.RegistryAccessRule2 get_AccessControlType() Security2\Registry\RegistrySecurity.cs 146 1 0 False REGISTRY-MODEL unused by cmdlets Explained
180 Security2 Security2.RegistryAccessRule2 get_IdentityReference() Security2\Registry\RegistrySecurity.cs 148 1 0 False REGISTRY-MODEL unused by cmdlets Explained
181 Security2 Security2.RegistryAccessRule2 get_InheritanceFlags() Security2\Registry\RegistrySecurity.cs 149 1 0 False REGISTRY-MODEL unused by cmdlets Explained
182 Security2 Security2.RegistryAccessRule2 get_IsInherited() Security2\Registry\RegistrySecurity.cs 150 1 0 False REGISTRY-MODEL unused by cmdlets Explained
183 Security2 Security2.RegistryAccessRule2 get_Name() Security2\Registry\RegistrySecurity.cs 117,119,121,122,126,131 6 4 False REGISTRY-MODEL unused by cmdlets Explained
184 Security2 Security2.RegistryAccessRule2 get_PropagationFlags() Security2\Registry\RegistrySecurity.cs 151 1 0 False REGISTRY-MODEL unused by cmdlets Explained
185 Security2 Security2.RegistryAccessRule2 get_RegistryRights() Security2\Registry\RegistrySecurity.cs 147 1 0 False REGISTRY-MODEL unused by cmdlets Explained
186 Security2 Security2.RegistryAccessRule2 GetHashCode() Security2\Registry\RegistrySecurity.cs 170 1 0 False REGISTRY-MODEL unused by cmdlets Explained
187 Security2 Security2.RegistryAccessRule2 op_Implicit(RegistryAccessRule) Security2\Registry\RegistrySecurity.cs 161 1 0 False REGISTRY-MODEL unused by cmdlets Explained
188 Security2 Security2.RegistryAccessRule2 op_Implicit(RegistryAccessRule2) Security2\Registry\RegistrySecurity.cs 157 1 0 False REGISTRY-MODEL unused by cmdlets Explained
189 Security2 Security2.RegistryAccessRule2 ToString() Security2\Registry\RegistrySecurity.cs 174 1 0 False REGISTRY-MODEL unused by cmdlets Explained
190 Security2 Security2.RegistryEffectivePermissionEntry .ctor(IdentityReference2,UInt32,String) Security2\Registry\RegistrySecurity.cs 252,254,255,256,257,259,261,265,266,268,271 13 4 False REGISTRY-MODEL unused by cmdlets Explained
191 Security2 Security2.RegistryEffectivePermissionEntry get_AccessAsString() Security2\Registry\RegistrySecurity.cs 250 1 0 False REGISTRY-MODEL unused by cmdlets Explained
192 Security2 Security2.RegistryEffectivePermissionEntry get_AccessMask() Security2\Registry\RegistrySecurity.cs 224 1 0 False REGISTRY-MODEL unused by cmdlets Explained
193 Security2 Security2.RegistryEffectivePermissionEntry get_Account() Security2\Registry\RegistrySecurity.cs 221 1 0 False REGISTRY-MODEL unused by cmdlets Explained
194 Security2 Security2.RegistryEffectivePermissionEntry get_FullName() Security2\Registry\RegistrySecurity.cs 227 1 0 False REGISTRY-MODEL unused by cmdlets Explained
195 Security2 Security2.RegistryEffectivePermissionEntry get_Name() Security2\Registry\RegistrySecurity.cs 230,232,234,235,239,244 6 4 False REGISTRY-MODEL unused by cmdlets Explained
196 Security2 Security2.RegistryInheritanceInfo get_FullName() Security2\Registry\RegistrySecurity.cs 191 1 0 False REGISTRY-MODEL unused by cmdlets Explained
197 Security2 Security2.RegistryInheritanceInfo get_Name() Security2\Registry\RegistrySecurity.cs 196,198,200,201,205,210 6 4 False REGISTRY-MODEL unused by cmdlets Explained
198 Security2 Security2.RegistryKeyOpenException .ctor() Security2\Registry\RegistrySecurity.cs 333,334 2 0 False REGISTRY-MODEL unused by cmdlets Explained
199 Security2 Security2.RegistryKeyOpenException .ctor(Exception,Int64) Security2\Registry\RegistrySecurity.cs 347,349,350 3 0 False REGISTRY-MODEL unused by cmdlets Explained
200 Security2 Security2.RegistryKeyOpenException .ctor(Exception) Security2\Registry\RegistrySecurity.cs 337,338 2 0 False REGISTRY-MODEL unused by cmdlets Explained
201 Security2 Security2.RegistryKeyOpenException .ctor(Int64) Security2\Registry\RegistrySecurity.cs 341,343,344 3 0 False REGISTRY-MODEL unused by cmdlets Explained
202 Security2 Security2.RegistryKeyOpenException get_Win32ErrorCode() Security2\Registry\RegistrySecurity.cs 330 1 0 False REGISTRY-MODEL unused by cmdlets Explained
203 Security2 Security2.RegistryKeySetSecurityException .ctor() Security2\Registry\RegistrySecurity.cs 359,360 2 0 False REGISTRY-MODEL unused by cmdlets Explained
204 Security2 Security2.RegistryKeySetSecurityException .ctor(Exception,Int64) Security2\Registry\RegistrySecurity.cs 373,375,376 3 0 False REGISTRY-MODEL unused by cmdlets Explained
205 Security2 Security2.RegistryKeySetSecurityException .ctor(Exception) Security2\Registry\RegistrySecurity.cs 363,364 2 0 False REGISTRY-MODEL unused by cmdlets Explained
206 Security2 Security2.RegistryKeySetSecurityException .ctor(Int64) Security2\Registry\RegistrySecurity.cs 367,369,370 3 0 False REGISTRY-MODEL unused by cmdlets Explained
207 Security2 Security2.RegistryKeySetSecurityException get_Win32ErrorCode() Security2\Registry\RegistrySecurity.cs 356 1 0 False REGISTRY-MODEL unused by cmdlets Explained
208 Security2 Security2.Win32RegistrySecurity GetRegistryKey(REGISTRY_ROOT,String,RegistryRights) Security2\Registry\RegistrySecurity.cs 429,431,433,435,437,440,442,444 8 0 False REGISTRY-MODEL unused by cmdlets Explained
209 Security2 Security2.Win32RegistrySecurity SetRegistryOwner(REGISTRY_ROOT,String,Security.Principal.SecurityIdentifier) Security2\Registry\RegistrySecurity.cs 448,450,451,452,454,458,459,461,463,464,466,470,473,474,478,479,481,483,484,486,490,494,495,496 24 4 False REGISTRY-MODEL unused by cmdlets Explained
210 NTFSSecurity NTFSSecurity.BaseCmdlet GetRelativePath(String) NTFSSecurity\BaseCmdlets.cs 229,231 1 1 True CopyItem2::BeginProcessing > BaseCmdlet::GetRelativePath RELATIVE-PATH-EMPTY defensive Explained
211 Security2 Security2.FileSystemAccessRule2 RemoveFileSystemAccessRuleAll(FileSystemSecurity2,List`1<IdentityReference2>) Security2\FileSystem\FileSystemAccessRule2 Class\FileSystemAccessRule2.RemoveFileSystemAccessRulesAll.cs 15,17 1 1 True ClearAccess::ProcessRecord > FileSystemAccessRule2::RemoveFileSystemAccessRuleAll REMOVEALL-ACCOUNT-FILTER unused by cmdlets Open
212 Security2 Security2.FileSystemAccessRule2/<>c__DisplayClass36_0 <RemoveFileSystemAccessRuleAll>b__0(FileSystemAccessRule) Security2\FileSystem\FileSystemAccessRule2 Class\FileSystemAccessRule2.RemoveFileSystemAccessRulesAll.cs 17 1 0 True ClearAccess::ProcessRecord > FileSystemAccessRule2::RemoveFileSystemAccessRuleAll > FileSystemAccessRule2/<>c__DisplayClass36_0::<RemoveFileSystemAccessRuleAll>b__0 REMOVEALL-ACCOUNT-FILTER unused by cmdlets Open
213 Security2 Security2.FileSystemAccessRule2/<>c__DisplayClass36_1 <RemoveFileSystemAccessRuleAll>b__1(IdentityReference2) Security2\FileSystem\FileSystemAccessRule2 Class\FileSystemAccessRule2.RemoveFileSystemAccessRulesAll.cs 17 1 0 True ClearAccess::ProcessRecord > FileSystemAccessRule2::RemoveFileSystemAccessRuleAll > FileSystemAccessRule2/<>c__DisplayClass36_0::<RemoveFileSystemAccessRuleAll>b__0 > FileSystemAccessRule2/<>c__DisplayClass36_1::<RemoveFileSystemAccessRuleAll>b__1 REMOVEALL-ACCOUNT-FILTER unused by cmdlets Open
214 Security2 Security2.FileSystemAuditRule2 RemoveFileSystemAuditRuleAll(FileSystemSecurity2,List`1<IdentityReference2>) Security2\FileSystem\FileSystemAuditRule2 Class\FileSystemAuditRule2.RemoveFileSystemAuditRuleAll.cs 15,17 1 1 True ClearAudit::ProcessRecord > FileSystemAuditRule2::RemoveFileSystemAuditRuleAll REMOVEALL-ACCOUNT-FILTER unused by cmdlets Open
215 Security2 Security2.FileSystemAuditRule2/<>c__DisplayClass7_0 <RemoveFileSystemAuditRuleAll>b__0(FileSystemAuditRule) Security2\FileSystem\FileSystemAuditRule2 Class\FileSystemAuditRule2.RemoveFileSystemAuditRuleAll.cs 17 1 0 True ClearAudit::ProcessRecord > FileSystemAuditRule2::RemoveFileSystemAuditRuleAll > FileSystemAuditRule2/<>c__DisplayClass7_0::<RemoveFileSystemAuditRuleAll>b__0 REMOVEALL-ACCOUNT-FILTER unused by cmdlets Open
216 Security2 Security2.FileSystemAuditRule2/<>c__DisplayClass7_1 <RemoveFileSystemAuditRuleAll>b__1(IdentityReference2) Security2\FileSystem\FileSystemAuditRule2 Class\FileSystemAuditRule2.RemoveFileSystemAuditRuleAll.cs 17 1 0 True ClearAudit::ProcessRecord > FileSystemAuditRule2::RemoveFileSystemAuditRuleAll > FileSystemAuditRule2/<>c__DisplayClass7_0::<RemoveFileSystemAuditRuleAll>b__0 > FileSystemAuditRule2/<>c__DisplayClass7_1::<RemoveFileSystemAuditRuleAll>b__1 REMOVEALL-ACCOUNT-FILTER unused by cmdlets Open
217 NTFSSecurity NTFSSecurity.TestPath2 ProcessRecord() NTFSSecurity\PathCmdlets\TestPath2.cs 53,55,56,65,67,68 6 0 True TestPath2::ProcessRecord TESTPATH-DEAD-CATCH defensive Explained
218 ProcessPrivileges ProcessPrivileges.AccessTokenHandle .ctor(ProcessPrivileges.ProcessHandle,ProcessPrivileges.TokenAccessRights) ProcessPrivileges\AccessTokenHandle.cs 21,23 1 1 True DisablePrivileges::ProcessRecord > PrivilegeControl::GetPrivileges > ProcessExtensions::GetPrivileges > AccessTokenHandle::.ctor TOKEN-NATIVE-FAILURE environment-specific Explained
219 ProcessPrivileges ProcessPrivileges.AccessTokenHandle ReleaseHandle() ProcessPrivileges\AccessTokenHandle.cs 32,34 1 1 False TOKEN-NATIVE-FAILURE environment-specific Explained
220 ProcessPrivileges ProcessPrivileges.Privileges GetLuid(ProcessPrivileges.Privilege) ProcessPrivileges\Privileges.cs 236,238 1 1 True BaseCmdletWithPrivControl::Dispose > BaseCmdletWithPrivControl::DisableEnabledPrivileges > BaseCmdletWithPrivControl::DisablePrivilege > PrivilegeControl::DisablePrivilege > ProcessExtensions::GetPrivilegeState > ProcessExtensions::GetPrivilegeAttributes > Privileges::GetPrivilegeAttributes > Privileges::GetLuid TOKEN-NATIVE-FAILURE environment-specific Explained
221 ProcessPrivileges ProcessPrivileges.Privileges GetPrivilegeName(ProcessPrivileges.Luid) ProcessPrivileges\Privileges.cs 249,251,255,257,261,263 3 3 True DisablePrivileges::ProcessRecord > PrivilegeControl::GetPrivileges > ProcessExtensions::GetPrivileges > Privileges::GetPrivileges > Privileges::GetPrivilegeName TOKEN-NATIVE-FAILURE environment-specific Explained
222 ProcessPrivileges ProcessPrivileges.Privileges GetTokenPrivileges(ProcessPrivileges.AccessTokenHandle) ProcessPrivileges\Privileges.cs 273,280,294,301 2 2 True DisablePrivileges::ProcessRecord > PrivilegeControl::GetPrivileges > ProcessExtensions::GetPrivileges > Privileges::GetPrivileges > Privileges::GetTokenPrivileges TOKEN-NATIVE-FAILURE environment-specific Explained
223 ProcessPrivileges ProcessPrivileges.ProcessHandle ReleaseHandle() ProcessPrivileges\ProcessHandle.cs 27,29,32 3 2 False TOKEN-NATIVE-FAILURE environment-specific Explained
224 NTFSSecurity NTFSSecurity.BaseCmdlet GetFileSystemInfo(String) NTFSSecurity\BaseCmdlets.cs 166,168,170,172,174,178 6 4 False UNUSED-CMDLET-HELPER unused by cmdlets Explained
225 NTFSSecurity NTFSSecurity.BaseCmdlet ProcessRecord() NTFSSecurity\BaseCmdlets.cs 145,146 2 0 True BaseCmdlet::ProcessRecord UNUSED-CMDLET-HELPER unused by cmdlets Explained
226 Security2 Security2.Win32 GetEffectiveAccess(ObjectSecurity,IdentityReference2,String,Boolean&,Exception&) Security2\Win32\Lib.cs 135,136 2 0 True GetEffectiveAccess::ProcessRecord > EffectiveAccess::GetEffectiveAccess > Win32::GetEffectiveAccess WIN32-AUTHZ-FAILURE environment-specific Explained
227 Security2 Security2.Win32 GetEffectivePermissions_AuthzAccessCheck(ObjectSecurity) Security2\Win32\Lib.cs 281 0 1 True GetEffectiveAccess::ProcessRecord > EffectiveAccess::GetEffectiveAccess > Win32::GetEffectiveAccess > Win32::GetEffectivePermissions_AuthzAccessCheck WIN32-AUTHZ-FAILURE environment-specific Explained
228 Security2 Security2.Win32 GetEffectivePermissions_AuthzInitializeContextFromSid(IdentityReference2) Security2\Win32\Lib.cs 245 0 1 True GetEffectiveAccess::ProcessRecord > EffectiveAccess::GetEffectiveAccess > Win32::GetEffectiveAccess > Win32::GetEffectivePermissions_AuthzInitializeContextFromSid WIN32-AUTHZ-FAILURE environment-specific Explained
229 Security2 Security2.Win32 GetEffectivePermissions_AuthzInitializeResourceManager(String,Boolean&) Security2\Win32\Lib.cs 188,194,196,210,218,223 3 3 True GetEffectiveAccess::ProcessRecord > EffectiveAccess::GetEffectiveAccess > Win32::GetEffectiveAccess > Win32::GetEffectivePermissions_AuthzInitializeResourceManager WIN32-AUTHZ-FAILURE environment-specific Explained
230 Security2 Security2.Win32 IsLocalComputer(String) Security2\Win32\Lib.cs 170,172 2 0 True GetEffectiveAccess::ProcessRecord > EffectiveAccess::GetEffectiveAccess > Win32::GetEffectiveAccess > Win32::GetEffectivePermissions_AuthzInitializeResourceManager > Win32::IsLocalComputer WIN32-LOCAL-NAME-LOOKUP defensive Explained
231 Security2 Security2.Win32 GetByteSecurityDescriptor(Microsoft.Win32.SafeHandles.SafeFileHandle,SecurityInformationClass) Security2\Win32\Lib.cs 311,312,315,323,325,328,330,331,332,335,336,337,339 13 2 False WIN32-RAW-DESCRIPTOR unused by cmdlets Explained
232 Security2 Security2.Win32 GetRawSecurityDescriptor(Microsoft.Win32.SafeHandles.SafeFileHandle,SecurityInformationClass) Security2\Win32\Lib.cs 306 1 0 False WIN32-RAW-DESCRIPTOR unused by cmdlets Explained

65
Tests/Coverage/Quality-Gate-Paths-2026-10-09-ParameterSets.csv

@ -0,0 +1,65 @@
"Cmdlet","ParameterSet","Default","TestInvocations","TestFiles","UnclassifiedInvocationsOfCmdlet"
"Add-NTFSAccess","PathComplex","True","33","Access (16); DriveRoot (1); FileHash (1); Inheritance (1); ObjectApis (1); PathErrors (7); PipelineControl (3); SecurityDescriptorSets (3)","2"
"Add-NTFSAccess","PathSimple","False","6","Access (1); Inheritance (3); ObjectApis (1); PermissionScopes (1)","2"
"Add-NTFSAccess","SDComplex","False","14","Access (4); PipelineControl (1); SecurityDescriptor (9)","2"
"Add-NTFSAccess","SDSimple","False","2","Access (1); SecurityDescriptor (1)","2"
"Add-NTFSAudit","PathComplex","True","27","Access (1); Audit (17); Inheritance (2); PipelineControl (4); SecurityDescriptor (1); SecurityDescriptorSets (2)","2"
"Add-NTFSAudit","PathSimple","False","5","Audit (2); Inheritance (3)","2"
"Add-NTFSAudit","SDComplex","False","5","Audit (4); SecurityDescriptor (1)","2"
"Add-NTFSAudit","SDSimple","False","0","","2"
"Clear-NTFSAccess","Path","True","6","Access (2); ObjectApis (1); PathErrors (3)","0"
"Clear-NTFSAccess","SD","False","3","SecurityDescriptor (1); SecurityDescriptorSets (2)","0"
"Clear-NTFSAudit","Path","True","3","Audit (3)","0"
"Clear-NTFSAudit","SD","False","2","Audit (1); SecurityDescriptorSets (1)","0"
"Copy-Item2","__AllParameterSets","False","6","ItemCmdlets (4); Owner (1); PipelineControl (1)","0"
"Disable-NTFSAccessInheritance","Path","True","9","DriveRoot (1); Inheritance (2); PathErrors (3); PipelineControl (2); SecurityDescriptorSets (1)","0"
"Disable-NTFSAccessInheritance","SecurityDescriptor","False","1","SecurityDescriptorSets (1)","0"
"Disable-NTFSAuditInheritance","Path","True","6","Inheritance (3); PipelineControl (2); SecurityDescriptorSets (1)","0"
"Disable-NTFSAuditInheritance","SecurityDescriptor","False","1","Inheritance (1)","0"
"Disable-Privileges","__AllParameterSets","False","19","Audit (1); OutputTypes (2); Privileges (16)","0"
"Enable-NTFSAccessInheritance","Path","True","3","DriveRoot (1); PathErrors (1); PipelineControl (1)","0"
"Enable-NTFSAccessInheritance","SecurityDescriptor","False","1","SecurityDescriptorSets (1)","0"
"Enable-NTFSAuditInheritance","Path","True","1","PipelineControl (1)","0"
"Enable-NTFSAuditInheritance","SecurityDescriptor","False","1","SecurityDescriptorSets (1)","0"
"Enable-Privileges","__AllParameterSets","False","5","OutputTypes (2); Privileges (3)","0"
"Get-ChildItem2","__AllParameterSets","False","34","ItemCmdlets (28); Links (1); PipelineControl (5)","0"
"Get-DiskSpace","__AllParameterSets","False","6","ItemCmdlets (5); PipelineControl (1)","0"
"Get-FileHash2","__AllParameterSets","False","12","FileHash (9); OutputTypes (1); PipelineControl (2)","0"
"Get-Item2","__AllParameterSets","False","17","Access (2); Audit (5); Inheritance (4); ItemCmdlets (1); ObjectApis (1); Owner (1); PipelineControl (1); SecurityDescriptor (2)","0"
"Get-NTFSAccess","Path","True","25","Access (12); DriveRoot (3); Inheritance (6); PermissionScopes (1); PipelineControl (1); Privileges (1); SecurityDescriptorSets (1)","2"
"Get-NTFSAccess","SD","False","1","Access (1)","2"
"Get-NTFSAudit","Path","False","24","Audit (11); Inheritance (6); PipelineControl (3); SecurityDescriptor (1); SecurityDescriptorSets (3)","2"
"Get-NTFSAudit","SD","False","1","Audit (1)","2"
"Get-NTFSEffectiveAccess","Path","True","11","Access (10); PipelineControl (1)","1"
"Get-NTFSEffectiveAccess","SecurityDescriptor","False","2","Access (2)","1"
"Get-NTFSHardLink","__AllParameterSets","False","9","Links (7); Owner (1); PipelineControl (1)","0"
"Get-NTFSInheritance","Path","True","21","Audit (1); Inheritance (12); ObjectApis (3); PipelineControl (3); SecurityDescriptorSets (2)","0"
"Get-NTFSInheritance","SecurityDescriptor","False","2","Inheritance (2)","0"
"Get-NTFSOrphanedAccess","Path","False","4","Access (3); PipelineControl (1)","0"
"Get-NTFSOrphanedAccess","SD","False","1","Access (1)","0"
"Get-NTFSOrphanedAudit","Path","False","9","Audit (8); PipelineControl (1)","2"
"Get-NTFSOrphanedAudit","SD","False","0","","2"
"Get-NTFSOwner","Path","True","23","DriveRoot (1); FileHash (2); Inheritance (2); ObjectApis (3); Owner (6); PipelineControl (1); Privileges (7); SecurityDescriptorSets (1)","2"
"Get-NTFSOwner","SecurityDescriptor","False","0","","2"
"Get-NTFSSecurityDescriptor","__AllParameterSets","False","77","Access (14); Audit (11); DriveRoot (1); Inheritance (10); ObjectApis (4); Owner (2); PermissionScopes (2); PipelineControl (2); SecurityDescriptor (22); SecurityDescriptorSets (9)","0"
"Get-NTFSSimpleAccess","Path","False","20","Access (19); PipelineControl (1)","2"
"Get-NTFSSimpleAccess","SD","False","1","Access (1)","2"
"Get-Privileges","__AllParameterSets","False","18","Access (2); Audit (4); FileHash (1); Inheritance (1); ObjectApis (1); OutputTypes (2); Owner (1); PathErrors (2); PipelineControl (1); Privileges (2); SecurityDescriptor (1)","0"
"Move-Item2","__AllParameterSets","False","7","ItemCmdlets (6); PipelineControl (1)","0"
"New-NTFSHardLink","__AllParameterSets","False","14","Links (14)","0"
"New-NTFSSymbolicLink","__AllParameterSets","False","12","ItemCmdlets (1); Links (10); OutputTypes (1)","0"
"Remove-Item2","__AllParameterSets","False","15","ItemCmdlets (1); Owner (1); PipelineControl (2); Remove-Item2 (11)","0"
"Remove-NTFSAccess","PathComplex","True","13","Access (10); DriveRoot (1); PathErrors (1); PipelineControl (1)","2"
"Remove-NTFSAccess","PathSimple","False","0","","2"
"Remove-NTFSAccess","SDComplex","False","4","Access (4)","2"
"Remove-NTFSAccess","SDSimple","False","0","","2"
"Remove-NTFSAudit","PathComplex","True","7","Audit (6); PipelineControl (1)","1"
"Remove-NTFSAudit","PathSimple","False","0","","1"
"Remove-NTFSAudit","SDComplex","False","2","Audit (2)","1"
"Remove-NTFSAudit","SDSimple","False","0","","1"
"Set-NTFSInheritance","Path","True","14","Inheritance (12); PathErrors (1); PipelineControl (1)","0"
"Set-NTFSInheritance","SecurityDescriptor","False","5","Inheritance (5)","0"
"Set-NTFSOwner","Path","True","8","FileHash (1); Owner (5); PipelineControl (2)","1"
"Set-NTFSOwner","SecurityDescriptor","False","2","Owner (2)","1"
"Set-NTFSSecurityDescriptor","__AllParameterSets","False","29","Audit (1); Inheritance (4); Owner (1); PipelineControl (2); SecurityDescriptor (15); SecurityDescriptorSets (6)","0"
"Test-Path2","__AllParameterSets","False","14","ItemCmdlets (7); Links (6); PipelineControl (1)","0"
Can't render this file because it contains an unexpected character in line 1 and column 7.

41
Tests/Coverage/Quality-Gate-Paths-2026-10-09-RedGreen-Logs.csv

@ -0,0 +1,41 @@
"State","Commit","Configuration","Log","Bytes","SHA256"
"S0","f11ff41","elevateddesktop","redgreen/S0/elevateddesktop.log","153301","8AD81D6CA40F3E6FAF854F9AD1F0F8D9FFEAE501DD95AA26398A91238F130A90"
"S0","f11ff41","elevatedcore","redgreen/S0/elevatedcore.log","139107","0E356271CE54C0BB3295B16B94376E807C1433FA9B39C4A5475425C9A00EB2D5"
"S0","f11ff41","basicdesktop","redgreen/S0/basicdesktop.log","163761","C5044850C31B48AD0DEB4811C68A20F2D0B12B63C82A3538A0769812E850D639"
"S0","f11ff41","basiccore","redgreen/S0/basiccore.log","149826","4E6CCA3D4E43A93A0132147FBA2A4257B36069EB377BA5ECC3240924011E41F0"
"S1","b14c90b","elevateddesktop","redgreen/S1/elevateddesktop.log","147261","F95D9B8C209BBBAF4A4733D46C6BA364E89731BDD430F6FD3D1C6FE8994024E3"
"S1","b14c90b","elevatedcore","redgreen/S1/elevatedcore.log","133239","0499FD8EE275B3B8E51AC89CE75BDF0EFA56EBBF867F01836A41E99444F72E67"
"S1","b14c90b","basicdesktop","redgreen/S1/basicdesktop.log","157716","BACF46A9499BE54D4F9B3894028CEE832214E3CB14F32E672FA306A58F2B147E"
"S1","b14c90b","basiccore","redgreen/S1/basiccore.log","143867","0785A7430FD648B79B430D57F10D9D02D06761B531A57451C971B5FAFD299701"
"S2","c7a0383","elevateddesktop","redgreen/S2/elevateddesktop.log","145073","536914D0622225027721EBBFFA6C0D290B0BB79D8ABB316777B7691E3150ED93"
"S2","c7a0383","elevatedcore","redgreen/S2/elevatedcore.log","131107","C60A7B7BCB444F28CFD109D1FDF47F8004F19599954C1BF6927CECF0001254CF"
"S2","c7a0383","basicdesktop","redgreen/S2/basicdesktop.log","155534","26CE04E2B1454AF4D11795F59899D9FC9EAC2FA0925BE27130F0926D19C33E87"
"S2","c7a0383","basiccore","redgreen/S2/basiccore.log","141731","FE431772D6EF3D359A04ECF6751253F383EFE5A3809934A362A17FF2099B5AA9"
"S3","2909a1c","elevateddesktop","redgreen/S3/elevateddesktop.log","142723","52C1241B11DEC3037F7EFDDCA014358C407D62B1FABC4201E9A533000D1303A1"
"S3","2909a1c","elevatedcore","redgreen/S3/elevatedcore.log","128849","AF8C0C175E2D08A8D568EBF107FB5A5B08A5448399A974ABAE5677581A157366"
"S3","2909a1c","basicdesktop","redgreen/S3/basicdesktop.log","154347","9DEB67568047AEADE5670DB77CBECBBDE6342188E2074BF03893C5485CB1DAAE"
"S3","2909a1c","basiccore","redgreen/S3/basiccore.log","140589","E18744BD9D0A5B9F69150B43E526BC95B92849F9FF6FC9A43D5425860131BE18"
"S4","c77ecbf","elevateddesktop","redgreen/S4/elevateddesktop.log","113411","9278A340122557D4F29A6DD61709274F0B1E46ADBB16EC27F7884FE2CBAE3CF1"
"S4","c77ecbf","elevatedcore","redgreen/S4/elevatedcore.log","99977","DD3FDB28C62E40B1958859F1C570263ACC1A728652C9CA5BE3ACA05E9F7D2EB9"
"S4","c77ecbf","basicdesktop","redgreen/S4/basicdesktop.log","125024","9B5B73A8B419BCA1F921A834770692CB48595703D9945A4BBCE6F99134DB5105"
"S4","c77ecbf","basiccore","redgreen/S4/basiccore.log","111710","A9DFF033F718B4B62C9FC32369C49A5C424560DDE6F2DAB1E80777B5B66ABEC1"
"S5","ee7c105","elevateddesktop","redgreen/S5/elevateddesktop.log","112510","C05F3269C9FF31880EDA679BB18722BA178E872736CAA79C1BAB05CC2A8F4707"
"S5","ee7c105","elevatedcore","redgreen/S5/elevatedcore.log","99110","985B3D41235CEECE542F9975BBE157FD1B855E75BB756A21E9C4903561AD0438"
"S5","ee7c105","basicdesktop","redgreen/S5/basicdesktop.log","124131","1468119C479D87CDAC9E5EA9A41E0B046F768F64A4325BCA517CAF23A664B60C"
"S5","ee7c105","basiccore","redgreen/S5/basiccore.log","110829","BFAB06F08756BB9A5723EAC94EE74EBF9F7B801230D97830A0DBAF2E116E019A"
"S6","ae3078f","elevateddesktop","redgreen/S6/elevateddesktop.log","110594","423BE1E4F05FEA407077380676106E22427408DFED0843C5B8381E835C4E1684"
"S6","ae3078f","elevatedcore","redgreen/S6/elevatedcore.log","97221","4F4075433D755FC01703DAC7A601D6C6738581BD7C1035D11DB1162581B02C00"
"S6","ae3078f","basicdesktop","redgreen/S6/basicdesktop.log","122224","29E8F41737B20090B3DE5181D249FED0C7FECBC0F87A8103F5497A3394C16069"
"S6","ae3078f","basiccore","redgreen/S6/basiccore.log","108928","F5857D66F68AFB58ED94EE9C1BABF53FCB88F8EA6C4DF42018B7B8876DD39B18"
"S7","40bf6a8","elevateddesktop","redgreen/S7/elevateddesktop.log","93289","465E86473B527421A4172403C4E77C299A78FAB919C1C373B48DFD533863480E"
"S7","40bf6a8","elevatedcore","redgreen/S7/elevatedcore.log","80285","37A0E26F20E30F2FE5A1D89BC652D196B8FC9111689D55736BA6096FCDD989C1"
"S7","40bf6a8","basicdesktop","redgreen/S7/basicdesktop.log","104902","6F9CA5107BC733AC6A534E3E4C0890981FF8247A82EB731DB317CC9D4B5765F8"
"S7","40bf6a8","basiccore","redgreen/S7/basiccore.log","91985","697450AE2EC6082D3E03E91B50B9E8DBF86AC25F74A9004C4EE1AC92C4D35EC6"
"S8","7aa8315","elevateddesktop","redgreen/S8/elevateddesktop.log","93300","979F16CB8C434D8EC2824339C90837446DAB52BBAF398AEA0BCFD32ED90340BE"
"S8","7aa8315","elevatedcore","redgreen/S8/elevatedcore.log","80288","396423C7C0A9C7898AE2AAFF56C924430FEDF9900BEC597A7C46295B5280D696"
"S8","7aa8315","basicdesktop","redgreen/S8/basicdesktop.log","104911","B3380A7F637B2E70AFEAB531334F5510BD674CDA14A84CB6622A44D88AA7B188"
"S8","7aa8315","basiccore","redgreen/S8/basiccore.log","92014","2C80201F942889AF44690A63E2EFD134A0C290C89C49078740FA5A031DC90BDB"
"S9","d44a200","elevateddesktop","redgreen/S9/elevateddesktop.log","90808","C44DED5A4C2B0198215E417D7B7CD8B589324A5AA9CE6BEADCC1BAC33BAB4E5E"
"S9","d44a200","elevatedcore","redgreen/S9/elevatedcore.log","77862","4DD3B548A1C12D812C4EEEE86A290EADF091C46FA314D2752EDE714FDA483B12"
"S9","d44a200","basicdesktop","redgreen/S9/basicdesktop.log","103983","48822DC49C252D25707E85C0BC738EFE0B2679DAC96EC7B9C3FFBE1ABC0ECFCB"
"S9","d44a200","basiccore","redgreen/S9/basiccore.log","91115","7C2F111FE688EB24D8644945793E84F7F6112BDA5DE8CBAF93A7527B3CEC2FDB"
1 State Commit Configuration Log Bytes SHA256
2 S0 f11ff41 elevateddesktop redgreen/S0/elevateddesktop.log 153301 8AD81D6CA40F3E6FAF854F9AD1F0F8D9FFEAE501DD95AA26398A91238F130A90
3 S0 f11ff41 elevatedcore redgreen/S0/elevatedcore.log 139107 0E356271CE54C0BB3295B16B94376E807C1433FA9B39C4A5475425C9A00EB2D5
4 S0 f11ff41 basicdesktop redgreen/S0/basicdesktop.log 163761 C5044850C31B48AD0DEB4811C68A20F2D0B12B63C82A3538A0769812E850D639
5 S0 f11ff41 basiccore redgreen/S0/basiccore.log 149826 4E6CCA3D4E43A93A0132147FBA2A4257B36069EB377BA5ECC3240924011E41F0
6 S1 b14c90b elevateddesktop redgreen/S1/elevateddesktop.log 147261 F95D9B8C209BBBAF4A4733D46C6BA364E89731BDD430F6FD3D1C6FE8994024E3
7 S1 b14c90b elevatedcore redgreen/S1/elevatedcore.log 133239 0499FD8EE275B3B8E51AC89CE75BDF0EFA56EBBF867F01836A41E99444F72E67
8 S1 b14c90b basicdesktop redgreen/S1/basicdesktop.log 157716 BACF46A9499BE54D4F9B3894028CEE832214E3CB14F32E672FA306A58F2B147E
9 S1 b14c90b basiccore redgreen/S1/basiccore.log 143867 0785A7430FD648B79B430D57F10D9D02D06761B531A57451C971B5FAFD299701
10 S2 c7a0383 elevateddesktop redgreen/S2/elevateddesktop.log 145073 536914D0622225027721EBBFFA6C0D290B0BB79D8ABB316777B7691E3150ED93
11 S2 c7a0383 elevatedcore redgreen/S2/elevatedcore.log 131107 C60A7B7BCB444F28CFD109D1FDF47F8004F19599954C1BF6927CECF0001254CF
12 S2 c7a0383 basicdesktop redgreen/S2/basicdesktop.log 155534 26CE04E2B1454AF4D11795F59899D9FC9EAC2FA0925BE27130F0926D19C33E87
13 S2 c7a0383 basiccore redgreen/S2/basiccore.log 141731 FE431772D6EF3D359A04ECF6751253F383EFE5A3809934A362A17FF2099B5AA9
14 S3 2909a1c elevateddesktop redgreen/S3/elevateddesktop.log 142723 52C1241B11DEC3037F7EFDDCA014358C407D62B1FABC4201E9A533000D1303A1
15 S3 2909a1c elevatedcore redgreen/S3/elevatedcore.log 128849 AF8C0C175E2D08A8D568EBF107FB5A5B08A5448399A974ABAE5677581A157366
16 S3 2909a1c basicdesktop redgreen/S3/basicdesktop.log 154347 9DEB67568047AEADE5670DB77CBECBBDE6342188E2074BF03893C5485CB1DAAE
17 S3 2909a1c basiccore redgreen/S3/basiccore.log 140589 E18744BD9D0A5B9F69150B43E526BC95B92849F9FF6FC9A43D5425860131BE18
18 S4 c77ecbf elevateddesktop redgreen/S4/elevateddesktop.log 113411 9278A340122557D4F29A6DD61709274F0B1E46ADBB16EC27F7884FE2CBAE3CF1
19 S4 c77ecbf elevatedcore redgreen/S4/elevatedcore.log 99977 DD3FDB28C62E40B1958859F1C570263ACC1A728652C9CA5BE3ACA05E9F7D2EB9
20 S4 c77ecbf basicdesktop redgreen/S4/basicdesktop.log 125024 9B5B73A8B419BCA1F921A834770692CB48595703D9945A4BBCE6F99134DB5105
21 S4 c77ecbf basiccore redgreen/S4/basiccore.log 111710 A9DFF033F718B4B62C9FC32369C49A5C424560DDE6F2DAB1E80777B5B66ABEC1
22 S5 ee7c105 elevateddesktop redgreen/S5/elevateddesktop.log 112510 C05F3269C9FF31880EDA679BB18722BA178E872736CAA79C1BAB05CC2A8F4707
23 S5 ee7c105 elevatedcore redgreen/S5/elevatedcore.log 99110 985B3D41235CEECE542F9975BBE157FD1B855E75BB756A21E9C4903561AD0438
24 S5 ee7c105 basicdesktop redgreen/S5/basicdesktop.log 124131 1468119C479D87CDAC9E5EA9A41E0B046F768F64A4325BCA517CAF23A664B60C
25 S5 ee7c105 basiccore redgreen/S5/basiccore.log 110829 BFAB06F08756BB9A5723EAC94EE74EBF9F7B801230D97830A0DBAF2E116E019A
26 S6 ae3078f elevateddesktop redgreen/S6/elevateddesktop.log 110594 423BE1E4F05FEA407077380676106E22427408DFED0843C5B8381E835C4E1684
27 S6 ae3078f elevatedcore redgreen/S6/elevatedcore.log 97221 4F4075433D755FC01703DAC7A601D6C6738581BD7C1035D11DB1162581B02C00
28 S6 ae3078f basicdesktop redgreen/S6/basicdesktop.log 122224 29E8F41737B20090B3DE5181D249FED0C7FECBC0F87A8103F5497A3394C16069
29 S6 ae3078f basiccore redgreen/S6/basiccore.log 108928 F5857D66F68AFB58ED94EE9C1BABF53FCB88F8EA6C4DF42018B7B8876DD39B18
30 S7 40bf6a8 elevateddesktop redgreen/S7/elevateddesktop.log 93289 465E86473B527421A4172403C4E77C299A78FAB919C1C373B48DFD533863480E
31 S7 40bf6a8 elevatedcore redgreen/S7/elevatedcore.log 80285 37A0E26F20E30F2FE5A1D89BC652D196B8FC9111689D55736BA6096FCDD989C1
32 S7 40bf6a8 basicdesktop redgreen/S7/basicdesktop.log 104902 6F9CA5107BC733AC6A534E3E4C0890981FF8247A82EB731DB317CC9D4B5765F8
33 S7 40bf6a8 basiccore redgreen/S7/basiccore.log 91985 697450AE2EC6082D3E03E91B50B9E8DBF86AC25F74A9004C4EE1AC92C4D35EC6
34 S8 7aa8315 elevateddesktop redgreen/S8/elevateddesktop.log 93300 979F16CB8C434D8EC2824339C90837446DAB52BBAF398AEA0BCFD32ED90340BE
35 S8 7aa8315 elevatedcore redgreen/S8/elevatedcore.log 80288 396423C7C0A9C7898AE2AAFF56C924430FEDF9900BEC597A7C46295B5280D696
36 S8 7aa8315 basicdesktop redgreen/S8/basicdesktop.log 104911 B3380A7F637B2E70AFEAB531334F5510BD674CDA14A84CB6622A44D88AA7B188
37 S8 7aa8315 basiccore redgreen/S8/basiccore.log 92014 2C80201F942889AF44690A63E2EFD134A0C290C89C49078740FA5A031DC90BDB
38 S9 d44a200 elevateddesktop redgreen/S9/elevateddesktop.log 90808 C44DED5A4C2B0198215E417D7B7CD8B589324A5AA9CE6BEADCC1BAC33BAB4E5E
39 S9 d44a200 elevatedcore redgreen/S9/elevatedcore.log 77862 4DD3B548A1C12D812C4EEEE86A290EADF091C46FA314D2752EDE714FDA483B12
40 S9 d44a200 basicdesktop redgreen/S9/basicdesktop.log 103983 48822DC49C252D25707E85C0BC738EFE0B2679DAC96EC7B9C3FFBE1ABC0ECFCB
41 S9 d44a200 basiccore redgreen/S9/basiccore.log 91115 7C2F111FE688EB24D8644945793E84F7F6112BDA5DE8CBAF93A7527B3CEC2FDB

75
Tests/Coverage/Quality-Gate-Paths-2026-10-09-RedGreen.csv

@ -0,0 +1,75 @@
"Step","FixCommit","Kind","File","Test","ElevatedDesktop","ElevatedCore","BasicDesktop","BasicCore"
"f11ff41 to b14c90b","b14c90b","red before, green after","ObjectApis","Access rule helpers that take a path.Should add the entry of a rule that carries its path","1","1","1","1"
"f11ff41 to b14c90b","b14c90b","red before, green after","ObjectApis","Privilege output comparisons and formatting.Should compare boxed and typed privilege values consistently without accepting an attributes enum","1","1","1","1"
"f11ff41 to b14c90b","b14c90b","red before, green after","ObjectApis","Rule constructors with a path.Should preserve the supplied path and name of an access rule","1","1","1","1"
"f11ff41 to b14c90b","b14c90b","red before, green after","ObjectApis","Rule constructors with a path.Should preserve the supplied path and name of an audit rule","1","1","1","1"
"f11ff41 to b14c90b","b14c90b","red before, green after","ObjectApis","Simplified audit entries.Should compare audit entries reflexively and symmetrically, never as access entries","1","1","1","1"
"f11ff41 to b14c90b","b14c90b","red before, green after","ObjectApis","Simplified audit entries.Should preserve the path, account and ReadData when converting an audit entry","1","1","1","1"
"f11ff41 to b14c90b","b14c90b","red before, green after","ObjectApis","Simplified audit entries.Should reduce ReadData to Read","1","1","1","1"
"f11ff41 to b14c90b","b14c90b","red before, green after","ObjectApis","Simplified entry comparison branches.Should distinguish identities, rights and types in audit entries and keep equal hashes consistent","1","1","1","1"
"b14c90b to c7a0383","c7a0383","red before, green after","PathErrors","An item whose owner may not change its permissions.Clear-NTFSAccess -DisableInheritance should take ownership, clear and protect the DACL, and not set an unchanged owner back","1","1","1","1"
"b14c90b to c7a0383","c7a0383","red before, green after","SecurityDescriptor","Set-NTFSSecurityDescriptor.When the write is denied until the cmdlet takes ownership.Should not report an owner that did not change when the write that took ownership leaves an empty DACL","1","1","1","1"
"c7a0383 to 2909a1c","2909a1c","red before, green after","Access","InheritedFrom of access entries.Should name an unknown parent for an inherited entry and no source for an explicit entry when Windows cannot resolve the folders","1","1","1","1"
"c7a0383 to 2909a1c","2909a1c","red before, green after","Audit","InheritedFrom of audit entries.Should name an unknown parent for an inherited entry and no source for an explicit entry when the privilege is disabled","1","1","0","0"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command and the error of a folder that Get-ChildItem2 cannot read.Should leave the loop for a break of a later command that takes the error of a nested folder","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command and the error of a folder that Get-ChildItem2 cannot read.Should leave the loop for a continue of a later command that takes the error of a nested folder","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Copy-Item2 should leave the loop for break after its first object and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Copy-Item2 should leave the loop for continue after its first object and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Copy-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Copy-Item2 should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Get-ChildItem2 should leave the loop for break after its first object and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Get-ChildItem2 should leave the loop for continue after its first object and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Get-DiskSpace should leave the loop for break after its first object and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Get-DiskSpace should leave the loop for continue after its first object and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Get-DiskSpace should stop after the first object for Select-Object -First 1 and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Get-DiskSpace should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Get-NTFSSecurityDescriptor should leave the loop for break after its first object and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Get-NTFSSecurityDescriptor should leave the loop for continue after its first object and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Get-NTFSSecurityDescriptor should stop after the first object for Select-Object -First 1 and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Get-NTFSSecurityDescriptor should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Get-NTFSSimpleAccess should leave the loop for break after its first object and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Get-NTFSSimpleAccess should leave the loop for continue after its first object and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Get-NTFSSimpleAccess should stop after the first object for Select-Object -First 1 and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Get-NTFSSimpleAccess should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Move-Item2 should leave the loop for break after its first object and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Move-Item2 should leave the loop for continue after its first object and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Move-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Move-Item2 should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Remove-Item2 should leave the loop for break after its first object and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Remove-Item2 should leave the loop for continue after its first object and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Remove-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Remove-Item2 should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Set-NTFSOwner should leave the loop for break after its first object and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Set-NTFSOwner should leave the loop for continue after its first object and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Set-NTFSOwner should stop after the first object for Select-Object -First 1 and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Set-NTFSOwner should stop at the debug message for Select-Object -First 1 of the later command and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Set-NTFSOwner should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Set-NTFSSecurityDescriptor should leave the loop for break after its first object and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Set-NTFSSecurityDescriptor should leave the loop for continue after its first object and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Set-NTFSSecurityDescriptor should stop after the first object for Select-Object -First 1 and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","A later command that ends the pipeline.Set-NTFSSecurityDescriptor should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","Recognizing the end of a pipeline by the type of the exception.Should not recognize <Description>","3","3","3","3"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","Recognizing the end of a pipeline by the type of the exception.Should recognize a PipelineStoppedException","1","1","1","1"
"2909a1c to c77ecbf","c77ecbf","red before, green after","PipelineControl","Recognizing the end of a pipeline by the type of the exception.Should recognize an exception whose base type is the flow control exception of PowerShell","1","1","1","1"
"c77ecbf to ee7c105","ee7c105","red before, green after","ItemCmdlets","Get-ChildItem2.Recursion, type filters, and depth.Should find a file whose name contains brackets by that name with -Filter","1","1","1","1"
"ee7c105 to ae3078f","ae3078f","red before, green after","ItemCmdlets","Get-ChildItem2.Recursion, type filters, and depth.Should reject a null -Filter","1","1","1","1"
"ae3078f to 40bf6a8","40bf6a8","red before, green after","PipelineControl","A later command that ends the pipeline.Copy-Item2 should stop for a terminating error (throw) of the later command and change nothing else","1","1","1","1"
"ae3078f to 40bf6a8","40bf6a8","red before, green after","PipelineControl","A later command that ends the pipeline.Get-ChildItem2 should stop for a terminating error (throw) of the later command and change nothing else","1","1","1","1"
"ae3078f to 40bf6a8","40bf6a8","red before, green after","PipelineControl","A later command that ends the pipeline.Get-DiskSpace should stop for a terminating error (throw) of the later command and change nothing else","1","1","1","1"
"ae3078f to 40bf6a8","40bf6a8","red before, green after","PipelineControl","A later command that ends the pipeline.Get-FileHash2 should stop at the verbose message for Select-Object -First 1 of the later command and change nothing else","1","1","1","1"
"ae3078f to 40bf6a8","40bf6a8","red before, green after","PipelineControl","A later command that ends the pipeline.Get-FileHash2 should stop at the verbose message for throw of the later command and change nothing else","1","1","1","1"
"ae3078f to 40bf6a8","40bf6a8","red before, green after","PipelineControl","A later command that ends the pipeline.Get-NTFSSecurityDescriptor should stop for a terminating error (throw) of the later command and change nothing else","1","1","1","1"
"ae3078f to 40bf6a8","40bf6a8","red before, green after","PipelineControl","A later command that ends the pipeline.Get-NTFSSimpleAccess should stop for a terminating error (throw) of the later command and change nothing else","1","1","1","1"
"ae3078f to 40bf6a8","40bf6a8","red before, green after","PipelineControl","A later command that ends the pipeline.Move-Item2 should stop for a terminating error (throw) of the later command and change nothing else","1","1","1","1"
"ae3078f to 40bf6a8","40bf6a8","red before, green after","PipelineControl","A later command that ends the pipeline.Remove-Item2 should stop for a terminating error (throw) of the later command and change nothing else","1","1","1","1"
"ae3078f to 40bf6a8","40bf6a8","red before, green after","PipelineControl","A later command that ends the pipeline.Set-NTFSOwner should stop at the debug message for throw of the later command and change nothing else","1","1","1","1"
"ae3078f to 40bf6a8","40bf6a8","red before, green after","PipelineControl","A later command that ends the pipeline.Set-NTFSOwner should stop for a terminating error (throw) of the later command and change nothing else","1","1","1","1"
"ae3078f to 40bf6a8","40bf6a8","red before, green after","PipelineControl","A later command that ends the pipeline.Set-NTFSSecurityDescriptor should stop at the verbose message for Select-Object -First 1 of the later command and change nothing else","1","1","1","1"
"ae3078f to 40bf6a8","40bf6a8","red before, green after","PipelineControl","A later command that ends the pipeline.Set-NTFSSecurityDescriptor should stop at the verbose message for throw of the later command and change nothing else","1","1","1","1"
"ae3078f to 40bf6a8","40bf6a8","red before, green after","PipelineControl","A later command that ends the pipeline.Set-NTFSSecurityDescriptor should stop for a terminating error (throw) of the later command and change nothing else","1","1","1","1"
"ae3078f to 40bf6a8","40bf6a8","red before, green after","PipelineControl","A later command that throws an exception of a type that a cmdlet handles.Get-ChildItem2 should pass on a thrown UnauthorizedAccessException","1","1","1","1"
"ae3078f to 40bf6a8","40bf6a8","red before, green after","PipelineControl","A later command that throws an exception of a type that a cmdlet handles.Remove-Item2 should pass on a thrown IOException and leave the next item","1","1","1","1"
"ae3078f to 40bf6a8","40bf6a8","red before, green after","ItemCmdlets","Get-ChildItem2.Recursion, type filters, and depth.Should return every item for -Filter *.*, also the ones without a dot in their names","1","1","1","1"
"7aa8315 to d44a200","d44a200","red before, green after","PipelineControl","A later command and the error of a folder that Get-ChildItem2 cannot read.Should pass on what a later command throws when it takes the error of a nested folder","1","1","1","1"
"7aa8315 to d44a200","d44a200","red before, green after","Privileges","Privileges when a later command takes the debug messages of the cmdlet.Should disable the privilege when Select-Object -First ends the pipeline at the message after its enabling","1","1","0","0"
"7aa8315 to d44a200","d44a200","red before, green after","Privileges","Privileges when a later command takes the debug messages of the cmdlet.Should pass on what a later command throws at the message after the enabling and disable the privileges","1","1","0","0"
1 Step FixCommit Kind File Test ElevatedDesktop ElevatedCore BasicDesktop BasicCore
2 f11ff41 to b14c90b b14c90b red before, green after ObjectApis Access rule helpers that take a path.Should add the entry of a rule that carries its path 1 1 1 1
3 f11ff41 to b14c90b b14c90b red before, green after ObjectApis Privilege output comparisons and formatting.Should compare boxed and typed privilege values consistently without accepting an attributes enum 1 1 1 1
4 f11ff41 to b14c90b b14c90b red before, green after ObjectApis Rule constructors with a path.Should preserve the supplied path and name of an access rule 1 1 1 1
5 f11ff41 to b14c90b b14c90b red before, green after ObjectApis Rule constructors with a path.Should preserve the supplied path and name of an audit rule 1 1 1 1
6 f11ff41 to b14c90b b14c90b red before, green after ObjectApis Simplified audit entries.Should compare audit entries reflexively and symmetrically, never as access entries 1 1 1 1
7 f11ff41 to b14c90b b14c90b red before, green after ObjectApis Simplified audit entries.Should preserve the path, account and ReadData when converting an audit entry 1 1 1 1
8 f11ff41 to b14c90b b14c90b red before, green after ObjectApis Simplified audit entries.Should reduce ReadData to Read 1 1 1 1
9 f11ff41 to b14c90b b14c90b red before, green after ObjectApis Simplified entry comparison branches.Should distinguish identities, rights and types in audit entries and keep equal hashes consistent 1 1 1 1
10 b14c90b to c7a0383 c7a0383 red before, green after PathErrors An item whose owner may not change its permissions.Clear-NTFSAccess -DisableInheritance should take ownership, clear and protect the DACL, and not set an unchanged owner back 1 1 1 1
11 b14c90b to c7a0383 c7a0383 red before, green after SecurityDescriptor Set-NTFSSecurityDescriptor.When the write is denied until the cmdlet takes ownership.Should not report an owner that did not change when the write that took ownership leaves an empty DACL 1 1 1 1
12 c7a0383 to 2909a1c 2909a1c red before, green after Access InheritedFrom of access entries.Should name an unknown parent for an inherited entry and no source for an explicit entry when Windows cannot resolve the folders 1 1 1 1
13 c7a0383 to 2909a1c 2909a1c red before, green after Audit InheritedFrom of audit entries.Should name an unknown parent for an inherited entry and no source for an explicit entry when the privilege is disabled 1 1 0 0
14 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command and the error of a folder that Get-ChildItem2 cannot read.Should leave the loop for a break of a later command that takes the error of a nested folder 1 1 1 1
15 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command and the error of a folder that Get-ChildItem2 cannot read.Should leave the loop for a continue of a later command that takes the error of a nested folder 1 1 1 1
16 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Copy-Item2 should leave the loop for break after its first object and change nothing else 1 1 1 1
17 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Copy-Item2 should leave the loop for continue after its first object and change nothing else 1 1 1 1
18 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Copy-Item2 should stop after the first object for Select-Object -First 1 and change nothing else 1 1 1 1
19 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Copy-Item2 should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else 1 1 1 1
20 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Get-ChildItem2 should leave the loop for break after its first object and change nothing else 1 1 1 1
21 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Get-ChildItem2 should leave the loop for continue after its first object and change nothing else 1 1 1 1
22 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Get-DiskSpace should leave the loop for break after its first object and change nothing else 1 1 1 1
23 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Get-DiskSpace should leave the loop for continue after its first object and change nothing else 1 1 1 1
24 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Get-DiskSpace should stop after the first object for Select-Object -First 1 and change nothing else 1 1 1 1
25 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Get-DiskSpace should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else 1 1 1 1
26 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Get-NTFSSecurityDescriptor should leave the loop for break after its first object and change nothing else 1 1 1 1
27 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Get-NTFSSecurityDescriptor should leave the loop for continue after its first object and change nothing else 1 1 1 1
28 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Get-NTFSSecurityDescriptor should stop after the first object for Select-Object -First 1 and change nothing else 1 1 1 1
29 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Get-NTFSSecurityDescriptor should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else 1 1 1 1
30 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Get-NTFSSimpleAccess should leave the loop for break after its first object and change nothing else 1 1 1 1
31 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Get-NTFSSimpleAccess should leave the loop for continue after its first object and change nothing else 1 1 1 1
32 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Get-NTFSSimpleAccess should stop after the first object for Select-Object -First 1 and change nothing else 1 1 1 1
33 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Get-NTFSSimpleAccess should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else 1 1 1 1
34 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Move-Item2 should leave the loop for break after its first object and change nothing else 1 1 1 1
35 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Move-Item2 should leave the loop for continue after its first object and change nothing else 1 1 1 1
36 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Move-Item2 should stop after the first object for Select-Object -First 1 and change nothing else 1 1 1 1
37 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Move-Item2 should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else 1 1 1 1
38 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Remove-Item2 should leave the loop for break after its first object and change nothing else 1 1 1 1
39 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Remove-Item2 should leave the loop for continue after its first object and change nothing else 1 1 1 1
40 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Remove-Item2 should stop after the first object for Select-Object -First 1 and change nothing else 1 1 1 1
41 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Remove-Item2 should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else 1 1 1 1
42 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Set-NTFSOwner should leave the loop for break after its first object and change nothing else 1 1 1 1
43 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Set-NTFSOwner should leave the loop for continue after its first object and change nothing else 1 1 1 1
44 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Set-NTFSOwner should stop after the first object for Select-Object -First 1 and change nothing else 1 1 1 1
45 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Set-NTFSOwner should stop at the debug message for Select-Object -First 1 of the later command and change nothing else 1 1 1 1
46 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Set-NTFSOwner should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else 1 1 1 1
47 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Set-NTFSSecurityDescriptor should leave the loop for break after its first object and change nothing else 1 1 1 1
48 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Set-NTFSSecurityDescriptor should leave the loop for continue after its first object and change nothing else 1 1 1 1
49 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Set-NTFSSecurityDescriptor should stop after the first object for Select-Object -First 1 and change nothing else 1 1 1 1
50 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl A later command that ends the pipeline.Set-NTFSSecurityDescriptor should stop for a terminating error (Write-Error -ErrorAction Stop) of the later command and change nothing else 1 1 1 1
51 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl Recognizing the end of a pipeline by the type of the exception.Should not recognize <Description> 3 3 3 3
52 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl Recognizing the end of a pipeline by the type of the exception.Should recognize a PipelineStoppedException 1 1 1 1
53 2909a1c to c77ecbf c77ecbf red before, green after PipelineControl Recognizing the end of a pipeline by the type of the exception.Should recognize an exception whose base type is the flow control exception of PowerShell 1 1 1 1
54 c77ecbf to ee7c105 ee7c105 red before, green after ItemCmdlets Get-ChildItem2.Recursion, type filters, and depth.Should find a file whose name contains brackets by that name with -Filter 1 1 1 1
55 ee7c105 to ae3078f ae3078f red before, green after ItemCmdlets Get-ChildItem2.Recursion, type filters, and depth.Should reject a null -Filter 1 1 1 1
56 ae3078f to 40bf6a8 40bf6a8 red before, green after PipelineControl A later command that ends the pipeline.Copy-Item2 should stop for a terminating error (throw) of the later command and change nothing else 1 1 1 1
57 ae3078f to 40bf6a8 40bf6a8 red before, green after PipelineControl A later command that ends the pipeline.Get-ChildItem2 should stop for a terminating error (throw) of the later command and change nothing else 1 1 1 1
58 ae3078f to 40bf6a8 40bf6a8 red before, green after PipelineControl A later command that ends the pipeline.Get-DiskSpace should stop for a terminating error (throw) of the later command and change nothing else 1 1 1 1
59 ae3078f to 40bf6a8 40bf6a8 red before, green after PipelineControl A later command that ends the pipeline.Get-FileHash2 should stop at the verbose message for Select-Object -First 1 of the later command and change nothing else 1 1 1 1
60 ae3078f to 40bf6a8 40bf6a8 red before, green after PipelineControl A later command that ends the pipeline.Get-FileHash2 should stop at the verbose message for throw of the later command and change nothing else 1 1 1 1
61 ae3078f to 40bf6a8 40bf6a8 red before, green after PipelineControl A later command that ends the pipeline.Get-NTFSSecurityDescriptor should stop for a terminating error (throw) of the later command and change nothing else 1 1 1 1
62 ae3078f to 40bf6a8 40bf6a8 red before, green after PipelineControl A later command that ends the pipeline.Get-NTFSSimpleAccess should stop for a terminating error (throw) of the later command and change nothing else 1 1 1 1
63 ae3078f to 40bf6a8 40bf6a8 red before, green after PipelineControl A later command that ends the pipeline.Move-Item2 should stop for a terminating error (throw) of the later command and change nothing else 1 1 1 1
64 ae3078f to 40bf6a8 40bf6a8 red before, green after PipelineControl A later command that ends the pipeline.Remove-Item2 should stop for a terminating error (throw) of the later command and change nothing else 1 1 1 1
65 ae3078f to 40bf6a8 40bf6a8 red before, green after PipelineControl A later command that ends the pipeline.Set-NTFSOwner should stop at the debug message for throw of the later command and change nothing else 1 1 1 1
66 ae3078f to 40bf6a8 40bf6a8 red before, green after PipelineControl A later command that ends the pipeline.Set-NTFSOwner should stop for a terminating error (throw) of the later command and change nothing else 1 1 1 1
67 ae3078f to 40bf6a8 40bf6a8 red before, green after PipelineControl A later command that ends the pipeline.Set-NTFSSecurityDescriptor should stop at the verbose message for Select-Object -First 1 of the later command and change nothing else 1 1 1 1
68 ae3078f to 40bf6a8 40bf6a8 red before, green after PipelineControl A later command that ends the pipeline.Set-NTFSSecurityDescriptor should stop at the verbose message for throw of the later command and change nothing else 1 1 1 1
69 ae3078f to 40bf6a8 40bf6a8 red before, green after PipelineControl A later command that ends the pipeline.Set-NTFSSecurityDescriptor should stop for a terminating error (throw) of the later command and change nothing else 1 1 1 1
70 ae3078f to 40bf6a8 40bf6a8 red before, green after PipelineControl A later command that throws an exception of a type that a cmdlet handles.Get-ChildItem2 should pass on a thrown UnauthorizedAccessException 1 1 1 1
71 ae3078f to 40bf6a8 40bf6a8 red before, green after PipelineControl A later command that throws an exception of a type that a cmdlet handles.Remove-Item2 should pass on a thrown IOException and leave the next item 1 1 1 1
72 ae3078f to 40bf6a8 40bf6a8 red before, green after ItemCmdlets Get-ChildItem2.Recursion, type filters, and depth.Should return every item for -Filter *.*, also the ones without a dot in their names 1 1 1 1
73 7aa8315 to d44a200 d44a200 red before, green after PipelineControl A later command and the error of a folder that Get-ChildItem2 cannot read.Should pass on what a later command throws when it takes the error of a nested folder 1 1 1 1
74 7aa8315 to d44a200 d44a200 red before, green after Privileges Privileges when a later command takes the debug messages of the cmdlet.Should disable the privilege when Select-Object -First ends the pipeline at the message after its enabling 1 1 0 0
75 7aa8315 to d44a200 d44a200 red before, green after Privileges Privileges when a later command takes the debug messages of the cmdlet.Should pass on what a later command throws at the message after the enabling and disable the privileges 1 1 0 0

649
Tests/Coverage/Quality-Gate-Paths-2026-10-09.md

@ -0,0 +1,649 @@
# Quality-gate path review, 2026-10-09
Handoff 1 of the quality gate before 5.0.0. The C# code that no test visited
at `3442194` was inventoried again. Each path is now covered by a behavior
test or explained from source, and the evidence of every explanation is
named. The work is on `ai/quality-gate-paths`, from the reviewed head
`f11ff41` of `ai/quality-gate-coverage` (#117). Nothing was pushed, merged,
tagged, or published; the stable version stays 4.2.6 and rc6 is the latest
published candidate. Cmdlet design and the fate of unused classes stay with
the maintainer (Decisions 16, 21, and 22; Decision 22 is still proposed).
This report does not close the quality gate. A coverage percentage never
closes it, the [open items](#open-items-for-the-maintainer) remain, and the
fixes below still need the lab acceptance of gate 3.
## Candidate and source identity
- Measured source: `5a5d58b` (`5a5d58b88c639ca560bc1e66b17d57cc6e682eca`).
Two commits follow it and change no test and no executable code: the
first rewords a code comment in `NTFSSecurity\BaseCmdlets.cs` and one
sentence of the `Get-ChildItem2` page with its generated help, as the last
review passes asked; the second adds this report, its tables (with the
red/green rows), the appendix, and the Memory Bank notes.
- Base `f11ff41`. Since then 26 commits up to the measured source (8 `fix`,
1 `refactor`, 16 `test`, 1 `docs`): 40 files, 3,708 insertions and 80
deletions. Production code is 18 files with 277 insertions and 52
deletions (the four projects, without the generated help); the rest is
tests, documentation, and help.
- Build: Release, .NET Framework 4.5.2, 0 errors and 317 warnings: 296 of
CS1591, 19 of CS1574, one of CS0169, and one of CS0618. All are legacy and
none was suppressed.
- Tools: Windows PowerShell 5.1.26100, PowerShell 7.6.6, Pester 5.7.1,
AltCover 9.0.145 (net472).
- The measured assemblies are the Release files that the Validate run used.
AltCover saved them unchanged before it instrumented them:
| Assembly | SHA-256 |
| --- | --- |
| `NTFSSecurity.dll` | `155DE103C14A7CC69BA179D0AE4EEFA81DCF6F43A34B1856BC01393DDB76D443` |
| `Security2.dll` | `F6E4B6340F55A070B8E7D7678B7A6289E1A8430D393618EC269A315D70F32FC7` |
| `ProcessPrivileges.dll` | `7D037B32AED6C5879993C431F3EB614D61A621C8AF3474B59A6E0A5C451E3016` |
| `PrivilegeControl.dll` | `06FBD3337FAB9CE541F9030803468B7759935E152FB814E99F1C15CE9FA82D73` |
The Release build is not byte-reproducible: another build of the same source
gives other hashes, so these identify the measured files, not the source.
## Local validation
Four configurations in separate processes with the CI wrappers: Windows
PowerShell 5.1 and PowerShell 7, each elevated and as a basic user through
a restricted token. Each discovered 1,310 test cases (914 at `f11ff41`),
and no test failed. The instrumented coverage runs that followed gave the same
counts.
| Configuration | Passed | Failed | Skipped | Total |
| --- | ---: | ---: | ---: | ---: |
| Windows PowerShell 5.1, elevated | 1,286 | 0 | 24 | 1,310 |
| Windows PowerShell 5.1, basic user | 1,076 | 0 | 234 | 1,310 |
| PowerShell 7, elevated | 1,255 | 0 | 55 | 1,310 |
| PowerShell 7, basic user | 1,045 | 0 | 265 | 1,310 |
Skip eligibility is checked by row, not by name. NUnit keeps the placeholders
of a skipped data row, so skipped and executed names do not match. Each
configuration ran the suite once more in one Pester process, and every row
was recorded with its file, line, path, name, data, and result. The skipped
rows were 24, 234, 55, and 265, the same counts as in the validation runs.
The 578 skipped rows are 137 distinct tests, 350 of them rows with data.
Every skipped row has the same test, with the same data, executed in two
other configurations; no skipped row lacks an executed counterpart.
## Coverage
AltCover 9.0.145 OpenCover report of the frozen source, with the method of
the `3442194` baseline: the four configurations ran one after the other
without `--save`, from copied Release assemblies with their PDBs, AlphaFS and
System.Management.Automation excluded, and `runner --collect` recalculated
the report. The ratios are visited sequence or branch points divided by their
totals, not unique source lines.
| Assembly | Sequence points | Sequence coverage | Branch points | Branch coverage |
| --- | ---: | ---: | ---: | ---: |
| NTFSSecurity | 1,912/2,168 | 88.19% | 683/1,094 | 62.43% |
| Security2 | 1,055/1,225 | 86.12% | 506/742 | 68.19% |
| ProcessPrivileges | 205/219 | 93.61% | 72/125 | 57.60% |
| PrivilegeControl | 20/22 | 90.91% | 12/17 | 70.59% |
| Aggregate | 3,192/3,634 | 87.84% | 1,273/1,978 | 64.36% |
| Measurement | Sequence points | Branch points |
| --- | ---: | ---: |
| `3442194`, the baseline of this work | 2,641/3,559 (74.21%) | 974/1,933 (50.39%) |
| `73a0a7e` | 2,826/3,563 (79.32%) | 1,091/1,935 (56.38%) |
| `360417a` | 2,860/3,566 (80.20%) | 1,105/1,939 (56.99%) |
| `51412e0` | 3,067/3,566 (86.01%) | 1,192/1,939 (61.48%) |
| `d0acda3` | 3,079/3,566 (86.34%) | 1,207/1,939 (62.25%) |
| `3c19747` | 3,133/3,592 (87.22%) | 1,250/1,966 (63.58%) |
| `ae3078f` | 3,134/3,592 (87.25%) | 1,252/1,966 (63.68%) |
| `40bf6a8` | 3,166/3,626 (87.31%) | 1,257/1,977 (63.58%) |
| `a50070a` | 3,168/3,626 (87.37%) | 1,259/1,977 (63.68%) |
| `7aa8315` | 3,168/3,626 (87.37%) | 1,257/1,975 (63.65%) |
| `d61dffa` | 3,192/3,634 (87.84%) | 1,273/1,978 (64.36%) |
| `5a5d58b`, final | 3,192/3,634 (87.84%) | 1,273/1,978 (64.36%) |
- The sequence-point ratio rose from 74.21% to 87.84%, and the number of
unvisited points fell from 918 to 442. The production code changed as well
(3,559 to 3,634 points, because of the fixes), so the ratios are not
increments of one source.
- The branch summary counts 820 compiler-generated points (patterns such as
`foreach` and `using`), of which only 185 are visited. The explicit branch
points that a person wrote are 1,088/1,158 (93.96%).
- The interim measurements stopped at the commits that are named; they show
the progress, not a different method. `d61dffa` and `5a5d58b` give the same
counts: the commits between them changed tests, documentation, and the
wording of one code comment only.
## What the work changed
### Tests
136 `It` blocks were added; with data rows the suite grew from 914 to
1,310 cases per configuration. The tests assert state on disk, error ID,
category and target, continuation after a failure, the owner of the item, and
that a failed item writes no success-shaped object.
| Area | Test files | New `It` blocks |
| --- | --- | ---: |
| Public rule, identity, descriptor, and comparison objects, inheritance helpers, privilege objects, the extension methods | ObjectApis | 46 |
| Token handles, the privilege enabler and its finalizer, privilege control, the Init script, a privilege that a later command interrupts | Privileges | 17 |
| Item cmdlets, filters and their dot rules, depth, links, hard links, root-drive changes, disk space | ItemCmdlets, DriveRoot, Links | 24 |
| Access, audit, inheritance, owner, hash, and path errors; deny entries without rights; a NULL DACL | Access, Audit, Inheritance, Owner, FileHash, PathErrors | 25 |
| Security descriptors | SecurityDescriptor | 3 |
| A later command that ends the pipeline, for all 30 cmdlets and for the verbose, debug, and error streams | PipelineControl | 15 |
| The test helpers | TestHelpers | 6 |
### Defects found and fixed
| # | Defect | Fixed in | Regression guard |
| --- | --- | --- | --- |
| 1 | Public rule constructors lost the supplied path; simplified audit entries kept `ReadData` and were compared with access entries; boxed privilege values were compared wrongly | `b14c90b` | ObjectApis.Tests; 8 rows in each configuration |
| 2 | `Clear-NTFSAccess -DisableInheritance` and `Set-NTFSSecurityDescriptor` reported `RestoreOwnerError` for an owner that had not changed | `c7a0383` | PathErrors and SecurityDescriptor tests; 2 rows in each configuration |
| 3 | `InheritedFrom` read `unknown paren` and showed it for explicit entries | `2909a1c` | Access and Audit tests; 2 rows in the elevated configurations and 1 in the basic ones (the audit row needs the privilege) |
| 4 | Nine cmdlets handled the end of the pipeline (`break`, `continue`, `Select-Object -First`) as a failure of the item and went on; `Remove-Item2 -PassThru \| Select-Object -First 1` removed every item | `c77ecbf` | PipelineControl.Tests; 42 rows in each configuration: 35 for the nine cmdlets, 2 for the error of a nested folder, and 5 that pin the new check by type |
| 5 | `Get-ChildItem2 -Filter` read a bracket as a character class, so `Report[1].txt` was not found by its name | `ee7c105` | ItemCmdlets.Tests; 1 row in each configuration |
| 6 | A null `-Filter` ended in a `NullReferenceException`; introduced by fix 5 and never released | `ae3078f` | ItemCmdlets.Tests; 1 row in each configuration |
| 7 | A `throw` of a later command reaches a cmdlet through its Write call as an ordinary exception; the catch for the failures of an item reported it as the error of the item and went on, so that `Remove-Item2` removed the next item and the caller never saw the exception. Fix 4 found only the end of the pipeline by its type. `Set-NTFSSecurityDescriptor`, `Get-FileHash2`, and `Set-NTFSOwner` caught it also at a verbose or debug message | `40bf6a8` | PipelineControl.Tests; 16 rows in each configuration: 9 for a `throw` of the later command, 5 for a verbose or debug message, and 2 for a thrown type that a cmdlet handles. The rows for `Write-Error -ErrorAction Stop` of the later command turn green with fix 4, not with this one |
| 8 | `Get-ChildItem2 -Filter *.*` dropped the items without a dot in their names, most folders among them, so a listing with that filter missed them | `40bf6a8` | ItemCmdlets.Tests; 1 row in each configuration; found by a probe and by the independent review |
| 9 | The failed lookup of `InheritedFrom` leaked its native buffer | `40bf6a8` | none: no observable behavior, and the fallback tests run the path |
| 10 | `Get-ChildItem2` swallowed what a later command threw for an error that the cmdlet wrote for a nested folder, for example `Get-ChildItem2 -Recurse -File 2>&1 \| ForEach-Object { throw 'x' }`: the recursion took it for a failure of the folder above, wrote a verbose message, and ended the listing early, and the caller never saw the exception | `d44a200` | PipelineControl.Tests; 1 row in each configuration. The `break`, `continue`, and `-ErrorAction Stop` cases on the same error pass before and after |
| 11 | A cmdlet that enables the privileges left a privilege enabled when a later command ended the pipeline (`5>&1 \| Select-Object -First 2`) or threw at the debug message after the enabling: it noted the privilege only after that message, and `TryEnablePrivilege` took the exception for a failure to enable it and went on, so that all four privileges stayed enabled and the exception was lost | `d44a200` | Privileges.Tests; 2 rows in the elevated configurations (they need the privileges) |
Fixes 4, 7, and 10 changed the catch blocks of 10 cmdlets (`Get-DiskSpace`
now writes outside its try). Fixes 7 and 10 added a record of the exception
that the `WriteObject`, `WriteError`, `WriteVerbose`, and `WriteDebug`
methods of `BaseCmdlet` raise, which every catch-all that writes directly
passes on. `WriteWarning` is not noted: no catch-all encloses it. A scan of
the source finds 85 catch-all handlers under `NTFSSecurity\`. Sixteen of
them enclose a Write call directly in their try block: eleven pass the
exception on, and the other five are the Write methods themselves, which
record it and rethrow. Seventeen enclose only a helper, `InvokeAsOwner` or
`WriteChangesAsOwner`, whose owner restore can write a `RestoreOwnerError`;
they do not pass the exception on (open item 8). The scan is a text match
and cannot see a write inside another helper. The type check
`PipelineControl.IsEnd` remains as a second line of defense (open item 9).
### Red before, green after
The last column of the table above counts the test rows that fail on the
production code before a fix and pass after it. The first runs that showed
this were taken as each fix was written, with the tests of that commit. Their
logs were deleted with the temporary run folders, so their counts could not be
reproduced, and they are not used here. The evidence was measured again with
the final tests.
The eight test files that guard the fixes (ObjectApis, Access, Audit,
PathErrors, SecurityDescriptor, PipelineControl, ItemCmdlets, and Privileges:
650 cases per configuration) were laid over the production code of ten states
of the branch: the base `f11ff41`, the commit of each fix, the refactoring
`7aa8315`, and `d44a200`, the last commit that changes behavior. Each state was
built in Release in a separate worktree and run in the four configurations
with the focused runner of this work, the one that the mutation rounds use. It
runs only these eight files with its own Pester configuration, sets
`$ErrorActionPreference` to `Stop` like the CI wrappers, starts the basic runs
with `runas /trustlevel:0x20000`, and writes no NUnit file. A row that fails
at a state and passes at the next one is a guard of the fix between them; a
row that passes before and fails after would be a break.
| Step | Defects | Rows red before and green after: elevated 5.1, elevated 7, basic 5.1, basic 7 | Test files |
| --- | --- | --- | --- |
| Rows red at the base `f11ff41` | all | 76, 76, 73, 73 | |
| `f11ff41` to `b14c90b` | 1 | 8, 8, 8, 8 | ObjectApis |
| `b14c90b` to `c7a0383` | 2 | 2, 2, 2, 2 | PathErrors, SecurityDescriptor |
| `c7a0383` to `2909a1c` | 3 | 2, 2, 1, 1 | Access, Audit |
| `2909a1c` to `c77ecbf` | 4 | 42, 42, 42, 42 | PipelineControl |
| `c77ecbf` to `ee7c105` | 5 | 1, 1, 1, 1 | ItemCmdlets |
| `ee7c105` to `ae3078f` | 6 | 1, 1, 1, 1 | ItemCmdlets |
| `ae3078f` to `40bf6a8` | 7, 8, 9 | 17, 17, 17, 17 | ItemCmdlets, PipelineControl |
| `40bf6a8` to `7aa8315` | none (refactoring) | 0, 0, 0, 0 | none |
| `7aa8315` to `d44a200` | 10, 11 | 3, 3, 1, 1 | PipelineControl, Privileges |
| Rows red at `d44a200`, the control | none | 0, 0, 0, 0 | |
The steps add up to the rows that are red at the base, and no row passes at one
state and fails at the next in any configuration. At `d44a200` no row of the
650 fails in any configuration; the production code after it differs only in
an XML comment of `BaseCmdlets.cs`. The counts are of rows, not of names:
three rows that Pester lists under one unexpanded template name (see below)
make a count of names two lower for the first four states. In all 40 logs (10
states, 4 configurations), the failed count of the `RESULT` line, the number
of `FAILEDTEST` lines, and the rows in the CSV file agree. The commit that
each state was built from is the `source=` line of its build log, and it equals
the commit that the table names for all ten states; the assemblies of the
states were not hashed, because each build rewrote the hash file of the
frozen runner.
[`Quality-Gate-Paths-2026-10-09-RedGreen.csv`](Quality-Gate-Paths-2026-10-09-RedGreen.csv)
lists each guard test with its test file and its rows per configuration, and
[`Quality-Gate-Paths-2026-10-09-RedGreen-Logs.csv`](Quality-Gate-Paths-2026-10-09-RedGreen-Logs.csv)
lists the 40 logs with their sizes and SHA-256 values.
- Of the 42 rows of fix 4, 37 call the cmdlets. The other five pin the new
check by type, `IsEnd`, through reflection, and are red before only because
that check did not exist. Three of them are the rows of one data-driven
test; the `BeforeAll` of their block fails without the type, so Pester
lists them under the unexpanded template name.
- The 17 rows of the step to `40bf6a8` are 16 for defect 7 and one for defect
8, the `*.*` row. Defect 9 has no row.
- The audit row of fix 3 and the two rows of fix 11 need the privileges. They
are skipped in the basic configurations, and the eligibility check shows
that each of them is executed in the elevated ones.
- The matrix ran the verbose and debug rows of defect 7 as they are, with
`-ErrorAction SilentlyContinue`. They need it: under `Stop` and without it,
a handler that reports the exception of the later command as an error of the
item ends the pipeline with it, and the row cannot tell that from passing
the exception on (the mutations M19 and M20 escaped for this reason at
`d61dffa`, see below).
- One of these rows fails without a message before fix 7, in all four
configurations: `Set-NTFSSecurityDescriptor`, verbose message,
`Select-Object -First 1`. The other rows name what they expected. The
matrix alone does not say why this one fails. The attribution to fix 7 rests
on the source and on a mutation: at `ae3078f` the verbose message is written
inside a try whose catch reports the exception as `WriteSdError` and goes on
(`SetSecurityDescriptor.cs`, lines 47, 51, and 68), and the mutation M16,
which removes that pass-on at the final source, fails this row with the same
empty message in all four configurations.
- The matrix lays the final tests over earlier production code. It shows that
a guard fails without its fix and passes with it, not how the test looked
when it was first written, and a test that needs two fixes flips at the
later step. The rows that pass at the base are characterization tests and
tests of behavior that no fix changed; the mutations of the next section,
not this matrix, show that they detect a change.
### Where a test or a classification was wrong
These cases are why an explanation below is a claim with evidence, not a
fact.
- The first version of the restored-owner test passed without reaching the
retry, because a deny entry for the user does not stop an owner. It now
uses an OWNER RIGHTS deny and asserts that a plain write is denied.
- The `continue` after the second name comparison of `Get-ChildItem2` was
first classified as defensive. A probe showed that `*.*` reaches it, which
led to defect 8.
- A mutation that removed that comparison escaped at `630926f`, and
checking why exposed defect 5.
- The throw rows that the independent review asked for exposed defect 7.
- The review claimed that `Get-ChildItem2 -Recurse -ErrorAction Stop`
swallows the error of a nested folder. A probe on the build of `7aa8315`
showed that this error reaches the caller in both editions (a pipeline
stop, which the catch already passes on). The same cause was real for a
`throw` of a later command that takes the error through `2>&1`, which is
defect 10; the review withdrew the claim.
- Three tests that take the error of a nested folder through `2>&1` relied
on the default error action and failed in the first frozen run, because
the CI wrappers set `$ErrorActionPreference` to `Stop`. The focused runner
of this work had not set it. It does now, and the tests name
`-ErrorAction Continue`.
- With the runner at `Stop`, the mutation rounds at `d61dffa` showed that the
verbose and debug `throw` rows of the later-command tests ran without an
error action. Under `Stop`, the handler that reports the exception of the
later command as an item error ends the pipeline with it, and the row
cannot tell that from passing the exception on. Two mutations escaped (the
verbose record in two configurations, the debug record in all four). The
red evidence of defect 7 for these rows had been taken with the default
preference, so at CI they would not have been red. The rows now name
`-ErrorAction SilentlyContinue`, the rounds ran again, and the matrix above
shows these rows red before `40bf6a8` under a runner that sets `Stop`.
- The first Init-script tests asserted only the state of the Backup
privilege. With the module setting `$true` the module enables the
privileges before the cmdlet runs, so the branch that the test names could
not fail it; the review predicted the mutation that then escaped (the
condition of that branch). The tests now also assert the verbose message
that only the cmdlet writes.
- A test variable named `$forEach` is the automatic variable of `foreach`
and was empty inside Pester.
- A `Get-Acl` precondition for the hard-link test failed in the elevated
configurations: the permissions were read despite the deny entries. The
claim about them was dropped instead of asserted.
- Explanation rules that were wrong, or too strong, before they were
checked by probes and the review: a rule listed the `Extensions.ForEach`
and `GetParent` helpers as unused (the static scan does not see calls of
generic methods, which hid the callers of `ForEach`, and the rule did not
use the result of the scan for `GetParent`, which `Get-NTFSSimpleAccess`
calls), so both are tested directly now; the catch-all rule said that no
input could trigger it (a deny entry without rights does, and so does a
full ACL); the effective-access rule said that the library never throws
(the descriptor read is outside its try); the retry of the hash cmdlet
cannot be made to succeed with an OWNER RIGHTS entry, because Windows drops
it when the owner changes (probe); a dangling junction is read as the link
itself and triggers nothing (probe); a NULL DACL does reach the branch that
was called unreachable, and is tested; the hard-link rule said that no file
ACL is checked, which the probe shows only for the refused data.
## Do the new tests detect faults?
Bounded mutations change one statement of a frozen copy of the source, rebuild
it, and run the guarding tests in the four configurations. A mutation is
detected when its guard test fails. The source is restored exactly (the
diff of the frozen copy is empty) and Release is rebuilt before any green
validation. The mutations of a round are applied together only when no
guard can fail because of another mutation of the round; the failures that
no guard of the round names are listed in the logs and are the collateral of
the mutations (for example, the `throw` rows next to the `Select-Object` rows
that guard the same catch).
Final rounds on the frozen source `5a5d58b`: 26 mutations in four rounds.
Twenty-five are detected by their guard test in every configuration where
that test runs (2 of 2 for the guards that need the privileges). The 26th,
M25, is an equivalent mutant and survives as expected: with the check by type
(`IsEnd`) removed from `IsFromLaterCommand`, the recording of the write
methods still passes on every exception that a test can raise, so no test
fails; the direct tests of `IsEnd` pin its rules (open item 9). The failures
that no guard of a round names are the other tests of the mutated code: in
round 3, the Init test for `$false` under M28, the privilege test that
throws at the debug message under M26, and the audit test for the error
category under M34. M25 has none.
| Round | Mutation | File | Change | Guard | Detected |
| ---: | --- | --- | --- | --- | --- |
| 1 | M10 | `BaseCmdlets.cs` | `IsEnd` no longer recognizes a flow-control exception by the name of its base type | Should recognize an exception whose base type is the flow control exception of PowerShell | 4/4 |
| 1 | M12 | `GetChildItem2.cs` | `[ValidateNotNull]` removed from `-Filter` | Should reject a null -Filter | 4/4 |
| 1 | M13 | `SetSecurityDescriptor.cs` | the previous owner is not set back after a write that took ownership | Should set a previous owner back that the user can assign after the write that took ownership | 2/2 |
| 1 | M14 | `FileSystemAccessRule2.RemoveFileSystemAccessRules.cs` | a deny entry is not removed from a descriptor | Remove-NTFSAccess should remove a deny entry from the descriptor and leave the item unchanged | 4/4 |
| 1 | M16 | `SetSecurityDescriptor.cs` | the catch no longer passes on the exception of a later command | Set-NTFSSecurityDescriptor should stop at the verbose message for Select-Object -First 1 of the later command | 4/4 |
| 1 | M17 | `GetFileHash2.cs` | the catch no longer passes on the exception of a later command | Get-FileHash2 should stop at the verbose message for Select-Object -First 1 of the later command | 4/4 |
| 1 | M22 | `GetChildItem2.cs` | the second comparison of the name with the pattern is skipped | Should return only the items that match the whole pattern for -Filter *.*.* | 4/4 |
| 1 | M23 | `BaseCmdlets.cs` | `WriteError` no longer records its exception | Should pass on what a later command throws when it takes the error of a nested folder | 4/4 |
| 1 | M27 | `BaseCmdlets.cs` | `TryEnablePrivilege` no longer passes on the exception of a later command | Should pass on what a later command throws at the message after the enabling and disable the privileges | 2/2 |
| 2 | M11 | `GetChildItem2.cs` | `*.*` is no longer treated as `*` | Should return every item for -Filter *.*, also the ones without a dot in their names | 4/4 |
| 2 | M18 | `BaseCmdlets.cs` | `WriteObject` no longer records its exception | Copy-Item2 should stop for a terminating error (throw) of the later command | 4/4 |
| 2 | M19 | `BaseCmdlets.cs` | `WriteVerbose` no longer records its exception | Get-FileHash2 should stop at the verbose message for throw of the later command | 4/4 |
| 2 | M20 | `BaseCmdlets.cs` | `WriteDebug` no longer records its exception | Set-NTFSOwner should stop at the debug message for throw of the later command | 4/4 |
| 2 | M7 | `GetChildItem2.cs` | the catch of the recursion no longer passes on the exception of a later command | Get-ChildItem2 should leave the loop for break after its first object | 4/4 |
| 2 | M8 | `RemoveItem2.cs` | the catch no longer passes on the exception of a later command | Remove-Item2 should leave the loop for break after its first object | 4/4 |
| 2 | M9 | `BaseCmdlets.cs` | `IsEnd` no longer recognizes `PipelineStoppedException` | Should recognize a PipelineStoppedException | 4/4 |
| 3 | M25 | `BaseCmdlets.cs` | `IsFromLaterCommand` without the check by type (`IsEnd`) | none: expected to survive | 0/4, as expected |
| 3 | M26 | `BaseCmdlets.cs` | `EnablePrivilege` notes the privilege after the debug message again | Should disable the privilege when Select-Object -First ends the pipeline at the message after its enabling | 2/2 |
| 3 | M28 | `OtherCmdlets.cs` | `Enable-Privileges` in the Init script tests `EnablePrivileges == false` | Should enable the privileges when the module setting EnablePrivileges is $true | 2/2 |
| 3 | M29 | `FileSystemAccessRule2.GetFileSystemAccessRules.cs` | the index guard of the `InheritedFrom` sources is removed | Should return the one entry that .NET reports for a NULL DACL, without a source | 4/4 |
| 3 | M30 | `Extensions.cs` | `ForEach` accepts a null source | ForEach should reject a source that is null | 4/4 |
| 3 | M31 | `Extensions.cs` | `GetParent` returns a `DirectoryInfo` for a parent path that names a file | Should return a parent path that names a file as a FileInfo of AlphaFS | 4/4 |
| 3 | M32 | `AddAccess.cs` | the loop goes on to `-PassThru` after an `AddAceError` | Add-NTFSAccess, a deny entry without rights: Should write an AddAceError | 4/4 |
| 3 | M33 | `RemoveAccess.cs` | another ID for the `RemoveAceError` | Remove-NTFSAccess, a deny entry without rights: Should write a RemoveAceError | 4/4 |
| 3 | M34 | `AddAudit.cs` | another category for the `AddAceError` | Add-NTFSAudit, rights None: Should write an AddAceError | 2/2 |
| 4 | M24 | `BaseCmdlets.cs` | `IsFromLaterCommand` is always false | Should leave the loop for a break of a later command that takes the error of a nested folder | 4/4 |
Rounds at earlier commits found what these repeat. At `630926f`, nine of ten
mutations were detected; the one that escaped, the second name comparison of
`Get-ChildItem2`, exposed defect 5. At `ae3078f`, four more were detected. At
`a50070a`, seven of nine were detected: M11 escaped because another mutation
of the same round bypassed the same line (my overlap, so it moved to another
round), and M21 was an equivalent mutant, an exception filter that no
exception can reach, which `7aa8315` removed. At `d61dffa` the 26 mutations
above ran once and three escaped: M19 and M20, because the verbose and debug
rows ran under the error action of the CI runner, and M28, because the Init
test could not fail for the branch it names. `f4a16e1` fixed the tests, and
the rounds above ran again at the final source. Each round restored the
source exactly (the diff of the frozen copy was empty) and rebuilt Release.
## The remaining unvisited code
The aggregate report leaves 231 methods with 442 unvisited sequence points
and 70 unvisited explicit branch points; at `3442194` it left 918 sequence
points. Every method is classified and none is unclassified: 223 are
explained and 8 are open for the maintainer. The largest explained blocks
are the 103 one-line getters of cmdlet parameters, the registry model that no
cmdlet uses (105 points), the ownership retry of an audit write (47 points),
the unused native handle wrappers (34 points), and the catch-all of the
per-item loops (32 points).
| Category | Disposition | Methods | Unvisited sequence points | Unvisited explicit branch points |
| --- | --- | ---: | ---: | ---: |
| unused by cmdlets | Explained | 60 | 173 | 34 |
| parameter/API surface | Explained | 103 | 103 | 0 |
| defensive | Explained | 33 | 77 | 16 |
| environment-specific | Explained | 27 | 81 | 18 |
| unused by cmdlets | Open | 8 | 8 | 2 |
| **Total** | | **231** | **442** | **70** |
The explanation of every rule is in
[Quality-Gate-Paths-2026-10-09-Explanations.md](./Quality-Gate-Paths-2026-10-09-Explanations.md),
each with its evidence (an executed probe, a static scan of the compiled
code, or reading the source), its residual risk, and the tests that run the
neighboring paths. [The method rows](./Quality-Gate-Paths-2026-10-09-Methods.csv)
give, for every method, the source file, the unvisited lines, the number of
unvisited sequence and branch points, whether a cmdlet reaches it in the
compiled code, and its rule. "Explained" means a source-backed explanation
exists, not that a test runs the path. The scan behind the column "reachable
from a cmdlet" reads the compiled code and does not see calls of generic
methods: it reported `Extensions.ForEach` as unreachable although cmdlets
call it. Treat that column as a hint, not as evidence.
## Per-cmdlet coverage
[The cmdlet rows](./Quality-Gate-Paths-2026-10-09-Cmdlets.csv) give the
sequence and branch points of each of the 36 cmdlets, including their
closures. Every cmdlet has 72.5% or more of its sequence points visited. The
lowest are `Clear-NTFSAudit` (72.5%), `Disable-NTFSAuditInheritance` and
`Enable-NTFSAuditInheritance` (73.5%), `Add-NTFSAudit` (75.7%), and
`Remove-NTFSAudit` (75.9%): their unvisited points are the retry after an
access denial, which a local audit write never raises (rule
AUDIT-OWNER-RETRY). `Get-NTFSSecurityDescriptor` (77.8%) has the catch-all of
its loop and the closure of its owner retry, `Get-NTFSEffectiveAccess`
(80.3%) the catch blocks around a library that hides its own failures, and
`Get-NTFSInheritance` (80.9%) the catch and the retry of its loop.
### Parameter sets
The 36 cmdlets have 64 parameter sets, and 20 of the cmdlets have several:
path or security descriptor, and for the four cmdlets that add and remove
entries also simple or complex. Two kinds of evidence exist; neither is a
matrix of judged error and state tests for each set.
- A parser-based scan of the test files counts, for each set, the
invocations that can only bind it
([the sets](./Quality-Gate-Paths-2026-10-09-ParameterSets.csv)). Fifty-seven
sets have at least one. Seven have none, because their tests splat the
parameters, pipe the descriptor, or call the command through a variable:
`Add-NTFSAudit` (SDSimple), `Get-NTFSOrphanedAudit` (SD), `Get-NTFSOwner`
(SecurityDescriptor), `Remove-NTFSAccess` (PathSimple and SDSimple), and
`Remove-NTFSAudit` (PathSimple and SDSimple).
- No unvisited point lies on a parameter-set switch. The 20 cmdlets with
several sets have 168 unvisited points, 165 sequence points and 3 branch
points. Sixty-nine sequence points are property getters; the other 96
sequence points and 3 branch points are catch handlers with their closing
braces, their `WriteError` and `continue`, and the closures of the owner
retry. None of them mentions `ParameterSetName`, the descriptor list, or
`AppliesTo`.
## Open items for the maintainer
None of these was changed or reclassified as harmless.
1. `FileSystemSecurity2` converts from `FileSecurity` and `DirectorySecurity`
through `FileInfo("")` and `DirectoryInfo("")`, so every conversion
throws. No cmdlet uses it; fix or remove it.
2. `RemoveFileSystemAccessRuleAll` and `RemoveFileSystemAuditRuleAll` ignore
their account list and remove every explicit entry (finding #113). No
cmdlet passes an account list. The predicate of the discarded filter,
`Count() > 1`, would match no account that appears once, so using its
result as it stands would remove nothing.
3. The overloads of `AddFileSystemAccessRule` and `AddFileSystemAuditRule`
for a path and several accounts are lazy iterators, so nothing is written
until the caller enumerates the result; the overloads for an item and for
a descriptor write at once.
4. A `PrivilegeEnabler` that enabled a privilege and was never disposed
keeps the privilege enabled: a static list holds it, so its finalizer
never runs.
5. `Get-ChildItem2 -Filter` matches names twice, in the AlphaFS enumeration
and in the cmdlet, and their rules for a dot differ from those of
`Get-ChildItem` (probe p32, both editions unless noted). `*.*` now means
every item, but `Report.*` does not return the file `Report` and `Rep*.`
returns nothing where `Get-ChildItem` returns `Report`; `Report.` returns
nothing, as in PowerShell 7 but not in Windows PowerShell 5.1, which
returns `Report`; an empty value returns nothing without an error. The
documentation lists this and `ItemCmdlets.Tests` pins it, so a change is a
decision. Decide whether to align the rules.
6. The registry model, the unused helper classes, and the raw descriptor
readers have no caller (Decisions 21 and 22 govern them). Their
explanations say so; removing them is your decision.
7. The ownership retry of an audit write over SMB has no test that a local
volume can run: a local audit write never fails with access denied. The
lab suite covers the cmdlets over SMB; gate 3 should repeat it.
8. Seventeen handlers enclose only a helper that can write a
`RestoreOwnerError` (`InvokeAsOwner`, `WriteChangesAsOwner`) and do not
pass on what a later command raises at that write. By reading the code,
the handler then writes the error of the item, which raises again for
`-ErrorAction Stop` and for a stopped pipeline, so those still end the
command (not run); a later command that throws only for the
`RestoreOwnerError` record would have its exception swallowed. Closing it
means one check in each handler and a test with an owner that cannot be
set back (elevated only).
9. The type check `PipelineControl.IsEnd` backs up the recording of the
write methods for calls into PowerShell that nothing records, for example
`ShouldProcess` in the try block of `Remove-Item2`. No test makes that
call raise it, and the mutation that removes it is not detected (round 3);
only direct tests of its type rules cover it. Keep it as defense in depth
or remove it.
10. The catch of `Enable-Privileges` that rethrows a `ParseException` for a
malformed module setting is unvisited: the base class casts the same
value first.
11. By reading the source, the `-SecurityDescriptor` sets of the four cmdlets
that add and remove entries have no handler around the change: an
exception, such as one for a deny entry without rights, ends the cmdlet
with a terminating error, where the `-Path` sets write an error for the
item and go on. The probe of a full ACL ran into it with
`-SecurityDescriptor`. The zero-mask tests use `-Path` only. Whether the
descriptor sets should report per item is a design decision.
12. The test helpers `Add-TestDenyRule`, `Set-TestOwner`, and
`Block-TestReadPermission` and `Block-TestWritePermission` accept an
existing link as the item: the check of `Assert-TestSandboxPath` covers
the folders of the path, not the item. Only `Set-TestNullDacl` refuses a
link. No test passes a link to them.
13. Decision 22 remains proposed, and the stable 5.0.0 gate stays open.
## Completion matrix
| Criterion of the handoff | Status | Evidence |
| --- | --- | --- |
| Every currently unvisited method is inventoried | Done | 231 methods in the CSV, none unclassified |
| Each path is tested or has a source-backed explanation | Done, with open items | explanations by rule; 8 methods stay open for the maintainer |
| Reachable gaps closed first, without duplicating earlier tests | Done | 136 new `It` blocks; defects 1 to 11 |
| Behavior tests before production changes | Not evidenced | each of the eight fix commits carries its tests and its fix together, and the red runs that were taken while the tests were written were deleted with their run folders; the order cannot be shown from the history or from kept logs. The first test of defect 8 pinned `*.*` as the design and was changed after a review finding |
| Regressions that fail without the fix | Done, with exceptions | the red/green matrix: 76 rows (73 in the basic configurations) fail at the base, each is green at the step of its fix, none breaks later; the mutations. Exceptions: defect 9 has no guard; the guards of defect 11 and the audit row of defect 3 run only elevated |
| All cmdlets and parameter sets have meaningful error and state tests | Partly | per-cmdlet coverage of 72.5% or more; the parameter-set evidence is a parser count and the coverage of the switches, not a matrix of judged tests |
| Full suite in both editions and privilege modes | Passed | 1,310 cases in each, zero failures |
| No required case is skipped across the matrix | Passed | 578 skipped rows, each executed in two other configurations |
| Frozen Release measurement with all four configurations | Done | source pin, XML, hashes, exclusions |
| Differences from the `3442194` baseline explained | Done | the coverage section |
| Self-review and one independent finished-diff review | Done, see the limits | the review section |
| Docs, help, changelog, Memory Bank | Done | `CHANGELOG.md`, cmdlet page, help, Memory Bank |
| Commit locally, no remote change | Done | the commits above; no push |
## Checks not run, and limits
- The lab suite, the published-package acceptance, and the OS matrix were not
run here; they belong to gate 3.
- A matrix of error and state tests for each parameter set was not built.
The evidence is described under [Parameter sets](#parameter-sets): a
parser-based count of the test invocations and the coverage of the
parameter-set switches. It does not judge how meaningful each test is.
- The explanations are not tests. Each one names its evidence; an
explanation that rests on reading the source only says so.
- The custom `security-reviewer` agent could not start because of its
configured model, and no model setting was changed. The built-in read-only
`code-review` agent made the static review passes below; none of them built
or ran anything.
- The coverage percentages are not a measure of the quality of the
assertions; the mutations are the measure for the new tests. A mutation
that is not detected is reported, not hidden.
- The CI scripts set `$ErrorActionPreference` to `Stop`, and a test that
relies on a non-terminating error must name its error action. The tests
were checked for this by reading and by the mutation rounds, and the
focused runner of this work sets `Stop` too, but nothing enforces it: a new
row without an error action would regain the weakness silently. A test
that every `Run` block of `PipelineControl.Tests` names one is a possible
hardening that was not added.
- The red/green matrix measures the final tests on older production code. It
does not show the order in which a test and its fix were written; the red
runs of that time were not kept. The ten states are identified by the
commit of their build, not by hashes of their assemblies.
- All runs are on one Windows Server 2025 host. The privileged tests skip
without the privileges and run elevated, so a basic-user run cannot show
them; the eligibility check shows that each of them runs elsewhere.
## Handoff to gate 3
Repeat the affected acceptance on the packaged candidate before it is
published. Each fix changes behavior that the lab can observe:
| Fix | What to repeat |
| --- | --- |
| `c7a0383` | `Clear-NTFSAccess -DisableInheritance` and `Set-NTFSSecurityDescriptor` on an item that the user owns, over SMB: no `RestoreOwnerError` |
| `2909a1c` | `Get-NTFSAccess` and `Get-NTFSAudit` with `GetInheritedFrom` for an item whose parent folder is unreadable |
| `c77ecbf`, `40bf6a8`, `d44a200` | `Remove-Item2`, `Copy-Item2`, `Move-Item2`, `Set-NTFSOwner`, `Set-NTFSSecurityDescriptor`, and `Get-ChildItem2` with `Select-Object -First 1`, `break`, and a `throw` of a later command, also on the verbose, debug, and error streams: no further item changes, and the caller sees the error |
| `d44a200` | `Get-NTFSOwner` or `Get-NTFSAccess` with the debug stream taken by `Select-Object -First 2`: the Take Ownership privilege is disabled again afterwards (elevated) |
| `ee7c105`, `ae3078f`, `40bf6a8` | `Get-ChildItem2 -Filter` with brackets, `*.*`, and a null value on a share |
| `b14c90b` | the rule, audit, and privilege objects in the packaged module |
## Review
The custom `security-reviewer` agent could not start because of its
configured model, and no model setting was changed. A built-in read-only
`code-review` agent made nine static passes: seven over the diff in ranges,
which also read the draft appendix and the draft report; a read of the final
report, the appendix, the tables, the Memory Bank notes, and the lab README;
and a read of the red/green evidence against its raw logs. It built and ran
nothing. No pass found a Blocker or a Major issue. Every finding was either
fixed, turned into an open item, or answered with a probe; one finding
(`-ErrorAction Stop` swallowed by the recursion) was refuted by a probe and
withdrawn by the reviewer.
| Pass | Range | Findings (Minor unless stated) | What became of them |
| ---: | --- | --- | --- |
| 1 | `f11ff41..cd56f49` | A break or continue test whose first output was above the recursion, so it never reached the frame where `Get-ChildItem2` swallowed both; tests that pinned the lazy path overloads as correct; a `PrivilegeEnabler` test without `finally`; three Nits | defect 4 for every cmdlet; open item 3; `finally` added |
| 2 | `cd56f49..630926f` | No test for the sandbox guard of the drive-mapping helper; its drive letter collided with two tests; no `throw` or `-ErrorAction Stop` row, which the engine could deliver as another exception | guard tests, the free-letter choice, and the rows that found defect 7 |
| 3 | `630926f..ae3078f` | `*.*` pinned as design; a restored-owner test that could pass without reaching the restore; the help dropped the asterisks of the `-Filter` paragraph; wording (Nit) | defect 8; a precondition in the test; the paragraph rewritten for platyPS |
| 4 | `ae3078f..40bf6a8` and the first appendix | The recording of the write methods judged sound; gaps in how handlers were counted; eight rules doubted with evidence | handlers and rules corrected, the tests of this report added |
| 5 | `40bf6a8..7aa8315` and the draft report | The draft described an older commit; the check by type had no behavior test; wording about "a later command" (Nits) | the report regenerated; reflection tests of the check by type; typed-throw rows |
| 6 | `7aa8315..d0bd1af` | The dot paragraph named the wrong layer; Init tests that could not fail for their branch; the first-nested-folder assertion; a link as the item of `Set-TestNullDacl`; wording (Nits) | `f4a16e1` and `5a5d58b` |
| 7 | `d0bd1af..5a5d58b` | A comment named three cmdlets where one calls `ShouldProcess` in a try block (Nit); optional: a test that every `Run` block names an error action | the comment fixed after the measured source; the guard test is not added (limits) |
| 8 | the final report, appendix, tables, Memory Bank notes, lab README | Four Minor: the production total left out `ProcessPrivileges`; the red runs of the fixes were not preserved, and "six `throw` cases" could not be reproduced; the hard-link rule stated more than its evidence; the Memory Bank said that AlphaFS follows the Windows dot rules. Nine Nits: the dot rule that depends on the edition, the count of unvisited points, the helper callers, the warning codes, the completion row together with the "test-first" wording of the Memory Bank, the wording "no test can make that call raise it", the trigger of a full ACL, and two missing items (the test helpers that accept a link, and the dependence on the ambient error action) | the numbers and wording corrected; the red evidence measured again (the red/green matrix above); the rule and the Memory Bank corrected; open items 11 and 12 and the limit about the error action added |
| 9 | the red/green section, its CSV, the completion row, the Memory Bank notes, against the 40 raw logs | Two Minor: the test file of defect 2 was named wrongly (PathErrors, not Access); the completion row said that tests came before the fixes, which no kept evidence shows. Four Nits: the focused runner was called the CI wrappers and its result an NUnit result; defect 7 read "a `throw` or a terminating error", where only the `throw` rows turn green with fix 7; one guard row fails without a message; the 40 logs had no fingerprint | the CSV has a test-file column and the step table follows it; the row is split into an order that is not evidenced and a guard that is measured; the wording corrected; the empty message explained by the source and the mutation M16; a manifest with the SHA-256 of every log |
Pass 8 checked the identity paragraph, the pass and skip table, the skipped
rows, the assembly and evidence hashes, the mutation table, the coverage and
category numbers, and the parameter-set counts against git and the CSVs, and
found them to agree; the production total was the one exception. Pass 9
recomputed the step table, the sub-counts of the defects table, and the
claims about the unexpanded name and about the production code after
`d44a200` from the raw logs and git, and found them to agree; its findings
are about the test file, the order of tests and fixes, and wording. The
corrections that followed pass 9 were checked by the author and by no other
pass. No pass is a substitute for the lab acceptance of gate 3.
## Evidence outside git
Raw evidence is local and not committed. It is in the session folder
`C:\Users\install\.copilot\session-state\4b12e2f4-d4c7-4a5d-883a-ddb7421c4848\files\qg-paths`:
the OpenCover XML of the four configurations and the aggregate, the NUnit
results of the Validate runs, the unvisited-method and unvisited-point
inventories, the per-row eligibility CSV files, the mutation logs, the logs of
the red/green matrix (the focused run of each of the ten states in the four
configurations), the probe scripts and their results, and the scripts that
produce the tables.
| File | SHA-256 |
| --- | --- |
| `coverage/coverage.xml` | `3EFAE43A574B62AB5DDC80022E52D28B8606E82F0BF9045BECB87B2ACBADB8D4` |
| `coverage/elevated-Desktop.xml` | `1DA9DAE5D5CDA1EB19079CFD69137EAC3314B8B926AD0D33C90026F2DE894B7D` |
| `coverage/basic-Desktop.xml` | `E43E7B457161E4C63A044215362694E5AB10AAE2AA3E057E459AD2033DA98462` |
| `coverage/elevated-Core.xml` | `DE329D7E640BD3E4F6C2DCC9EBF2217EF5E0E7F38C114738D0591FEBEAAEDD94` |
| `coverage/basic-Core.xml` | `55D167E4764A9EE5DA2EBBAE64C3E9776434D70144DD6C6A810DEA8890524AA6` |
| `validate/elevated-Desktop.xml` | `0FC57455840153FBCBAB53A1AEBF6ABA02088C3A647CD4116CAE29E23F587AAE` |
| `validate/basic-Desktop.xml` | `9E27622F9ED0A92948C6BF59DD46AEF8AB6D4BEBF45BEAE4B0DB2AFDDF5D3011` |
| `validate/elevated-Core.xml` | `1B8A5DB7A4A9337AAB5E63011E4807CCCC1C43BF1E171ADF3E15BB312AEED80E` |
| `validate/basic-Core.xml` | `BFA5D4BC57EBC36A108F465418D5567FF05A0E4BFBF2E7096E4F785CAC74E8C2` |
| `inventory-5a5d58b/unvisited-methods.csv` | `F5B736EDF662C9C0E3DEBD4302EE6F7A59DF0623FDCC2A479ECCA4C89773F007` |
| `inventory-5a5d58b/unvisited-points.csv` | `56AA0B5DDA67B45545CCCCEF41497CB300B54A487BAFB2CAA4EA4E0D024749A5` |
| `classification-5a5d58b.csv` | `E2CD8700C5C8DFB52D33D6E6DB96E3EBD630D94CB4A4D224B1BD1C1F356CECB0` |
| `eligibility-run-5a5d58b/skipped-rows.csv` | `AD584A885AB5C73B4E4CC7C3CF42CC92114C198E48ABE0D367BB001AB93E7489` |
| `measured-assemblies-5a5d58b.csv` | `A8030B7AD00E2B4632C88C46C336DFBC4873383A021F6B446DA4CED6462DA51C` |
| `mutations-5a5d58b-r1/mutation-results.csv` | `29D4E5EEC5FCCD91E8E2C1323C030A75691B5FA530C8345757729E9DCB58EC61` |
| `mutations-5a5d58b-r2/mutation-results.csv` | `87EC131C64C6F3AD0F440C41FA8CF0F0618E7875AAB5EC7D6A1C647CF361C98E` |
| `mutations-5a5d58b-r3/mutation-results.csv` | `05D7752A5C9B0DA5F5A3F35ED83C471FCEDDAE5EA967D557AA0AD334A02A244C` |
| `mutations-5a5d58b-r4/mutation-results.csv` | `EB774D4621658DFA8B4390FFC6201F913E88A214FB2510B69A5B9F50D922B18D` |
| `redgreen/redgreen-results.csv` | `C2327B991608489A85CC32CE17CC58F258348677EA31815F123D228AECBCB55A` |
| `redgreen/redgreen-failed-rows.csv` | `5AA4E1514F75CEEAACD8206F3E8C7727C2E970E2D45E7D545FD51F02EF21ED1F` |
| `redgreen/redgreen-guards.csv` (the CSV file of this folder with the red/green rows) | `D5D4720B2E6DA3619889C75DF3A2876E505FEED19BDD34695C92EF7421CD3558` |
| `redgreen/redgreen-summary.csv` | `46987DE40D526A279871E5F1915CDC346C576B3E644E169A1E3C79B7181B9052` |
| `redgreen/redgreen-log-manifest.csv` (the CSV file of this folder with the hash of each of the 40 logs) | `3E45ABFD850155F276DA5AF0E3FBEE83800D2C269DBB59B800042AC0080D4615` |
| `redgreen/redgreen-verify.csv` | `717AB27434D35A59750C35DB8B33F548FC6F7F3E01353F3BC290629516735DF7` |
| `redgreen/redgreen-driver.log` | `FCB9AE5980092BE97E362B2BE412CAE771C0EFB0CC326CBE814726A246D535EA` |

70
Tests/DriveRoot.Tests.ps1

@ -1,12 +1,18 @@
<#
Tests the cmdlets of the module built in NTFSSecurity\bin\Release on the root folder of the system drive. The tests
only read, so they need no sandbox.
Tests the cmdlets of the module built in NTFSSecurity\bin\Release on the root folder of the system drive, which they
only read, and on the root of a drive that maps a folder of a sandbox, which they change.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.'
)]
param ()
BeforeDiscovery {
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force
# The restricted token of the basic-user runner cannot define a drive letter.
$canMapDrive = Test-DriveMappingAvailable
}
BeforeAll {
$modulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\NTFSSecurity\bin\Release\NTFSSecurity.psd1'
Import-Module -Name $modulePath -Force -ErrorAction Stop
@ -51,3 +57,63 @@ Describe 'The root folder of a drive' {
@($entries | ForEach-Object -Process { $_.Account.Sid } | Sort-Object) | Should -Be $expected
}
}
# A test must not change the permissions of a volume. A drive letter that subst maps to a folder of a sandbox is the root
# of a drive for Windows and for the module, so the code that changes the root folder of a drive changes that folder.
Describe 'Changing the root folder of a drive' -Skip:(-not $canMapDrive) {
BeforeAll {
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force
$sandbox = New-TestSandbox -Name 'DriveRootChange'
$mapped = New-TestSandboxItem -Sandbox $sandbox -Name 'Mapped' -Directory
$driveRoot = New-TestDriveMapping -Sandbox $sandbox -Path $mapped
if (-not $driveRoot) {
throw 'No drive letter could be mapped to the sandbox folder.'
}
function Get-MappedEntry {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseSingularNouns', '', Justification = 'The helper returns the explicit entries of the folder.'
)]
param ([string] $Account)
@((Get-Acl -LiteralPath $mapped).GetAccessRules($true, $false, $sidType) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq $Account })
}
}
AfterAll {
if ($driveRoot) {
Remove-TestDriveMapping -Root $driveRoot
}
Remove-TestSandbox -Sandbox $sandbox
}
It 'Should read the access entries of the folder that the drive maps' {
$expected = @((Get-Acl -LiteralPath $mapped).GetAccessRules($true, $true, $sidType) |
ForEach-Object -Process { $_.IdentityReference.Value } | Sort-Object)
$entries = @(Get-NTFSAccess -Path $driveRoot)
@($entries | ForEach-Object -Process { $_.Account.Sid } | Sort-Object) | Should -Be $expected
}
It 'Should add and remove an access entry of the folder that the drive maps' {
Add-NTFSAccess -Path $driveRoot -Account 'S-1-1-0' -AccessRights ReadData -ErrorAction Stop
Get-MappedEntry -Account 'S-1-1-0' | Should -HaveCount 1
Remove-NTFSAccess -Path $driveRoot -Account 'S-1-1-0' -AccessRights ReadData -ErrorAction Stop
Get-MappedEntry -Account 'S-1-1-0' | Should -BeNullOrEmpty
}
It 'Should block and restore the access inheritance of the folder that the drive maps' {
Disable-NTFSAccessInheritance -Path $driveRoot -ErrorAction Stop
(Get-Acl -LiteralPath $mapped).AreAccessRulesProtected | Should -BeTrue
Enable-NTFSAccessInheritance -Path $driveRoot -ErrorAction Stop
(Get-Acl -LiteralPath $mapped).AreAccessRulesProtected | Should -BeFalse
}
}

19
Tests/FileHash.Tests.ps1

@ -61,6 +61,17 @@ Describe 'Get-FileHash2' {
$hashError.FullyQualifiedErrorId | Should -BeLike 'HashAlgorithmNotAvailable,*'
}
# PowerShell binds only the named algorithms to -Algorithm, so a program that calls the public method with an
# undefined value is the only way to get here.
It 'Should refuse an algorithm that the enumeration does not define when the public method creates it' {
$unknown = [Enum]::ToObject([Security2.FileSystem.FileInfo.HashAlgorithms], 99)
$failure = { [Security2.FileSystem.FileInfo.Extensions]::CreateHashAlgorithm($unknown) } | Should -Throw -PassThru
$failure.Exception.GetBaseException() | Should -BeOfType [System.ArgumentOutOfRangeException]
$failure.Exception.GetBaseException().ParamName | Should -BeExactly 'algorithm'
}
It 'Should warn once that MACTripleDES is deprecated' -Skip:$isCore {
$results = @(Get-FileHash2 -Path $first, $second -Algorithm MACTripleDES -WarningVariable hashWarnings -WarningAction SilentlyContinue)
@ -69,6 +80,14 @@ Describe 'Get-FileHash2' {
$hashWarnings | Should -HaveCount 1
$hashWarnings[0].Message | Should -BeLike '*MACTripleDES*random key*deprecated*'
}
# PowerShell calls the cmdlet once for each object in the pipeline; the warning belongs to the command.
It 'Should warn once that MACTripleDES is deprecated for several objects in the pipeline' -Skip:$isCore {
$results = @($first, $second | Get-FileHash2 -Algorithm MACTripleDES -WarningVariable hashWarnings -WarningAction SilentlyContinue)
$results | Should -HaveCount 2
$hashWarnings | Should -HaveCount 1
}
}
Context 'When -Path contains a folder' {
It 'Should skip the folder and hash the files that follow it' {

124
Tests/Inheritance.Tests.ps1

@ -430,3 +430,127 @@ Describe 'Access inheritance cmdlets' {
}
}
}
Describe 'Set-NTFSInheritance with an in-memory descriptor' {
It 'Should set access inheritance enabled=<Enable> on a <Type> only when the descriptor is written' -ForEach @(
@{ Type = 'file'; Enable = $false }
@{ Type = 'file'; Enable = $true }
@{ Type = 'folder'; Enable = $false }
@{ Type = 'folder'; Enable = $true }
) {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'DescriptorAccessState' -Directory:($Type -eq 'folder')
Assert-TestSandboxPath -Sandbox $sandbox -Path $path
Set-NTFSInheritance -Path $path -AccessInheritanceEnabled (-not $Enable) -ErrorAction Stop
Add-NTFSAccess -Path $path -Account 'S-1-1-0' -AccessRights ReadData -AppliesTo ThisFolderOnly
$before = (Get-Acl -LiteralPath $path).GetSecurityDescriptorSddlForm('Access')
$ownerBefore = (Get-Acl -LiteralPath $path).Owner
$sd = Get-NTFSSecurityDescriptor -Path $path
$result = @(Set-NTFSInheritance -SecurityDescriptor $sd -AccessInheritanceEnabled $Enable -PassThru -ErrorAction Stop)
$result | Should -HaveCount 1
$result[0].FullName | Should -BeExactly $path
$result[0].Name | Should -BeExactly ([IO.Path]::GetFileName($path))
$result[0].AccessInheritanceEnabled | Should -Be $Enable
$sd.SecurityDescriptor.AreAccessRulesProtected | Should -Be (-not $Enable)
(Get-Acl -LiteralPath $path).GetSecurityDescriptorSddlForm('Access') | Should -BeExactly $before
Set-NTFSSecurityDescriptor -SecurityDescriptor $sd -ErrorAction Stop
(Get-Acl -LiteralPath $path).AreAccessRulesProtected | Should -Be (-not $Enable)
(Get-Acl -LiteralPath $path).Owner | Should -BeExactly $ownerBefore
@(Get-NTFSAccess -Path $path -ExcludeInherited | Where-Object { $_.Account.Sid -eq 'S-1-1-0' }) |
Should -HaveCount 1
}
It 'Should set audit inheritance enabled=<Enable> on a <Type> without changing its DACL or owner' -Skip:(-not $canChangeAudit) -ForEach @(
@{ Type = 'file'; Enable = $false }
@{ Type = 'file'; Enable = $true }
@{ Type = 'folder'; Enable = $false }
@{ Type = 'folder'; Enable = $true }
) {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'DescriptorAuditState' -Directory:($Type -eq 'folder')
Assert-TestSandboxPath -Sandbox $sandbox -Path $path
Set-NTFSInheritance -Path $path -AuditInheritanceEnabled (-not $Enable) -ErrorAction Stop
Add-NTFSAudit -Path $path -Account 'S-1-1-0' -AccessRights Delete -AuditFlags Success -AppliesTo ThisFolderOnly
$before = (Get-NTFSSecurityDescriptor -Path $path).SecurityDescriptor.GetSecurityDescriptorSddlForm('Audit')
$daclBefore = (Get-Acl -LiteralPath $path).GetSecurityDescriptorSddlForm('Access')
$ownerBefore = (Get-Acl -LiteralPath $path).Owner
$sd = Get-NTFSSecurityDescriptor -Path $path
$result = @(Set-NTFSInheritance -SecurityDescriptor $sd -AuditInheritanceEnabled $Enable -PassThru -ErrorAction Stop)
$result | Should -HaveCount 1
$result[0].AuditInheritanceEnabled | Should -Be $Enable
$sd.SecurityDescriptor.AreAuditRulesProtected | Should -Be (-not $Enable)
(Get-NTFSSecurityDescriptor -Path $path).SecurityDescriptor.GetSecurityDescriptorSddlForm('Audit') |
Should -BeExactly $before
Set-NTFSSecurityDescriptor -SecurityDescriptor $sd -ErrorAction Stop
(Get-NTFSInheritance -Path $path).AuditInheritanceEnabled | Should -Be $Enable
(Get-Acl -LiteralPath $path).GetSecurityDescriptorSddlForm('Access') | Should -BeExactly $daclBefore
(Get-Acl -LiteralPath $path).Owner | Should -BeExactly $ownerBefore
@(Get-NTFSAudit -Path $path -ExcludeInherited | Where-Object { $_.Account.Sid -eq 'S-1-1-0' }) |
Should -HaveCount 1
}
It 'Should keep audit inheritance unknown when requested enabled=<_> on an access-only descriptor' -ForEach @($false, $true) {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'DescriptorUnknownAudit'
$sd = New-Object -TypeName 'Security2.FileSystemSecurity2' -ArgumentList (
(Get-Item2 -Path $path), [System.Security.AccessControl.AccessControlSections]::Access
)
$before = (Get-Acl -LiteralPath $path).Sddl
$result = @(Set-NTFSInheritance -SecurityDescriptor $sd -AuditInheritanceEnabled $_ -PassThru -ErrorAction Stop)
$result | Should -HaveCount 1
$result[0].AuditInheritanceEnabled | Should -BeNullOrEmpty
$raw = New-Object -TypeName 'System.Security.AccessControl.RawSecurityDescriptor' -ArgumentList (
$sd.SecurityDescriptor.GetSecurityDescriptorBinaryForm(), 0
)
$null -eq $raw.SystemAcl | Should -BeTrue
(Get-Acl -LiteralPath $path).Sddl | Should -BeExactly $before
}
}
Describe 'Dedicated inheritance descriptor output' {
It '<Command> should return the changed descriptor state without writing the <Type>' -ForEach @(
@{ Command = 'Enable-NTFSAccessInheritance'; Type = 'file'; Enable = $true }
@{ Command = 'Enable-NTFSAccessInheritance'; Type = 'folder'; Enable = $true }
@{ Command = 'Disable-NTFSAccessInheritance'; Type = 'file'; Enable = $false }
@{ Command = 'Disable-NTFSAccessInheritance'; Type = 'folder'; Enable = $false }
) {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'DedicatedAccessDescriptor' -Directory:($Type -eq 'folder')
Assert-TestSandboxPath -Sandbox $sandbox -Path $path
Set-NTFSInheritance -Path $path -AccessInheritanceEnabled (-not $Enable) -ErrorAction Stop
$before = (Get-Acl -LiteralPath $path).Sddl
$sd = Get-NTFSSecurityDescriptor -Path $path
$result = @(& $Command -SecurityDescriptor $sd -PassThru -ErrorAction Stop)
$result | Should -HaveCount 1
$result[0].FullName | Should -BeExactly $path
$result[0].AccessInheritanceEnabled | Should -Be $Enable
(Get-Acl -LiteralPath $path).Sddl | Should -BeExactly $before
Set-NTFSSecurityDescriptor -SecurityDescriptor $sd -ErrorAction Stop
(Get-Acl -LiteralPath $path).AreAccessRulesProtected | Should -Be (-not $Enable)
}
It '<Command> should return the changed audit state without writing the <Type>' -Skip:(-not $canChangeAudit) -ForEach @(
@{ Command = 'Enable-NTFSAuditInheritance'; Type = 'file'; Enable = $true }
@{ Command = 'Enable-NTFSAuditInheritance'; Type = 'folder'; Enable = $true }
@{ Command = 'Disable-NTFSAuditInheritance'; Type = 'file'; Enable = $false }
@{ Command = 'Disable-NTFSAuditInheritance'; Type = 'folder'; Enable = $false }
) {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'DedicatedAuditDescriptor' -Directory:($Type -eq 'folder')
Assert-TestSandboxPath -Sandbox $sandbox -Path $path
Set-NTFSInheritance -Path $path -AuditInheritanceEnabled (-not $Enable) -ErrorAction Stop
$before = (Get-NTFSSecurityDescriptor -Path $path).SecurityDescriptor.GetSecurityDescriptorSddlForm('Audit')
$sd = Get-NTFSSecurityDescriptor -Path $path
$result = @(& $Command -SecurityDescriptor $sd -PassThru -ErrorAction Stop)
$result | Should -HaveCount 1
$result[0].FullName | Should -BeExactly $path
$result[0].AuditInheritanceEnabled | Should -Be $Enable
(Get-NTFSSecurityDescriptor -Path $path).SecurityDescriptor.GetSecurityDescriptorSddlForm('Audit') |
Should -BeExactly $before
Set-NTFSSecurityDescriptor -SecurityDescriptor $sd -ErrorAction Stop
(Get-NTFSInheritance -Path $path).AuditInheritanceEnabled | Should -Be $Enable
}
}

339
Tests/ItemCmdlets.Tests.ps1

@ -140,12 +140,134 @@ Describe 'Get-ChildItem2' {
($relative | Sort-Object) -join ',' | Should -Be (($Expected | Sort-Object) -join ',')
}
# The pattern must match the name of the item, not its short name (8.3), which Get-ChildItem in Windows PowerShell
# also compares: there, *.htm returns Page2.html on a volume that creates short names.
It 'Should return only the items whose name matches -Filter <Filter>' -ForEach @(
@{ Filter = '*.htm'; Expected = @('Page.htm') }
@{ Filter = 'Page?.html'; Expected = @('Page2.html') }
@{ Filter = 'PAGE*'; Expected = @('Page.htm', 'Page2.html') }
) {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'FilterNames' -Directory
foreach ($name in 'Page.htm', 'Page2.html') {
$file = Join-Path -Path $folder -ChildPath $name
Assert-TestSandboxPath -Sandbox $sandbox -Path $file
Set-Content -LiteralPath $file -Value $name
}
$result = @(Get-ChildItem2 -Path $folder -Filter $Filter -ErrorAction Stop)
($result.Name | Sort-Object) -join ',' | Should -Be (($Expected | Sort-Object) -join ',')
}
# Only * and ? are wildcards in -Filter. A bracket stands for itself, so a file with brackets in its name is found
# by its name, as Get-ChildItem finds it, and the file that the brackets would select as a character class is not.
# Before 5.0.0, the cmdlet read [1] as a character class and returned nothing.
It 'Should find a file whose name contains brackets by that name with -Filter' {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'FilterBrackets' -Directory
foreach ($name in 'Report[1].txt', 'Report1.txt') {
$file = Join-Path -Path $folder -ChildPath $name
Assert-TestSandboxPath -Sandbox $sandbox -Path $file
Set-Content -LiteralPath $file -Value $name
}
$result = @(Get-ChildItem2 -Path $folder -Filter 'Report[1].txt' -ErrorAction Stop)
$result | Should -HaveCount 1
$result[0].Name | Should -BeExactly 'Report[1].txt'
}
# The dot is an ordinary character of the pattern, but not in *.*, which Windows, Get-ChildItem, and .NET read as
# every item. Before 5.0.0, the cmdlet compared each name with the pattern again and dropped the items without a
# dot, files and folders alike, so that a listing of a tree with this filter missed most of its folders.
It 'Should return every item for -Filter *.*, also the ones without a dot in their names' {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'FilterDot' -Directory
$paths = @('Page.htm', 'NoExtension', 'NoExtensionFolder') | ForEach-Object -Process { Join-Path -Path $folder -ChildPath $_ }
Assert-TestSandboxPath -Sandbox $sandbox -Path $paths
Set-Content -LiteralPath $paths[0] -Value 'Page'
Set-Content -LiteralPath $paths[1] -Value 'NoExtension'
New-Item -ItemType Directory -Path $paths[2] | Out-Null
$result = @(Get-ChildItem2 -Path $folder -Filter '*.*' -ErrorAction Stop)
($result.Name | Sort-Object) -join ',' | Should -BeExactly 'NoExtension,NoExtensionFolder,Page.htm'
}
# The enumeration returns every item for *.*.*, as Get-ChildItem does. The cmdlet compares each name with the whole
# pattern again, so that the dots of the pattern are characters of the name, as the help says.
It 'Should return only the items that match the whole pattern for -Filter *.*.*' {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'FilterDots' -Directory
foreach ($name in 'Page.htm', 'NoExtension', 'Two.dots.txt') {
$file = Join-Path -Path $folder -ChildPath $name
Assert-TestSandboxPath -Sandbox $sandbox -Path $file
Set-Content -LiteralPath $file -Value $name
}
$result = @(Get-ChildItem2 -Path $folder -Filter '*.*.*' -ErrorAction Stop)
($result.Name -join ',') | Should -BeExactly 'Two.dots.txt'
}
# The names are matched twice, by the enumeration and by the cmdlet, and the rules for a dot differ from those of
# Get-ChildItem, where Report.* also returns Report. The documentation lists this as a limitation; these cases pin
# it, so that a change of the rules is a decision. Report* is the control: without a dot in the pattern, the names
# without a dot are returned.
It 'Should return <Outcome> for -Filter "<Filter>"' -ForEach @(
@{ Filter = 'Report.*'; Expected = 'Report.txt'; Outcome = 'only the names with a dot' }
@{ Filter = 'Report*'; Expected = 'Report,Report.txt'; Outcome = 'the names with and without a dot' }
@{ Filter = 'Report.'; Expected = ''; Outcome = 'nothing' }
@{ Filter = 'Rep*.'; Expected = ''; Outcome = 'nothing' }
@{ Filter = '*.'; Expected = ''; Outcome = 'nothing' }
@{ Filter = ''; Expected = ''; Outcome = 'nothing' }
) {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'FilterDotRules' -Directory
foreach ($name in 'Report', 'Report.txt', 'Other') {
$file = Join-Path -Path $folder -ChildPath $name
Assert-TestSandboxPath -Sandbox $sandbox -Path $file
Set-Content -LiteralPath $file -Value $name
}
$result = @(Get-ChildItem2 -Path $folder -Filter $Filter -ErrorAction Stop)
($result.Name | Sort-Object) -join ',' | Should -BeExactly $Expected
}
It 'Should reject a null -Filter' {
{ Get-ChildItem2 -Path $tree -Filter $null -ErrorAction Stop } |
Should -Throw -ErrorId 'ParameterArgumentValidationError,NTFSSecurity.GetChildItem2' -ExpectedMessage "*'Filter'*"
}
It 'Should stop a recursive pipeline without recording an enumeration error' {
$result = @(Get-ChildItem2 -Path $tree -Recurse -ErrorVariable childErrors -ErrorAction SilentlyContinue | Select-Object -First 1)
$result | Should -HaveCount 1
$childErrors | Should -BeNullOrEmpty
}
# The second file comes from a sub folder, so the pipeline stops while the cmdlet is inside the recursion.
It 'Should stop a recursive pipeline inside a sub folder without recording an enumeration error' {
$result = @(Get-ChildItem2 -Path $tree -Recurse -File -ErrorVariable childErrors -ErrorAction SilentlyContinue | Select-Object -First 2)
$result | Should -HaveCount 2
$childErrors | Should -BeNullOrEmpty
}
# A break or continue in a later pipeline stage passes through the cmdlet as an exception, which it must not
# report as a failed folder.
It 'Should end a recursive enumeration for <Keyword> in a later pipeline stage without recording an enumeration error' -ForEach @(
@{ Keyword = 'break' }
@{ Keyword = 'continue' }
) {
$names = [System.Collections.Generic.List[string]]::new()
foreach ($round in 1) {
Get-ChildItem2 -Path $tree -Recurse -File -ErrorVariable childErrors -ErrorAction SilentlyContinue | ForEach-Object -Process {
$names.Add($_.Name)
if ($Keyword -eq 'break') { break } else { continue }
}
}
$names | Should -HaveCount 1
$childErrors | Should -BeNullOrEmpty
}
}
Context 'Unreadable directories' {
@ -200,6 +322,31 @@ Describe 'Get-ChildItem2' {
$result[0].FullName | Should -Be $link
Get-Content -LiteralPath $file | Should -Be 'Target'
}
# A junction whose target is gone passes the existence check, but the folder behind it can't be opened. The
# error belongs to that folder, and the enumeration goes on with the next one.
It 'Should report a junction whose target was removed as a DirUnspecifiedError and continue with the next folder' {
$root = New-TestSandboxItem -Sandbox $sandbox -Name 'BrokenJunction' -Directory
$target = New-TestSandboxItem -Sandbox $sandbox -Name 'RemovedTarget' -Directory
$link = Join-Path -Path $root -ChildPath 'Broken'
$sibling = Join-Path -Path $root -ChildPath 'Sibling'
$file = Join-Path -Path $sibling -ChildPath 'Sibling.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $link, $sibling, $file
New-Item -ItemType Directory -Path $sibling | Out-Null
Set-Content -LiteralPath $file -Value 'Sibling'
New-Item -ItemType Junction -Path $link -Value $target | Out-Null
Remove-Item -LiteralPath $target -Force
$result = @(Get-ChildItem2 -Path $root -Recurse -ErrorVariable childErrors -ErrorAction SilentlyContinue)
$childErrors | Should -HaveCount 1
$childErrors[0].FullyQualifiedErrorId | Should -BeLike 'DirUnspecifiedError,*'
$childErrors[0].CategoryInfo.Category | Should -Be 'NotSpecified'
$childErrors[0].TargetObject | Should -Be $link
$childErrors[0].Exception | Should -BeOfType [System.IO.DirectoryNotFoundException]
@($result.FullName | Sort-Object) | Should -Be @(@($link, $sibling, $file) | Sort-Object)
Get-Content -LiteralPath $file | Should -Be 'Sibling'
}
}
Context 'Optional object properties' {
@ -244,6 +391,46 @@ Describe 'Get-ChildItem2' {
$item.Mode | Should -BeExactly '--rhs'
}
It 'Should render a folder with a d in the Mode property' {
$parent = New-TestSandboxItem -Sandbox $sandbox -Name 'ModeFolder' -Directory
$folder = Join-Path -Path $parent -ChildPath 'Inner'
Assert-TestSandboxPath -Sandbox $sandbox -Path $folder
New-Item -ItemType Directory -Path $folder | Out-Null
$settings['GetFileSystemModeProperty'] = $true
$item = Get-ChildItem2 -Path $parent -ErrorAction Stop
$item | Should -BeOfType [Alphaleonis.Win32.Filesystem.DirectoryInfo]
$item.Mode | Should -BeExactly 'd----'
}
It 'Should return an empty Mode for no object' {
[NTFSSecurity.FileSystemCodeMembers]::Mode($null) | Should -BeExactly ''
}
# Windows can't list the hard links of a file on a network share, (50) "The request is not supported". The cmdlet
# still returns the file, without HardLinkCount, and says why in a debug message. The test sets the preference,
# because -Debug would prompt in Windows PowerShell.
It 'Should return a file on a network share without HardLinkCount and say why in a debug message' -Skip:(-not $canUseAdminShare) {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'ShareProperties' -Directory
$file = Join-Path -Path $folder -ChildPath 'Share.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $file
Set-Content -LiteralPath $file -Value 'Share'
$sharePath = ConvertTo-TestAdminSharePath -Sandbox $sandbox -Path $file
$settings['IdentifyHardLinks'] = $true
$DebugPreference = 'Continue'
$output = @(Get-ChildItem2 -Path $sharePath -ErrorVariable childErrors -ErrorAction SilentlyContinue 5>&1)
$childErrors | Should -BeNullOrEmpty
$items = @($output | Where-Object -FilterScript { $_ -isnot [Management.Automation.DebugRecord] })
$items | Should -HaveCount 1
$items[0].Name | Should -BeExactly 'Share.txt'
$items[0].PSObject.Properties['HardLinkCount'] | Should -BeNullOrEmpty
$messages = @($output | Where-Object -FilterScript { $_ -is [Management.Automation.DebugRecord] } | ForEach-Object -Process { $_.Message })
$messages | Should -Contain "Could not read hard links for '$sharePath'"
}
}
Context 'Default table view' {
@ -557,6 +744,83 @@ Describe 'Copy-Item2, Move-Item2, and Remove-Item2 with several paths' {
$itemErrors[0].Exception.Message | Should -BeLike "*'$missingShare'*"
$first | Should -Exist
}
# A sharing violation is an IOException, which both cmdlets write as InvalidData; the error belongs to its source
# only, and no object comes out for it with -PassThru.
It '<Command> should write a <ErrorId> for a source that another process has locked and continue with the next path' -ForEach @(
@{ Command = 'Copy-Item2'; ErrorId = 'CopyError' }
@{ Command = 'Move-Item2'; ErrorId = 'MoveError' }
) {
$stream = [IO.File]::Open($first, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::None)
try {
$result = @(& $Command -Path $first, $second -Destination $destination -PassThru $true -ErrorVariable itemErrors -ErrorAction SilentlyContinue)
}
finally {
$stream.Dispose()
}
$itemErrors | Should -HaveCount 1
$itemErrors[0].FullyQualifiedErrorId | Should -BeLike "$ErrorId,*"
$itemErrors[0].CategoryInfo.Category | Should -Be 'InvalidData'
$itemErrors[0].TargetObject | Should -Be $first
$itemErrors[0].Exception | Should -BeOfType [System.IO.IOException]
$result | Should -HaveCount 1
$result[0].FullName | Should -Be (Join-Path -Path $destination -ChildPath 'Second.txt')
Join-Path -Path $destination -ChildPath 'First.txt' | Should -Not -Exist
Get-Content -LiteralPath $first | Should -Be 'First'
Get-Content -LiteralPath (Join-Path -Path $destination -ChildPath 'Second.txt') | Should -Be 'Second'
}
# Any other failure of Windows is not an IOException, and both cmdlets write it as NotSpecified. A deny entry for
# Everyone also applies to an administrator, who doesn't bypass the DACL without a backup privilege.
It '<Command> should write a <ErrorId> for each source when the destination folder denies new files' -ForEach @(
@{ Command = 'Copy-Item2'; ErrorId = 'CopyError' }
@{ Command = 'Move-Item2'; ErrorId = 'MoveError' }
) {
$denied = Join-Path -Path $folder -ChildPath 'Denied'
Assert-TestSandboxPath -Sandbox $sandbox -Path $denied
New-Item -ItemType Directory -Path $denied | Out-Null
Add-TestDenyRule -Sandbox $sandbox -Path $denied -Rights @{ 'S-1-1-0' = 'CreateFiles' }
$result = @(& $Command -Path $first, $second -Destination $denied -PassThru $true -ErrorVariable itemErrors -ErrorAction SilentlyContinue)
$itemErrors | Should -HaveCount 2
for ($index = 0; $index -lt 2; $index++) {
$itemErrors[$index].FullyQualifiedErrorId | Should -BeLike "$ErrorId,*"
$itemErrors[$index].CategoryInfo.Category | Should -Be 'NotSpecified'
$itemErrors[$index].TargetObject | Should -Be @($first, $second)[$index]
$itemErrors[$index].Exception | Should -BeOfType [System.UnauthorizedAccessException]
}
$result | Should -BeNullOrEmpty
@(Get-ChildItem -LiteralPath $denied -Force) | Should -BeNullOrEmpty
Get-Content -LiteralPath $first | Should -Be 'First'
Get-Content -LiteralPath $second | Should -Be 'Second'
}
# A destination on a drive letter without a volume has no folder that the cmdlet could name, so Windows reports the
# drive as not ready, which AlphaFS raises as an IOException.
It '<Command> should write a <ErrorId> for a destination on a drive that does not exist and keep the source' -ForEach @(
@{ Command = 'Copy-Item2'; ErrorId = 'CopyError' }
@{ Command = 'Move-Item2'; ErrorId = 'MoveError' }
) {
$used = @((Get-PSDrive -PSProvider FileSystem).Name) + @([System.IO.DriveInfo]::GetDrives() | ForEach-Object -Process { $_.Name.Substring(0, 1) })
# The lowest free letter: New-TestDriveMapping takes letters from Z downward, also in a run in parallel.
$letter = [char[]](68..90) | Where-Object -FilterScript { [string] $_ -notin $used } | Select-Object -First 1
if (-not $letter) {
Set-ItResult -Skipped -Because 'every drive letter is in use'
return
}
$result = @(& $Command -Path $first -Destination "${letter}:\" -PassThru $true -ErrorVariable itemErrors -ErrorAction SilentlyContinue)
$itemErrors | Should -HaveCount 1
$itemErrors[0].FullyQualifiedErrorId | Should -BeLike "$ErrorId,*"
$itemErrors[0].CategoryInfo.Category | Should -Be 'InvalidData'
$itemErrors[0].TargetObject | Should -Be $first
$itemErrors[0].Exception | Should -BeOfType [System.IO.IOException]
$result | Should -BeNullOrEmpty
Get-Content -LiteralPath $first | Should -Be 'First'
}
}
Describe 'Move-Item2' {
@ -640,6 +904,45 @@ Describe 'Copy-Item2' {
}
}
Describe 'Relative paths' {
BeforeAll {
$parent = New-TestSandboxItem -Sandbox $sandbox -Name 'RelativeParent' -Directory
$child = Join-Path -Path $parent -ChildPath 'Child'
$sibling = Join-Path -Path $parent -ChildPath 'Sibling'
$siblingFile = Join-Path -Path $sibling -ChildPath 'Sibling.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $child, $sibling, $siblingFile
New-Item -ItemType Directory -Path $child, $sibling | Out-Null
Set-Content -LiteralPath $siblingFile -Value 'Sibling'
}
It 'Get-Item2 should resolve <Path> against the current location' -ForEach @(
@{ Path = '.'; Expected = 'Child' }
@{ Path = '.\'; Expected = 'Child' }
@{ Path = '..'; Expected = 'Parent' }
@{ Path = '..\Sibling'; Expected = 'Sibling' }
@{ Path = '..\Sibling\Sibling.txt'; Expected = 'SiblingFile' }
@{ Path = '..\..'; Expected = 'Grandparent' }
) {
$expectedPath = switch ($Expected) {
'Child' { $child }
'Parent' { $parent }
'Sibling' { $sibling }
'SiblingFile' { $siblingFile }
'Grandparent' { Split-Path -Path $parent -Parent }
}
Push-Location -LiteralPath $child
try {
$result = @(Get-Item2 -Path $Path -ErrorAction Stop)
}
finally {
Pop-Location
}
$result | Should -HaveCount 1
$result[0].FullName.TrimEnd('\') | Should -Be $expectedPath
}
}
Describe 'Test-Path2' {
BeforeAll {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'TestPath' -Directory
@ -759,7 +1062,8 @@ Describe 'Get-DiskSpace' {
It 'Should warn and return nothing for a drive letter without a volume' {
$used = @((Get-PSDrive -PSProvider FileSystem).Name) + @([System.IO.DriveInfo]::GetDrives() | ForEach-Object -Process { $_.Name.Substring(0, 1) })
$letter = [char[]](68..90) | Where-Object -FilterScript { [string] $_ -notin $used } | Select-Object -Last 1
# The lowest free letter: New-TestDriveMapping takes letters from Z downward, also in a run in parallel.
$letter = [char[]](68..90) | Where-Object -FilterScript { [string] $_ -notin $used } | Select-Object -First 1
if (-not $letter) {
Set-ItResult -Skipped -Because 'every drive letter is in use'
return
@ -777,3 +1081,36 @@ Describe 'Get-DiskSpace' {
Should -Throw -ErrorId 'ParameterArgumentValidationError,NTFSSecurity.GetDiskSpace'
}
}
Describe 'Get-ChildItem2 when recursive enumeration becomes denied' {
It 'Should name the failed recursion in verbose output and continue with the next path' {
$root = New-TestSandboxItem -Sandbox $sandbox -Name 'ChangingReadPermission' -Directory
$child = Join-Path -Path $root -ChildPath 'Child'
$first = Join-Path -Path $root -ChildPath 'First.txt'
$nested = Join-Path -Path $child -ChildPath 'Nested.txt'
$next = New-TestSandboxItem -Sandbox $sandbox -Name 'NextRecursivePath' -Directory
$nextFile = Join-Path -Path $next -ChildPath 'Next.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $root, $child, $first, $nested, $nextFile
New-Item -ItemType Directory -Path $child | Out-Null
Set-Content -LiteralPath $first -Value 'First'
Set-Content -LiteralPath $nested -Value 'Nested'
Set-Content -LiteralPath $nextFile -Value 'Next'
$ownerBefore = (Get-Acl -LiteralPath $root).Owner
# The first file is emitted before the separate recursive directory enumeration opens the folder again.
$records = @(Get-ChildItem2 -Path $root, $next -File -Recurse -Verbose -ErrorVariable childErrors -ErrorAction SilentlyContinue 4>&1 |
ForEach-Object {
if ($_ -is [Alphaleonis.Win32.Filesystem.FileInfo] -and $_.FullName -eq $first) {
Add-TestDenyRule -Sandbox $sandbox -Path $root -Rights @{ 'S-1-1-0' = 'ReadData' }
}
$_
})
$childErrors | Should -BeNullOrEmpty
$files = @($records | Where-Object { $_ -is [Alphaleonis.Win32.Filesystem.FileInfo] })
@($files.FullName | Sort-Object) | Should -Be @(@($first, $nextFile) | Sort-Object)
$messages = @($records | Where-Object { $_ -is [System.Management.Automation.VerboseRecord] })
$messages.Message | Should -Contain "Cannot access folder '$root' for recursive operation"
(Get-Acl -LiteralPath $root).Owner | Should -BeExactly $ownerBefore
Get-Content -LiteralPath $nested | Should -BeExactly 'Nested'
}
}

5
Tests/Lab/README.md

@ -143,7 +143,10 @@ and record the evidence in this folder:
Records: [5.0.0-rc6](Acceptance-2026-10-08-5.0.0-rc6.md),
[5.0.0-rc7](Acceptance-2026-10-08-5.0.0-rc7.md), and
[quality-gate follow-up](Acceptance-2026-10-09-quality-gate.md).
[quality-gate follow-up](Acceptance-2026-10-09-quality-gate.md). The review
of the code that no unit test visits, with the fixes that the lab has to
repeat, is in
[Tests/Coverage](../Coverage/Quality-Gate-Paths-2026-10-09.md).
## Files

73
Tests/Links.Tests.ps1

@ -159,6 +159,25 @@ Describe 'New-NTFSHardLink' {
$linkErrors[0].FullyQualifiedErrorId | Should -BeLike 'GetHardLinkError,*'
$result | Should -BeNullOrEmpty
}
It 'Should write a PermissionDenied error and create no link in a folder that denies new files' {
$target = New-TestSandboxItem -Sandbox $sandbox -Name 'DeniedTarget'
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'DeniedFolder' -Directory
$link = Join-Path -Path $folder -ChildPath 'Link.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $link
Add-TestDenyRule -Sandbox $sandbox -Path $folder -Rights @{ 'S-1-1-0' = 'CreateFiles' }
$result = @(New-NTFSHardLink -Path $link -Target $target -PassThru -ErrorVariable linkErrors -ErrorAction SilentlyContinue)
$linkErrors | Should -HaveCount 1
$linkErrors[0].FullyQualifiedErrorId | Should -BeLike 'CreateHardLinkError,*'
$linkErrors[0].CategoryInfo.Category | Should -Be 'PermissionDenied'
$linkErrors[0].TargetObject | Should -Be $link
$linkErrors[0].Exception | Should -BeOfType [System.UnauthorizedAccessException]
$result | Should -BeNullOrEmpty
$link | Should -Not -Exist
Get-Content -LiteralPath $target | Should -Be 'DeniedTarget'
}
}
Describe 'Get-NTFSHardLink' {
@ -237,6 +256,26 @@ Describe 'Get-NTFSHardLink' {
$linkErrors[0].TargetObject | Should -Be $sharePath
$result.FullName | Should -Be $other
}
# Windows lists the names of a file without opening it, so no deny entry stops the cmdlet. This is why a test cannot
# reach the handler for an UnauthorizedAccessException of the cmdlet.
It 'Should list the names of a file whose read rights are denied for its owner and for the user' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'ReadDenied'
$link = Join-Path -Path $sandbox -ChildPath 'ReadDeniedLink.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $link
New-NTFSHardLink -Path $link -Target $file -ErrorAction Stop
$readRights = 'ReadAttributes, ReadData, ReadPermissions'
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = $readRights; $currentUser = $readRights }
# Reading the data is refused. ReadAttributes and ReadPermissions are denied as well, but nothing shows that in
# every session: an elevated one was seen to read the permissions anyway.
{ Get-Content -LiteralPath $file -ErrorAction Stop } | Should -Throw
$result = @(Get-NTFSHardLink -Path $file -ErrorVariable linkErrors -ErrorAction SilentlyContinue)
$linkErrors | Should -BeNullOrEmpty
($result.FullName | Sort-Object) -join '|' | Should -Be ((@($file, $link) | Sort-Object) -join '|')
}
}
Describe 'New-NTFSSymbolicLink' {
@ -368,6 +407,26 @@ Describe 'New-NTFSSymbolicLink' {
'0x{0:X8}' -f $linkErrors[0].Exception.HResult | Should -Be '0x80070522'
Test-Path2 -Path $link | Should -BeFalse
}
# With the right to create symbolic links, Windows refuses the link only for the folder of the link, which denies
# new files here.
It 'Should write a PermissionDenied error and create no link in a folder that denies new files' -Skip:(-not $canCreateSymbolicLinks) {
$target = New-TestSandboxItem -Sandbox $sandbox -Name 'SymbolicDeniedTarget'
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'SymbolicDeniedFolder' -Directory
$link = Join-Path -Path $folder -ChildPath 'Link.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $link
Add-TestDenyRule -Sandbox $sandbox -Path $folder -Rights @{ 'S-1-1-0' = 'CreateFiles' }
$result = @(New-NTFSSymbolicLink -Path $link -Target $target -PassThru -ErrorVariable linkErrors -ErrorAction SilentlyContinue)
$linkErrors | Should -HaveCount 1
$linkErrors[0].FullyQualifiedErrorId | Should -BeLike 'CreateSymbolicLinkError,*'
$linkErrors[0].CategoryInfo.Category | Should -Be 'PermissionDenied'
$linkErrors[0].TargetObject | Should -Be $link
$linkErrors[0].Exception | Should -BeOfType [System.UnauthorizedAccessException]
$result | Should -BeNullOrEmpty
Test-Path2 -Path $link | Should -BeFalse
}
}
# Each error names its item, so that the errors of many links can be told apart. Before 5.0.0-rc7, the errors of
@ -456,4 +515,18 @@ Describe 'Parameters of the cmdlets that create links' {
$sets | Should -Not -BeNullOrEmpty
$sets | ForEach-Object -Process { $_.IsMandatory | Should -BeTrue }
}
# PowerShell reads a parameter that takes pipeline input before it binds the input, so the getter must not fail
# while the cmdlet has no -Path. Before 5.0.0-rc7, it threw an index error, and every piped object failed with
# GetDefaultValueFailed.
It '<Type> should return no -Path until it has one, and the first one afterwards' -ForEach @(
@{ Type = 'NTFSSecurity.NewHardLink' }
@{ Type = 'NTFSSecurity.NewSymbolicLink' }
) {
$cmdlet = New-Object -TypeName $Type
$cmdlet.Path | Should -BeNullOrEmpty
$cmdlet.Path = 'C:\NTFSSecurity\Link.txt'
$cmdlet.Path | Should -BeExactly 'C:\NTFSSecurity\Link.txt'
}
}

903
Tests/ObjectApis.Tests.ps1

@ -0,0 +1,903 @@
<#
Tests the public object APIs used with cmdlet output, on files and folders in a sandbox folder.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.'
)]
param ()
BeforeDiscovery {
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force
$holdsSecurityPrivilege = Test-PrivilegeHeld -Name 'SeSecurityPrivilege'
}
BeforeAll {
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force
$modulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\NTFSSecurity\bin\Release\NTFSSecurity.psd1'
Import-Module -Name $modulePath -Force -ErrorAction Stop
$sandbox = New-TestSandbox -Name 'ObjectApis'
$objectPath = Join-Path -Path $sandbox -ChildPath 'Rule.txt'
$identity = [Security2.IdentityReference2] 'S-1-1-0'
$sid = New-Object -TypeName 'System.Security.Principal.SecurityIdentifier' -ArgumentList 'S-1-1-0'
}
AfterAll {
Remove-TestSandbox -Sandbox $sandbox
Remove-Module -Name NTFSSecurity -Force -ErrorAction SilentlyContinue
}
Describe 'Rule constructors with a path' {
It 'Should preserve the supplied path and name of an <Kind> rule' -ForEach @(
@{ Kind = 'access' }
@{ Kind = 'audit' }
) {
if ($Kind -eq 'access') {
$raw = New-Object -TypeName 'System.Security.AccessControl.FileSystemAccessRule' -ArgumentList (
$sid, [System.Security.AccessControl.FileSystemRights]::ReadData,
[System.Security.AccessControl.AccessControlType]::Allow
)
$rule = New-Object -TypeName 'Security2.FileSystemAccessRule2' -ArgumentList $raw, $objectPath
}
else {
$raw = New-Object -TypeName 'System.Security.AccessControl.FileSystemAuditRule' -ArgumentList (
$sid, [System.Security.AccessControl.FileSystemRights]::ReadData,
[System.Security.AccessControl.AuditFlags]::Success
)
$rule = New-Object -TypeName 'Security2.FileSystemAuditRule2' -ArgumentList $raw, $objectPath
}
$rule.FullName | Should -BeExactly $objectPath
$rule.Name | Should -BeExactly 'Rule.txt'
$rule.InheritanceEnabled = $true
$rule.InheritedFrom = $sandbox
$rule.InheritanceEnabled | Should -BeTrue
$rule.InheritedFrom | Should -BeExactly $sandbox
$rule.GetHashCode() | Should -Be $raw.GetHashCode()
}
}
Describe 'Simplified audit entries' {
It 'Should reduce <Rights> to <Expected>' -ForEach @(
@{ Rights = 'None'; Expected = 'None' }
@{ Rights = 'ReadData'; Expected = 'Read' }
@{ Rights = 'Read'; Expected = 'Read' }
@{ Rights = 'CreateFiles'; Expected = 'Write' }
@{ Rights = 'AppendData'; Expected = 'Write' }
@{ Rights = 'ReadExtendedAttributes'; Expected = 'Read' }
@{ Rights = 'WriteExtendedAttributes'; Expected = 'Write' }
@{ Rights = 'ExecuteFile'; Expected = 'Read' }
@{ Rights = 'DeleteSubdirectoriesAndFiles'; Expected = 'Delete' }
@{ Rights = 'ReadAttributes'; Expected = 'Read' }
@{ Rights = 'WriteAttributes'; Expected = 'Write' }
@{ Rights = 'Delete'; Expected = 'Delete' }
@{ Rights = 'ReadPermissions'; Expected = 'Read' }
@{ Rights = 'ChangePermissions'; Expected = 'Write' }
@{ Rights = 'TakeOwnership'; Expected = 'Write' }
@{ Rights = 'Synchronize'; Expected = 'Read' }
@{ Rights = 'FullControl'; Expected = 'Read, Write, Delete' }
@{ Rights = 'GenericRead'; Expected = 'Read' }
@{ Rights = 'GenericWrite'; Expected = 'Write' }
@{ Rights = 'GenericExecute'; Expected = 'Read' }
@{ Rights = 'GenericAll'; Expected = 'Read, Write, Delete' }
) {
$rule = New-Object -TypeName 'Security2.SimpleFileSystemAuditRule' -ArgumentList (
$objectPath, $identity, [Security2.FileSystemRights2] $Rights
)
$rule.AccessRights | Should -Be ([Security2.SimpleFileSystemAccessRights] $Expected)
$rule.FullName | Should -BeExactly $objectPath
$rule.Name | Should -BeExactly 'Rule.txt'
$rule.Identity.Sid | Should -BeExactly 'S-1-1-0'
}
It 'Should compare audit entries reflexively and symmetrically, never as access entries' {
$first = New-Object -TypeName 'Security2.SimpleFileSystemAuditRule' -ArgumentList (
$objectPath, $identity, [Security2.FileSystemRights2]::Read
)
$second = New-Object -TypeName 'Security2.SimpleFileSystemAuditRule' -ArgumentList (
$objectPath, $identity, [Security2.FileSystemRights2]::Read
)
$access = New-Object -TypeName 'Security2.SimpleFileSystemAccessRule' -ArgumentList (
$objectPath, $identity, [Security2.FileSystemRights2]::Read,
[System.Security.AccessControl.AccessControlType]::Allow
)
$first.Equals($first) | Should -BeTrue
$first.Equals($second) | Should -BeTrue
$second.Equals($first) | Should -BeTrue
$first.GetHashCode() | Should -Be $second.GetHashCode()
$first.Equals($access) | Should -BeFalse
$access.Equals($first) | Should -BeFalse
$first.Equals($null) | Should -BeFalse
$first.Equals('Read') | Should -BeFalse
$second.AccessControlType = 'Deny'
$first.Equals($second) | Should -BeFalse
}
It 'Should preserve the path, account and ReadData when converting an audit entry' {
$raw = New-Object -TypeName 'System.Security.AccessControl.FileSystemAuditRule' -ArgumentList (
$sid, [System.Security.AccessControl.FileSystemRights]::ReadData,
[System.Security.AccessControl.AuditFlags]::Success
)
$wrapped = New-Object -TypeName 'Security2.FileSystemAuditRule2' -ArgumentList $raw, $objectPath
$simple = $wrapped.ToSimpleFileSystemAuditRule2()
$simple.FullName | Should -BeExactly $objectPath
$simple.Identity.Sid | Should -BeExactly 'S-1-1-0'
$simple.AccessRights | Should -Be ([Security2.SimpleFileSystemAccessRights]::Read)
$wrapped.ToString() | Should -BeExactly $raw.ToString()
}
}
Describe 'Identity comparisons and conversions' {
It 'Should compare <Value> with the identity by SID or resolved name' -ForEach @(
@{ Value = 'self'; Expected = $true }
@{ Value = 'same SID'; Expected = $true }
@{ Value = 'different SID'; Expected = $false }
@{ Value = 'SecurityIdentifier'; Expected = $true }
@{ Value = 'NTAccount'; Expected = $true }
@{ Value = 'SID string'; Expected = $true }
@{ Value = 'account name'; Expected = $true }
@{ Value = 'different string'; Expected = $false }
@{ Value = 'null'; Expected = $false }
@{ Value = 'other type'; Expected = $false }
) {
$other = switch ($Value) {
'self' { $identity }
'same SID' { [Security2.IdentityReference2] 'S-1-1-0' }
'different SID' { [Security2.IdentityReference2] 'S-1-5-32-546' }
'SecurityIdentifier' { $sid }
'NTAccount' { $sid.Translate([System.Security.Principal.NTAccount]) }
'SID string' { 'S-1-1-0' }
'account name' { $identity.AccountName.ToUpperInvariant() }
'different string' { 'NTFSSecurity-not-an-account' }
'null' { $null }
'other type' { 42 }
}
$identity.Equals($other) | Should -Be $Expected
}
It 'Should compare null operands and distinct instances through both operators' {
$same = [Security2.IdentityReference2] 'S-1-1-0'
$different = [Security2.IdentityReference2] 'S-1-5-32-546'
[Security2.IdentityReference2]::op_Equality($null, $null) | Should -BeTrue
[Security2.IdentityReference2]::op_Equality($identity, $null) | Should -BeFalse
[Security2.IdentityReference2]::op_Equality($null, $identity) | Should -BeFalse
[Security2.IdentityReference2]::op_Equality($identity, $same) | Should -BeTrue
[Security2.IdentityReference2]::op_Inequality($identity, $identity) | Should -BeFalse
[Security2.IdentityReference2]::op_Inequality($identity, $null) | Should -BeTrue
[Security2.IdentityReference2]::op_Inequality($null, $identity) | Should -BeTrue
[Security2.IdentityReference2]::op_Inequality($identity, $different) | Should -BeTrue
[Security2.IdentityReference2]::op_Inequality($identity, $same) | Should -BeFalse
$identity.GetHashCode() | Should -Be $same.GetHashCode()
}
It 'Should round-trip native identities and the binary SID without changing the account' {
$account = $sid.Translate([System.Security.Principal.NTAccount])
$fromSid = [Security2.IdentityReference2]::op_Explicit($sid)
$fromAccount = [Security2.IdentityReference2]::op_Explicit($account)
$fromSid.Sid | Should -BeExactly 'S-1-1-0'
$fromAccount.Sid | Should -BeExactly $fromSid.Sid
([System.Security.Principal.SecurityIdentifier] $fromSid).Value | Should -BeExactly 'S-1-1-0'
([System.Security.Principal.NTAccount] $fromAccount).Value | Should -BeExactly $account.Value
$binarySid = New-Object -TypeName 'System.Security.Principal.SecurityIdentifier' -ArgumentList (
$fromSid.GetBinaryForm(), 0
)
$binarySid.Value | Should -BeExactly 'S-1-1-0'
}
}
Describe 'Unrepresentable inheritance flags' {
It 'Should reject propagation flags without inheritance instead of inventing an AppliesTo value' {
$failure = $null
try {
$null = [Security2.FileSystemSecurity2]::ConvertToApplyTo('None', 'InheritOnly')
}
catch {
$failure = $_.Exception.GetBaseException()
}
$failure | Should -BeOfType [Security2.RightsConverionException]
$failure.Message | Should -BeExactly 'The combination of InheritanceFlags and PropagationFlags could not be translated'
}
}
Describe 'Privilege output comparisons and formatting' {
It 'Should compare boxed and typed privilege values consistently without accepting an attributes enum' {
$values = @(Get-Privileges)
$values.Count | Should -BeGreaterThan 0
$first = $values[0].PSObject.BaseObject
$copy = $values[0].PSObject.BaseObject
# PowerShell prefers the typed overload; reflection selects the public boxed-object contract explicitly.
$equalsObject = [ProcessPrivileges.PrivilegeAndAttributes].GetMethod('Equals', [type[]] @([object]))
$equalsObject.Invoke($first, [object[]] @($copy)) | Should -BeTrue
$first.Equals($copy) | Should -BeTrue
[ProcessPrivileges.PrivilegeAndAttributes]::op_Equality($first, $copy) | Should -BeTrue
[ProcessPrivileges.PrivilegeAndAttributes]::op_Inequality($first, $copy) | Should -BeFalse
$first.GetHashCode() | Should -Be $copy.GetHashCode()
$equalsObject.Invoke($first, [object[]] @($null)) | Should -BeFalse
$equalsObject.Invoke($first, [object[]] @('Backup')) | Should -BeFalse
$equalsObject.Invoke($first, [object[]] @([ProcessPrivileges.PrivilegeAttributes]::Disabled)) | Should -BeFalse
}
It 'Should format the collection with one aligned privilege and attributes row per value' {
$control = New-Object -TypeName 'Security2.PrivilegeControl'
$values = $control.GetPrivileges()
$expectedWidth = ($values | ForEach-Object { $_.Privilege.ToString().Length } | Measure-Object -Maximum).Maximum
$text = $values.ToString()
$rows = @($text.TrimEnd("`r", "`n") -split '\r?\n')
$rows | Should -HaveCount $values.Count
for ($index = 0; $index -lt $values.Count; $index++) {
$value = $values[$index]
$rows[$index] | Should -BeExactly ('{0} => {1}' -f $value.Privilege.ToString().PadRight($expectedWidth),
$value.PrivilegeAttributes)
}
}
}
Describe 'Legacy effective-permission output objects' {
It 'Should retain a <Mask> mask and report the supplied path and identity without a native access check' -ForEach @(
@{ Mask = 0; ObjectName = 'Effective.txt' }
@{ Mask = 1; ObjectName = 'Effective.txt' }
@{ Mask = 3; ObjectName = $null }
) {
$path = if ($ObjectName) { Join-Path -Path $sandbox -ChildPath $ObjectName } else { $null }
$entry = New-Object -TypeName 'Security2.FileSystemEffectivePermissionEntry' -ArgumentList (
$identity, [uint32] $Mask, $path
)
$entry.Account.Sid | Should -BeExactly 'S-1-1-0'
$entry.AccessMask | Should -Be $Mask
[int] $entry.AccessRights | Should -Be $Mask
$entry.FullName | Should -BeExactly ([string] $path)
$entry.Name | Should -BeExactly $ObjectName
$entry.AccessAsString | Should -Not -BeNullOrEmpty
if ($Mask -eq 0) {
$entry.AccessAsString | Should -Be @('None')
}
else {
$entry.AccessAsString | Should -Not -Contain 'None'
}
}
}
Describe 'Simplified entry comparison branches' {
It 'Should distinguish identities, rights and types in <Kind> entries and keep equal hashes consistent' -ForEach @(
@{ Kind = 'access' }
@{ Kind = 'audit' }
) {
$typeName = if ($Kind -eq 'access') { 'Security2.SimpleFileSystemAccessRule' } else { 'Security2.SimpleFileSystemAuditRule' }
$arguments = @($objectPath, $identity, [Security2.FileSystemRights2]::Read)
if ($Kind -eq 'access') { $arguments += [System.Security.AccessControl.AccessControlType]::Allow }
$first = New-Object -TypeName $typeName -ArgumentList $arguments
$equal = New-Object -TypeName $typeName -ArgumentList $arguments
$arguments[1] = [Security2.IdentityReference2] 'S-1-5-32-546'
$differentIdentity = New-Object -TypeName $typeName -ArgumentList $arguments
$arguments[1] = $identity
$arguments[2] = [Security2.FileSystemRights2]::Delete
$differentRights = New-Object -TypeName $typeName -ArgumentList $arguments
$first.Equals($equal) | Should -BeTrue
$first.GetHashCode() | Should -Be $equal.GetHashCode()
$first.Equals($differentIdentity) | Should -BeFalse
$first.Equals($differentRights) | Should -BeFalse
$first.Equals($null) | Should -BeFalse
$equal.AccessControlType = 'Deny'
$first.Equals($equal) | Should -BeFalse
$first.Name | Should -BeExactly 'Rule.txt'
}
It 'Should reduce the generic <Rights> mask in access entries' -ForEach @(
@{ Rights = 'GenericRead'; Expected = 'Read' }
@{ Rights = 'GenericWrite'; Expected = 'Write' }
@{ Rights = 'GenericExecute'; Expected = 'Read' }
@{ Rights = 'GenericAll'; Expected = 'Read, Write, Delete' }
) {
$entry = New-Object -TypeName 'Security2.SimpleFileSystemAccessRule' -ArgumentList (
$objectPath, $identity, [Security2.FileSystemRights2] $Rights,
[System.Security.AccessControl.AccessControlType]::Allow
)
$entry.AccessRights | Should -Be ([Security2.SimpleFileSystemAccessRights] $Expected)
}
It 'Should convert an identity implicitly to its resolved display name and reject an unresolved name' {
$conversion = [Security2.IdentityReference2].GetMethods([Reflection.BindingFlags] 'Public, Static') |
Where-Object { $_.Name -eq 'op_Implicit' -and $_.ReturnType -eq [string] }
$conversion.Invoke($null, [object[]] @($identity.PSObject.BaseObject)) | Should -BeExactly $identity.ToString()
$unresolved = [Security2.IdentityReference2] 'S-1-5-21-1-2-3-1001'
$unresolved.Equals('Not-resolved') | Should -BeFalse
$identity.Equals($identity.AccountName) | Should -BeTrue
}
It 'Should compare different privilege values as unequal' {
$constructor = [ProcessPrivileges.PrivilegeAndAttributes].GetConstructor(
[Reflection.BindingFlags] 'NonPublic, Instance', $null,
[type[]] @([ProcessPrivileges.Privilege], [ProcessPrivileges.PrivilegeAttributes]), $null
)
$first = $constructor.Invoke(@([ProcessPrivileges.Privilege]::Backup, [ProcessPrivileges.PrivilegeAttributes]::Disabled))
$different = $constructor.Invoke(@([ProcessPrivileges.Privilege]::Restore, [ProcessPrivileges.PrivilegeAttributes]::Disabled))
$first.Equals($different) | Should -BeFalse
[ProcessPrivileges.PrivilegeAndAttributes]::op_Inequality($first, $different) | Should -BeTrue
}
}
Describe 'Access rule helpers that take a path' {
BeforeAll {
$allow = [System.Security.AccessControl.AccessControlType]::Allow
$noInheritance = [System.Security.AccessControl.InheritanceFlags]::None
$noPropagation = [System.Security.AccessControl.PropagationFlags]::None
$users = [Security2.IdentityReference2] 'S-1-5-32-545'
function Get-ExplicitEntries {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseSingularNouns', '', Justification = 'The helper returns the explicit entries of an item.'
)]
param ([string] $Path, [string] $Account = 'S-1-1-0')
$acl = Get-Acl -LiteralPath $Path
@($acl.GetAccessRules($true, $false, [System.Security.Principal.SecurityIdentifier]) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq $Account })
}
function New-AccountList {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Test helper that only creates a list.'
)]
param ()
$accounts = New-Object -TypeName 'System.Collections.Generic.List[Security2.IdentityReference2]'
$accounts.Add($identity)
$accounts.Add($users)
, $accounts
}
}
It 'Should add an allow entry with Synchronize to a <Kind> by its path' -ForEach @(
@{ Kind = 'file'; Directory = $false }
@{ Kind = 'folder'; Directory = $true }
) {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'AddByPath' -Directory:$Directory
$rule = [Security2.FileSystemAccessRule2]::AddFileSystemAccessRule(
$path, $identity, [Security2.FileSystemRights2]::ReadData, $allow, $noInheritance, $noPropagation
)
$rule.Account.Sid | Should -BeExactly 'S-1-1-0'
$entries = @(Get-ExplicitEntries -Path $path)
$entries | Should -HaveCount 1
$entries[0].AccessControlType | Should -Be 'Allow'
$entries[0].FileSystemRights | Should -Be ([System.Security.AccessControl.FileSystemRights] 'ReadData, Synchronize')
}
# The overload that takes a path returns an iterator, so the caller must enumerate the result to write the entries.
# The overloads that take an item write them at once; this test doesn't pin the difference.
It 'Should add the entries of several accounts to a <Kind> by its path when the result is enumerated' -ForEach @(
@{ Kind = 'file'; Directory = $false }
@{ Kind = 'folder'; Directory = $true }
) {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'AddListByPath' -Directory:$Directory
$accounts = New-AccountList
$pending = [Security2.FileSystemAccessRule2]::AddFileSystemAccessRule(
$path, $accounts, [Security2.FileSystemRights2]::ReadData, $allow, $noInheritance, $noPropagation
)
@($pending) | Should -HaveCount 2
@(Get-ExplicitEntries -Path $path) | Should -HaveCount 1
@(Get-ExplicitEntries -Path $path -Account 'S-1-5-32-545') | Should -HaveCount 1
}
It 'Should add the deny entries of several accounts without Synchronize when the result is enumerated' {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'DenyListByPath'
$accounts = New-AccountList
$deny = [System.Security.AccessControl.AccessControlType]::Deny
@([Security2.FileSystemAccessRule2]::AddFileSystemAccessRule(
$path, $accounts, [Security2.FileSystemRights2]::ReadData, $deny, $noInheritance, $noPropagation
)) | Should -HaveCount 2
foreach ($account in 'S-1-1-0', 'S-1-5-32-545') {
$entries = @(Get-ExplicitEntries -Path $path -Account $account)
$entries | Should -HaveCount 1
$entries[0].AccessControlType | Should -Be 'Deny'
$entries[0].FileSystemRights | Should -Be ([System.Security.AccessControl.FileSystemRights]::ReadData)
}
}
It 'Should add and remove a deny entry by its path without adding Synchronize' {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'DenyByPath'
$deny = [System.Security.AccessControl.AccessControlType]::Deny
[void] [Security2.FileSystemAccessRule2]::AddFileSystemAccessRule(
$path, $identity, [Security2.FileSystemRights2]::ReadData, $deny, $noInheritance, $noPropagation
)
$entries = @(Get-ExplicitEntries -Path $path)
$entries | Should -HaveCount 1
$entries[0].AccessControlType | Should -Be 'Deny'
$entries[0].FileSystemRights | Should -Be ([System.Security.AccessControl.FileSystemRights]::ReadData)
[Security2.FileSystemAccessRule2]::RemoveFileSystemAccessRule(
$path, $identity, [Security2.FileSystemRights2]::ReadData, $deny, $noInheritance, $noPropagation
)
@(Get-ExplicitEntries -Path $path) | Should -BeNullOrEmpty
}
It 'Should return no entries for an empty DACL when the sources of inherited entries are requested' {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'EmptyDacl'
Clear-NTFSAccess -Path $path -DisableInheritance -ErrorAction Stop
$rules = @([Security2.FileSystemAccessRule2]::GetFileSystemAccessRules($path, $true, $true, $true))
$rules | Should -BeNullOrEmpty
}
It 'Should add the entry of a rule that carries its path' {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'AddRule'
$raw = New-Object -TypeName 'System.Security.AccessControl.FileSystemAccessRule' -ArgumentList (
$sid, [System.Security.AccessControl.FileSystemRights]::ReadData, $allow
)
$rule = New-Object -TypeName 'Security2.FileSystemAccessRule2' -ArgumentList $raw, $path
[Security2.FileSystemAccessRule2]::AddFileSystemAccessRule($rule)
@(Get-ExplicitEntries -Path $path) | Should -HaveCount 1
}
# RemoveSpecific removes only an entry that matches exactly; without it, Windows removes the named rights from the
# matching entry.
It 'Should remove only an exactly matching entry of a <Kind> with removeSpecific and the named rights without it' -ForEach @(
@{ Kind = 'file'; Directory = $false }
@{ Kind = 'folder'; Directory = $true }
) {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'RemoveByPath' -Directory:$Directory
[void] [Security2.FileSystemAccessRule2]::AddFileSystemAccessRule(
$path, $identity, [Security2.FileSystemRights2] 'ReadData, WriteData', $allow, $noInheritance, $noPropagation
)
[Security2.FileSystemAccessRule2]::RemoveFileSystemAccessRule(
$path, $identity, [Security2.FileSystemRights2]::ReadData, $allow, $noInheritance, $noPropagation, $true
)
@(Get-ExplicitEntries -Path $path)[0].FileSystemRights |
Should -Be ([System.Security.AccessControl.FileSystemRights] 'ReadData, WriteData, Synchronize')
[Security2.FileSystemAccessRule2]::RemoveFileSystemAccessRule(
$path, $identity, [Security2.FileSystemRights2]::ReadData, $allow, $noInheritance, $noPropagation, $false
)
@(Get-ExplicitEntries -Path $path)[0].FileSystemRights |
Should -Be ([System.Security.AccessControl.FileSystemRights] 'WriteData, Synchronize')
[Security2.FileSystemAccessRule2]::RemoveFileSystemAccessRule(
$path, $identity, [Security2.FileSystemRights2]::WriteData, $allow, $noInheritance, $noPropagation, $true
)
@(Get-ExplicitEntries -Path $path) | Should -BeNullOrEmpty
}
It 'Should remove the entries of several accounts of a <Kind> by path' -ForEach @(
@{ Kind = 'file'; Directory = $false }
@{ Kind = 'folder'; Directory = $true }
) {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'RemoveListByPath' -Directory:$Directory
$accounts = New-AccountList
@([Security2.FileSystemAccessRule2]::AddFileSystemAccessRule(
$path, $accounts, [Security2.FileSystemRights2]::ReadData, $allow, $noInheritance, $noPropagation
)) | Should -HaveCount 2
[Security2.FileSystemAccessRule2]::RemoveFileSystemAccessRule(
$path, $accounts, [Security2.FileSystemRights2]::ReadData, $allow, $noInheritance, $noPropagation, $false
)
@(Get-ExplicitEntries -Path $path) | Should -BeNullOrEmpty
@(Get-ExplicitEntries -Path $path -Account 'S-1-5-32-545') | Should -BeNullOrEmpty
}
It 'Should remove the entry that a rule object describes from an item' {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'RemoveRuleObject'
[void] [Security2.FileSystemAccessRule2]::AddFileSystemAccessRule(
$path, $identity, [Security2.FileSystemRights2]::ReadData, $allow, $noInheritance, $noPropagation
)
$raw = New-Object -TypeName 'System.Security.AccessControl.FileSystemAccessRule' -ArgumentList (
$sid, [System.Security.AccessControl.FileSystemRights]::ReadData, $allow
)
$item = New-Object -TypeName 'Alphaleonis.Win32.Filesystem.FileInfo' -ArgumentList $path
[Security2.FileSystemAccessRule2]::RemoveFileSystemAccessRule($item, $raw, $false)
@(Get-ExplicitEntries -Path $path) | Should -BeNullOrEmpty
}
It 'Should return the explicit entries of a folder, and its inherited ones when asked, by its path' {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'GetByPath' -Directory
[void] [Security2.FileSystemAccessRule2]::AddFileSystemAccessRule(
$path, $identity, [Security2.FileSystemRights2]::ReadData, $allow, $noInheritance, $noPropagation
)
$explicit = @([Security2.FileSystemAccessRule2]::GetFileSystemAccessRules($path, $true, $false, $false))
$all = @([Security2.FileSystemAccessRule2]::GetFileSystemAccessRules($path, $true, $true, $true))
$explicit | Should -HaveCount 1
$explicit[0].Account.Sid | Should -BeExactly 'S-1-1-0'
$all.Count | Should -BeGreaterThan 1
@($all | Where-Object -FilterScript { $_.Account.Sid -eq 'S-1-1-0' }) | Should -HaveCount 1
}
}
Describe 'Audit rule helpers that take a path' -Skip:(-not $holdsSecurityPrivilege) {
BeforeAll {
$success = [System.Security.AccessControl.AuditFlags]::Success
$noInheritance = [System.Security.AccessControl.InheritanceFlags]::None
$noPropagation = [System.Security.AccessControl.PropagationFlags]::None
$users = [Security2.IdentityReference2] 'S-1-5-32-545'
function Get-AuditEntries {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseSingularNouns', '', Justification = 'The helper returns the audit entries of an item.'
)]
param ([string] $Path, [string] $Account = 'S-1-1-0')
$descriptor = Get-NTFSSecurityDescriptor -Path $Path
@($descriptor.SecurityDescriptor.GetAuditRules($true, $false, [System.Security.Principal.SecurityIdentifier]) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq $Account })
}
}
It 'Should add an audit entry to a <Kind> by its path' -ForEach @(
@{ Kind = 'file'; Directory = $false }
@{ Kind = 'folder'; Directory = $true }
) {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'AuditByPath' -Directory:$Directory
$rule = [Security2.FileSystemAuditRule2]::AddFileSystemAuditRule(
$path, $identity, [Security2.FileSystemRights2]::Delete, $success, $noInheritance, $noPropagation
)
$rule.Account.Sid | Should -BeExactly 'S-1-1-0'
$entries = @(Get-AuditEntries -Path $path)
$entries | Should -HaveCount 1
$entries[0].AuditFlags | Should -Be 'Success'
$entries[0].FileSystemRights | Should -Be ([System.Security.AccessControl.FileSystemRights]::Delete)
$found = @([Security2.FileSystemAuditRule2]::GetFileSystemAuditRules($path, $true, $false))
$found | Should -HaveCount 1
$found[0].Account.Sid | Should -BeExactly 'S-1-1-0'
$found[0].FullName | Should -BeExactly $path
}
It 'Should add the entries of several accounts to a <Kind> by its path when the result is enumerated, and remove them again' -ForEach @(
@{ Kind = 'file'; Directory = $false }
@{ Kind = 'folder'; Directory = $true }
) {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'AuditListByPath' -Directory:$Directory
$accounts = New-Object -TypeName 'System.Collections.Generic.List[Security2.IdentityReference2]'
$accounts.Add($identity)
$accounts.Add($users)
$pending = [Security2.FileSystemAuditRule2]::AddFileSystemAuditRule(
$path, $accounts, [Security2.FileSystemRights2]::Delete, $success, $noInheritance, $noPropagation
)
@($pending) | Should -HaveCount 2
@(Get-AuditEntries -Path $path) | Should -HaveCount 1
@(Get-AuditEntries -Path $path -Account 'S-1-5-32-545') | Should -HaveCount 1
[Security2.FileSystemAuditRule2]::RemoveFileSystemAuditRule(
$path, $identity, [Security2.FileSystemRights2]::Delete, $success, $noInheritance, $noPropagation, $true
)
@(Get-AuditEntries -Path $path) | Should -BeNullOrEmpty
@(Get-AuditEntries -Path $path -Account 'S-1-5-32-545') | Should -HaveCount 1
[Security2.FileSystemAuditRule2]::RemoveFileSystemAuditRule(
$path, $users, [Security2.FileSystemRights2]::Delete, $success, $noInheritance, $noPropagation, $false
)
@(Get-AuditEntries -Path $path -Account 'S-1-5-32-545') | Should -BeNullOrEmpty
}
It 'Should name the item of a rule, replay it, read it by path, and remove it by its rule object' {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'AuditReplay'
$raw = New-Object -TypeName 'System.Security.AccessControl.FileSystemAuditRule' -ArgumentList (
$sid, [System.Security.AccessControl.FileSystemRights]::Delete, $success
)
$item = New-Object -TypeName 'Alphaleonis.Win32.Filesystem.FileInfo' -ArgumentList $path
$rule = New-Object -TypeName 'Security2.FileSystemAuditRule2' -ArgumentList $raw, $item
$rule.FullName | Should -BeExactly $path
$rule.Name | Should -BeExactly (Split-Path -Path $path -Leaf)
[Security2.FileSystemAuditRule2]::AddFileSystemAuditRule($rule)
@(Get-AuditEntries -Path $path) | Should -HaveCount 1
$found = @([Security2.FileSystemAuditRule2]::GetFileSystemAuditRules($path, $true, $true))
$found | Should -HaveCount 1
$found[0].Account.Sid | Should -BeExactly 'S-1-1-0'
[Security2.FileSystemAuditRule2]::RemoveFileSystemAuditRule($item, $raw)
@(Get-AuditEntries -Path $path) | Should -BeNullOrEmpty
}
It 'Should remove a rule object from an item without audit entries and change nothing' {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'AuditNothing'
$raw = New-Object -TypeName 'System.Security.AccessControl.FileSystemAuditRule' -ArgumentList (
$sid, [System.Security.AccessControl.FileSystemRights]::Delete, $success
)
$item = New-Object -TypeName 'Alphaleonis.Win32.Filesystem.FileInfo' -ArgumentList $path
$before = (Get-Acl -LiteralPath $path).Sddl
[Security2.FileSystemAuditRule2]::RemoveFileSystemAuditRule($item, $raw)
(Get-Acl -LiteralPath $path).Sddl | Should -BeExactly $before
@(Get-AuditEntries -Path $path) | Should -BeNullOrEmpty
}
}
Describe 'Inheritance helpers that take a path' {
It 'Should block and restore the access inheritance of a <Kind> by its path' -ForEach @(
@{ Kind = 'file'; Directory = $false; Remove = $false }
@{ Kind = 'folder'; Directory = $true; Remove = $true }
) {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'InheritanceByPath' -Directory:$Directory
$inherited = @((Get-Acl -LiteralPath $path).GetAccessRules($false, $true, [System.Security.Principal.SecurityIdentifier])).Count
$inherited | Should -BeGreaterThan 0
[Security2.FileSystemInheritanceInfo]::DisableAccessInheritance($path, $Remove)
$acl = Get-Acl -LiteralPath $path
$acl.AreAccessRulesProtected | Should -BeTrue
@($acl.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier])).Count |
Should -Be $(if ($Remove) { 0 } else { $inherited })
[Security2.FileSystemInheritanceInfo]::EnableAccessInheritance($path, $Remove)
$acl = Get-Acl -LiteralPath $path
$acl.AreAccessRulesProtected | Should -BeFalse
@($acl.GetAccessRules($false, $true, [System.Security.Principal.SecurityIdentifier])).Count | Should -Be $inherited
}
It 'Should read the access inheritance of a file by its path and keep what the caller sets on the result' {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'InheritanceInfo'
$info = [Security2.FileSystemInheritanceInfo]::GetFileSystemInheritanceInfo($path)
$info.AccessInheritanceEnabled | Should -BeTrue
$info.Item.FullName | Should -BeExactly $path
$info.AccessInheritanceEnabled = $false
$info.AuditInheritanceEnabled = $true
$info.Item = New-Object -TypeName 'Alphaleonis.Win32.Filesystem.FileInfo' -ArgumentList $path
$info.AccessInheritanceEnabled | Should -BeFalse
$info.AuditInheritanceEnabled | Should -BeTrue
(Get-Acl -LiteralPath $path).AreAccessRulesProtected | Should -BeFalse
}
# The overloads that take a path do nothing for a path that is neither a file nor a folder.
It '<Method> should change nothing for a path that does not exist' -ForEach @(
@{ Method = 'EnableAccessInheritance' }
@{ Method = 'DisableAccessInheritance' }
@{ Method = 'EnableAuditInheritance' }
@{ Method = 'DisableAuditInheritance' }
) {
$missing = Join-Path -Path $sandbox -ChildPath ('Missing-{0}' -f [guid]::NewGuid().ToString('N'))
Assert-TestSandboxPath -Sandbox $sandbox -Path $missing
{ [Security2.FileSystemInheritanceInfo]::$Method($missing, $true) } | Should -Not -Throw
Test-Path -LiteralPath $missing | Should -BeFalse
}
It 'Should block and restore the audit inheritance of a <Kind> by its path' -Skip:(-not $holdsSecurityPrivilege) -ForEach @(
@{ Kind = 'file'; Directory = $false }
@{ Kind = 'folder'; Directory = $true }
) {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'AuditInheritanceByPath' -Directory:$Directory
(Get-NTFSInheritance -Path $path).AuditInheritanceEnabled | Should -BeTrue
[Security2.FileSystemInheritanceInfo]::DisableAuditInheritance($path, $false)
(Get-NTFSInheritance -Path $path).AuditInheritanceEnabled | Should -BeFalse
[Security2.FileSystemInheritanceInfo]::EnableAuditInheritance($path, $false)
(Get-NTFSInheritance -Path $path).AuditInheritanceEnabled | Should -BeTrue
}
}
Describe 'Owner and descriptor objects' {
It 'Should name the item and the account of an owner object' {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'OwnerObject'
$owner = Get-NTFSOwner -Path $path
$owner.Item.FullName | Should -BeExactly $path
$owner.FullName | Should -BeExactly $path
$owner.Account.Sid | Should -BeExactly $owner.Owner.Sid
}
It 'Should read the owner of a drive root also for a lowercase drive letter' {
$root = [IO.Path]::GetPathRoot($sandbox)
$expected = (Get-NTFSOwner -Path $root).Owner.Sid
$owner = Get-NTFSOwner -Path $root.ToLowerInvariant()
$owner.Owner.Sid | Should -BeExactly $expected
}
It 'Should name the item of a descriptor and write it to a folder by its path' {
$source = New-TestSandboxItem -Sandbox $sandbox -Name 'DescriptorSource' -Directory
$target = New-TestSandboxItem -Sandbox $sandbox -Name 'DescriptorTarget' -Directory
Add-NTFSAccess -Path $source -Account 'S-1-1-0' -AccessRights ReadData -AppliesTo ThisFolderOnly
$descriptor = Get-NTFSSecurityDescriptor -Path $source
$descriptor.Name | Should -BeExactly (Split-Path -Path $source -Leaf)
$descriptor.Write([string] $target)
@((Get-Acl -LiteralPath $target).GetAccessRules($true, $false, [System.Security.Principal.SecurityIdentifier]) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' }) | Should -HaveCount 1
}
# The item decides where Write puts the sections that the descriptor was read with, and Name and FullName follow it.
It 'Should write a descriptor to the item that the caller assigns' {
$source = New-TestSandboxItem -Sandbox $sandbox -Name 'RetargetSource'
$target = New-TestSandboxItem -Sandbox $sandbox -Name 'RetargetTarget'
Add-NTFSAccess -Path $source -Account 'S-1-1-0' -AccessRights ReadData
$descriptor = Get-NTFSSecurityDescriptor -Path $source
$descriptor.Item = Get-Item2 -Path $target
$descriptor.FullName | Should -BeExactly $target
$descriptor.Name | Should -BeExactly (Split-Path -Path $target -Leaf)
$descriptor.Write()
foreach ($path in $source, $target) {
@((Get-Acl -LiteralPath $path).GetAccessRules($true, $false, [System.Security.Principal.SecurityIdentifier]) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' }) | Should -HaveCount 1
}
}
It 'Should name the missing path when it writes a descriptor to an item that does not exist' {
$source = New-TestSandboxItem -Sandbox $sandbox -Name 'DescriptorMissingSource'
$missing = Join-Path -Path $sandbox -ChildPath ('Missing-{0}' -f [guid]::NewGuid().ToString('N'))
Assert-TestSandboxPath -Sandbox $sandbox -Path $missing
$descriptor = Get-NTFSSecurityDescriptor -Path $source
$failure = { $descriptor.Write($missing) } | Should -Throw -PassThru
$failure.Exception.GetBaseException() | Should -BeOfType [System.IO.FileNotFoundException]
$failure.Exception.GetBaseException().FileName | Should -BeExactly $missing
}
It 'Should leave both flags unset for an AppliesTo value that no case names' {
$inheritance = [System.Security.AccessControl.InheritanceFlags]::ContainerInherit
$propagation = [System.Security.AccessControl.PropagationFlags]::InheritOnly
[Security2.FileSystemSecurity2]::ConvertToFileSystemFlags(
[Enum]::ToObject([Security2.ApplyTo], 99), [ref] $inheritance, [ref] $propagation
)
$inheritance | Should -Be 'None'
$propagation | Should -Be 'None'
}
}
Describe 'Generic access rights and identity errors' {
It 'Should map the generic mask <Mask> to the file system rights <Expected>' -ForEach @(
@{ Mask = '80000000'; Expected = '00120089' }
@{ Mask = '40000000'; Expected = '00120116' }
@{ Mask = '20000000'; Expected = '001200A0' }
@{ Mask = '10000000'; Expected = '001F01FF' }
@{ Mask = 'C0000000'; Expected = '0012019F' }
@{ Mask = '80010000'; Expected = '00130089' }
@{ Mask = '001F01FF'; Expected = '001F01FF' }
@{ Mask = '00120089'; Expected = '00120089' }
@{ Mask = '02000000'; Expected = '02000000' }
@{ Mask = '82000000'; Expected = '02120089' }
@{ Mask = '00000000'; Expected = '00000000' }
) {
$rights = [Security2.FileSystemSecurity2]::MapGenericRightsToFileSystemRights([Convert]::ToUInt32($Mask, 16))
[int] $rights | Should -Be ([Convert]::ToInt32($Expected, 16))
}
It 'Should reject <Case> when it creates an identity' -ForEach @(
@{ Case = 'an empty value'; Value = ''; Expected = [System.ArgumentException] }
@{ Case = 'a SID with too many sub authorities'; Value = ('S-1-' + (('1-' * 20) + '1')); Expected = [System.InvalidCastException] }
@{ Case = 'an account that does not exist'; Value = 'NTFSSecurityNoSuchAccount'; Expected = [System.Security.Principal.IdentityNotMappedException] }
) {
$failure = { [Security2.IdentityReference2]::new($Value) } | Should -Throw -PassThru
# PowerShell wraps the exception of a constructor, which here wraps the cause of an invalid SID in turn.
$failure.Exception.InnerException | Should -BeOfType $Expected
}
}
# The helpers of the cmdlets are public extension methods, so a script can call them. The cmdlets pass what the guards
# of ForEach refuse and a parent that is a folder, never a file; the generic method is invoked through reflection,
# because Windows PowerShell can't name the type argument of a call.
Describe 'The extension methods of the cmdlets' {
BeforeAll {
$forEachMethod = [NTFSSecurity.Extensions].GetMethod('ForEach').MakeGenericMethod([string])
function New-ItemObject {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Test helper that only creates an object.'
)]
param ([string] $Library, [string] $Path)
if ($Library -eq 'AlphaFS') {
[Alphaleonis.Win32.Filesystem.FileInfo]::new($Path)
}
else {
[System.IO.FileInfo]::new($Path)
}
}
}
It 'ForEach should reject <Case>' -ForEach @(
@{ Case = 'a source that is null'; NullSource = $true }
@{ Case = 'an action that is null'; NullSource = $false }
) {
$arguments = [object[]]::new(2)
if ($NullSource) {
$arguments[1] = [System.Action[string]] { param ($Element) $null = $Element }
}
else {
$arguments[0] = [string[]] @('One')
}
$failure = { $forEachMethod.Invoke($null, $arguments) } | Should -Throw -PassThru
# Reflection wraps the exception of the method, and PowerShell wraps that in turn.
$failure.Exception.GetBaseException() | Should -BeOfType [System.ArgumentException]
}
It 'ForEach should run the action for each element in order' {
$seen = New-Object -TypeName 'System.Collections.Generic.List[string]'
$arguments = [object[]]::new(2)
$arguments[0] = [string[]] @('One', 'Two', 'Three')
$arguments[1] = [System.Action[string]] { param ($Element) $seen.Add($Element) }
$forEachMethod.Invoke($null, $arguments) | Out-Null
$seen -join ',' | Should -BeExactly 'One,Two,Three'
}
Context 'GetParent' {
BeforeAll {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'ParentFolder' -Directory
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'ParentFile'
$inFolder = Join-Path -Path $folder -ChildPath 'Child.txt'
$belowFile = Join-Path -Path $file -ChildPath 'Child.txt'
$belowMissing = Join-Path -Path $sandbox -ChildPath 'Missing\Child.txt'
}
It 'Should return a folder as a DirectoryInfo of <Library>' -ForEach @(
@{ Library = 'AlphaFS'; TypeName = 'Alphaleonis.Win32.Filesystem.DirectoryInfo' }
@{ Library = 'System.IO'; TypeName = 'System.IO.DirectoryInfo' }
) {
$parent = [NTFSSecurity.Extensions]::GetParent((New-ItemObject -Library $Library -Path $inFolder))
$parent.GetType().FullName | Should -BeExactly $TypeName
$parent.FullName | Should -BeExactly $folder
}
# A FileInfo can name a path below a file, which no file system holds, so the parent path names a file.
It 'Should return a parent path that names a file as a FileInfo of <Library>' -ForEach @(
@{ Library = 'AlphaFS'; TypeName = 'Alphaleonis.Win32.Filesystem.FileInfo' }
@{ Library = 'System.IO'; TypeName = 'System.IO.FileInfo' }
) {
$parent = [NTFSSecurity.Extensions]::GetParent((New-ItemObject -Library $Library -Path $belowFile))
$parent.GetType().FullName | Should -BeExactly $TypeName
$parent.FullName | Should -BeExactly $file
}
It 'Should throw a FileNotFoundException for a parent that does not exist, for <Library>' -ForEach @(
@{ Library = 'AlphaFS' }
@{ Library = 'System.IO' }
) {
$failure = { [NTFSSecurity.Extensions]::GetParent((New-ItemObject -Library $Library -Path $belowMissing)) } |
Should -Throw -PassThru
$failure.Exception.GetBaseException() | Should -BeOfType [System.IO.FileNotFoundException]
}
}
}

14
Tests/Owner.Tests.ps1

@ -299,5 +299,19 @@ Describe 'Set-NTFSOwner' {
Set-NTFSSecurityDescriptor -SecurityDescriptor $sd -ErrorAction Stop
Get-TestOwner -Path $file | Should -Be $currentUser
}
It 'Should write nothing without -PassThru and leave the owner of the item unchanged' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'SetOwnerDescriptorQuiet'
$owner = Get-TestOwner -Path $file
$sd = Get-NTFSSecurityDescriptor -Path $file
$sidType = [System.Security.Principal.SecurityIdentifier]
$sd.SecurityDescriptor.GetOwner($sidType).Value | Should -Not -Be 'S-1-1-0'
$result = @(Set-NTFSOwner -SecurityDescriptor $sd -Account 'S-1-1-0' -ErrorAction Stop)
$result | Should -BeNullOrEmpty
$sd.SecurityDescriptor.GetOwner($sidType).Value | Should -Be 'S-1-1-0'
Get-TestOwner -Path $file | Should -Be $owner
}
}
}

39
Tests/PathErrors.Tests.ps1

@ -35,6 +35,7 @@ BeforeDiscovery {
@{ Command = 'Set-NTFSOwner'; Parameters = @{ Account = $currentUser }; ErrorId = 'ReadFileError'; Output = $false }
@{ Command = 'Get-NTFSSecurityDescriptor'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true }
@{ Command = 'Get-Item2'; Parameters = @{}; ErrorId = 'FileNotFound'; Output = $true }
@{ Command = 'Get-ChildItem2'; Parameters = @{}; ErrorId = 'FileNotFound'; Output = $true }
@{ Command = 'Get-FileHash2'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true }
@{ Command = 'Get-NTFSHardLink'; Parameters = @{}; ErrorId = 'FileNotFound'; Output = $true }
)
@ -259,6 +260,44 @@ Describe 'An item whose owner may not change its permissions' {
@($acl.GetAccessRules($true, $false, $sidType)) | Should -BeNullOrEmpty
}
# With the DACL cleared and protected, nobody keeps the right to set an owner, so setting the previous owner back
# fails. The user owned the item already, so there is no owner to set back.
It 'Clear-NTFSAccess -DisableInheritance should take ownership, clear and protect the DACL, and not set an unchanged owner back' {
$user = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
Set-TestOwner -Sandbox $sandbox -Path $file -Sid $user
Add-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' }
Clear-NTFSAccess -Path $file -DisableInheritance -ErrorVariable changeErrors -ErrorAction SilentlyContinue
$changeErrors | Should -BeNullOrEmpty
$acl = Get-TestAcl -Path $file
$acl.GetOwner($sidType).Value | Should -Be $user
$acl.AreAccessRulesProtected | Should -BeTrue
@($acl.GetAccessRules($true, $true, $sidType)) | Should -BeNullOrEmpty
}
# Windows lets the owner of an item set another owner only with the Restore privilege, which the tests turn off, or
# with the right in the DACL, which the cleared DACL no longer holds. The cmdlet reports the owner it cannot set back.
It 'Clear-NTFSAccess -DisableInheritance should report RestoreOwnerError for a previous owner that it cannot set back' -Skip:(-not $holdsRestorePrivilege) {
$user = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
Set-TestOwner -Sandbox $sandbox -Path $file -Sid 'S-1-5-32-544'
(Get-Privileges | Where-Object -Property Privilege -EQ -Value 'Restore').PrivilegeState | Should -Be 'Disabled'
Add-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' }
Clear-NTFSAccess -Path $file -DisableInheritance -ErrorVariable changeErrors -ErrorAction SilentlyContinue
$changeErrors | Should -HaveCount 1
$changeErrors[0].FullyQualifiedErrorId | Should -BeLike 'RestoreOwnerError,*'
$changeErrors[0].CategoryInfo.Category | Should -Be 'WriteError'
$changeErrors[0].TargetObject | Should -Be $file
$acl = Get-TestAcl -Path $file
$acl.GetOwner($sidType).Value | Should -Be $user
$acl.AreAccessRulesProtected | Should -BeTrue
@($acl.GetAccessRules($true, $true, $sidType)) | Should -BeNullOrEmpty
}
It 'Disable-NTFSAccessInheritance should take ownership, protect the DACL, and set the owner back' {
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' }

638
Tests/PipelineControl.Tests.ps1

@ -0,0 +1,638 @@
<#
Tests how the cmdlets of the module built in NTFSSecurity\bin\Release behave when a later command in the pipeline
ends it: a break or continue in a script block, Select-Object -First, or a terminating error such as a throw. The
exception that carries it passes through the cmdlet while it writes an object, an error, a verbose message, or a debug
message. A catch-all for the failures of an item must not report it as an error of that item and go on with the next
one: a cmdlet that removes, copies, moves, or changes items would change them all, although the caller ended the
pipeline, and the caller would never see the exception. Every test works on files and folders in a sandbox.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.'
)]
param ()
BeforeDiscovery {
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force
$canReadAudit = Test-PrivilegeHeld -Name 'SeSecurityPrivilege'
$names = @(
'Get-ChildItem2', 'Get-DiskSpace', 'Get-FileHash2', 'Get-Item2', 'Get-NTFSAccess', 'Get-NTFSEffectiveAccess',
'Get-NTFSHardLink', 'Get-NTFSInheritance', 'Get-NTFSOrphanedAccess', 'Get-NTFSOwner', 'Get-NTFSSecurityDescriptor',
'Get-NTFSSimpleAccess', 'Get-Privileges', 'Test-Path2', 'Add-NTFSAccess', 'Remove-NTFSAccess',
'Disable-NTFSAccessInheritance', 'Enable-NTFSAccessInheritance', 'Set-NTFSInheritance', 'Set-NTFSOwner',
'Set-NTFSSecurityDescriptor', 'Copy-Item2', 'Move-Item2', 'Remove-Item2'
)
$auditNames = @(
'Get-NTFSAudit', 'Get-NTFSOrphanedAudit', 'Add-NTFSAudit', 'Remove-NTFSAudit', 'Disable-NTFSAuditInheritance',
'Enable-NTFSAuditInheritance'
)
$loopCases = foreach ($name in $names) {
foreach ($keyword in 'break', 'continue') {
@{ Name = $name; Keyword = $keyword }
}
}
$stopCases = foreach ($name in $names) {
@{ Name = $name }
}
$auditLoopCases = foreach ($name in $auditNames) {
foreach ($keyword in 'break', 'continue') {
@{ Name = $name; Keyword = $keyword }
}
}
$auditStopCases = foreach ($name in $auditNames) {
@{ Name = $name }
}
$failureCases = foreach ($name in $names) {
foreach ($style in 'throw', 'Write-Error -ErrorAction Stop') {
@{ Name = $name; Style = $style }
}
}
$auditFailureCases = foreach ($name in $auditNames) {
foreach ($style in 'throw', 'Write-Error -ErrorAction Stop') {
@{ Name = $name; Style = $style }
}
}
$streamCases = foreach ($case in @(
@{ Name = 'Get-FileHash2'; Stream = 'verbose' }
@{ Name = 'Set-NTFSSecurityDescriptor'; Stream = 'verbose' }
@{ Name = 'Set-NTFSOwner'; Stream = 'debug' }
)) {
foreach ($style in 'Select-Object -First 1', 'throw') {
@{ Name = $case.Name; Stream = $case.Stream; Style = $style }
}
}
}
BeforeAll {
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force
$modulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\NTFSSecurity\bin\Release\NTFSSecurity.psd1'
Import-Module -Name $modulePath -Force -ErrorAction Stop
$sandbox = New-TestSandbox -Name 'PipelineControl'
Push-Location -LiteralPath $sandbox
$sidType = [System.Security.Principal.SecurityIdentifier]
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
$orphan = 'S-1-5-21-1-2-3-1001'
$privateData = (Get-Module -Name NTFSSecurity).PrivateData
function New-Pair {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Test helper that only writes to the sandbox.'
)]
param ([switch] $Directory)
@{
First = New-TestSandboxItem -Sandbox $sandbox -Name 'First' -Directory:$Directory
Second = New-TestSandboxItem -Sandbox $sandbox -Name 'Second' -Directory:$Directory
}
}
function Test-ExplicitEntry {
param ([string] $Path, [string] $Account)
@((Get-Acl -LiteralPath $Path).GetAccessRules($true, $false, $sidType) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq $Account }).Count -gt 0
}
# Each case runs one command over the two items of its context. Untouched tells whether the second item is as it
# was, which it is only when the command stopped after the first one.
$cases = @{
'Get-ChildItem2' = @{
# The first file is two levels below the folder, so the exception passes the frames of the recursion.
Prepare = {
$top = New-TestSandboxItem -Sandbox $sandbox -Name 'Tree' -Directory
foreach ($relative in 'A\B\Two.txt', 'C\Three.txt') {
$file = Join-Path -Path $top -ChildPath $relative
Assert-TestSandboxPath -Sandbox $sandbox -Path $file
New-Item -ItemType Directory -Path (Split-Path -Path $file -Parent) -Force | Out-Null
Set-Content -LiteralPath $file -Value 'Tree'
}
@{ Top = $top }
}
Run = { param ($Context) Get-ChildItem2 -Path $Context.Top -Recurse -File -ErrorAction SilentlyContinue }
}
'Get-DiskSpace' = @{
Prepare = { @{} }
Run = { Get-DiskSpace -ErrorAction SilentlyContinue }
}
'Get-FileHash2' = @{
Prepare = { New-Pair }
Run = { param ($Context) Get-FileHash2 -Path $Context.First, $Context.Second -ErrorAction SilentlyContinue }
}
'Get-Item2' = @{
Prepare = { New-Pair }
Run = { param ($Context) Get-Item2 -Path $Context.First, $Context.Second -ErrorAction SilentlyContinue }
}
'Get-NTFSAccess' = @{
Prepare = { New-Pair }
Run = { param ($Context) Get-NTFSAccess -Path $Context.First, $Context.Second -ErrorAction SilentlyContinue }
}
'Get-NTFSEffectiveAccess' = @{
Prepare = { New-Pair }
Run = { param ($Context) Get-NTFSEffectiveAccess -Path $Context.First, $Context.Second -WarningAction SilentlyContinue -ErrorAction SilentlyContinue }
}
'Get-NTFSHardLink' = @{
Prepare = { New-Pair }
Run = { param ($Context) Get-NTFSHardLink -Path $Context.First, $Context.Second -ErrorAction SilentlyContinue }
}
'Get-NTFSInheritance' = @{
Prepare = { New-Pair }
Run = { param ($Context) Get-NTFSInheritance -Path $Context.First, $Context.Second -ErrorAction SilentlyContinue }
}
'Get-NTFSOrphanedAccess' = @{
Prepare = {
$context = New-Pair
Add-NTFSAccess -Path $context.First, $context.Second -Account $orphan -AccessRights ReadData -ErrorAction Stop
$context
}
Run = { param ($Context) Get-NTFSOrphanedAccess -Path $Context.First, $Context.Second -ErrorAction SilentlyContinue }
}
'Get-NTFSOwner' = @{
Prepare = { New-Pair }
Run = { param ($Context) Get-NTFSOwner -Path $Context.First, $Context.Second -ErrorAction SilentlyContinue }
}
'Get-NTFSSecurityDescriptor' = @{
Prepare = { New-Pair }
Run = { param ($Context) Get-NTFSSecurityDescriptor -Path $Context.First, $Context.Second -ErrorAction SilentlyContinue }
}
'Get-NTFSSimpleAccess' = @{
Prepare = { New-Pair -Directory }
Run = { param ($Context) Get-NTFSSimpleAccess -Path $Context.First, $Context.Second -IncludeRootFolder:$false -ErrorAction SilentlyContinue }
}
'Get-Privileges' = @{
Prepare = { @{} }
Run = { Get-Privileges -ErrorAction SilentlyContinue }
}
'Test-Path2' = @{
Prepare = { New-Pair }
Run = { param ($Context) Test-Path2 -Path $Context.First, $Context.Second -ErrorAction SilentlyContinue }
}
'Add-NTFSAccess' = @{
Prepare = { New-Pair }
Run = { param ($Context) Add-NTFSAccess -Path $Context.First, $Context.Second -Account 'S-1-1-0' -AccessRights ReadData -PassThru -ErrorAction SilentlyContinue }
Untouched = { param ($Context) -not (Test-ExplicitEntry -Path $Context.Second -Account 'S-1-1-0') }
}
'Remove-NTFSAccess' = @{
Prepare = {
$context = New-Pair
Add-NTFSAccess -Path $context.First, $context.Second -Account 'S-1-1-0' -AccessRights ReadData -ErrorAction Stop
$context
}
Run = { param ($Context) Remove-NTFSAccess -Path $Context.First, $Context.Second -Account 'S-1-1-0' -AccessRights ReadData -PassThru -ErrorAction SilentlyContinue }
Untouched = { param ($Context) Test-ExplicitEntry -Path $Context.Second -Account 'S-1-1-0' }
}
'Disable-NTFSAccessInheritance' = @{
Prepare = { New-Pair }
Run = { param ($Context) Disable-NTFSAccessInheritance -Path $Context.First, $Context.Second -PassThru -ErrorAction SilentlyContinue }
Untouched = { param ($Context) -not (Get-Acl -LiteralPath $Context.Second).AreAccessRulesProtected }
}
'Enable-NTFSAccessInheritance' = @{
Prepare = {
$context = New-Pair
Disable-NTFSAccessInheritance -Path $context.First, $context.Second -ErrorAction Stop
$context
}
Run = { param ($Context) Enable-NTFSAccessInheritance -Path $Context.First, $Context.Second -PassThru -ErrorAction SilentlyContinue }
Untouched = { param ($Context) (Get-Acl -LiteralPath $Context.Second).AreAccessRulesProtected }
}
'Set-NTFSInheritance' = @{
Prepare = { New-Pair }
Run = { param ($Context) Set-NTFSInheritance -Path $Context.First, $Context.Second -AccessInheritanceEnabled $false -PassThru -ErrorAction SilentlyContinue }
Untouched = { param ($Context) -not (Get-Acl -LiteralPath $Context.Second).AreAccessRulesProtected }
}
'Set-NTFSOwner' = @{
Prepare = { New-Pair }
Run = { param ($Context) Set-NTFSOwner -Path $Context.First, $Context.Second -Account $currentUser -PassThru -ErrorAction SilentlyContinue }
}
'Set-NTFSSecurityDescriptor' = @{
Prepare = {
$context = New-Pair
$context.Descriptors = @(Get-NTFSSecurityDescriptor -Path $context.First, $context.Second -ErrorAction Stop)
Add-NTFSAccess -SecurityDescriptor $context.Descriptors -Account 'S-1-1-0' -AccessRights ReadData -ErrorAction Stop
$context
}
Run = { param ($Context) Set-NTFSSecurityDescriptor -SecurityDescriptor $Context.Descriptors -PassThru -ErrorAction SilentlyContinue }
Untouched = { param ($Context) -not (Test-ExplicitEntry -Path $Context.Second -Account 'S-1-1-0') }
}
'Copy-Item2' = @{
Prepare = {
$context = New-Pair
$context.Destination = New-TestSandboxItem -Sandbox $sandbox -Name 'CopyTo' -Directory
$context
}
Run = { param ($Context) Copy-Item2 -Path $Context.First, $Context.Second -Destination $Context.Destination -PassThru $true -ErrorAction SilentlyContinue }
Untouched = { param ($Context) -not (Test-Path -LiteralPath (Join-Path -Path $Context.Destination -ChildPath (Split-Path -Path $Context.Second -Leaf))) }
}
'Move-Item2' = @{
Prepare = {
$context = New-Pair
$context.Destination = New-TestSandboxItem -Sandbox $sandbox -Name 'MoveTo' -Directory
$context
}
Run = { param ($Context) Move-Item2 -Path $Context.First, $Context.Second -Destination $Context.Destination -PassThru $true -ErrorAction SilentlyContinue }
Untouched = { param ($Context) Test-Path -LiteralPath $Context.Second }
}
'Remove-Item2' = @{
Prepare = { New-Pair }
Run = { param ($Context) Remove-Item2 -Path $Context.First, $Context.Second -PassThru -ErrorAction SilentlyContinue }
Untouched = { param ($Context) Test-Path -LiteralPath $Context.Second }
}
'Get-NTFSAudit' = @{
Prepare = {
$context = New-Pair
Add-NTFSAudit -Path $context.First, $context.Second -Account 'S-1-1-0' -AccessRights Delete -AuditFlags Success -ErrorAction Stop
$context
}
Run = { param ($Context) Get-NTFSAudit -Path $Context.First, $Context.Second -ErrorAction SilentlyContinue }
}
'Get-NTFSOrphanedAudit' = @{
Prepare = {
$context = New-Pair
Add-NTFSAudit -Path $context.First, $context.Second -Account $orphan -AccessRights Delete -AuditFlags Success -ErrorAction Stop
$context
}
Run = { param ($Context) Get-NTFSOrphanedAudit -Path $Context.First, $Context.Second -ErrorAction SilentlyContinue }
}
'Add-NTFSAudit' = @{
Prepare = { New-Pair }
Run = { param ($Context) Add-NTFSAudit -Path $Context.First, $Context.Second -Account 'S-1-1-0' -AccessRights Delete -AuditFlags Success -PassThru -ErrorAction SilentlyContinue }
Untouched = { param ($Context) @(Get-NTFSAudit -Path $Context.Second -ErrorAction Stop).Count -eq 0 }
}
'Remove-NTFSAudit' = @{
# The entry of the orphan goes; the one of Everyone stays, so that there is an object to write.
Prepare = {
$context = New-Pair
foreach ($account in $orphan, 'S-1-1-0') {
Add-NTFSAudit -Path $context.First, $context.Second -Account $account -AccessRights Delete -AuditFlags Success -ErrorAction Stop
}
$context
}
Run = { param ($Context) Remove-NTFSAudit -Path $Context.First, $Context.Second -Account $orphan -AccessRights Delete -AuditFlags Success -PassThru -ErrorAction SilentlyContinue }
Untouched = { param ($Context) @(Get-NTFSAudit -Path $Context.Second -Account $orphan -ErrorAction Stop).Count -gt 0 }
}
'Disable-NTFSAuditInheritance' = @{
Prepare = { New-Pair }
Run = { param ($Context) Disable-NTFSAuditInheritance -Path $Context.First, $Context.Second -PassThru -ErrorAction SilentlyContinue }
Untouched = { param ($Context) (Get-NTFSInheritance -Path $Context.Second -ErrorAction Stop).AuditInheritanceEnabled }
}
'Enable-NTFSAuditInheritance' = @{
Prepare = {
$context = New-Pair
Disable-NTFSAuditInheritance -Path $context.First, $context.Second -ErrorAction Stop
$context
}
Run = { param ($Context) Enable-NTFSAuditInheritance -Path $Context.First, $Context.Second -PassThru -ErrorAction SilentlyContinue }
Untouched = { param ($Context) -not (Get-NTFSInheritance -Path $Context.Second -ErrorAction Stop).AuditInheritanceEnabled }
}
}
# The commands that write a verbose or a debug message inside the try of their loop, which the later command takes.
# The first record that reaches Select-Object ends the pipeline there. The preference of the debug stream is set by
# Assert-StreamStop: the Debug switch would ask before every message. The error action is named because the CI runner
# sets $ErrorActionPreference to Stop: a catch that reports the exception of the later command as an error of the
# item would then end the pipeline with it, and the test could not tell that catch from passing the exception on.
$streamRuns = @{
'Get-FileHash2/verbose' = @{
# The first path is a folder, which the cmdlet skips with a verbose message.
Prepare = {
$context = New-Pair -Directory
$context.File = New-TestSandboxItem -Sandbox $sandbox -Name 'Hashed'
$context
}
Run = { param ($Context) Get-FileHash2 -Path $Context.First, $Context.File -Verbose -ErrorAction SilentlyContinue 4>&1 }
}
'Set-NTFSSecurityDescriptor/verbose' = @{
Prepare = $cases['Set-NTFSSecurityDescriptor'].Prepare
Run = { param ($Context) Set-NTFSSecurityDescriptor -SecurityDescriptor $Context.Descriptors -Verbose -ErrorAction SilentlyContinue 4>&1 }
Untouched = $cases['Set-NTFSSecurityDescriptor'].Untouched
}
'Set-NTFSOwner/debug' = @{
Prepare = { New-Pair }
Run = { param ($Context) Set-NTFSOwner -Path $Context.First, $Context.Second -Account $currentUser -ErrorAction SilentlyContinue 5>&1 }
}
}
# The command writes its first object, and the break or continue of the later command ends the loop around the
# pipeline before the next statement of the loop runs.
function Assert-LoopControl {
param ([string] $Name, [string] $Keyword)
$case = $cases[$Name]
$context = & $case.Prepare
$emitted = 0
$reachedEnd = $false
$Error.Clear()
foreach ($round in 1) {
& $case.Run $context | ForEach-Object -Process {
$emitted++
if ($Keyword -eq 'break') { break } else { continue }
}
$reachedEnd = $true
}
$emitted | Should -Be 1
$reachedEnd | Should -BeFalse
$Error.Count | Should -Be 0
if ($case.Untouched) {
(& $case.Untouched $context) | Should -BeTrue
}
}
function Assert-PipelineStop {
param ([string] $Name)
$case = $cases[$Name]
$context = & $case.Prepare
$Error.Clear()
$result = @(& $case.Run $context | Select-Object -First 1)
$result | Should -HaveCount 1
$Error.Count | Should -Be 0
if ($case.Untouched) {
(& $case.Untouched $context) | Should -BeTrue
}
}
# A later command that fails with a terminating error ends the pipeline for the commands before it. The error is the
# caller's: the cmdlet must neither report it as an error of an item nor go on with the next item. PowerShell wraps
# the exception of a throw, so the cmdlet never sees the type that was thrown.
function Assert-DownstreamFailure {
param ([string] $Name, [string] $Style)
$case = $cases[$Name]
$context = & $case.Prepare
$emitted = 0
$caught = $null
$Error.Clear()
try {
& $case.Run $context | ForEach-Object -Process {
$emitted++
if ($Style -eq 'throw') { throw 'Downstream failure' }
Write-Error -Message 'Downstream failure' -ErrorAction Stop
}
}
catch {
$caught = $_
}
$caught.Exception.Message | Should -BeLike '*Downstream failure*'
$emitted | Should -Be 1
@($Error | Where-Object -FilterScript { $_.Exception.Message -notlike '*Downstream failure*' }) | Should -BeNullOrEmpty
if ($case.Untouched) {
(& $case.Untouched $context) | Should -BeTrue
}
}
# The first verbose or debug record reaches the later command, which ends the pipeline inside the try of the loop:
# Select-Object raises the end of the pipeline, a throw raises an exception of its own. With the privileges enabled,
# the cmdlet writes a message before that, outside the try, so they stay off here.
function Assert-StreamStop {
param ([string] $Name, [string] $Stream, [string] $Style)
$case = $streamRuns["$Name/$Stream"]
$recordType = if ($Stream -eq 'debug') { [System.Management.Automation.DebugRecord] } else { [System.Management.Automation.VerboseRecord] }
$context = & $case.Prepare
$saved = $privateData['EnablePrivileges']
$savedDebugPreference = $DebugPreference
$privateData['EnablePrivileges'] = $false
$DebugPreference = if ($Stream -eq 'debug') { 'Continue' } else { $savedDebugPreference }
$emitted = 0
$caught = $null
$result = @()
$Error.Clear()
try {
if ($Style -eq 'throw') {
try {
& $case.Run $context | ForEach-Object -Process {
$emitted++
throw 'Downstream failure'
}
}
catch {
$caught = $_
}
}
else {
$result = @(& $case.Run $context | Select-Object -First 1)
}
}
finally {
$privateData['EnablePrivileges'] = $saved
$DebugPreference = $savedDebugPreference
}
if ($Style -eq 'throw') {
$caught.Exception.Message | Should -BeLike '*Downstream failure*'
$emitted | Should -Be 1
@($Error | Where-Object -FilterScript { $_.Exception.Message -notlike '*Downstream failure*' }) | Should -BeNullOrEmpty
}
else {
$result | Should -HaveCount 1
$result[0] | Should -BeOfType $recordType
$Error.Count | Should -Be 0
}
if ($case.Untouched) {
(& $case.Untouched $context) | Should -BeTrue
}
}
}
AfterAll {
Pop-Location
Remove-TestSandbox -Sandbox $sandbox
Remove-Module -Name NTFSSecurity -Force -ErrorAction SilentlyContinue
}
Describe 'A later command that ends the pipeline' {
It '<Name> should leave the loop for <Keyword> after its first object and change nothing else' -ForEach $loopCases {
Assert-LoopControl -Name $Name -Keyword $Keyword
}
It '<Name> should stop after the first object for Select-Object -First 1 and change nothing else' -ForEach $stopCases {
Assert-PipelineStop -Name $Name
}
It '<Name> should leave the loop for <Keyword> after its first object and change nothing else' -Skip:(-not $canReadAudit) -ForEach $auditLoopCases {
Assert-LoopControl -Name $Name -Keyword $Keyword
}
It '<Name> should stop after the first object for Select-Object -First 1 and change nothing else' -Skip:(-not $canReadAudit) -ForEach $auditStopCases {
Assert-PipelineStop -Name $Name
}
It '<Name> should stop for a terminating error (<Style>) of the later command and change nothing else' -ForEach $failureCases {
Assert-DownstreamFailure -Name $Name -Style $Style
}
It '<Name> should stop for a terminating error (<Style>) of the later command and change nothing else' -Skip:(-not $canReadAudit) -ForEach $auditFailureCases {
Assert-DownstreamFailure -Name $Name -Style $Style
}
It '<Name> should stop at the <Stream> message for <Style> of the later command and change nothing else' -ForEach $streamCases {
Assert-StreamStop -Name $Name -Stream $Stream -Style $Style
}
}
# The errors that Get-ChildItem2 writes for a folder that it cannot read reach a later command too, for example with 2>&1.
# The folders that cannot be read come first in the order of the file system, so that the folder with the file is reached
# only if the listing goes on after the first error.
Describe 'A later command and the error of a folder that Get-ChildItem2 cannot read' {
BeforeAll {
$errorTree = New-TestSandboxItem -Sandbox $sandbox -Name 'ErrorTree' -Directory
$unreadable = foreach ($name in 'A', 'B') {
$folder = Join-Path -Path $errorTree -ChildPath $name
Assert-TestSandboxPath -Sandbox $sandbox -Path $folder
New-Item -ItemType Directory -Path $folder | Out-Null
$folder
}
$readableFile = Join-Path -Path $errorTree -ChildPath 'C\Three.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $readableFile
New-Item -ItemType Directory -Path (Split-Path -Path $readableFile -Parent) | Out-Null
Set-Content -LiteralPath $readableFile -Value 'Three'
foreach ($folder in $unreadable) {
Add-TestDenyRule -Sandbox $sandbox -Path $folder -Rights @{ 'S-1-1-0' = 'ReadData' }
}
}
# Before 5.0.0-rc7, the recursion took what the later command threw for the error of a nested folder as a failure of
# the folder above it, wrote a verbose message, and left the loop over the folders: the listing ended early and the
# caller never saw the exception. The error action is named because the CI runner sets $ErrorActionPreference to Stop,
# which would end the listing at the first error before the later command saw it.
It 'Should pass on what a later command throws when it takes the error of a nested folder' {
$emitted = 0
$caught = $null
try {
Get-ChildItem2 -Path $errorTree -Recurse -File -ErrorAction Continue 2>&1 | ForEach-Object -Process {
$emitted++
throw 'Downstream failure'
}
}
catch {
$caught = $_
}
$caught.Exception.Message | Should -BeLike '*Downstream failure*'
$emitted | Should -Be 1
}
It 'Should leave the loop for a <Keyword> of a later command that takes the error of a nested folder' -ForEach @(
@{ Keyword = 'break' }
@{ Keyword = 'continue' }
) {
$emitted = 0
$reachedEnd = $false
foreach ($round in 1) {
Get-ChildItem2 -Path $errorTree -Recurse -File -ErrorAction Continue 2>&1 | ForEach-Object -Process {
$emitted++
if ($Keyword -eq 'break') { break } else { continue }
}
$reachedEnd = $true
}
$emitted | Should -Be 1
$reachedEnd | Should -BeFalse
}
It 'Should stop with the error of the first nested folder that it cannot read for -ErrorAction Stop' {
$listed = New-Object -TypeName 'System.Collections.Generic.List[object]'
$caught = $null
try {
Get-ChildItem2 -Path $errorTree -Recurse -File -ErrorAction Stop | ForEach-Object -Process { $listed.Add($_) }
}
catch {
$caught = $_
}
$caught | Should -Not -BeNullOrEmpty
$caught.FullyQualifiedErrorId | Should -BeLike 'DirUnauthorizedAccessError,*'
$caught.TargetObject | Should -Be $unreadable[0]
$listed | Should -BeNullOrEmpty
}
}
# The catches of Get-ChildItem2 for an UnauthorizedAccessException and of Remove-Item2 for an IOException don't ask where
# the exception comes from: they rely on PowerShell wrapping what a later command throws, so that an exception of these
# types never reaches them as it was thrown. A PowerShell version that hands it on as it is fails these tests.
Describe 'A later command that throws an exception of a type that a cmdlet handles' {
It 'Get-ChildItem2 should pass on a thrown UnauthorizedAccessException' {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'ThrownDenied' -Directory
$files = 'One.txt', 'Two.txt' | ForEach-Object -Process { Join-Path -Path $folder -ChildPath $_ }
Assert-TestSandboxPath -Sandbox $sandbox -Path $files
Set-Content -LiteralPath $files -Value 'File'
$emitted = 0
$caught = $null
$Error.Clear()
try {
Get-ChildItem2 -Path $folder -File -ErrorAction SilentlyContinue | ForEach-Object -Process {
$emitted++
throw [System.UnauthorizedAccessException]::new('Downstream failure')
}
}
catch {
$caught = $_
}
$caught.Exception | Should -BeOfType [System.UnauthorizedAccessException]
$caught.Exception.Message | Should -BeExactly 'Downstream failure'
$emitted | Should -Be 1
@($Error | Where-Object -FilterScript { $_.FullyQualifiedErrorId -like 'DirUnauthorizedAccessError,*' }) | Should -BeNullOrEmpty
}
It 'Remove-Item2 should pass on a thrown IOException and leave the next item' {
$pair = New-Pair
$emitted = 0
$caught = $null
$Error.Clear()
try {
Remove-Item2 -Path $pair.First, $pair.Second -PassThru -ErrorAction SilentlyContinue | ForEach-Object -Process {
$emitted++
throw [System.IO.IOException]::new('Downstream failure')
}
}
catch {
$caught = $_
}
$caught.Exception | Should -BeOfType [System.IO.IOException]
$caught.Exception.Message | Should -BeExactly 'Downstream failure'
$emitted | Should -Be 1
$pair.Second | Should -Exist
@($Error | Where-Object -FilterScript { $_.FullyQualifiedErrorId -like 'DeleteError,*' }) | Should -BeNullOrEmpty
}
}
# A cmdlet also meets the end of the pipeline where it did not write: another call of PowerShell can raise it too. The
# check that every catch-all makes recognizes the exceptions by their types. PowerShell keeps the exceptions of break and
# continue internal, so they are recognized by the name of their base type, which a stand-in with that name shows.
Describe 'Recognizing the end of a pipeline by the type of the exception' {
BeforeAll {
if (-not ('NtfsSecurityTests.StandInForBreak' -as [type])) {
# The compiler warns that the stand-in has the name of an imported type, and Add-Type treats a warning as an error.
Add-Type -IgnoreWarnings -TypeDefinition @'
namespace System.Management.Automation { public class FlowControlException : System.Exception { } }
namespace NtfsSecurityTests { public class StandInForBreak : System.Management.Automation.FlowControlException { } }
'@
}
$isEnd = [NTFSSecurity.BaseCmdlet].Assembly.GetType('NTFSSecurity.PipelineControl').GetMethod(
'IsEnd', [System.Reflection.BindingFlags] 'NonPublic, Static')
}
It 'Should recognize a PipelineStoppedException' {
$isEnd.Invoke($null, @([System.Management.Automation.PipelineStoppedException]::new())) | Should -BeTrue
}
It 'Should recognize an exception whose base type is the flow control exception of PowerShell' {
$isEnd.Invoke($null, @([NtfsSecurityTests.StandInForBreak]::new())) | Should -BeTrue
}
It 'Should not recognize <Description>' -ForEach @(
@{ Description = 'a failure of an item'; Exception = [System.InvalidOperationException]::new('Failure') }
@{ Description = 'an access denial'; Exception = [System.UnauthorizedAccessException]::new('Denied') }
@{ Description = 'a failure with an inner exception that ends the pipeline'; Exception = [System.InvalidOperationException]::new('Failure', [System.Management.Automation.PipelineStoppedException]::new()) }
) {
$isEnd.Invoke($null, @($Exception)) | Should -BeFalse
}
}

401
Tests/Privileges.Tests.ps1

@ -182,6 +182,130 @@ Describe 'Privileges when the pipeline stops early' {
}
}
# A cmdlet enables the privileges one after the other and writes a debug message before and after each one. A later command
# that takes the debug stream can end the pipeline or throw at the message after the enabling, before the cmdlet has noted
# that it enabled the privilege.
Describe 'Privileges when a later command takes the debug messages of the cmdlet' {
BeforeAll {
$privateData['EnablePrivileges'] = $true
$debugFile = New-TestSandboxItem -Sandbox $sandbox -Name 'DebugStopped'
}
AfterAll {
$privateData['EnablePrivileges'] = $enablePrivileges
}
BeforeEach {
Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
}
AfterEach {
Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
}
# Before 5.0.0-rc7, the privilege that the cmdlet had enabled at that moment stayed enabled in the session: nothing
# disabled it, because the cmdlet had not noted yet that it enabled it.
It 'Should disable the privilege when Select-Object -First ends the pipeline at the message after its enabling' -Skip:(-not $holdsPrivileges) {
$DebugPreference = 'Continue'
$messages = @(Get-NTFSOwner -Path $debugFile 5>&1 | ForEach-Object -Process { $_.Message })
$enabledAt = $messages.IndexOf('..enabled') + 1
$enabledAt | Should -BeGreaterThan 0
Get-EnabledFileSystemPrivilege | Should -BeNullOrEmpty
$result = @(Get-NTFSOwner -Path $debugFile 5>&1 | Select-Object -First $enabledAt)
$result | Should -HaveCount $enabledAt
$result[-1].Message | Should -BeExactly '..enabled'
Get-EnabledFileSystemPrivilege | Should -BeNullOrEmpty
}
# Before 5.0.0-rc7, the cmdlet took the exception for the failure to enable the privilege, went on with the next
# privilege, and the caller never saw it; all four privileges stayed enabled.
It 'Should pass on what a later command throws at the message after the enabling and disable the privileges' -Skip:(-not $holdsPrivileges) {
$DebugPreference = 'Continue'
$caught = $null
try {
Get-NTFSOwner -Path $debugFile 5>&1 | ForEach-Object -Process {
if ($_.Message -eq '..enabled') { throw 'Downstream failure' }
$_
} | Out-Null
}
catch {
$caught = $_
}
$caught.Exception.Message | Should -BeLike '*Downstream failure*'
Get-EnabledFileSystemPrivilege | Should -BeNullOrEmpty
}
}
# Enable-Privileges recognizes the script NTFSSecurity.Init.ps1, which a user adds to start the module, by its name: from
# that script, it enables the privileges only for the module setting EnablePrivileges, from any other script always. Each
# test runs the script in a child process, which inherits the privilege states of this one (disabled here, see BeforeEach),
# so that the privileges of this process stay as they are. With the setting $true, the module enables the privileges
# itself before the cmdlet runs, so the state alone does not show that the cmdlet did: it also announces that in a verbose
# message.
Describe 'Enable-Privileges in the script NTFSSecurity.Init.ps1' {
BeforeAll {
function Invoke-StartScript {
param ([string] $ScriptName, [bool] $Setting)
$folder = Join-Path -Path $sandbox -ChildPath ('Start-{0}' -f [guid]::NewGuid().ToString('N').Substring(0, 8))
$script = Join-Path -Path $folder -ChildPath $ScriptName
Assert-TestSandboxPath -Sandbox $sandbox -Path $script
New-Item -ItemType Directory -Path $folder | Out-Null
Set-Content -LiteralPath $script -Value @'
param ($ModulePath, $Setting)
Import-Module -Name $ModulePath -ErrorAction Stop
(Get-Module -Name NTFSSecurity).PrivateData['EnablePrivileges'] = ($Setting -eq 'True')
$messages = @(Enable-Privileges -Verbose 4>&1 | ForEach-Object -Process { "$($_.Message)" })
'ANNOUNCED:{0}' -f [bool] @($messages -like '*are now enabled giving you access*').Count
'BACKUP:{0}' -f (Get-Privileges | Where-Object -Property Privilege -EQ -Value 'Backup').PrivilegeState
'@
$output = @(& (Get-Process -Id $PID).Path -NoProfile -NonInteractive -ExecutionPolicy Bypass -File $script -ModulePath ([IO.Path]::GetFullPath($modulePath)) -Setting $Setting)
[pscustomobject]@{
Announced = @($output | Where-Object -FilterScript { $_ -like 'ANNOUNCED:*' }) -replace '^ANNOUNCED:'
Backup = @($output | Where-Object -FilterScript { $_ -like 'BACKUP:*' }) -replace '^BACKUP:'
}
}
}
BeforeEach {
Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
}
AfterEach {
Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
}
It 'Should enable the privileges when the module setting EnablePrivileges is $true' -Skip:(-not $holdsPrivileges) {
Get-BackupPrivilegeState | Should -Be 'Disabled'
$result = Invoke-StartScript -ScriptName 'NTFSSecurity.Init.ps1' -Setting $true
$result.Backup | Should -Be 'Enabled'
$result.Announced | Should -Be 'True'
}
It 'Should leave the privileges disabled when the module setting EnablePrivileges is $false' -Skip:(-not $holdsPrivileges) {
Get-BackupPrivilegeState | Should -Be 'Disabled'
$result = Invoke-StartScript -ScriptName 'NTFSSecurity.Init.ps1' -Setting $false
$result.Backup | Should -Be 'Disabled'
$result.Announced | Should -Be 'False'
}
It 'Should enable the privileges in a script of another name also when the module setting EnablePrivileges is $false' -Skip:(-not $holdsPrivileges) {
Get-BackupPrivilegeState | Should -Be 'Disabled'
$result = Invoke-StartScript -ScriptName 'Other.ps1' -Setting $false
$result.Backup | Should -Be 'Enabled'
$result.Announced | Should -Be 'True'
}
}
Describe 'Privileges that another command in the pipeline changes' {
BeforeAll {
$privateData['EnablePrivileges'] = $true
@ -262,3 +386,280 @@ Describe 'Privileges that another command in the pipeline changes' {
Get-EnabledFileSystemPrivilege | Should -BeNullOrEmpty
}
}
# The library class of the module that the cmdlets leave unused; the tests change only the privileges of the test process.
Describe 'The PrivilegeEnabler class' {
BeforeAll {
$privateData['EnablePrivileges'] = $false
$backup = [ProcessPrivileges.Privilege]::Backup
$changeNotify = [ProcessPrivileges.Privilege]::ChangeNotify
$currentProcess = [System.Diagnostics.Process]::GetCurrentProcess()
# The enabler goes out of scope in the function, so that nothing but the caller's handle refers to what it owns.
function New-AbandonedHandle {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Test helper that only creates an object.'
)]
param ($Process)
$enabler = New-Object -TypeName 'ProcessPrivileges.PrivilegeEnabler' -ArgumentList $Process
$field = [ProcessPrivileges.PrivilegeEnabler].GetField('accessTokenHandle', [System.Reflection.BindingFlags] 'NonPublic, Instance')
$field.GetValue($enabler)
}
}
AfterAll {
$privateData['EnablePrivileges'] = $enablePrivileges
}
BeforeEach {
Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
}
AfterEach {
Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
}
It 'Should enable a disabled privilege until it is disposed' -Skip:(-not $holdsPrivileges) {
Get-BackupPrivilegeState | Should -Be 'Disabled'
$enabler = New-Object -TypeName 'ProcessPrivileges.PrivilegeEnabler' -ArgumentList $currentProcess, $backup
try {
Get-BackupPrivilegeState | Should -Be 'Enabled'
}
finally {
$enabler.Dispose()
}
Get-BackupPrivilegeState | Should -Be 'Disabled'
$enabler.Dispose()
Get-BackupPrivilegeState | Should -Be 'Disabled'
}
It 'Should report a privilege that it modified once and leave it to the instance that enabled it' -Skip:(-not $holdsPrivileges) {
$first = New-Object -TypeName 'ProcessPrivileges.PrivilegeEnabler' -ArgumentList $currentProcess
$second = New-Object -TypeName 'ProcessPrivileges.PrivilegeEnabler' -ArgumentList $currentProcess
try {
$first.EnablePrivilege($backup) | Should -Be 'PrivilegeModified'
Get-BackupPrivilegeState | Should -Be 'Enabled'
$first.EnablePrivilege($backup) | Should -Be 'None'
$second.EnablePrivilege($backup) | Should -Be 'None'
$second.Dispose()
Get-BackupPrivilegeState | Should -Be 'Enabled'
}
finally {
$first.Dispose()
$second.Dispose()
}
Get-BackupPrivilegeState | Should -Be 'Disabled'
}
It 'Should not disable a privilege that was enabled before' -Skip:(-not $holdsPrivileges) {
$null = [ProcessPrivileges.ProcessExtensions]::EnablePrivilege($currentProcess, $backup)
$enabler = New-Object -TypeName 'ProcessPrivileges.PrivilegeEnabler' -ArgumentList $currentProcess, $backup
try {
$enabler.EnablePrivilege($backup) | Should -Be 'None'
}
finally {
$enabler.Dispose()
}
Get-BackupPrivilegeState | Should -Be 'Enabled'
}
It 'Should enable a privilege through an access token handle that the caller owns' -Skip:(-not $holdsPrivileges) {
$rights = [ProcessPrivileges.TokenAccessRights]::AdjustPrivileges -bor [ProcessPrivileges.TokenAccessRights]::Query
$handle = [ProcessPrivileges.ProcessExtensions]::GetAccessTokenHandle($currentProcess, $rights)
$enabler = $null
try {
$enabler = New-Object -TypeName 'ProcessPrivileges.PrivilegeEnabler' -ArgumentList $handle, $backup
Get-BackupPrivilegeState | Should -Be 'Enabled'
$enabler.Dispose()
$enabler = $null
Get-BackupPrivilegeState | Should -Be 'Disabled'
$handle.IsClosed | Should -BeFalse
}
finally {
# The enabler first: a handle that is closed under an enabler that still owns a privilege fails when the
# enabler disables the privilege.
if ($enabler) {
$enabler.Dispose()
}
$handle.Dispose()
}
$handle.IsClosed | Should -BeTrue
}
# The finalizer closes the token handle that an abandoned enabler opened and drops its registration, so that the next
# enabler for the process opens a handle of its own instead of taking a closed one. The handle is private, so the test
# reads it by reflection. An enabler that enabled a privilege stays referenced by a static list until it is disposed,
# so it is never finalized and its privilege stays enabled; only an enabler without a privilege can be abandoned.
It 'Should close the token handle of an enabler that was never disposed when it is finalized' {
$handle = New-AbandonedHandle -Process $currentProcess
$handle.IsClosed | Should -BeFalse
for ($attempt = 0; $attempt -lt 10 -and -not $handle.IsClosed; $attempt++) {
[GC]::Collect()
[GC]::WaitForPendingFinalizers()
}
$handle.IsClosed | Should -BeTrue
$enabler = New-Object -TypeName 'ProcessPrivileges.PrivilegeEnabler' -ArgumentList $currentProcess
try {
$enabler.EnablePrivilege($changeNotify) | Should -Be 'None'
}
finally {
$enabler.Dispose()
}
}
# The access tokens of administrators don't hold the privilege to create a token, and those of basic users don't hold
# most of the others.
It 'Should leave a privilege that the access token does not hold alone' {
$removed = [ProcessPrivileges.Privilege]::CreateToken
[ProcessPrivileges.ProcessExtensions]::GetPrivilegeState($currentProcess, $removed) | Should -Be 'Removed'
$enabler = New-Object -TypeName 'ProcessPrivileges.PrivilegeEnabler' -ArgumentList $currentProcess
try {
$enabler.EnablePrivilege($removed) | Should -Be 'None'
}
finally {
$enabler.Dispose()
}
[ProcessPrivileges.ProcessExtensions]::GetPrivilegeState($currentProcess, $removed) | Should -Be 'Removed'
}
# The enabled flag decides first, then the removed flag; the attributes are not a flags enumeration in .NET.
It 'Should derive the state <Expected> from the attribute value <Value>' -ForEach @(
@{ Value = 0; Expected = 'Disabled' }
@{ Value = 1; Expected = 'Disabled' }
@{ Value = 2; Expected = 'Enabled' }
@{ Value = 3; Expected = 'Enabled' }
@{ Value = 4; Expected = 'Removed' }
@{ Value = 6; Expected = 'Enabled' }
@{ Value = -2147483648; Expected = 'Disabled' }
) {
$attributes = [Enum]::ToObject([ProcessPrivileges.PrivilegeAttributes], $Value)
[ProcessPrivileges.ProcessExtensions]::GetPrivilegeState($attributes) | Should -Be $Expected
}
}
# Every access token holds the privilege to bypass traverse checking, enabled. The tests use it because they need no other
# privilege and change nothing: a handle that lacks a right fails before it adjusts anything.
Describe 'The access token handle of a process' {
BeforeAll {
$currentProcess = [System.Diagnostics.Process]::GetCurrentProcess()
$changeNotify = [ProcessPrivileges.Privilege]::ChangeNotify
$tokenRights = [ProcessPrivileges.TokenAccessRights]
}
It 'Should open a handle with all access rights when the caller names none and close it on dispose' {
$handle = [ProcessPrivileges.ProcessExtensions]::GetAccessTokenHandle($currentProcess)
try {
$handle.IsInvalid | Should -BeFalse
@([ProcessPrivileges.ProcessExtensions]::GetPrivileges($handle)) | Should -Not -BeNullOrEmpty
[ProcessPrivileges.ProcessExtensions]::GetPrivilegeState($handle, $changeNotify) | Should -Be 'Enabled'
}
finally {
$handle.Dispose()
}
$handle.IsClosed | Should -BeTrue
}
It 'Should refuse to enable a privilege through a handle that may only query' {
$handle = [ProcessPrivileges.ProcessExtensions]::GetAccessTokenHandle($currentProcess, $tokenRights::Query)
try {
$failure = { [ProcessPrivileges.ProcessExtensions]::EnablePrivilege($handle, $changeNotify) } | Should -Throw -PassThru
$failure.Exception.InnerException | Should -BeOfType [System.ComponentModel.Win32Exception]
$failure.Exception.InnerException.NativeErrorCode | Should -Be 5
[ProcessPrivileges.ProcessExtensions]::GetPrivilegeState($handle, $changeNotify) | Should -Be 'Enabled'
}
finally {
$handle.Dispose()
}
}
It 'Should refuse to <Operation> through a handle that may only adjust privileges' -ForEach @(
@{ Operation = 'list the privileges' }
@{ Operation = 'read the state of a privilege' }
) {
$handle = [ProcessPrivileges.ProcessExtensions]::GetAccessTokenHandle($currentProcess, $tokenRights::AdjustPrivileges)
try {
$failure = {
if ($Operation -eq 'list the privileges') {
[ProcessPrivileges.ProcessExtensions]::GetPrivileges($handle)
}
else {
[ProcessPrivileges.ProcessExtensions]::GetPrivilegeState($handle, $changeNotify)
}
} | Should -Throw -PassThru
$failure.Exception.InnerException | Should -BeOfType [System.ComponentModel.Win32Exception]
$failure.Exception.InnerException.NativeErrorCode | Should -Be 5
}
finally {
$handle.Dispose()
}
}
}
Describe 'The PrivilegeControl class' {
BeforeAll {
$privateData['EnablePrivileges'] = $false
$control = New-Object -TypeName 'Security2.PrivilegeControl'
$backup = [ProcessPrivileges.Privilege]::Backup
}
AfterAll {
$privateData['EnablePrivileges'] = $enablePrivileges
}
BeforeEach {
Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
}
AfterEach {
Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
}
It 'Should refuse to <Operation> a privilege that the access token does not hold' -ForEach @(
@{ Operation = 'enable' }
@{ Operation = 'disable' }
) {
$failure = {
if ($Operation -eq 'enable') {
$control.EnablePrivilege([ProcessPrivileges.Privilege]::CreateToken)
}
else {
$control.DisablePrivilege([ProcessPrivileges.Privilege]::CreateToken)
}
} | Should -Throw -PassThru
$failure.Exception.InnerException | Should -BeOfType [System.Security.AccessControl.PrivilegeNotHeldException]
$failure.Exception.InnerException.PrivilegeName | Should -BeExactly 'CreateToken'
}
It 'Should enable and disable a held privilege and refuse to repeat either' -Skip:(-not $holdsPrivileges) {
Get-BackupPrivilegeState | Should -Be 'Disabled'
$failure = { $control.DisablePrivilege($backup) } | Should -Throw -PassThru
$failure.Exception.InnerException | Should -BeOfType [Security2.AdjustPriviledgeException]
$failure.Exception.InnerException.Message | Should -BeExactly 'Priviledge already disabled'
$control.EnablePrivilege($backup) | Should -Be 'PrivilegeModified'
Get-BackupPrivilegeState | Should -Be 'Enabled'
$failure = { $control.EnablePrivilege($backup) } | Should -Throw -PassThru
$failure.Exception.InnerException | Should -BeOfType [Security2.AdjustPriviledgeException]
$failure.Exception.InnerException.Message | Should -BeExactly 'Priviledge already enabled'
$control.DisablePrivilege($backup) | Should -Be 'PrivilegeModified'
Get-BackupPrivilegeState | Should -Be 'Disabled'
}
}

59
Tests/SecurityDescriptor.Tests.ps1

@ -225,6 +225,65 @@ Describe 'Set-NTFSSecurityDescriptor' {
$result[0].FullName | Should -Be $file
$result[0].SecurityDescriptor.GetOwner($sidType).Value | Should -Be 'S-1-5-32-544'
}
# With a cleared, protected DACL, nobody keeps the right to set an owner, so setting the previous owner back would
# fail. The user owned the item already, so there is no owner to set back.
It 'Should not report an owner that did not change when the write that took ownership leaves an empty DACL' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'RetryUnchangedOwner'
Set-TestOwner -Sandbox $sandbox -Path $file -Sid $currentUser
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' }
$sd = Get-NTFSSecurityDescriptor -Path $file
Clear-NTFSAccess -SecurityDescriptor $sd -DisableInheritance -ErrorAction Stop
Set-NTFSSecurityDescriptor -SecurityDescriptor $sd -ErrorVariable setErrors -ErrorAction SilentlyContinue
$setErrors | Should -BeNullOrEmpty
$acl = Get-Acl -LiteralPath $file
$acl.GetOwner($sidType).Value | Should -Be $currentUser
$acl.AreAccessRulesProtected | Should -BeTrue
@($acl.GetAccessRules($true, $true, $sidType)) | Should -BeNullOrEmpty
}
# Without the Restore privilege, the user can't set an owner such as TrustedInstaller back. The cmdlet reports it
# after it wrote the descriptor.
It 'Should report RestoreOwnerError for a previous owner that it cannot set back after the write' -Skip:(-not $canAssignAnyOwner) {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'RetryRestoreDenied'
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ $currentUser = 'ChangePermissions' }
Set-TestOwner -Sandbox $sandbox -Path $file -Sid $trustedInstaller
Get-RestorePrivilegeState | Should -Be 'Disabled'
$sd = Get-NTFSSecurityDescriptor -Path $file
Add-NTFSAccess -SecurityDescriptor $sd -Account 'Everyone' -AccessRights ReadData
Set-NTFSSecurityDescriptor -SecurityDescriptor $sd -ErrorVariable setErrors -ErrorAction SilentlyContinue
$setErrors | Should -HaveCount 1
$setErrors[0].FullyQualifiedErrorId | Should -BeLike 'RestoreOwnerError,*'
$setErrors[0].CategoryInfo.Category | Should -Be 'WriteError'
$setErrors[0].TargetObject.FullName | Should -Be $file
@(Get-EveryoneRule -Path $file) | Should -HaveCount 1
(Get-Acl -LiteralPath $file).GetOwner($sidType).Value | Should -Be $currentUser
}
# The user can set a group of its access token back as the owner without the Restore privilege, such as the group
# Administrators of an elevated session, so the cmdlet restores the owner and reports nothing. A deny entry for
# OWNER RIGHTS stops the first write, also for the owner; taking ownership drops that entry.
It 'Should set a previous owner back that the user can assign after the write that took ownership' -Skip:(-not $canAssignAnyOwner) {
$administrators = 'S-1-5-32-544'
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'RetryRestored'
Set-TestOwner -Sandbox $sandbox -Path $file -Sid $administrators
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' }
Get-RestorePrivilegeState | Should -Be 'Disabled'
# A plain write of the DACL is denied, so that the cmdlet has to take ownership for its write.
{ Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-5-32-546' = 'ReadData' } } | Should -Throw
$sd = Get-NTFSSecurityDescriptor -Path $file
Add-NTFSAccess -SecurityDescriptor $sd -Account 'Everyone' -AccessRights ReadData
Set-NTFSSecurityDescriptor -SecurityDescriptor $sd -ErrorVariable setErrors -ErrorAction SilentlyContinue
$setErrors | Should -BeNullOrEmpty
@(Get-EveryoneRule -Path $file) | Should -HaveCount 1
(Get-Acl -LiteralPath $file).GetOwner($sidType).Value | Should -Be $administrators
}
}
Context 'A descriptor that cannot be written' {

74
Tests/TestHelpers.Tests.ps1

@ -257,6 +257,59 @@ Describe 'Test helpers' {
}
}
Context 'Set-TestNullDacl' {
BeforeAll {
$sandbox = New-TestSandbox -Name 'Helpers'
}
AfterAll {
Remove-TestSandbox -Sandbox $sandbox
}
It 'Should replace the DACL of an item in the sandbox with a protected NULL DACL' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'NullDacl'
$before = [System.Security.AccessControl.RawSecurityDescriptor]::new((Get-Acl -LiteralPath $file).GetSecurityDescriptorBinaryForm(), 0)
$before.ControlFlags.HasFlag([System.Security.AccessControl.ControlFlags]::DiscretionaryAclPresent) | Should -BeTrue
Set-TestNullDacl -Sandbox $sandbox -Path $file
$after = [System.Security.AccessControl.RawSecurityDescriptor]::new((Get-Acl -LiteralPath $file).GetSecurityDescriptorBinaryForm(), 0)
$after.ControlFlags.HasFlag([System.Security.AccessControl.ControlFlags]::DiscretionaryAclPresent) | Should -BeFalse
$after.ControlFlags.HasFlag([System.Security.AccessControl.ControlFlags]::DiscretionaryAclProtected) | Should -BeTrue
}
It 'Should refuse an item outside the sandbox' {
{ Set-TestNullDacl -Sandbox $sandbox -Path "$sandbox-Other\File.txt" } |
Should -Throw -ExpectedMessage 'Refusing to change*'
}
# The native call follows a link, so a junction in the sandbox that points to another folder is refused as the
# item itself, not only as a folder of its path.
It 'Should refuse an item that is a link, which can point outside the sandbox' {
$otherSandbox = New-TestSandbox -Name 'Helpers'
try {
$link = Join-Path -Path $sandbox -ChildPath 'NullDaclLink'
Assert-TestSandboxPath -Sandbox $sandbox -Path $link
New-Item -ItemType Junction -Path $link -Value $otherSandbox | Out-Null
$before = (Get-Acl -LiteralPath $otherSandbox).Sddl
{ Set-TestNullDacl -Sandbox $sandbox -Path $link } | Should -Throw -ExpectedMessage '*because it is a link*'
(Get-Acl -LiteralPath $otherSandbox).Sddl | Should -BeExactly $before
}
finally {
Remove-TestSandbox -Sandbox $otherSandbox
}
}
It 'Should throw its own error when Windows refuses' {
$missing = Join-Path -Path $sandbox -ChildPath 'Missing.txt'
{ Set-TestNullDacl -Sandbox $sandbox -Path $missing } |
Should -Throw -ExpectedMessage 'SetNamedSecurityInfo could not set a NULL DACL*'
}
}
Context 'Test-IsElevated and Test-PrivilegeHeld' {
It 'Should tell whether the process is elevated' {
Test-IsElevated | Should -BeOfType [bool]
@ -304,4 +357,25 @@ Describe 'Test helpers' {
Test-AdminShareAvailable | Should -BeFalse
}
}
# subst maps a letter for the whole logon session, so the guard has to stop before it runs, for every configuration.
Context 'New-TestDriveMapping and Remove-TestDriveMapping' {
BeforeAll {
$sandbox = New-TestSandbox -Name 'Helpers'
}
AfterAll {
Remove-TestSandbox -Sandbox $sandbox
}
It 'Should refuse a folder outside the sandbox before it maps anything' {
{ New-TestDriveMapping -Sandbox $sandbox -Path "$sandbox-Other\Folder" } |
Should -Throw -ExpectedMessage 'Refusing to change*'
}
It 'Should refuse a value that is not the root of a drive' {
{ Remove-TestDriveMapping -Root 'C:\Windows' } |
Should -Throw -ErrorId 'ParameterArgumentValidationError,Remove-TestDriveMapping'
}
}
}

170
Tests/TestHelpers.psm1

@ -341,6 +341,65 @@ function Set-TestOwner {
}
}
function Set-TestNullDacl {
<#
.SYNOPSIS
Replaces the DACL of an item in the sandbox with a NULL DACL, which gives everyone every access.
.DESCRIPTION
Neither Set-Acl nor icacls can write a NULL DACL, so the helper calls SetNamedSecurityInfo. The DACL is
protected, so the item inherits no entries. Everyone can delete the item, so Remove-TestSandbox removes it.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Test helper that only writes to sandboxes.'
)]
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string]
$Sandbox,
[Parameter(Mandatory)]
[string]
$Path
)
Assert-TestSandboxPath -Sandbox $Sandbox -Path $Path
$location = (Get-Location -PSProvider FileSystem).ProviderPath
$fullName = [IO.Path]::GetFullPath([IO.Path]::Combine($location, $Path))
# Assert-TestSandboxPath checks the folders of the path for links, not the item itself, and the native call follows a
# link: a junction to a folder outside the sandbox would give everyone every access to that folder.
$attributes = try { [IO.File]::GetAttributes($fullName) } catch { $null }
if ($null -ne $attributes -and ($attributes -band [IO.FileAttributes]::ReparsePoint)) {
throw "Refusing to change '$fullName', because it is a link."
}
if (-not ('NtfsSecurityTests.NativeAcl' -as [type])) {
Add-Type -TypeDefinition @'
namespace NtfsSecurityTests
{
public static class NativeAcl
{
[System.Runtime.InteropServices.DllImport("advapi32.dll", CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
private static extern uint SetNamedSecurityInfoW(string objectName, int objectType, uint securityInfo,
System.IntPtr owner, System.IntPtr group, System.IntPtr dacl, System.IntPtr sacl);
// SE_FILE_OBJECT, with DACL_SECURITY_INFORMATION and PROTECTED_DACL_SECURITY_INFORMATION and no DACL
public static uint SetNullDacl(string path)
{
return SetNamedSecurityInfoW(path, 1, 0x00000004u | 0x80000000u,
System.IntPtr.Zero, System.IntPtr.Zero, System.IntPtr.Zero, System.IntPtr.Zero);
}
}
}
'@
}
$result = [NtfsSecurityTests.NativeAcl]::SetNullDacl($fullName)
if ($result -ne 0) {
throw "SetNamedSecurityInfo could not set a NULL DACL on '$fullName' (error $result)."
}
}
function Test-IsElevated {
<#
.SYNOPSIS
@ -422,6 +481,113 @@ function ConvertTo-TestAdminSharePath {
'\\localhost\{0}${1}' -f $Path.Substring(0, 1), $Path.Substring(2)
}
function New-TestDriveMapping {
<#
.SYNOPSIS
Maps a free drive letter to a folder of the sandbox with subst and returns the root of the drive, such as Z:\.
Returns nothing when the process cannot define a drive letter, as the restricted token of a basic user cannot.
.DESCRIPTION
For Windows and for the module, the root of the mapped drive is the root folder of a drive, so that a test can
change it without changing a volume. The helper checks the folder with Assert-TestSandboxPath first and unmaps
the letter again, with an error, when a marker file of the folder is not visible through it, so that a mapping
that points elsewhere is never used. Remove the mapping with Remove-TestDriveMapping.
.PARAMETER Sandbox
The sandbox folder that New-TestSandbox returned.
.PARAMETER Path
The full path of the folder to map, in the sandbox.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Test helper that only maps sandbox folders.'
)]
[CmdletBinding()]
[OutputType([string])]
param (
[Parameter(Mandatory)]
[string]
$Sandbox,
[Parameter(Mandatory)]
[string]
$Path
)
Assert-TestSandboxPath -Sandbox $Sandbox -Path $Path
$marker = [guid]::NewGuid().ToString('N')
$markerPath = Join-Path -Path $Path -ChildPath $marker
Assert-TestSandboxPath -Sandbox $Sandbox -Path $markerPath
Set-Content -LiteralPath $markerPath -Value $marker
$subst = Join-Path -Path $env:SystemRoot -ChildPath 'System32\subst.exe'
# A test run in parallel can map a letter at the same moment, which makes subst fail for that letter.
foreach ($letter in 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S') {
$root = '{0}:\' -f $letter
if (Test-Path -LiteralPath $root) {
continue
}
& $subst ('{0}:' -f $letter) $Path *> $null
if ($LASTEXITCODE -ne 0) {
continue
}
if (Test-Path -LiteralPath (Join-Path -Path $root -ChildPath $marker)) {
return $root
}
& $subst ('{0}:' -f $letter) /d *> $null
throw "The drive '$root' does not show the sandbox folder '$Path'."
}
}
function Remove-TestDriveMapping {
<#
.SYNOPSIS
Removes a mapping of New-TestDriveMapping.
.PARAMETER Root
The root of the drive that New-TestDriveMapping returned, such as Z:\.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Test helper that only removes its own mapping.'
)]
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[ValidatePattern('^[A-Z]:\\$')]
[string]
$Root
)
& (Join-Path -Path $env:SystemRoot -ChildPath 'System32\subst.exe') $Root.TrimEnd('\') /d *> $null
if (Test-Path -LiteralPath $Root) {
Write-Error -Message "The drive mapping '$Root' could not be removed."
}
}
function Test-DriveMappingAvailable {
<#
.SYNOPSIS
Returns $true when the process can define a drive letter for a folder with subst, which the restricted token of
the basic-user runner cannot.
#>
[CmdletBinding()]
[OutputType([bool])]
param ()
$sandbox = New-TestSandbox -Name 'DriveProbe'
try {
$root = New-TestDriveMapping -Sandbox $sandbox -Path $sandbox
if ($root) {
Remove-TestDriveMapping -Root $root
}
[bool] $root
}
finally {
Remove-TestSandbox -Sandbox $sandbox
}
}
Export-ModuleMember -Function New-TestSandbox, Assert-TestSandboxPath, Remove-TestSandbox, New-TestSandboxItem,
Block-TestReadPermission, Block-TestWritePermission, Add-TestDenyRule, Set-TestOwner, Test-IsElevated,
Test-PrivilegeHeld, Test-AdminShareAvailable, ConvertTo-TestAdminSharePath
Block-TestReadPermission, Block-TestWritePermission, Add-TestDenyRule, Set-TestOwner, Set-TestNullDacl, Test-IsElevated,
Test-PrivilegeHeld, Test-AdminShareAvailable, ConvertTo-TestAdminSharePath, New-TestDriveMapping,
Remove-TestDriveMapping, Test-DriveMappingAvailable

Loading…
Cancel
Save