Skip to content

Add a resumable PowerShell setup flow for Windows developer machines - #93

Merged
AmirMS (AmelBawa-msft) merged 39 commits into
mainfrom
user/amelbawa/dev-config-2
Sep 16, 2026
Merged

AmirMS (AmelBawa-msft) merged 39 commits into
mainfrom
user/amelbawa/dev-config-2

Conversation

@AmelBawa-msft

Copy link
Copy Markdown
Collaborator

Introduces a PowerShell implementation of the developer workstation setup that
runs from a single command, checks every change before applying it, and continues
across the reboot WSL requires.

@AmelBawa-msft
AmirMS (AmelBawa-msft) marked this pull request as ready for review August 19, 2026 17:04
@JohnMcPMS
JohnMcPMS requested a balanced review from Copilot August 20, 2026 17:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Replaces the DSC-based Calm OS flow with a modular, idempotent PowerShell setup that supports elevation and reboot resumption.

Changes:

  • Adds bootstrap, orchestration, setup phases, and shared helpers.
  • Adds WSL reboot/resume, package, registry, Terminal, font, and Copilot configuration.
  • Updates manifests and documentation while removing the old DSC implementation.

Reviewed changes

Copilot reviewed 32 out of 33 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
.gitignore Ignores setup tally state.
README.md Documents the new PowerShell flow.
src/docs/development.md Describes PowerShell-native flow conventions.
src/future/cmdpal/README.md Notes extension launch limitations.
src/manifest.yml Points Calm OS at the new entry script.
src/tests/calm-os/probe.ps1 Updates smoke-test commentary.
src/windows-dev-config/README.md Rewrites usage and configuration documentation.
src/windows-dev-config/bootstrap.ps1 Downloads and verifies the setup payload.
src/windows-dev-config/dev-config.ps1 Orchestrates setup phases and reporting.
src/windows-dev-config/dev-config.winget Removes the legacy DSC configuration.
src/windows-dev-config/install.ps1 Removes the legacy install shim.
steps/_console.ps1 Adds logging and pause helpers.
steps/_elevation.ps1 Adds elevation and single-instance handling.
steps/_environment.ps1 Adds process, PATH, TLS, and file helpers.
steps/_pwsh-bootstrap.ps1 Bootstraps and relaunches under PowerShell 7.
steps/_reboot-resume.ps1 Registers post-reboot continuation.
steps/_registry.ps1 Adds registry helpers.
steps/_resume-wrapper.ps1 Relays resumed-run output.
steps/_retry.ps1 Adds retry handling.
steps/_step-runner.ps1 Implements check/apply/verify execution.
steps/_terminal.ps1 Manages Terminal settings JSON.
steps/_winget.ps1 Manages WinGet front ends and packages.
steps/copilot.ps1 Configures Copilot integrations.
steps/edge.ps1 Applies Edge policies.
steps/fonts.ps1 Installs and configures Cascadia fonts.
steps/packages.ps1 Defines the package set.
steps/powershell-profile.ps1 Adds Oh My Posh initialization.
steps/prerequisites.ps1 Verifies PowerShell and WinGet.
steps/registry-explorer.ps1 Applies Explorer settings.
steps/registry-system.ps1 Applies system settings.
steps/registry-taskbar-search.ps1 Applies taskbar and search settings.
steps/terminal.ps1 Configures Terminal defaults and theme.
steps/wsl.ps1 Installs WSL, reboots, and installs Ubuntu.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/windows-dev-config/README.md Outdated
Comment thread src/docs/development.md
Comment thread src/windows-dev-config/steps/_winget.ps1 Outdated
Comment thread src/windows-dev-config/steps/_terminal.ps1 Outdated
Comment thread src/windows-dev-config/steps/_step-runner.ps1
Comment thread src/windows-dev-config/steps/fonts.ps1 Outdated
Comment thread src/windows-dev-config/steps/registry-taskbar-search.ps1 Outdated
Comment thread src/windows-dev-config/README.md Outdated
Comment thread src/windows-dev-config/steps/_terminal.ps1 Outdated
Comment thread src/windows-dev-config/steps/wsl.ps1
Comment thread src/windows-dev-config/steps/packages.ps1 Outdated

@ranm-msft ranm-msft left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went through this properly rather than skimming, mostly the resume path since that's the part that's hard to get right.

Most of what I'd have flagged is already handled. Moving Clear-DevConfigStepFlag into the check loop is the right call - doing it only in the reporting loop would have missed resumed phases that return early, which is exactly the case that matters. I also ran the JSONC change on a real box: 7.6.5 parses the comments fine, 5.1 throws, and Read-DevConfigTerminalSettings refuses rather than quietly mangling someone's settings.json. And the $Ref regex plus the .. check does genuinely keep the archive URL inside the repo.

The one thing I couldn't talk myself out of: Invoke-DevConfigNativeCommand has no timeout, while Invoke-DevConfigProcess directly below it makes TimeoutSeconds mandatory and prints a heartbeat every minute. The four copilot calls in copilot.ps1 go through the unbounded one. copilot gets installed earlier in the same run, so on a fresh machine it has never been signed in (it does have a login subcommand), and marketplace add / plugin install both do network work on top of that. The Copilot phase runs immediately before WSL, so a stall there doesn't just skip a step - it strands the run short of the restart the whole resume flow depends on. BestEffort won't catch it either, since a hang never throws.

Have you had this run against a machine that's never signed into copilot? If it fails fast there, this is a non-issue.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The verified payload remains user-writable before elevated execution, and several state checks can incorrectly accept drifted configurations.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (5)

Previously missed (5) — in code that hasn't changed since the last review.

src/windows-dev-config/steps/_registry.ps1:29

  • This check compares only the coerced value and ignores the registry value kind. PowerShell considers values such as REG_SZ "1" equal to integer 1, so a policy that requires REG_DWORD can be reported as already configured even though Windows ignores the wrong type. Include the expected type in this helper's contract and compare it with the key's GetValueKind; the Edge caller already carries a $Type value that should be passed through.
    src/windows-dev-config/steps/copilot.ps1:18
  • An empty, malformed, or drifted fragment file is treated as configured solely because the path exists, so reruns never repair the GitHub Copilot profile despite the flow's check/apply/verify contract. Parse the fragment and verify the expected GUID and profile fields; return false on parse failure so the apply step rewrites it.
    src/windows-dev-config/steps/fonts.ps1:26
  • This check accepts any files with the two expected names and matching registry entries, without validating their contents. An older or corrupt same-named font therefore skips installation forever, and changing the pinned version/hash later will not upgrade existing machines. Verify each installed file against the expected per-font hash (or a trustworthy installed-version marker) before returning true.
    src/windows-dev-config/README.md:252
  • The WSL steps are both marked BestEffort, so this exception is caught and reported as a flag; the orchestrator then prints the normal completion summary. The run does not stop as stated here. Describe the flagged completion behavior so users know to inspect the summary and rerun after fixing virtualization or connectivity.
    src/windows-dev-config/README.md:377
  • The Terminal settings steps are BestEffort, so invalid JSON flags those steps and allows all remaining phases to continue; it does not stop the setup. This troubleshooting text still contradicts the runner behavior and the corrected warning earlier in this README.
  • Files reviewed: 32/33 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment thread src/windows-dev-config/bootstrap.ps1
Comment thread README.md Outdated
Michael Von Hippel (Kixantrix) pushed a commit to Kixantrix/WindowsDeveloperConfig that referenced this pull request Sep 9, 2026
Replace AI DSC acquisition with PR microsoft#93-style PowerShell setup, add AMD ROCm and Intel AI flows, centralize provider promotion metadata, and emit portable hardware acceptance reports.

Co-authored-by: Copilot App <[email protected]>
Comment thread src/windows-dev-config/steps/registry-explorer.ps1 Outdated
Comment thread src/windows-dev-config/steps/registry-explorer.ps1 Outdated
Comment thread src/windows-dev-config/steps/registry-explorer.ps1 Outdated
Comment thread src/windows-dev-config/steps/registry-taskbar-search.ps1 Outdated

@ranm-msft ranm-msft left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pulled the three new commits and traced the launch paths. The post-copy Assert-CalmOsMicrosoftSigned plus AllSigned on every relaunch does close the script-replacement path the bot flagged, since the engine revalidates each file at load time inside the elevated process. Two things I'd want settled before this is the documented path:

1. The only currently working Quick start requires persistent policy changes. It sends users to Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass in both 5.1 and 7, and as the README notes, CurrentUser survives reboot. Previously the bypass was process-scoped and died with the run. Since -AllowUnsigned is already an explicit opt-in, could that path propagate a process-scoped -ExecutionPolicy Bypass through launch/elevation/pwsh/resume instead, so the weakening stays inside the setup flow rather than saved to the user's profile?

2. AllSigned doesn't trust a Microsoft signature by itself. It matches the signer against CurrentUser\TrustedPublisher (LocalMachine isn't consulted), and the Microsoft code-signing cert isn't there by default. I checked a Status=Valid, CN=Microsoft Corporation-signed script with MOTW cleared under -ExecutionPolicy AllSigned: it stops at the publisher prompt, and the default answer is "do not run" (exit 1, UnauthorizedAccess). "Always run" resolves it persistently; "Run once" doesn't carry into the elevated or pwsh relaunch.

Have you walked the signed path end-to-end on a machine that doesn't already trust the publisher, particularly the reboot resume?

Assert-CalmOsMicrosoftSigned -Directory $setupDir
}

New-Item -ItemType Directory -Path $InstallRoot -Force | Out-Null

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this location secured to admin only writes?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No—it remains user-writable. In signed mode, Microsoft-signature checks are point-in-time, and AllSigned accepts other trusted publishers too. Is that acceptable for this PR, with admin-only storage handled as follow-up hardening?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Answering only for my folded-in duplicate - John's thread is his call.

Yes, acceptable for this PR. The precondition is same-user code execution, and medium-to-high IL via UAC isn't a serviced security boundary under MSRC's Windows servicing criteria, so admin-only storage is hardening rather than a fix for a boundary that's currently defended.

What makes it sit right is that the Security section already states it plainly - "Verification does not prevent later file replacement in the writable install directory." That's the correct disclosure rather than overclaiming the signature check. Worth a tracked follow-up issue so it doesn't get lost after merge.

@ranm-msft ranm-msft left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On John's question at bootstrap.ps1:178 - pulled head (bbe05eb) to check.

No, %LOCALAPPDATA%\CalmOS is not admin-only. It stays writable by the setup user, so the directory ACL is not the control here.

The part I would push on is the follow-up, because AllSigned and Assert-CalmOsMicrosoftSigned do not check the same predicate. AllSigned does revalidate each .ps1 as PowerShell loads it, so a plain unsigned swap is blocked. But it accepts anything signed by a publisher the user trusts, not specifically Microsoft. In the split-token case the unelevated and elevated processes are the same account and share per-user publisher trust, and the medium-integrity side can add its own code-signing certificate without elevation. So a same-user process can trust its own cert, rewrite the copied payload between the post-copy Assert-CalmOsMicrosoftSigned and the Start-Process -Verb RunAs, and the elevated relaunch loads it: passes AllSigned, would have failed the Microsoft check.

With -AllowUnsigned there is no load-time requirement at all, which matters more than "development only" implies while it is the switch the quick start uses.

Would it work to do the copy, or at least the Microsoft-signature revalidation, inside the elevated process and into somewhere medium integrity cannot rewrite, before anything is dot-sourced?

@ranm-msft ranm-msft left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The three key-path corrections here are real bugs fixed - CabinetState\FullPath, Explorer\ShowFrequent, and Advanced\TaskbarDeveloperSettings\TaskbarEndTask are the documented locations, and the previous paths meant those three tweaks silently did nothing.

One thing that makes this class of bug hard to see, having now read _registry.ps1: Set-DevConfigRegistryValue creates the key when it is missing (New-Item -Path $psPath -Force), and Test-DevConfigRegistryValue reads back the same $KeyPath it was handed. So a mistyped path does not fail - it gets created, written, and then validates successfully. The in-run check can confirm the write happened, but by construction it cannot tell you whether the tweak table names the location Windows actually reads.

That makes the three new assertions in cli-checks.ps1 the only independent check, since their expected paths are hardcoded rather than sourced from the tweak table. They cover 3 of ~24 values today. Are the others validated independently somewhere, or were these three added specifically as regressions for the bugs above? A table-driven mapping test feels like reasonable follow-up rather than something to grow this PR with.

Also: was dropping NavPaneShowVersionControl intentional? It reads as removed rather than relocated, unlike the other three.

Separately - I do not see changes for the three earlier threads (1, 2, 3). Which of those are you planning to handle in this PR versus track as follow-up?

@ranm-msft ranm-msft left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-pulled at 538570c7 and traced the two new commits. The registry fixes check out, and confirming NavPaneShowVersionControl was dropped along with the Git-integration step answers my question there. Removing Set-ExecutionPolicy from the quick start also resolves my persistent-policy thread - the in-memory script block runs without touching the user's policy at all.

One consequence of that fix looks still open. bootstrap.ps1 only sets a policy on the signed path:

$arguments = @('-NoProfile')
if (-not $AllowUnsigned) {
    $arguments += '-ExecutionPolicy', 'AllSigned'
}
...
$arguments += '-File', "$target"

-ExecutionPolicy here is process-scoped, so the signed path is self-sufficient whatever the machine policy is. The -AllowUnsigned path passes none, so the child -File launch inherits the ambient policy. Unblock-File clears MOTW, which covers RemoteSigned, but a stock Windows 11 client is Restricted, where no .ps1 loads regardless of signature. That is the exact command the README quick start hands to someone on a fresh box.

Is -AllowUnsigned intended as the contributor path, with the signed one-liner being what the quick start should lead with?

Separately, Invoke-DevConfigNativeCommand is unchanged at head, so my timeout thread is still live - leaving it unresolved rather than restating it here.

@AmelBawa-msft
AmirMS (AmelBawa-msft) merged commit f0a61ad into main Sep 16, 2026
12 checks passed
@AmelBawa-msft
AmirMS (AmelBawa-msft) deleted the user/amelbawa/dev-config-2 branch September 16, 2026 18:20
Michael Von Hippel (Kixantrix) pushed a commit to Kixantrix/WindowsDeveloperConfig that referenced this pull request Sep 17, 2026
Replace AI DSC acquisition with PR microsoft#93-style PowerShell setup, add AMD ROCm and Intel AI flows, centralize provider promotion metadata, and emit portable hardware acceptance reports.

Co-authored-by: Copilot App <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants