(#1990) Draft: .NET 10 + PowerShell 7.6 migration prototype#3904
Draft
fdcastel wants to merge 46 commits into
Draft
(#1990) Draft: .NET 10 + PowerShell 7.6 migration prototype#3904fdcastel wants to merge 46 commits into
fdcastel wants to merge 46 commits into
Conversation
Remove the Mono Ubuntu/macOS and Docker build jobs from build.yml and the Ubuntu/macOS jobs from test.yml. The .NET 10 migration is Windows-only (net10.0-windows), so the cross-platform Mono jobs no longer apply.
Add 10.0.x to actions/setup-dotnet in build.yml and test.yml so the runner has the .NET 10 SDK for the net10.0-windows build. 5.0.x is kept for GitVersion.
build.yml previously ran only the unit suite on push (integration was nightly-only in test.yml). Run --testExecutionType=all on every push/PR so integration scenarios gate each change. Use the 'test' target (build + test) for the per-push job during the migration: the 'CI' target's ILMerge/MSI packaging is net48-specific and is restored with the self-contained publish in Phase 5 (DM-50). Trim the uploaded artifacts to the test/coverage results the test target produces.
Flip every project's TargetFramework from net48 to net10.0-windows. Enable the Windows Desktop SDK (UseWindowsForms/UseWPF) on choco.exe so the self-contained runtime keeps WinForms/WPF/System.Drawing available for package Add-Type scripts.
Drop the lib\PowerShell\System.Management.Automation.dll HintPath references (a Windows PowerShell 5.1 assembly). The host (chocolatey.dll) now uses Microsoft.PowerShell.SDK 7.6.2 (PowerShell 7 on .NET 10); the Chocolatey.PowerShell binary module uses the compile-only PowerShellStandard.Library so it binds to the host's SMA at load time. choco.exe picks up SMA transitively from chocolatey.dll.
- Remove AutoGenerateBindingRedirects/GenerateBindingRedirectsOutputType from every project (SDK-style net10 resolves assemblies without them). - Set <LangVersion>latest</LangVersion> everywhere. - choco.exe: move choco.exe.manifest from a side-by-side EmbeddedResource to <ApplicationManifest> (embedded into the PE; drop NoWin32Manifest). - Drop Microsoft.Bcl.HashCode; System.HashCode is in the net10 BCL.
- StringExtensions: drop the unused 'using System.Web.UI' (System.Web is not available on .NET 10). - CryptoHashProvider: replace the FIPS-only SHA*Cng types with the SHA*.Create() factories. On .NET the default implementations are OS/CNG backed and FIPS-compliant, so the AllowOnlyFipsAlgorithms special-casing is gone. - DotNetFileSystem: the static Directory.Get/SetAccessControl don't exist on .NET; call them on a DirectoryInfo (FileSystemAclExtensions). - ObjectExtensions.DeepCopy: suppress SYSLIB0011 so it compiles; the BinaryFormatter replacement is DM-20 (Phase 2).
The benchmark pinned a large net48-era package closure that downgraded the PowerShell SDK's transitive versions (NU1605). Reduce it to the BenchmarkDotNet packages and let the net10 shared framework + the chocolatey project reference supply the rest. Also drop the stale TraceEvent packages.config props import and the removed CAS attributes (HostProtection/SecurityPermission) that don't exist on .NET. Dependency audit (DM-13): Chocolatey.NuGet.PackageManagement 3.5.0, log4net 3.3.1, SimpleInjector 5.5.0, System.Reactive 6.1.0 and the Microsoft.PowerShell.SDK 7.6.2 closure all restore and compile on net10.0-windows; Rhino.Licensing is referenced via its net40 assembly.
Set DM-03..DM-05 and DM-10..DM-14 to IN PROGRESS with their commit hashes and add Phase 0-1 implementation notes (per-push CI target, PowerShellStandard module reference, net10 API fixes, BinaryFormatter/ AlphaFS deferred). Flip to DONE once CI is green.
CI run 26418337603 on windows-latest: DotNetBuild = 'Build succeeded, 0 Error(s)', so the Phase 1 gate (solution builds) is green. Mark DM-03..DM-05 and DM-10..DM-14 DONE. The job is red only in DotNetTest (Phase 2/3): add DM-24 (initonly static-field reflection -> FieldAccessException) and DM-25 (ReadKey/ReadLine console crash aborts the test host) to Phase 2.
.NET no longer lets reflection write initonly static fields, so the
spec setup that did `GetField("AllowPrompts").SetValue(null, false)`
threw FieldAccessException in OneTimeSetUp and cascaded to ~64 unit
failures. Drop `readonly` from AllowPrompts (production only reads it;
it exists solely to be toggled by specs) and have ConsoleSpecs and
AutomaticUninstallerServiceSpecs assign it directly.
The background reader thread called Console.ReadKey/ReadLine unguarded: in a headless/redirected host that throws InvalidOperationException on the background thread, which crashed the test host and aborted the run. Also Thread.Abort() (called in Dispose) throws PlatformNotSupportedException on .NET. Guard the thread body with try/catch (return on failure) and drop Thread.Abort() — the reader is a background thread reclaimed at exit.
BinaryFormatter is removed from .NET and threw PlatformNotSupportedException at runtime. Reimplement DeepCopy<T> as a field-based recursive cloner that mirrors the formatter's semantics (copies private/inherited fields, skips [NonSerialized], preserves cycles) for the in-process configuration graph. The container registrator deep-copied dictionaries keyed/valued by Type and a HashSet; Types are runtime singletons that must be shared, so rebuild those collections explicitly in Clone() instead of serializing them.
…rkaround .NET ships only Unicode/UTF encodings; Encoding.GetEncoding(1252) throws unless CodePagesEncodingProvider is registered. Without it the non-ASCII password workaround fell back to Encoding.Default (UTF-8 on .NET) and returned the password unchanged, breaking source auth with non-ASCII passwords. Register the provider in the credential provider's static ctor.
DM-20/24/25/26 DONE. DM-23 (unit gate) green locally — awaiting CI confirmation. DM-21 (AlphaFS) and DM-22 (AssemblyResolve) are not needed for the unit suite; they will be driven by Phase 3 integration.
CI run 26421485191: chocolatey.tests.dll 'Passed! Failed: 0, Passed: 1188' (build green, 0 errors). Mark DM-23 DONE. Integration is uniformly blocked by FieldAccessException in NUnitSetup.FixApplicationParameterVariables (initonly ApplicationParameters location fields) — recorded under DM-30.
…ations NUnitSetup.FixApplicationParameterVariables redirected ~12 initonly ApplicationParameters.* location fields via reflection, which threw FieldAccessException on .NET and failed all 1567 integration tests in OneTimeSetUp. Make those fields settable (InstallLocation + the derived locations + LockTransactionalInstallFiles) and assign them directly. Drop the dead TIMES_TO_TRY_OPERATION reflection (the field is _timesToTryOperation, so the lookup always returned null). Locally: List scenarios 18/18 and Install scenarios 516/0/54 pass.
…-isolation debt (DM-32)
…lation) Root cause of the last 20 integration failures: the proxy specs set Configuration.Proxy.Location and call ConfigurationBuilder.SetupConfiguration, which (via EnvironmentSettings.SetEnvironmentVariables) writes the *real* process env vars HTTP_PROXY/HTTPS_PROXY/NO_PROXY (= 'EnvironmentVariableSet') and never restored them. On .NET, HttpClient.DefaultProxy / NuGet's ProxyCache honor those env vars (they did not on .NET Framework), so every later NuGet HTTP request routed through a bogus 'EnvironmentVariableSet:80' proxy -> FatalProtocolException 'Unable to load the service index' -> empty results. This only manifested in the full suite (the proxy specs run before the WireMock-based NugetListSpecs/UpgradeScenarios), which is why those tests passed in isolation. Save and restore the proxy-related env vars around the proxy specs. Full local integration suite now: 0 failed, 1463 passed, 104 skipped.
… integration 1463/0
AlphaFS was a .NET Framework library used only as a long-path fallback (try System.IO / catch IOException -> AlphaFS, or length >= MAX_PATH). .NET 10's System.IO handles long paths natively and choco.exe.manifest already declares longPathAware, so drop AlphaFS entirely: - DotNetFileSystem: collapse all 16 AlphaFS fallbacks to plain System.IO (AllowRetries already covers transient IO errors); remove the now-unused MaxPathFile/MaxPathDirectory constants. - ChocolateySourceCacheContext: use System.IO instead of AlphaFS. - Drop the AlphaFS PackageReference (removes the last net48 NU1701 dependency). Validated: unit 1188/0, integration 1463/0, 104 skipped.
…e-file The Release InstallLocation used Assembly(.GetEntryAssembly()).CodeBase, which is obsolete on .NET and, for a self-contained single-file publish, is empty or points at the bundle's temp extraction directory -- so choco looked for lib/config under an ephemeral temp path even with ChocolateyInstall set. Resolve from Environment.ProcessPath (the real choco.exe), falling back to the ChocolateyInstall env var then ProgramData. Validated: single-file choco.exe now resolves InstallLocation to its real directory; unit suite 1188/0.
With the self-contained publish there is no ILMerge: choco.exe and chocolatey.dll ship as real, separately-loaded assemblies. The old fallback returned typeof(ConsoleApplication).Assembly for any choco-keyed request that wasn't an extension/resource/Chocolatey.PowerShell, which in the non-merged world hands back the wrong assembly. Remove it and let normal runtime resolution handle those; drop the now-unused runners import.
New 'Self-Contained Smoke Test' workflow publishes the single-file self-contained choco.exe (PublishSingleFile + IncludeAllContentForSelfExtract, win-x64, Desktop runtime) and runs 'choco --version' inside a Windows Server Core container that has NO .NET runtime installed. This proves the migration's core goal: choco runs on a clean box with nothing installed and no reboot. (DM-54 gate.)
… recipe - Disable ILMerge (shouldRunILMerge: false) and delete the net48 ILMerge config function. - Prepare-Chocolatey-Packages: publish choco.exe self-contained single-file (win-x64, PublishSingleFile + IncludeAllContentForSelfExtract) and copy it; take helpers/redirects/tools/LICENSE from the net10.0-windows publish; drop the side-by-side manifest copy (now embedded in choco.exe). - Prepare-NuGet-Packages: chocolatey.lib lib/net48 -> lib/net10.0, ship chocolatey.dll directly (no merge). - Update remaining net48 paths (tar.gz working dir). - build.yml: per-push CI back to --target=CI (build + test + package) now that packaging no longer depends on net48/ILMerge; re-upload the nupkg artifacts.
New pester-e2e job (needs: windows-build) downloads the packaged chocolatey nupkg and runs Invoke-Tests.ps1 under pwsh (PowerShell 7) over the tests/pester-tests suite. This is the Phase 4 'dominant risk' check: real package install/helper behavior under PS7 instead of Windows PowerShell 5.1.
…rs don't abort Invoke-Tests.ps1 uses Write-Error for two nuspecs that are intentionally expected to fail to pack (per its own comment), but GH Actions pwsh shell defaults to ErrorActionPreference=Stop, turning those non-terminating errors into a job abort before Pester even runs. Set Continue for the step and report testResults.xml location for visibility.
The GH runner ships PowerShell 7.4 LTS (.NET 8) and cannot load Chocolatey.PowerShell.dll which targets net10.0-windows (System.Runtime 10.0.0.0 doesn't exist on .NET 8). PowerShell 7.6 runs on .NET 10 and matches the Microsoft.PowerShell.SDK 7.6.2 host the choco migration uses, so install it and run Invoke-Tests.ps1 with it.
Invoke-Tests.ps1 doesn't propagate Pester pass/fail (Pester writes results to host but doesn't set the exit code), so the job reported 'success' even with 194 test failures (3565/4567 passed under PS 7.6/.NET 10 on the first full run). Parse the NUnit-format testResults.xml and exit non-zero when failures+errors > 0 so CI honestly reflects the Pester gate.
…removed) PS6+ removed Get-Content -Encoding Byte (replaced by -AsByteStream), so on PowerShell 7 the helper read no bytes and every 'Should have a Byte Order Mark' assertion failed even though the files DO have BOMs (verified: source, publish output, and the built nupkg all show EF BB BF on each helper .ps1). Read the first 4 bytes via [System.IO.File]::ReadAllBytes so the helper works under both Windows PowerShell 5.1 and PowerShell 7+. Expected to clear ~42 of the 194 Pester failures on PS 7.6/.NET 10.
…6/4567) CI run 26432619751: full build+test+package+smoke green; Pester E2E under PowerShell 7.6/.NET 10 at 3606 passed / 153 failed / 367 skipped / 441 not-run (no test files broke). The 41 BOM 'failures' were a PS7-incompat in the test helper Test-ByteOrderMark (removed Get-Content -Encoding Byte); fixed. Mark DM-43 DONE (Pester E2E job wired + honestly exits non-zero on failures). Mark DM-40 IN PROGRESS (BOM-helper fix; rest is part of the long tail). DM-44 remains the gate. Plan now records the remaining failure clusters as concrete follow-up items so the next session can pick them up pattern-by-pattern.
The repo ships several testpackages under tests/pester-tests/commands/testpackages (zip-log-disable-test, packagewithscript, installpackage, chocolatey-dummy-package, too-long-description, too-long-title) that Pester tests install by name. Outside Test Kitchen these were never packed into the seeded hermes source, so the tests exited with 'package was not found' instead of exercising real install paths. Add the directory to the recursive nuspec search so Invoke-Tests.ps1 packs them the same way it packs tests/packages/*. Verified locally that 'choco install zip-log-disable-test' against the seeded source now exits 0 on the migrated net10 choco.exe. Clears ~10 Phase 4 Pester failures with no source-code change.
…T.md Plan update: - DM-40 marked DONE (BOM helper PS7 fix complete; remaining helper-module work folded into Phase 6). - DM-42 deferred (UseWindowsPowerShell path is subsumed by the Phase 7 5.1 fallback). - DM-44 marked DONE (substantial). Comprehensive classification of all 148 CI baseline failures shows >=90 are Test Kitchen environmental cascades from packages this repo does not ship (test-environment, hasinnoinstaller, test-params, wget, uninstallfailure, failingdependency 0.9.9, nuget.commandline from CCR, chocolatey 0.11.2 from CCR) or an authenticated NuGet source (hermes-setup). The net48 install/upgrade/uninstall code paths the Pester suite would have exercised are all already green in the NUnit integration suite (1463/0). Report: docs/DOTNET_MIGRATION_REPORT.md is an upstream-PR-reviewer-targeted narrative covering motivation, all eight phases, the Phase 4 Test Kitchen finding and its justification, remaining work, reproduction steps, and open questions for upstream.
The migrated chocolatey CLI is a self-contained single-file executable that bundles the .NET 10 Desktop runtime; there is no longer any need to install or verify .NET Framework 4.8 at install or run time. Removed: - Install-DotNet48IfMissing function and its call site in Initialize-Chocolatey (chocolateysetup.psm1) -- ndp48 download/install + retry loop + reboot signalling. - The DotNetInstallRequiredReboot branch in the success message (also chocolateysetup.psm1) -- there is no reboot path anymore. - The PS<=3 early return in Invoke-Chocolatey-Initial -- only relevant when the previous reboot path fired. - ThrowIfNotDotNet48() in chocolatey.console/Program.cs and its NDP4 registry probe -- the published self-contained choco.exe does not depend on the installed Framework version. Also drops the now-unused Microsoft.Win32 using. WiX (NetFx48.wxs + the WIX_IS_NETFRAMEWORK_48_OR_LATER_INSTALLED condition in chocolatey.wxs) is deliberately left in place for a separate DM-81 commit so the MSI packaging decision can be discussed with maintainers on the PR. Build verified: dotnet build src/chocolatey.console/chocolatey.console.csproj clean. chocolateysetup.psm1 re-parses cleanly under PowerShell.
Runtime requirements: - Replace '.NET Framework 4.8+' with 'no prior runtime install required'; Chocolatey CLI 3.0+ ships as a self-contained choco.exe bundling the .NET 10 Desktop runtime and PowerShell 7.6 SDK. - Drop the PowerShell 2.0+ line (the bundled host is PS 7.6); add a note that Windows PowerShell 5.1 is only relevant for the Phase 7 5.1 fallback runner (per-package opt-in). - Tighten the supported OS line: Windows Server 2016+ / Windows 10 1809+ (the minimum Microsoft supports for the .NET 10 Desktop runtime). Build prerequisites (Windows section): - '.NET Framework 4.8' and '.NET Framework 4.8 Dev Pack' -> '.NET 10 SDK (build target net10.0-windows)'. - 'Visual Studio 2019 / VS 2019 Build Tools' -> 'Visual Studio 2022 17.14+ / VS 2022 Build Tools', matching the toolchain that ships with the .NET 10 SDK. The 'Other Platforms' (Mono) section is left in place pending maintainer input on cross-platform build scope post-migration.
Member
|
HI @fdcastel , I wanted to take a minute and say thanks for the PR! There is a lot to look at and consider here, and at this time I'm unable to give any further direction. We will discuss this and get back to you soon. |
Member
|
I also have been working on a Dotnet build on my fork. It's a bit old at this point, but I got most of the basics working. One note is that I think the team would want to keep Mac/Linux compatibility, so it would need to be a net10.0 target. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
[Draft / RFC] Chocolatey CLI — .NET 10 + PowerShell 7.6 migration prototype
Refs: #1990 (Upgrade PowerShell Host to PowerShell 7+ (Core))
What this is — and what it isn't
This is a draft prototype that migrates Chocolatey CLI from
.NET Framework 4.8+ Windows PowerShell 5.1 tonet10.0-windows+ PowerShell SDK 7.6, with a self-contained single-filechoco.exethat runs on a clean Windows box with no .NET runtime install and no reboot.Up front, three things I want to acknowledge:
CoreTeam— "Issues that will only be accepted from the core Chocolatey Team." I am not on the core team. This PR is not an attempt to land the official migration; it's exploratory work I did on my fork to see what the migration actually requires. If the core team would prefer I close this and contribute differently (or not at all), please say so and I will — no offense taken.recipe.cake, the bootstrap, and several core services. I tried to keep each commit small, atomic, and prefixed with a(DM-NN)migration-plan ID, but it is by nature a wide change.I'm opening this early as a draft because the riskiest pieces of remaining work — specifically the WinPS 5.1 fallback design (Phase 7) — really want core-team input before being built. Designing them solo and then submitting another huge PR is the worst sequencing for both sides. This is the buyoff conversation.
If the framing is wrong, please tell me. I'm happy to convert this to an issue-first discussion.
Status of CI
chocolatey.<v>.nupkgproducedsmoke.ymlagainstmcr.microsoft.com/windows/servercore:ltsc2022What's in this PR — by phase
Every change is gated by a CI signal. The full per-task table with commit hashes lives in
docs/DOTNET_MIGRATION_PLAN.md; the upstream-facing narrative with architectural rationale isdocs/DOTNET_MIGRATION_REPORT.md. Highlights:net10.0-windows.Microsoft.PowerShell.SDK 7.6.2replaces the bundled SMA dll. Binding redirects deleted; manifest embedded; LangVersion modernized. The benchmark project's net48-era closure trimmed.BinaryFormatter→ reflective deep clone;Thread.Abort()removed fromReadKeyTimeout/ReadLineTimeout;CodePagesEncodingProviderregistered for cp1252.ConfigurationBuilderSpecswrites real proxy env vars that net10'sHttpClient.DefaultProxyhonors (net48 did not), leaking across spec isolation — fixed with save/restore.dotnet publish -r win-x64 --self-contained -p:PublishSingleFile=true -p:IncludeAllContentForSelfExtract=true.InstallLocationrewritten toEnvironment.ProcessPath(single-file bundles don't exposeAssembly.Location).recipe.cakeupdated;/net48/paths swept tonet10.0-windows.net10.0-windows-targetedChocolatey.PowerShell.dll). The runner parsestestResults.xmland exits non-zero honestly. Effective pass rate 95.5%; classification shows ≥90 of 148 residual failures are Test-Kitchen environmental cascades from packages this repo doesn't ship (test-environment,hasinnoinstaller,test-params,wget,uninstallfailure,failingdependency 0.9.9,nuget.commandlinefrom CCR,chocolatey 0.11.2/chocolatey-agent 0.11.2from CCR) and one authenticated source (hermes-setup). My read: the suite is shaped for Test Kitchen; the same code paths it would have exercised are already covered by the NUnit integration suite (1463/0). This is the largest place I'd appreciate your judgment.Install-DotNet48IfMissing+ the reboot path deleted fromchocolateysetup.psm1;ThrowIfNotDotNet48()NDP4 registry probe deleted fromProgram.cs; README requirements + build prereqs updated. WiXNetFx48.wxsand theWIX_IS_NETFRAMEWORK_48_OR_LATER_INSTALLEDcondition are deliberately left in place — that's a packaging decision that belongs to maintainers.What's NOT here — and why I stopped
AssemblyLoadContextmigrationAppDomain.AssemblyResolveworks on .NET 10; revisit if a concrete need arises.Open questions for the core team
net10.0-windows(notnet10.0), PowerShell SDK 7.6, self-contained single-file publish,Environment.ProcessPatheverywhereAssembly.CodeBaseused to resolve. If any of these is a hard "no," I'd rather know now than after Phase 7.Microsoft.PowerShellruntime within the same process? Per-package opt-in via what —chocolateyInstall.ps1directive, nuspec metadata, package tag, separate file? Whatever you decide, that shape changes Phase 6's triage buckets too.What I'm asking for
Just your steer. Not approval, not merge. Comment "we're interested, proceed with Phase 6 along these lines" or "this direction won't fit; please close" or anything in between. I'll pause work on this branch pending your reply.
If you'd like to chat about Phase 7 design before any further code lands, that's exactly the conversation I'd most value.
— Thanks for reading.