You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

8.4 KiB

status last-verified owner source
current 2026-10-08 active-agent repository evidence

System patterns

Architecture

NTFSSecurity.psd1 ─┬─ ScriptsToProcess: NTFSSecurity.Init.ps1
                   │    Add-Type: Security2.dll, PrivilegeControl.dll,
                   │    ProcessPrivileges.dll, inline NTFS.DriveInfoExt;
                   │    Update-FormatData -PrependPath format.ps1xml
                   ├─ TypesToProcess: NTFSSecurity.types.ps1xml
                   │    (Owner, IsInheritanceBlocked, LengthOnDisk on
                   │    FileInfo/DirectoryInfo; AccountType on ACEs)
                   ├─ RootModule: NTFSSecurity.psm1 (aliases)
                   ├─ NestedModules: NTFSSecurity.dll (36 cmdlets)
                   └─ en-US\NTFSSecurity.dll-Help.xml (Get-Help; generated
                        from Docs/Cmdlets, Decision 8)
NTFSSecurity.dll ── cmdlets ──> Security2.dll (FileSystemAccessRule2,
                                FileSystemAuditRule2, IdentityReference2,
                                FileSystemInheritanceInfo, EffectiveAccess)
                 ── long paths ──> AlphaFS
                 ── privileges ──> PrivilegeControl / ProcessPrivileges
  • BaseCmdlet resolves only relative paths, against the current file system location of the session (not $PWD, #86). Path parameters carry [FileSystemPathTransformation], which binds file objects as full paths.
  • On access denied, most cmdlets retry through InvokeAsOwner, which takes ownership and restores the previous owner on every exit path.
  • BaseCmdletWithPrivControl enables Backup, Restore, TakeOwnership, and Security in BeginProcessing when PrivateData.EnablePrivileges is $true, and disables the ones it enabled in EndProcessing and, since 5.0.0-rc6, in Dispose: PowerShell skips EndProcessing when a later command, such as Select-Object -First, or a terminating error stops the pipeline, but calls Dispose. Enable-Privileges keeps them (KeepEnabledPrivileges). The cleanup reads the current state of each privilege, because another command in the pipeline can have changed it, and tries every privilege even when one fails: EndProcessing warns, Dispose stays silent, because PowerShell ignores exceptions thrown there and no stream is open anymore.
  • PrivateData switches: EnablePrivileges, GetInheritedFrom, GetFileSystemModeProperty, IdentifyHardLinks, ShowAccountSid.
  • Cmdlets accept -Path (alias FullName) or -SecurityDescriptor; the SD sets change the object in memory until Set-NTFSSecurityDescriptor.

Decisions

Each Decision record is a file in decisions/; read only the relevant ones.

# Decision
1 Use the canonical Memory Bank base
2 Cmdlet reference stays platyPS markdown
3 Document the source at HEAD
4 Online help points to GitHub
5 Document defects, don't fix them in docs work
6 CI checks the docs against a build of the source
7 CHANGELOG lists user-visible changes only
8 Commit the generated help file and check it in CI
9 Keep the documentation on GitHub
10 One version for the manifest, assemblies, and changelog
11 CI and the wiki run on GitHub Actions
12 Releases are built and published by CI on a version tag
13 Set-NTFSInheritance keeps entries like the dedicated cmdlets
14 Repository hardening is optional
15 Merge stacked pull requests in order with merge commits
16 Fix only reproducible bugs
17 Issue labels
18 NTFSSecurity will be archived
19 Cmdlets write only the sections that they change
20 Live tests in a lab live in Tests\Lab
21 A quality gate before 5.0.0
22 The behavior changes of Phase 2 (proposed)

Patterns

Writing cmdlets

  • A parameter that takes pipeline input needs a getter that doesn't throw: PowerShell reads it before it binds each input object, and an exception turns every object into GetDefaultValueFailed (the link cmdlets before 5.0.0-rc7).
  • An error for one item is non-terminating, so that the cmdlet goes on with the next path or pipeline object; since 5.0.0-rc7, the link cmdlets too. Its message names the item, and its target object is the item that the cmdlet was asked to process. Resolving a path can throw in Windows PowerShell for an invalid character, so that belongs inside the per-item error handling.
  • Folders move without MoveOptions.CopyAllowed: for another volume, AlphaFS then copies and deletes, which lost empty folders. Windows refuses such a move with NotSameDeviceException (17). Tests reach another volume through \\localhost\C$, elevated only.

Verifying documentation

  • Run platyPS in Windows PowerShell 5.1 against a Release build; a copy of Docs/Cmdlets must round-trip through Update-MarkdownHelp unchanged. Keep cmdlet pages ASCII-only. platyPS takes Position and Required from the shipped help file: after such a change, edit the page YAML, run New-ExternalHelp, rebuild, and check the round trip.
  • MarkdownLinkCheck checks relative Docs links, Tests\Wiki.Tests.ps1 the wiki links and anchors. The wiki is generated from Docs (never edit it); Docs/README.md becomes Home, its cmdlet groups the sidebar.
  • In cmdlet pages, end a sentence with a link (platyPS drops the space after it). Verify examples in a $env:TEMP sandbox, never on real data.

Testing the module

  • Pester 5 tests in Tests/*.Tests.ps1 import the Release build; CI runs them in Windows PowerShell 5.1 and PowerShell 7 (Decision 11).
  • A test that changes files, links, or security descriptors uses Tests\TestHelpers.psm1: its own sandbox, Assert-TestSandboxPath before each change, Remove-TestSandbox. Cases that need a privilege skip with Test-PrivilegeHeld, cases that need its absence skip when elevated; CI runs the suite elevated and as a basic user in both editions, so each case runs somewhere. Block-Test* make a read or a write fail without elevation; Set-TestOwner with EnablePrivileges = $false reproduces an owner the user can't assign.
  • Fixtures write a DACL with SetAccessControl, never with Set-Acl: Set-Acl compares AreAuditRulesProtected of the new descriptor with AreAccessRulesProtected of the item (FileSystemSecurity.cs of PowerShell), so for an item with a protected DACL it writes the audit section too. Without the Security privilege that fails with PrivilegeNotHeldException; with it, Set-Acl writes every section and drops the audit entries. Windows PowerShell has FileInfo/DirectoryInfo.SetAccessControl; PowerShell 7 has [System.IO.FileSystemAclExtensions]::SetAccessControl.
  • Get-Help -Online tests run only in Windows PowerShell, which honors the hook BypassOnlineHelpRetrieval. Manifest.Tests.ps1 and Release.Tests.ps1 check the manifest, the version (Decision 10), the release notes, and the packages.
  • The live tests in Tests\Lab (Decision 20) run as domain accounts in a lab: on the client over SMB, then on the file server, which checks what the client runs left. They read and write descriptors as Windows stores them with GetFileSecurity and SetFileSecurity, because GetNamedSecurityInfo converts a DACL without the auto-inherit flag and returns its owner. The expected effective rights come from the S4U tokens of the file server and the client, like the Effective Access tab.