diff --git a/.memory-bank/activeContext.md b/.memory-bank/activeContext.md index b153a85..2323c66 100644 --- a/.memory-bank/activeContext.md +++ b/.memory-bank/activeContext.md @@ -1,6 +1,6 @@ --- status: current -last-verified: 2026-10-04 +last-verified: 2026-10-05 owner: active-agent source: current task evidence --- @@ -9,62 +9,47 @@ source: current task evidence ## Current focus -Overnight run 2026-10-04/05 (autopilot, maintainer asleep): fix the code -defects A to D of `progress.md`, implement the maintainer's E decisions, -triage the 37 open issues, and prepare 5.0.0-rc2. Eight stacked local -branches, each a PR against `master`, to be merged in this order with merge -commits: `ai/maintenance`, `ai/defects-a`, `ai/defects-b`, `ai/defects-c`, -`ai/defects-d`, `ai/decisions-e`, `ai/issue-fixes`, -`ai/release-5.0.0-rc2`. Nothing is pushed; the maintainer pushes, opens -the PRs, and tags `5.0.0-rc2` after the merges. +The overnight run of 2026-10-04/05 is finished. 5.0.0-rc2 waits on eight +stacked local branches (see `progress.md`): the maintainer pushes them, +opens one PR each against `master`, merges them in order with merge +commits, and tags `5.0.0-rc2` on the last merge commit once CI on `master` +is green. The run's report lists the commands, the PR descriptions, a +reply for each issue, and the open questions. ## Maintainer decisions for the run (2026-10-04) -- D1: the fixes ship in 5.0.0; entries go under `[Unreleased]` (`Fixed`; - intended behavior changes under `Changed` with the way back). The last - PR sets `Prerelease = 'rc2'`. -- D2: `Clear-NTFSAccess -DisableInheritance` keeps leaving an empty DACL; - the page states the result and the risk. -- D3: `Set-NTFSInheritance` keeps entries like the dedicated cmdlets. +- D1: the fixes ship in 5.0.0, under `[Unreleased]`; the behavior changes + of D3 and D4 are listed under `Changed` with the way back. +- D2: `Clear-NTFSAccess -DisableInheritance` keeps leaving an empty DACL. +- D3: `Set-NTFSInheritance` keeps entries like the dedicated cmdlets + (Decision 13). - D4: `-RemoveInheritedAuditRules` and `-RemoveExplicitAuditRules`, with - the `*AccessRules` names as aliases. -- D5: `Get-FileHash2` works in PowerShell 7 for every algorithm .NET has; - a missing one fails only when requested. -- D7: Dependabot for `github-actions` only; AlphaFS 2.2.1 in - `NTFSSecurity\packages.config` (no upgrade, no changelog entry). + the old names as aliases. +- D5: `Get-FileHash2` works in PowerShell 7; `MACTripleDES` is deprecated. +- D6: only reproducible bugs are fixed; other behavior changes are + questions for the maintainer. +- D7: Dependabot for `github-actions` only; AlphaFS 2.2.1 everywhere. +- D8: the manifest `Description` and a version-neutral README. +- D9: merged local branches deleted after the run. +- 2026-10-05: the report's recommendations accepted. #5 and #82 ship in + 5.0.0 as breaking changes; #34 for rc3 if a file server is available; + #41 and #90 after 5.0.0; Minor findings become issues; the `pwsh` crash + is watched in CI. ## Evidence -- Baseline at `e0f5366` (Release build, workstation): Windows PowerShell - 261 passed, 7 skipped; PowerShell 7 232 passed, 36 skipped (268 tests). -- `ai/maintenance` adds `Tests\Repository.Tests.ps1` (8 tests): Windows - PowerShell 269 passed, 7 skipped; PowerShell 7 240 passed, 36 skipped. - Review: Dependabot PRs ran unreviewed actions in a job with - `contents: write`; the wiki preview is now read-only (`publish-wiki`). -- `ai/defects-a` fixes defects 1 to 13 and the same repeat bug in - `Get-NTFSAccess` (found with 4); its review fixes are in the last - commit: Windows PowerShell 312 passed, 17 skipped; PowerShell 7 282 - passed, 47 skipped (329 tests). -- `ai/defects-b` fixes defects 14 to 17 (`-AppliesTo` is mandatory in the - `Simple` sets; `-RemoveSpecific` is back): Windows PowerShell 333 passed, - 19 skipped; PowerShell 7 303 passed, 49 skipped (352 tests). -- `ai/defects-c` fixes defects 18 to 21, the same missing `continue` in - `Remove-NTFSAudit`, and a stale hash in `Get-FileHash2`. Its review - found that a failed retry after taking ownership left the owner changed; - `BaseCmdlet.InvokeAsOwner` now restores it: Windows PowerShell 356 - passed, 20 skipped; PowerShell 7 325 passed, 51 skipped (376 tests). -- `ai/defects-d` fixes defects 22 to 24 and `-PassThru` under `-WhatIf` in - the `*-Item2` cmdlets: Windows PowerShell 379 passed, 23 skipped; - PowerShell 7 348 passed, 54 skipped (402 tests). - -- `ai/decisions-e` implements D2 to D5 (Decision 13): Windows PowerShell - 395 passed, 26 skipped; PowerShell 7 366 passed, 55 skipped (421 - tests). `Get-FileHash2` tests now run in PowerShell 7 as well. - -- `ai/issue-fixes` fixes #3, #86, and #88 and adds `Docs/FAQ.md`: Windows - PowerShell 402 passed, 26 skipped; PowerShell 7 373 passed, 55 skipped - (428 tests). +- Every branch tip: Release build without new warnings (296 at the top, + 305 at the baseline), docs checks clean, package dry run passed. +- Tests at the top branch: Windows PowerShell 423 passed, 26 skipped; + PowerShell 7 394 passed, 55 skipped (449). The baseline had 268 tests. +- Tests that need privileges skip on the workstation and run in CI only; + the PR descriptions list them. +- Reviews: one security review per PR; the Major findings were fixed in + the PR that had them (PR 1: 1, PR 2: 5, PR 4: 2, PR 6: 1, PR 7: 3, + PR 8: 2). ## Next step -5.0.0-rc2 on `ai/release-5.0.0-rc2`. +The maintainer applies the repository settings, pushes the eight branches, +opens and merges the PRs in order, and tags `5.0.0-rc2` once CI on +`master` is green. diff --git a/.memory-bank/progress.md b/.memory-bank/progress.md index 97e21f9..5cf0f32 100644 --- a/.memory-bank/progress.md +++ b/.memory-bank/progress.md @@ -1,6 +1,6 @@ --- status: current -last-verified: 2026-10-04 +last-verified: 2026-10-05 owner: active-agent source: repository evidence --- @@ -9,188 +9,76 @@ source: repository evidence ## Current status -PRs #91 to #98 are merged. CI published the prerelease 5.0.0-rc1 from -`master` (`e0f5366`, tag `5.0.0-rc1`) to the PowerShell Gallery and GitHub; -the stable Gallery version is still 4.2.6. CI runs on GitHub Actions: -build, docs checks, tests in Windows PowerShell 5.1 and PowerShell 7, -packages, the wiki generated from `Docs` (43 pages), and releases on a -version tag (Decision 12). Next: the maintainer tests 5.0.0-rc1. +5.0.0-rc2 is ready on eight stacked local branches from the overnight run of +2026-10-04/05; none is pushed. Each is a PR against `master`, merged in +this order with merge commits: `ai/maintenance`, `ai/defects-a`, +`ai/defects-b`, `ai/defects-c`, `ai/defects-d`, `ai/decisions-e`, +`ai/issue-fixes`, `ai/release-5.0.0-rc2`. The tag `5.0.0-rc2` on the last +merge commit then publishes it through CI (Decision 12). `master` +(`e0f5366`) carries 5.0.0-rc1, published on 2026-10-04; the stable Gallery +version is still 4.2.6. ## Recent milestones -- 2026-10-02 to 2026-10-04: #91 aligned the docs with the code (Decision - 6; #83 closed as superseded), #92 did housekeeping (Decision 7), and - #93 shipped the generated help file (Decision 8, `Tests\Help.Tests.ps1`). -- 2026-10-04: #94 to #96 dropped Read the Docs (Decision 9), set version - 5.0.0 with a valid manifest (Decision 10), and moved CI and a wiki - generated from `Docs` to GitHub Actions (Decision 11). #97 completed the - version history, kept separate from `CHANGELOG.md`, from the six Gallery - packages and the commit history. +- 2026-10-02 to 2026-10-04: #91 to #97 aligned the docs with the code, + shipped the help file, kept the docs on GitHub, set version 5.0.0, moved + CI and a wiki generated from `Docs` to GitHub Actions, and completed the + version history (Decisions 6 to 11). - 2026-10-04: #98 (`e0f5366`) added releases on a version tag through CI - (Decision 12), with a prerelease first; `New-ModulePackage.ps1` adds the - command tags that PSResourceGet drops. The tag `5.0.0-rc1` (run - 37230802387) published to the Gallery at 20:12 UTC and created the - GitHub prerelease. Verified: the Gallery nupkg and the GitHub zip are - byte-identical to the CI artifacts, the DLLs are optimized Release - builds, the Gallery shows the prerelease flag, the release notes link, - and all 36 `PSCmdlet_` and `PSCommand_` tags, and the installed module - passes the full suite (Windows PowerShell 261 passed, 7 skipped; - PowerShell 7 232 passed, 36 skipped). + (Decision 12). The tag `5.0.0-rc1` published to the Gallery and created + the GitHub prerelease; the installed module passed the full suite. +- 2026-10-05, overnight run (local branches): the 24 code defects of work + package 5 fixed with regression tests, plus what the reviews found: a + failed retry after taking ownership left the owner changed, and + `-PassThru` wrote objects after a failed change or under `-WhatIf`. + Maintainer decisions D2 to D5 (Decision 13), the issues #3, #4, #17, #74, + #86, and #88 fixed, the 37 open issues triaged, `Docs/FAQ.md`, Dependabot for + the actions, and the prerelease label `rc2`. One security review per PR; + every Major finding fixed. +- 2026-10-05, morning: the maintainer accepted the report's + recommendations. #5 (`Get-ChildItem2 -Attributes` matches any listed + attribute; an empty value is an error) and #82 (no `Size` alias) ship in + 5.0.0 as breaking changes on `ai/issue-fixes`. Tests at the top branch: + Windows PowerShell 423 passed, 26 skipped; PowerShell 7 394 passed, 55 + skipped (449). ## Stable capabilities - 36 cmdlets: access (7), audit (5), inheritance (6), owner and security descriptor (4), privileges (3), long-path items (6), links, hash, and disk space (5). -- Works in Windows PowerShell 5.1 and PowerShell 7 (smoke-tested), except - `Get-FileHash2`, which fails in PowerShell 7 (missing `RIPEMD160` type). +- Works in Windows PowerShell 5.1 and PowerShell 7. In PowerShell 7, + `Get-FileHash2` lacks `RIPEMD160` and `MACTripleDES`, which .NET lacks + (on `master` it still fails there for every algorithm). +- Pester tests in `Tests\` run in `$env:TEMP` sandboxes through + `Tests\TestHelpers.psm1`; tests that need privileges skip without them + and run in CI, whose runners are elevated. ## Open work -Work packages in the order agreed with the maintainer. Each gets one -`ai/` branch and PR, committed locally. The maintainer pushes and -opens the PR (the agent can't; see `techContext.md`, Constraints), and the -next package starts only after the maintainer's go-ahead. - -Items 1 to 4c are done: housekeeping (#92), shipped help (#93), docs on -GitHub (#94), manifest and version 5.0.0 (#95), CI and the wiki on GitHub -Actions (#96), version history from the Gallery (#97). Optional for the -maintainer: delete the AppVeyor project and revoke its GitHub -authorization, restrict wiki editing to collaborators, and ask -`Sup3rlativ3` to delete the Read the Docs project. - -4d. Release 5.0.0 through CI (Decision 12): 5.0.0-rc1 published and - verified (#98); the maintainer tests it. The final release PR comes on - release day (CI warns when the changelog date isn't that day): remove - the label, date `[Unreleased]` as `[5.0.0]`, tag `5.0.0` (steps in - `Docs/Contributing/05-Releasing.md`). Also consider the manifest - `Description` ("Windows PowerShell Module") and the `5.0.0-rc1` example - in `Docs/README.md`. Releases no longer come from a local Debug build. -4e. Repository settings, proposed to the maintainer on 2026-10-04 (not yet - agreed): the `powershell-gallery` environment has no protection rules - and no deployment policy, so a workflow on any branch can use - `PSGALLERY_API_KEY` (`nyanhp` also has write access); `master` has no - protection or ruleset; head branches aren't deleted on merge; the - remote branches `fix/#34` and `test/transfer` (2023-11-28, two commits - each) aren't merged. Dependabot for the SHA-pinned actions comes with - `ai/maintenance` (maintainer decision D7, 2026-10-04). -5. Code defects, listed below: `review: on`, one PR per group, regression - test first. Pester 5 tests import `NTFSSecurity\bin\Release`, run in a - `$env:TEMP` sandbox and in the CI workflow (pattern: - `Tests\Help.Tests.ps1`), and skip elevated cases when not elevated. - GitHub-hosted Windows runners run as administrators with UAC disabled - (GitHub docs, checked 2026-10-04), so elevated cases run in CI; the - workstation session isn't elevated. Each fix updates its cmdlet page - and `CHANGELOG.md`. Start by triaging the 37 open issues (none newer - than May 2025): #15, #47, and #66 (documentation) and #19 (fixed in - 4.2.4) can be closed; #4 is defect (5); #34 has the WIP branch - `fix/#34` (`Extensions.cs`, `FileSystemSecurity2.cs`, `TestClient`); - `test/transfer` only adds a 3 MB `New.zip`. The E decisions set the - next version: fixes only 5.0.1, additions 5.1.0, changed defaults - 6.0.0, unless they ship in 5.0.0 (rc2). - -### Code defects (work package 5) - -Numbered as agreed with the maintainer; each is documented on its page. - -#### A: Crashes and wrong results (fixed on `ai/defects-a`, not merged) - -- (1) `Set-NTFSInheritance` reads an unset `Nullable` when - `-AccessInheritanceEnabled` is omitted; omitted should mean unchanged. -- (2) `Get-ChildItem2` casts a file `-Path` to `DirectoryInfo` - (`InvalidCastException`). -- (3) `Get-FileHash2` returns at a folder in `-Path` and skips the rest. -- (4) `Get-NTFSAudit` re-emits the previous item's entries after a failed - path, and returns nothing without the Security privilege instead of an - error. -- (5) `Add-NTFSAudit`: `-Account` and `-AccessRights` share position 2; - use 2 and 3 like `Remove-NTFSAudit`. -- (6) `-PassThru` returns access entries in `Add-NTFSAudit` SD sets and - `Remove-NTFSAudit` Path sets. -- (7) `FileSystemAuditRule2.GetFileSystemAuditRules` takes - `InheritanceEnabled` from `AreAccessRulesProtected`. -- (8) `Get-NTFSInheritance -SecurityDescriptor` reports - `AuditInheritanceEnabled = $true` without a SACL (Path set: `$null`). -- (9) `Get-NTFSOwner`: the access-denied retry repeats the failing call, - and the catch-all turns `PipelineStoppedException` into - `ReadSecurityError`. -- (10) `Copy-Item2` fails on a folder with files - (`DirectoryNotFoundException`). -- (11) `Disable-Privileges` hits a null `privileges` field when - `EnablePrivileges = $false`. -- (12) The six inheritance cmdlets enable privileges unconditionally in - `BeginProcessing` and leave them enabled. -- (13) Format view `Children2`: the `Inherits` column uses - `IsInheritanceBlocked`, so it always shows `True` for `Get-ChildItem2`. -- Review of group A: five Major findings fixed in the last commit; the - Minor ones are listed in the PR description. - -#### B: Ignored parameters and parameter sets (fixed on `ai/defects-b`, not merged) - -- (14) SD sets of `Add-/Remove-NTFSAccess` and `Add-/Remove-NTFSAudit` - cannot resolve without `-AppliesTo` or the flag parameters. -- (15) `Get-NTFSEffectiveAccess`: `-ExcludeNoneAccessEntries` has no - effect, there is no output without `-Path`, and the SD set returns - nothing. -- (16) `Get-NTFSOrphanedAccess`, `Get-NTFSOrphanedAudit`, and - `Get-NTFSSimpleAccess` ignore `-Account` and `-SecurityDescriptor`; - `Get-NTFSOrphanedAudit` writes collections; `SimpleFileSystemAccessRule` - has no format view. -- (17) `removeSpecific` in `Remove-NTFSAccess/Audit` is never bound; - `Remove-NTFSAudit` leaves `appliesTo` uninitialized. -- Review of group B: no Blocker or Major; three Minor findings fixed, the - rest are listed in the PR description. - -#### C: Error handling (fixed on `ai/defects-c`, not merged) - -- (18) `Copy-Item2`, `Move-Item2`, `Remove-Item2`: one failing path skips - the rest of `-Path` (`return` instead of `continue`). -- (19) `Remove-NTFSAccess` continues after a failed read and writes a - second, misleading `RemoveAceError`. -- (20) The inheritance cmdlets write `-PassThru` output in `finally`, even - after a failure. -- (21) `New-NTFSHardLink` says the target path exists when it doesn't. -- Review of group C: the add and remove cmdlets also wrote `-PassThru` - after a failed change, and a failed retry after taking ownership left the - owner changed; `BaseCmdlet.InvokeAsOwner` now restores it on every path. - -#### D: Metadata and cosmetics (fixed on `ai/defects-d`, not merged) - -- (22) Wrong or missing `[OutputType]` (`Test-Path2`, `Get-FileHash2`, - `Add-NTFSAudit`, `*-Item2`, inheritance cmdlets); - `Enable-/Disable-Privileges -PassThru` writes one collection; - `New-NTFSSymbolicLink -PassThru` returns `FileInfo` for folder links. -- (23) Typos: "Privliege" in the `Get-NTFSEffectiveAccess` warning; "are - now enabled" in the `Disable-Privileges` verbose message. -- (24) Dead code in `RemoveItem2.cs` and `OtherCmdlets.cs`. -- Found with group D: `-PassThru` of the `*-Item2` cmdlets wrote the item - also when `-WhatIf` skipped the operation. - -#### E: Maintainer decisions (implemented on `ai/decisions-e`, not merged) - -- D2: `Clear-NTFSAccess -DisableInheritance` keeps leaving an empty DACL; - the page states the result and the risk, and a test pins it. -- D3: `Set-NTFSInheritance` keeps entries like the dedicated cmdlets - (Decision 13; listed under `Changed`). -- D4: `-RemoveInheritedAuditRules` and `-RemoveExplicitAuditRules`, with - the `*AccessRules` names as aliases. -- D5: `Get-FileHash2` works in PowerShell 7; `RIPEMD160` and - `MACTripleDES` stop it there with `HashAlgorithmNotAvailable`. - `MACTripleDES` uses a random key (verified), so it is deprecated. -- Review of group E: the changelog now marks the `Set-NTFSInheritance` - change as breaking and warns that it leaves broader access in place. - -#### Issue triage (fixes on `ai/issue-fixes`, not merged) - -- Fixed: #3 (braces in a path), #86 (`$PWD` shadowed, also for the default - location of nine cmdlets, found by the review), #88 (an object passed by - position), #17 (an entry with `GenericAll`); `Docs/FAQ.md` answers the - recurring questions. -- Review of #17: generic rights are removed the way .NET removes other - rights, an exact match as it is, otherwise without `Synchronize`. -- Maintainer decisions of 2026-10-05: #5 (`Get-ChildItem2 -Attributes` - matches any listed attribute) and #82 (no `Size` alias) ship in 5.0.0 as - breaking changes. Their review added that an empty `-Attributes` value is - an error, as in `Get-ChildItem`. -- Open bugs: #34 and #67 (writes owner and group, rc3 if a file server is - available), #41 (drive root) and #90 (trailing space), both after 5.0.0. +1. The maintainer pushes the eight branches, opens the PRs, merges them in + order, and tags `5.0.0-rc2` once CI on `master` is green; then tests + rc2. +2. Release 5.0.0 through CI (Decision 12) after the tests: remove the + label, date `[Unreleased]` as `[5.0.0]`, tag `5.0.0` (steps in + `Docs/Contributing/05-Releasing.md`; CI warns when the changelog date + isn't the release day). +3. Repository settings: the hardening proposed on 2026-10-04 isn't applied + yet (checked 2026-10-05); the maintainer applies it before pushing the + stack. The details are with the maintainer, not in the repository. +4. Open bugs from the triage: #34 and #67 (the write includes owner and + group; `fix/#34` swallows every error) for rc3 if a file server to test + against is available; #41 (a drive root reads the device object) and #90 + (a trailing space in a folder name) after 5.0.0. Enhancements: #22, #49, + #68, #77, #87. +5. `pwsh` 7.6.1 crashed three times during test runs on the ARM64 + workstation (x64 emulation) with an access violation in `coreclr.dll` or + `System.Management.Automation.dll`, without module frames; not + reproducible on demand. Check whether CI on native x64 shows it. +6. Minor review findings that the PRs list but don't fix, for example + `Copy-Item2 -WhatIf` reporting a destination conflict as an error, and + relative path forms that the `*-Item2` cmdlets resolve themselves; the + maintainer tracks the useful ones as issues. +7. Optional for the maintainer: delete the AppVeyor project and revoke its + GitHub authorization, restrict wiki editing to collaborators, and ask + `Sup3rlativ3` to delete the Read the Docs project. diff --git a/.memory-bank/systemPatterns.md b/.memory-bank/systemPatterns.md index 83394d7..264cbeb 100644 --- a/.memory-bank/systemPatterns.md +++ b/.memory-bank/systemPatterns.md @@ -1,6 +1,6 @@ --- status: current -last-verified: 2026-10-04 +last-verified: 2026-10-05 owner: active-agent source: repository evidence --- @@ -28,17 +28,18 @@ NTFSSecurity.dll ── cmdlets ──> Security2.dll (FileSystemAccessRule2, ── privileges ──> PrivilegeControl / ProcessPrivileges ``` -- `BaseCmdlet` resolves relative paths against `$PWD`. -- `BaseCmdletWithPrivControl` (access, audit, inheritance, owner, security - descriptor, and privilege cmdlets) enables Backup, Restore, TakeOwnership, - and Security in `BeginProcessing` when `PrivateData.EnablePrivileges` is +- `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`. -- `PrivateData` switches: `EnablePrivileges` (base cmdlet), `GetInheritedFrom` - (`Get-NTFSAccess`, `Get-NTFSAudit`), `GetFileSystemModeProperty` and - `IdentifyHardLinks` (`Get-ChildItem2`), `ShowAccountSid` (format file). -- Cmdlets accept either `-Path` (alias `FullName`) or `-SecurityDescriptor` - (from `Get-NTFSSecurityDescriptor`); SD sets change the in-memory object - until `Set-NTFSSecurityDescriptor` writes it back. +- `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 @@ -64,36 +65,28 @@ Each Decision record is a file in `decisions/`; read only the relevant ones. ### Verifying documentation -- Run platyPS in Windows PowerShell 5.1 against a module build; a copy of +- Run platyPS in Windows PowerShell 5.1 against a Release build; a copy of `Docs/Cmdlets` must round-trip through `Update-MarkdownHelp` unchanged. - platyPS rewrites non-ASCII punctuation, so keep cmdlet pages ASCII-only. - It takes a parameter's `Position` from `Get-Help`, that is from the shipped - help file: after a position change, edit the page YAML, run + 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. -- Links: MarkdownLinkCheck checks relative `Docs` links (no anchors), - `Tests\Wiki.Tests.ps1` the wiki links and anchors; neither covers - `README.md` and `CHANGELOG.md`. 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. +- 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 - `NTFSSecurity\bin\Release\NTFSSecurity.psd1`; CI runs every file of - `Tests` in Windows PowerShell 5.1 and in PowerShell 7 (Decision 11) with - `.github/scripts/Invoke-Tests.ps1` (job summary, NUnit `test-results`). +- 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 below - `$env:TEMP\NTFSSecurity.Tests`, `Assert-TestSandboxPath` before each - change, `Remove-TestSandbox` (links first, then ACL reset). Cases that need - a privilege skip with `Test-PrivilegeHeld` and run in CI (elevated); - `Block-TestReadPermission` (OWNER RIGHTS deny) makes a read fail without - elevation. -- `Get-Help -Online` tests use the internal hook `BypassOnlineHelpRetrieval`, - which PowerShell 7 ignores for the help file: 36 tests run only in Windows - PowerShell. -- `Manifest.Tests.ps1`: `Test-ModuleManifest` clean, exactly 36 cmdlets, one - version in manifest and assemblies (Decision 10). `Release.Tests.ps1`: - release notes for the manifest version, and the packages (`FileList` - files, version with label, command tags, zip layout). + `Tests\TestHelpers.psm1`: its own sandbox, `Assert-TestSandboxPath` + before each change, `Remove-TestSandbox`. Cases that need a privilege + skip with `Test-PrivilegeHeld` and run in CI (elevated); + `Block-TestReadPermission` and `Block-TestWritePermission` make a read or + a write fail without elevation. +- `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. diff --git a/.memory-bank/techContext.md b/.memory-bank/techContext.md index 9f97a6f..d1ddc93 100644 --- a/.memory-bank/techContext.md +++ b/.memory-bank/techContext.md @@ -65,8 +65,9 @@ source: repository evidence ## Constraints -- `ModuleVersion` on `master` is `5.0.0` with the prerelease label `rc1`; - 5.0.0-rc1 is on the Gallery (published 2026-10-04 by CI). The latest +- `ModuleVersion` on `master` is `5.0.0` with the prerelease label `rc1` + (`ai/release-5.0.0-rc2` sets `rc2`); 5.0.0-rc1 is on the Gallery + (published 2026-10-04 by CI). The latest stable tag and Gallery release is `4.2.6`. The manifest requires PowerShell 5.1 and .NET Framework 4.5.2, uses `RootModule`, and lists exactly 36 cmdlets; `Test-ModuleManifest` passes in Windows PowerShell 5.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e73cd7..a258020 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,9 @@ The format is based on into the documentation, and complete the version history with the release dates from the PowerShell Gallery, the missing notes for 4.2.2, 4.2.5, and 4.2.6, and detailed notes for 4.2.4 +- Describe the module as a PowerShell module in the manifest, which the + PowerShell Gallery shows; it said Windows PowerShell, although the module + supports PowerShell 7 as well - Rename `-RemoveInheritedAccessRules` of `Disable-NTFSAuditInheritance` to `-RemoveInheritedAuditRules` and `-RemoveExplicitAccessRules` of `Enable-NTFSAuditInheritance` to `-RemoveExplicitAuditRules`, because they @@ -198,5 +201,9 @@ The format is based on `GenericAll`, which Windows keeps in the inherit-only entries of folders; it failed with "The value '269484032' is not valid" ([#17](https://github.com/raandree/NTFSSecurity/issues/17)) +- Fix `Enable-NTFSAuditInheritance`, `Disable-NTFSAuditInheritance`, and + `Set-NTFSInheritance -AuditInheritanceEnabled`, which failed with "Access + is denied" for a file or folder without audit entries, also in an elevated + session with the Security privilege [Unreleased]: https://github.com/raandree/NTFSSecurity/compare/4.2.6...HEAD diff --git a/Docs/Contributing/05-Releasing.md b/Docs/Contributing/05-Releasing.md index 713dc55..116c7d7 100644 --- a/Docs/Contributing/05-Releasing.md +++ b/Docs/Contributing/05-Releasing.md @@ -45,7 +45,9 @@ release notes for the version of the module manifest. 1. Set the version and the `Prerelease` label, such as `rc1`, and make sure that the `[Unreleased]` section of `CHANGELOG.md` describes the changes. - Merge the change into `master`. + Add the version that the Gallery has now to `$publishedVersions` in + `Tests/Repository.Tests.ps1`, so that a test catches a version that is + reused. Merge the change into `master`. 2. Tag the commit on `master` with the version and push the tag: ```powershell diff --git a/Docs/README.md b/Docs/README.md index 956b1c0..c2823ea 100644 --- a/Docs/README.md +++ b/Docs/README.md @@ -38,8 +38,8 @@ Install the module from the Install-Module -Name NTFSSecurity ``` -To try a prerelease of the next version, such as `5.0.0-rc1`, add -`-AllowPrerelease`. A prerelease is for testing; don't use it in production. +To try a prerelease of the next version, add `-AllowPrerelease`. A +prerelease is for testing; don't use it in production. You can also install a release without the PowerShell Gallery. Download `NTFSSecurity.zip` from the diff --git a/NTFSSecurity/NTFSSecurity.psd1 b/NTFSSecurity/NTFSSecurity.psd1 index 394bfad..f32223b 100644 --- a/NTFSSecurity/NTFSSecurity.psd1 +++ b/NTFSSecurity/NTFSSecurity.psd1 @@ -11,7 +11,7 @@ Copyright = '2018' - Description = 'Windows PowerShell Module for managing file and folder security on NTFS volumes' + Description = 'PowerShell module for managing file and folder security on NTFS volumes' PowerShellVersion = '5.1' @@ -102,7 +102,7 @@ ProjectUri = 'https://github.com/raandree/NTFSSecurity' ReleaseNotes = 'https://github.com/raandree/NTFSSecurity/blob/master/CHANGELOG.md' # Remove the prerelease label for the final release, see Docs/Contributing/05-Releasing.md - Prerelease = 'rc1' + Prerelease = 'rc2' } } } \ No newline at end of file diff --git a/Security2/FileSystem/FileSystemInheritanceInfo.cs b/Security2/FileSystem/FileSystemInheritanceInfo.cs index 81163db..5de30f7 100644 --- a/Security2/FileSystem/FileSystemInheritanceInfo.cs +++ b/Security2/FileSystem/FileSystemInheritanceInfo.cs @@ -106,8 +106,37 @@ namespace Security2 #endregion GetFileSystemInheritanceInfo #region Enable / DisableInheritance internal + // An item without audit entries can have no SACL at all. AlphaFS writes the flag that disables or enables + // audit inheritance only together with a SACL, and a write without any section is denied: (5) Access is + // denied. The empty SACL is added only to a descriptor that was read with its SACL, so that writing it can't + // remove audit entries. + private static void AddMissingSystemAcl(FileSystemSecurity2 sd) + { + if (!sd.HasAuditSection) + { + return; + } + + var rawDescriptor = new RawSecurityDescriptor(sd.SecurityDescriptor.GetSecurityDescriptorBinaryForm(), 0); + if (rawDescriptor.SystemAcl != null) + { + return; + } + + rawDescriptor.SystemAcl = new RawAcl(GenericAcl.AclRevision, 0); + rawDescriptor.SetFlags(rawDescriptor.ControlFlags | ControlFlags.SystemAclPresent); + var binaryForm = new byte[rawDescriptor.BinaryLength]; + rawDescriptor.GetBinaryForm(binaryForm, 0); + sd.SecurityDescriptor.SetSecurityDescriptorBinaryForm(binaryForm, AccessControlSections.Audit); + } + private static void EnableInheritance(FileSystemSecurity2 sd, bool removeExplicitAccessRules, InheritanceScope scope) { + if (scope == InheritanceScope.Audit) + { + AddMissingSystemAcl(sd); + } + if (sd.IsFile) { if (scope == InheritanceScope.Access) @@ -174,6 +203,11 @@ namespace Security2 private static void DisableInheritance(FileSystemSecurity2 sd, bool removeInheritedAccessRules, InheritanceScope scope) { + if (scope == InheritanceScope.Audit) + { + AddMissingSystemAcl(sd); + } + if (sd.IsFile) { if (scope == InheritanceScope.Access) diff --git a/Tests/Inheritance.Tests.ps1 b/Tests/Inheritance.Tests.ps1 index 19afd2c..ce489b7 100644 --- a/Tests/Inheritance.Tests.ps1 +++ b/Tests/Inheritance.Tests.ps1 @@ -124,12 +124,18 @@ Describe 'Set-NTFSInheritance' { } # In memory, the kept entries stay marked as inherited; Windows stores them as explicit ones on write. + # The descriptor holds only the access entries. Windows marks the inherited entries of a DACL that isn't in + # the auto-inherit format, such as that of a file in the temp folder of the user, only when the SACL isn't + # read with it, and Get-NTFSSecurityDescriptor reads the SACL with the Security privilege. It 'Should keep the inherited access entries of a security descriptor' { $file = New-TestSandboxItem -Sandbox $sandbox -Name 'KeepDescriptor' Assert-TestSandboxPath -Sandbox $sandbox -Path $file - $sd = Get-NTFSSecurityDescriptor -Path $file + $sd = New-Object -TypeName 'Security2.FileSystemSecurity2' -ArgumentList ( + (Get-Item2 -Path $file), [System.Security.AccessControl.AccessControlSections]::Access + ) $sidType = [System.Security.Principal.SecurityIdentifier] $inheritedCount = @($sd.SecurityDescriptor.GetAccessRules($false, $true, $sidType)).Count + $inheritedCount | Should -BeGreaterThan 0 Set-NTFSInheritance -SecurityDescriptor $sd -AccessInheritanceEnabled $false @@ -141,10 +147,14 @@ Describe 'Set-NTFSInheritance' { $file = New-TestSandboxItem -Sandbox $sandbox -Name 'KeepAudit' Assert-TestSandboxPath -Sandbox $sandbox -Path $file Add-NTFSAudit -Path $file -Account 'Everyone' -AccessRights Delete -AuditFlags Failure - Disable-NTFSAuditInheritance -Path $file + Disable-NTFSAuditInheritance -Path $file -ErrorVariable disableErrors -ErrorAction SilentlyContinue + $disableErrors | Should -BeNullOrEmpty + (Get-NTFSInheritance -Path $file).AuditInheritanceEnabled | Should -BeFalse - Set-NTFSInheritance -Path $file -AuditInheritanceEnabled $true + Set-NTFSInheritance -Path $file -AuditInheritanceEnabled $true -ErrorVariable inheritanceErrors -ErrorAction SilentlyContinue + $inheritanceErrors | Should -BeNullOrEmpty + (Get-NTFSInheritance -Path $file).AuditInheritanceEnabled | Should -BeTrue @(Get-NTFSAudit -Path $file -ExcludeInherited) | Should -HaveCount 1 } } @@ -190,6 +200,43 @@ Describe 'Set-NTFSInheritance' { } } +Describe 'Audit inheritance of an item without audit entries' { + # A new item has no SACL. Before 5.0.0, the cmdlets changed only the flag that disables or enables audit + # inheritance, which is written only together with a SACL, so they wrote no section at all: (5) Access is denied. + It ' should set the audit inheritance of a and keep its access entries' -Skip:(-not $canChangeAudit) -ForEach @( + @{ Command = 'Disable-NTFSAuditInheritance'; Parameters = @{}; Type = 'file'; Expected = $false } + @{ Command = 'Disable-NTFSAuditInheritance'; Parameters = @{}; Type = 'folder'; Expected = $false } + @{ Command = 'Enable-NTFSAuditInheritance'; Parameters = @{}; Type = 'file'; Expected = $true } + @{ Command = 'Enable-NTFSAuditInheritance'; Parameters = @{}; Type = 'folder'; Expected = $true } + @{ Command = 'Set-NTFSInheritance'; Parameters = @{ AuditInheritanceEnabled = $false }; Type = 'folder'; Expected = $false } + ) { + $path = New-TestSandboxItem -Sandbox $sandbox -Name 'NoAudit' -Directory:($Type -eq 'folder') + Assert-TestSandboxPath -Sandbox $sandbox -Path $path + @((Get-Acl -LiteralPath $path -Audit).Audit) | Should -BeNullOrEmpty + $accessEntries = (Get-Acl -LiteralPath $path).GetSecurityDescriptorSddlForm('Access') + + & $Command -Path $path @Parameters -ErrorVariable inheritanceErrors -ErrorAction SilentlyContinue + + $inheritanceErrors | Should -BeNullOrEmpty + (Get-NTFSInheritance -Path $path).AuditInheritanceEnabled | Should -Be $Expected + (Get-Acl -LiteralPath $path).GetSecurityDescriptorSddlForm('Access') | Should -Be $accessEntries + } + + # Written later, an added empty SACL would replace the audit entries of the item. + It 'Should add no SACL to a security descriptor that was read without its audit entries' { + $file = New-TestSandboxItem -Sandbox $sandbox -Name 'NoAuditSection' + $sd = New-Object -TypeName 'Security2.FileSystemSecurity2' -ArgumentList ( + (Get-Item2 -Path $file), [System.Security.AccessControl.AccessControlSections]::Access + ) + + Disable-NTFSAuditInheritance -SecurityDescriptor $sd + + $binaryForm = $sd.SecurityDescriptor.GetSecurityDescriptorBinaryForm() + $descriptor = New-Object -TypeName 'System.Security.AccessControl.RawSecurityDescriptor' -ArgumentList $binaryForm, 0 + $null -eq $descriptor.SystemAcl | Should -BeTrue + } +} + Describe 'Audit inheritance switches' { # Before 5.0.0, the switches were named after access entries, although they remove audit entries. It ' should take - with the alias -' -ForEach @( diff --git a/Tests/Repository.Tests.ps1 b/Tests/Repository.Tests.ps1 index 4f519e0..bf5c3b1 100644 --- a/Tests/Repository.Tests.ps1 +++ b/Tests/Repository.Tests.ps1 @@ -1,6 +1,7 @@ <# Tests repository files that the build and GitHub use, without a build: every packages.config lists the AlphaFS - version that the projects reference and ship, and Dependabot keeps the actions of the CI workflow up to date. + version that the projects reference and ship, Dependabot keeps the actions of the CI workflow up to date, and the + manifest and the README describe the release. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.' @@ -75,3 +76,32 @@ Describe 'Dependabot configuration' { $raw | Should -Match '(?m)^\s+groups:\s*\n\s+[\w-]+:\s*\n\s+patterns:\s*\n\s+-\s*["'']\*["'']\s*$' } } + +Describe 'Release metadata' { + BeforeAll { + $repositoryPath = Join-Path -Path $PSScriptRoot -ChildPath '..' + $manifest = Import-PowerShellDataFile -Path (Join-Path -Path $repositoryPath -ChildPath 'NTFSSecurity\NTFSSecurity.psd1') + $version = $manifest.ModuleVersion + if ($manifest.PrivateData.PSData.Prerelease) { + $version = '{0}-{1}' -f $version, $manifest.PrivateData.PSData.Prerelease + } + } + + # Before 5.0.0-rc2, the description said "Windows PowerShell Module", although the module supports PowerShell 7. + It 'Should have the description that the PowerShell Gallery shows for the module' { + $manifest.Description | Should -BeExactly 'PowerShell module for managing file and folder security on NTFS volumes' + } + + # The PowerShell Gallery doesn't accept a version twice. Add every published version to this list + # (Docs/Contributing/05-Releasing.md). + It 'Should not reuse a version that the PowerShell Gallery already has' { + $publishedVersions = '4.0', '4.2.2', '4.2.3', '4.2.4', '4.2.5', '4.2.6', '5.0.0-rc1' + + $publishedVersions | Should -Not -Contain $version + } + + It 'Should not name a prerelease version in the README, which outlives the release' { + Get-Content -LiteralPath (Join-Path -Path $repositoryPath -ChildPath 'Docs\README.md') -Raw | + Should -Not -Match '\d+\.\d+\.\d+-[A-Za-z]' + } +}