--- status: current last-verified: 2026-10-08 owner: active-agent source: repository evidence --- # Tech context ## Stack - C# class libraries, old-style `.csproj`, .NET Framework 4.5.2, solution `NTFSSecurity.sln` (Visual Studio 2017 format). - Projects: `NTFSSecurity` (cmdlets), `Security2` (ACL object model, Win32 interop), `PrivilegeControl` and `ProcessPrivileges` (token privileges), `Log`, `TestClient`, `NTFSSecurityTest` (MSTest, minimal coverage). - NuGet (`packages.config`): AlphaFS 2.2.x for long paths; `System.Management.Automation.dll` 10.0.10586.0. For a drive or volume root, AlphaFS `DirectoryInfo` reaches the device object, while `Directory.Get/SetAccessControl('C:\')` reaches the root folder (#41). - Module: `NTFSSecurity.psd1` loads `NTFSSecurity.psm1` (aliases `dir2`, `gi2`, `rm2`, `del2`), `NTFSSecurity.Init.ps1` (Add-Type of the helper assemblies, prepends `NTFSSecurity.format.ps1xml`), and `NTFSSecurity.dll`. - Documentation: Markdown in `Docs` and `README.md`, rendered by GitHub and published to the wiki by CI; no documentation site (Decisions 9 and 11). Cmdlet pages are platyPS 0.14 markdown (schema 2.0.0) in `Docs/Cmdlets`. - Help: `NTFSSecurity\en-US\NTFSSecurity.dll-Help.xml`, generated from `Docs/Cmdlets` and committed (Decision 8). - Tests: Pester 5 in `Tests`, one file per area, against the Release build; `Wiki.Tests.ps1` (wiki conversion) runs without a build. - CI: GitHub Actions, `.github/workflows/ci.yml` with the scripts in `.github/scripts` (Decision 11). ## Environment - Windows only (NTFS, Win32 security APIs). - The Debug build writes straight into `C:\Program Files\WindowsPowerShell\Modules\NTFSSecurity\`. - No Visual Studio MSBuild or .NET Framework targeting pack on the workstation. A local build works with the .NET Framework MSBuild (`%WINDIR%\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe`) plus `/p:CscToolPath` to the Roslyn `csc.exe` of the `Microsoft.Net.Compilers` package; the legacy C# 5 compiler fails with CS0136. `dotnet msbuild` fails on the binary resources in `Resources.resx` (MSB3822, MSB3823). - platyPS 0.14.2, Pester 5.7.1, PSScriptAnalyzer, and powershell-yaml are installed only for PowerShell 7. Windows PowerShell 5.1, started from PowerShell 7, imports platyPS and Pester by full path (`~\OneDrive\Documents\PowerShell\Modules\platyPS\0.14.2`, `C:\Program Files\PowerShell\Modules\Pester\5.7.1`). Leave `$env:PSModulePath` alone: PowerShell 7 hands the child the Windows PowerShell default path, and clearing it leaves Windows PowerShell without its core modules (Pester fails: `Add-Member` not found). - MarkdownLinkCheck is not installed, and `Save-Module` crashed (FailFast) in PowerShell 7.6 on 2026-10-04. Download the 0.2.0 package from `https://www.powershellgallery.com/api/v2/package/MarkdownLinkCheck/0.2.0` into `$env:TEMP`, extract it, and import it by path. - The first workstation is ARM64; PowerShell 7 runs as x64 under emulation. - The NuGet cache (`~\.nuget\packages`) holds every build dependency: copy `alphafs\2.2.1`, `system.management.automation.dll\10.0.10586`, and `microsoft.netframework.referenceassemblies.net452\1.0.3` into `packages\.`, and point `CscToolPath` at `microsoft.net.compilers\4.2.0\tools`. - The second workstation (x64, used since 2026-10-05) runs the agent session elevated, so the tests that need privileges run there as in CI. It has no NuGet cache with these packages: download each from `https://api.nuget.org/v3-flatcontainer///..nupkg`, extract the first three into `packages\.` and the compilers into `$env:TEMP`; Pester 5.7.1 comes from the Gallery package API the same way, its folder first on `$env:PSModulePath` of the test process. The GitHub CLI is in `C:\Program Files\GitHub CLI`, outside the PATH of sessions started before its installation. - The third workstation (`ExHost`, a Windows Server 2025 VM, x64, used since 2026-10-07) runs the agent session elevated and hosts the AutomatedLab lab `WindowsAccessControlLab` (Decision 20) with Hyper-V and AutomatedLab 5.61.704. It has no NuGet cache or platyPS: check each nuget.org package against the SHA-512 `packageHash` of its catalog entry (`https://api.nuget.org/v3/registration5-semver1//.json`, then `catalogEntry`), and each Gallery package against `PackageHash` of `api/v2/Packages(Id='',Version='')`. Pester 5.7.1 is in `V:\Git\WindowsAccessControl\output\RequiredModules`. The GitHub CLI 2.102.0 is in `C:\Program Files\GitHub CLI`, outside the PATH, and signed in as `raandree` since 2026-10-08; `Block-RemoteMutation` denies its mutating commands, so the agent uses it read-only. The lab domains `a.forest1.net` and `b.forest1.net` had a maximum password age of 42 days, so the password of `install` expired on 2026-09-15 and AutomatedLab got access denied; it never expires since 2026-10-07, as in `forest1.net`. ## Constraints - `ModuleVersion` is `5.0.0` with the prerelease label `rc6` on the branch `ai/release-5.0.0-rc6` (`rc5` on `master`). 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 and PowerShell 7.6. - The module source at `master` differs from tag `4.2.6` by the changes that `CHANGELOG.md` lists under `[Unreleased]`, the release notes of each 5.0.0 prerelease. - PowerShell Gallery versions (publish dates): 4.0.0 (2015-08-19), 4.2.2 (2016-05-18), 4.2.3 (2016-05-19), 4.2.4 (2018-08-13), 4.2.5 (2019-07-11), 4.2.6 (2019-07-12), none with release notes; 5.0.0-rc1 (2026-10-04), 5.0.0-rc2 (2026-10-05), 5.0.0-rc3 and 5.0.0-rc4 (2026-10-06), 5.0.0-rc5 (2026-10-08), published by CI. Older versions were released on CodePlex only, and their dates are lost. The git history starts on 2016-10-10, when the project moved from CodePlex. - Releases up to 4.2.6 were Debug builds published by hand, with the whole output folder; their tags carry the previous version. From 5.0.0 on, CI publishes on a version tag (Decision 12). GitHub releases attach `NTFSSecurity.zip`. - CI: GitHub Actions on pull requests, pushes to `master`, and version tags (Decision 11); AppVeyor and Read the Docs aren't used (Decision 9). - `CHANGELOG.md` lists user-visible changes only; CI and build-only changes get no entry ([Decision 7](decisions/0007-changelog-user-visible-only.md)). - Remote mutations are the maintainer's: the user-level preToolUse hook `Block-RemoteMutation.ps1` denies `git push` and mutating `gh` commands (`pr create`, `pr close`, and others) from the agent session, even after an explicit request. Its override, `COPILOT_ATELIER_ALLOW_REMOTE=1`, is read from the environment that VS Code starts the hook with; setting it inside an agent command has no effect (verified 2026-10-04). The hook matches the whole command text, so a commit message that quotes such a command is blocked too. Prepare the commands and descriptions; the maintainer runs them. Hand over each command as its own fenced code block at the end of the reply, which the chat shows with a copy button, and end the turn there; the maintainer reports back in the chat. The question dialog joins the lines of its text, has no copy button, and covers the reply before it (maintainer, 2026-10-06). A long question also hides its choices, so that it can't be answered: keep it to a few short sentences (2026-10-07). A pull request description names an issue without a closing keyword (fixes, closes, resolves) unless the merge should close it: "fixes #34" in #112 closed #34. Simulated `gh` commands in offline tests must print what the real ones print, such as the URL of a new comment. ## Validation - CI (`.github/workflows/ci.yml`): job `build` on `windows-2025` installs platyPS 0.14.2, MarkdownLinkCheck 0.2.0, and Pester 5.7.1 for all users, restores `packages.config` per project plus `Microsoft.NETFramework.ReferenceAssemblies.net452` 1.0.3, builds `NTFSSecurity.csproj` in Release with the MSBuild that `vswhere` finds, then: 01 `Update-MarkdownHelp` and fail on `git diff -- Docs/Cmdlets`; 02 `Get-MarkdownLink -BrokenOnly`; 03 regenerate the help file and fail on `git status --porcelain -- NTFSSecurity/en-US`; 04 `Invoke-Tests.ps1` in Windows PowerShell 5.1 and in PowerShell 7, then `Invoke-TestsAsBasicUser.ps1` in both editions (since 5.0.0-rc6). Job `wiki` on `ubuntu-latest` (read-only) clones the wiki (`gh auth setup-git` with the built-in token), runs `Export-WikiContent.ps1`, and lists the changed pages in the job summary; job `publish-wiki` (`contents: write`) repeats that and publishes, for `master` only. After the tests, `build` runs `New-ModulePackage.ps1` and uploads the artifact `packages` (nupkg and `NTFSSecurity.zip`). Job `release` runs only for tags matching `[0-9]+.[0-9]+.[0-9]+` or `[0-9]+.[0-9]+.[0-9]+-*`, in the environment `powershell-gallery` (secret `PSGALLERY_API_KEY`); see Decision 12. Actions are pinned by commit SHA: `actions/checkout` v7.0.1, `actions/upload-artifact` v7.0.1, `actions/download-artifact` v8.0.1; Dependabot proposes updates weekly, one week after a release. - Packaging needs PSResourceGet (`Compress-PSResource`, PowerShell 7.4 or later); its tests skip in Windows PowerShell. Dry run locally: run `New-ModulePackage.ps1` against `NTFSSecurity\bin\Release` into `$env:TEMP`, then extract the nupkg into a folder and import it there. - Read CI runs with `gh run list --repo raandree/NTFSSecurity --workflow ci.yml`, `gh pr checks `, and `gh run view --log-failed` (read-only). - Workflow lint: actionlint (download the release zip into `$env:TEMP` and check its SHA-256 against the checksum file; 1.7.12 on 2026-10-08); PowerShell steps check `$LASTEXITCODE` after every native command, because GitHub checks only the last one. - Run platyPS in Windows PowerShell 5.1 to avoid PowerShell 7.4+ `-ProgressAction` noise. - Placeholder check: no `{{` left in `Docs/Cmdlets/*.md`. - Help file: `New-ExternalHelp -Path .\Docs\Cmdlets -OutputPath .\NTFSSecurity\en-US -Force` must leave `git status` unchanged. - Pester: run detached (`Start-DetachedPowerShell.ps1`) in Windows PowerShell 5.1: the launcher starts `pwsh`, and its payload runs `powershell.exe -NoProfile -EncodedCommand` with Pester imported by full path. A run without `bin\Release\en-US` must fail. - Tests that run only without a privilege skip in an elevated session. `.github\scripts\Invoke-TestsAsBasicUser.ps1` runs the suite from an elevated session with a token of the SAFER level Normal User, like `runas /trustlevel:0x20000`, and CI runs it in both editions. For a single file, `runas /trustlevel:0x20000` works too; give Windows PowerShell its own `PSModulePath`, and note that `runas` returns at once, so the script it starts writes its own log. Both tokens hold only the privilege to bypass traverse checking. Pester reports a skipped `-ForEach` test under its template name, such as `<_> should ...`, and a test that ran under the expanded name: compare runs by template. - C# coverage (Decision 21): AltCover 9.0.145 (`tools\net472\AltCover.exe` of the nuget.org package) instruments a copy of the local Release build, which has the PDB files that the published package lacks: `--reportFormat=OpenCover`, AlphaFS and `System.Management.Automation` excluded with `--assemblyFilter`, and no `--save`: then every process writes its hits into the report when it exits. With `--save`, each process writes a recorder file, and `runner --collect` keeps only the first one (verified 2026-10-08), so the numbers measured that way held only the main process of the elevated Windows PowerShell run. Put the instrumented module in `NTFSSecurity\bin\Release` of a `git worktree`, run `.github\scripts\Invoke-Tests.ps1` elevated and `Invoke-TestsAsBasicUser.ps1` in both editions, then `AltCover.exe runner --collect --recorderDirectory=`, which recalculates the summary of the report from the hits. All four configurations, 2026-10-08: the rc5 tree 58.1% of the lines (2,020 of 3,476) and 38.0% of the branches (711 of 1,873), 62.5% without 244 lines in classes that no cmdlet calls; the rc6 candidate (`1b9edbb`) 68.1% of the lines (2,412 of 3,540) and 44.3% of the branches (850 of 1,918), 73.2% without those classes, the `NTFSSecurity` assembly 78.1%. The earlier figures, 55.9% for rc5 and 65.6% for rc6, used `--save`. - Live tests (Decision 20): in an elevated Windows PowerShell 5.1 session on the lab host, `Tests\Lab\Invoke-NTFSSecurityLabTest.ps1` with `-Version` for Gallery packages or `-ModulePath` for a build; it writes the results to `$env:TEMP\NTFSSecurityLab\Results`. A run of two versions in both editions takes about 30 minutes; `-RemoveFixture` removes its accounts, share, and folders from the lab. For a check on the client as an account without administrator rights, use `NtfsLiveServerAdmin` (Remote Management Users on the client, CredSSP by IP address like the controller): reset its password on the PDC emulator to a random value in memory; the next run of the controller sets a new one anyway. - Lab acceptance of a candidate (modeled on the WindowsAccessControl handoff 07): build once, package it with `New-ModulePackage.ps1`, and record the SHA-256 of the packages and module files; check WinRM, LDAP (RootDSE), Kerberos (`klist get`), the secure channel, and the clock of the six VMs; take a Production checkpoint named `ntfs-