Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -427,3 +427,7 @@ FodyWeavers.xsd
*.msix
*.msm
*.msp

# Windows Dev Config run artifacts, written next to dev-config.ps1 when the
# flow is run from a checkout (*.log above already covers devconfig-log.txt).
devconfig-tally.json
42 changes: 17 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,19 @@

---

Go from a fresh Windows install to a fully configured dev box in one command. These declarative, CI-tested configs set up your tools, settings, and shells the same way every time — so any machine can be your machine in minutes.
Go from a fresh Windows install to a fully configured dev box in one command. These CI-tested setups install your tools, settings, and shells the same way every time — so any machine can be your machine in minutes.

## 🎯 Pick your setup

Three developer setups live in this repo. Pick the one that matches what you want:

| You want... | Go to |
| --- | --- |
| A complete dev workstation: tools, OS settings, WSL, and terminal. One command, may reboot. | [Windows Dev Config](#%EF%B8%8F-windows-dev-config) |
| A complete dev workstation: tools, OS settings, WSL, and terminal. One command, restarts once. | [Windows Dev Config](#%EF%B8%8F-windows-dev-config) |
| A polished WSL shell: zsh/bash, Starship, CLI tools, and a themed terminal profile. Interactive or unattended. | [WSL Comfort](#-wsl-comfort) |
| A single language toolchain: Node, Python, SQL, PowerShell, .NET, Rust, Go, Java, PHP, WinForms, or WinUI 3. One command each. | [Workloads](#-single-language-workloads) |

Most of them use [`winget configure`](https://learn.microsoft.com/en-us/windows/package-manager/winget/configure). If you've never used it before, enable it once:
Most of the single-language workloads use [`winget configure`](https://learn.microsoft.com/en-us/windows/package-manager/winget/configure). If you've never used it before, enable it once:

```powershell
winget configure --enable
Expand All @@ -49,48 +49,40 @@ winget configure --enable
> winget install Microsoft.VCRedist.2015+.arm64
> ```

If that fails or `winget configure` is still not recognized, see [Troubleshooting](#-troubleshooting).
If that fails or `winget configure` is still not recognized, see [Troubleshooting](#-troubleshooting). Windows Dev Config doesn't use `winget configure` and needs none of this.

<br/>

## 🖥️ Windows Dev Config

*Turns a fresh Windows 11 box into a clean, distraction-free dev workstation in one shot.*

A single [winget configuration](https://learn.microsoft.com/en-us/windows/package-manager/configuration/) file that installs dev tools, applies opinionated Windows settings, and bootstraps WSL + Ubuntu through the required reboot. Non-interactive. Idempotent. Safe to re-run on an existing machine.
A set of PowerShell scripts that installs dev tools, applies opinionated Windows settings, and sets up WSL + Ubuntu through the required reboot. Nothing to clone, nothing to install first. Idempotent, so it's safe to re-run on an existing machine.

First, get the files onto the box. The config is invoked from a local path, but the bootstrap itself is what installs Git — so on a clean Windows install you'll typically download the repo as a ZIP. If Git is already there, clone it:
Open any PowerShell window — elevated or not — and run:

```powershell
# Git already installed:
git clone https://github.com/microsoft/WindowsDeveloperConfig.git
cd WindowsDeveloperConfig

# Otherwise, download and extract the ZIP:
Invoke-WebRequest -Uri https://github.com/microsoft/WindowsDeveloperConfig/archive/refs/heads/main.zip -OutFile WindowsDeveloperConfig.zip
Expand-Archive .\WindowsDeveloperConfig.zip -DestinationPath .
cd .\WindowsDeveloperConfig-main
$url = 'https://raw.githubusercontent.com/microsoft/WindowsDeveloperConfig/main/src/windows-dev-config/bootstrap.ps1'
& ([scriptblock]::Create((irm $url))) -AllowUnsigned
```

Then apply the configuration:
You'll get one UAC prompt. Expect about 30 minutes on a clean machine.

```powershell
winget configure -f .\windows-dev-config\dev-config.winget --accept-configuration-agreements --disable-interactivity
```
> `-AllowUnsigned` runs the source copy under `src/` instead of the signed copy at the repository root.

> ⚠️ **May reboot.** Enabling WSL needs a Windows optional feature that requires a restart. A `RunOnce` task picks the configuration back up after you sign in, installs Ubuntu, and finishes the run. Expect one hard reboot plus about a minute of post-login work. Save your work first.
> ⚠️ **It will restart your machine, once.** Enabling WSL needs a Windows optional feature that requires a restart. You get a 10-second warning, and a scheduled task finishes the run automatically after you sign back in. **Save your work before you start.**

<details>
<summary><strong>What you get</strong></summary>

- **Dev tools:** PowerShell 7, Git, GitHub CLI, VS Code, .NET SDK 10, Python 3.14 + uv, Node.js, Coreutils for Windows, Oh My Posh, and PowerToys.
- **Terminal:** PowerShell 7 is the default profile, Oh My Posh is enabled, and Cascadia Mono NF is set as the default font.
- **Windows settings:** Dark theme, developer mode, long paths, File Explorer defaults, Start/Search cleanup, Edge policies, and other workstation defaults.
- **WSL:** WSL platform + Ubuntu, including the reboot and the `RunOnce` resume step.
- **Dev tools:** Windows Terminal, PowerShell 7, Git, GitHub CLI, GitHub Copilot CLI, VS Code, .NET SDK 10, Python 3.14 + uv, Node.js LTS + nvm, Coreutils for Windows, Windows App CLI, Oh My Posh, and PowerToys.
- **Terminal:** PowerShell 7 as the default profile, Oh My Posh in your prompt, Cascadia Mono NF as the default font, and a GitHub Copilot profile in the dropdown.
- **Windows settings:** Dark theme, Developer Mode, Sudo, long paths, File Explorer defaults, Start/Search cleanup, Do Not Disturb, widgets off, and Edge policies.
- **WSL:** WSL platform + Ubuntu, including the restart and the automatic resume afterwards.

</details>

Full details: [`windows-dev-config/README.md`](./windows-dev-config/README.md).
Full details — every setting it changes, how to undo them, and troubleshooting: [`windows-dev-config/README.md`](./src/windows-dev-config/README.md).

<br/>

Expand Down Expand Up @@ -195,7 +187,7 @@ Open a new terminal, or run the matching `install.ps1` shim to refresh PATH in t
<details>
<summary><strong>Windows Dev Config rebooted the machine and looks stuck</strong></summary>

It registered a `RunOnce` entry, so `winget configure` resumes once you sign back in. Give it a minute after login.
It registered a scheduled task named `WindowsDevConfigResume`, so the run picks itself back up about 30 seconds after you sign back in. A window opens on its own and finishes the WSL setup. If nothing appears after a couple of minutes, run the one-liner again — it's safe to re-run and skips everything already done. More detail in [`windows-dev-config/README.md`](./src/windows-dev-config/README.md#troubleshooting).

</details>

Expand Down
51 changes: 30 additions & 21 deletions src/docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,41 @@
> CI / "how the sausage gets made" guide.

Opinionated, CI-validated configurations for bootstrapping developer
toolchains and Windows-desktop personalities using `winget` /
`winget configure`.

On Windows the **core artifact of each flow is a [winget DSC configuration
file](https://learn.microsoft.com/windows/package-manager/configuration/)**
(`configuration.winget` for language toolchains, `dev-config.winget` for the
Calm OS flow) — a declarative, idempotent description of the machine state
required for that flow. Where winget alone is not enough (e.g. `npm install
--global typescript`, registry tweaks, or a `RunOnce` reboot dance) the
toolchains and Windows-desktop personalities.

Most flows are built around a [winget DSC configuration
file](https://learn.microsoft.com/windows/package-manager/configuration/)
(`configuration.winget`) — a declarative, idempotent description of the
machine state required for that flow. Where winget alone is not enough
(e.g. `npm install --global typescript` or a registry tweak) the
configuration calls a DSC `Script` / `RunCommandOnSet` / `Registry`
resource, so everything the flow needs lives in one YAML file. A small
`install.ps1` shim next to it applies the config with `winget configure`
and handles session-level glue (PATH refresh, CI sentinel).

Every flow is **exercised on a real GitHub-hosted runner** on every push, pull
request, and nightly: the DSC config is applied, then a canonical "hello
Two flows are **PowerShell-native** instead: Calm OS
(`src/windows-dev-config/`) and Comfort Shell (`src/wsl-comfort/`). They
need work a configuration file can't express — elevation, a reboot with an
automatic resume, an interactive progress display — so they ship as
PowerShell scripts with no configuration file at all. They keep the same
idempotency contract: every step checks current state, acts only when
needed, and verifies the result.

Every automated flow is **exercised on a real GitHub-hosted runner** on every
push, pull request, and nightly: the flow is applied, then a canonical "hello
world" is built and executed, and its stdout is diffed against a checked-in
expected output. If a flow's hello world prints the right thing, we know the
configuration actually produced a working toolchain.

## Supported flows

Each flow's `configuration.winget` (or `dev-config.winget` for Calm OS)
is the source of truth for what gets installed; the table below
summarizes it for quick scanning. Flows marked **manual** are excluded
from the automated CI matrix (they need an interactive desktop session
or pull multi-GB workloads we don't want to chew minutes on), but are
still verified end-to-end on demand and surfaced in the Command Palette
extension.
Each flow's `configuration.winget` — or, for the two PowerShell-native
flows, its entry script — is the source of truth for what gets installed;
the table below summarizes it for quick scanning. Flows marked **manual**
are excluded from the automated CI matrix (they need an interactive
desktop session or pull multi-GB workloads we don't want to chew minutes
on), but are still verified end-to-end on demand and surfaced in the
Command Palette extension.

| Flow | CI status | Installs |
| ----------------- | ------------- | --------------------------------------------------------------------------------------- |
Expand All @@ -48,7 +54,7 @@ extension.
| PowerShell | ✅ automated | `Microsoft.PowerShell`, `Microsoft.VisualStudioCode`, VS Code PowerShell/Pester extensions + PSScriptAnalyzer settings |
| WinForms | 🙋 manual | `Microsoft.DotNet.SDK.10` + the .NET desktop workload (multi-GB; manual to spare CI minutes) |
| WinUI 3 | 🙋 manual | `Microsoft.DotNet.SDK.10`, `Microsoft.VisualStudio.Community`, `Microsoft.WinAppCLI` + WinUI/Universal/ManagedDesktop VS workloads |
| Calm OS | 🙋 manual | A full distraction-free workstation: apps + ~24 registry tweaks + WSL + Ubuntu (see [`windows-dev-config/README.md`](../windows-dev-config/README.md)) |
| Calm OS | 🙋 manual | A full distraction-free workstation, in PowerShell: 15 apps + 25 registry values + fonts + Windows Terminal + WSL + Ubuntu (see [`windows-dev-config/README.md`](../windows-dev-config/README.md)) |
| Comfort Shell | 🙋 manual | WSL distro + zsh/bash + starship + modern CLI bundle + Cascadia Code Nerd Font + themed Windows Terminal profile (see [`wsl-comfort/readme.md`](../wsl-comfort/readme.md)) |

See [`manifest.yml`](../manifest.yml) for the canonical declarative
Expand Down Expand Up @@ -80,7 +86,7 @@ Workloads/
rust/ # configuration.winget (core) + install.ps1 (thin shim)
winforms/ # configuration.winget (core) + install.ps1 (thin shim)
winui/ # configuration.winget (core) + install.ps1 (thin shim)
windows-dev-config/ # Calm OS — dev-config.winget (single-file DSC) + install.ps1 + README.md
windows-dev-config/ # Calm OS — bootstrap.ps1 (remote entry) + dev-config.ps1 (orchestrator) + steps/*.ps1 + README.md
wsl-comfort/ # Comfort Shell — install.ps1 (Windows side) + comfort-shell-bootstrap.sh (Linux side, self-contained) + readme.md
tests/
_harness/ # build-run-diff harness used by CI:
Expand Down Expand Up @@ -122,16 +128,19 @@ This repo carries **two parallel copies** of every flow:
| `src/docs/development.md` | Contributor docs (CI, validation, how to add a language). | **Yes** | n/a |
| `src/tests/` | Hello-world programs + expected stdout used by the CI harness. | **Yes** | CI only |

**End users**: the commands in the top-level [README](../../README.md) point at the **top-level signed copies** on purpose. If you're following the README on a Windows box you don't need to know `src/` exists. Every `winget configure -f .\windows-dev-config\dev-config.winget`-style invocation in the README is correct as written.
**End users**: the commands in the top-level [README](../../README.md) point at the **top-level signed copies** wherever those copies exist, so on a Windows box you don't need to know `src/` exists. The one exception is Calm OS: its `bootstrap.ps1` is new and hasn't been through a sign cycle yet, so the README's one-liner points at `src/windows-dev-config/bootstrap.ps1`. That's deliberate — the bootstrap requires the *signed* payload from the repository root by default, verifies every payload `.ps1` has a valid Microsoft Corporation Authenticode signature, and stops before installation if any check fails. Contributors can explicitly select `src/windows-dev-config/` and bypass signature validation with `-AllowUnsigned` while testing a ref before its signed copy exists. Repoint the README at the top-level copy once it lands.

**Contributors**: edit `src/`. The top-level paths are **regenerated** by [`.pipelines/OneBranch.SignAndPackage.yml`](../../.pipelines/OneBranch.SignAndPackage.yml), which Authenticode-signs every `src/**/*.ps1` and ships them (plus the `.winget` configs and the manifest) as the release artifact. The signed copies were merged into `main` from the `signed` branch in [PR #6](https://github.com/microsoft/WindowsDeveloperConfig/pull/6). A change to a `src/` script becomes a new signed top-level copy on the next sign cycle, not at PR merge, so the two can briefly disagree on a script's body until that cycle runs.

**Deleting a file is the one case where you must touch both trees.** The sign pipeline only adds and overwrites — it never deletes. A file removed from `src/` therefore stays at the top level forever, still published and still runnable, until someone removes it by hand. So when you delete or rename a flow artifact, `git rm` it from **both** `src/…` and the matching top-level path in the same PR. (The drift guard won't catch this for you: a file that exists in neither tree produces no report entry at all.)

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.

This PR only changes src/. The top-level copies are generated by the sign pipeline and aren't hand-edited, so removing them isn't part of this change.

You're right that the pipeline won't delete them on its own — that cleanup needs to happen through whatever updates the signed tree.


**CI**: GitHub Actions ([`.github/workflows/ci.yml`](../../.github/workflows/ci.yml)) runs the **unsigned `src/` copies** (e.g. `./src/Workloads/_common/preflight.ps1`). This is intentional: CI exercises what contributors edit; signing is a release-time concern, not a build-time one.

**Don't**:

- Don't edit a top-level signed copy directly. The next sign cycle will overwrite it, and the cycle signs `src/`, not the top level.
- Don't expect the two trees to be byte-identical. The signed copies carry an Authenticode signature block (`# SIG # Begin signature block` … `# SIG # End signature block`); the bodies above that marker should match what's in `src/`. They will diverge for the window between a `src/` change landing on `main` and the next sign cycle catching up.
- Don't delete from `src/` only. See above — removals are the one change the pipeline can't propagate.
- Don't add a third copy of anything. Both copies exist for one reason only (to ship signed PS1s without losing the unsigned source), and any new flow or shared script lives only in `src/` until the sign pipeline mirrors it.

### Signed-copy drift guard
Expand Down
6 changes: 6 additions & 0 deletions src/future/cmdpal/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ If `windows.configuration` is omitted in `manifest.yml`, the extension falls
back to `<dir of windows.install>/configuration.winget` — i.e. the
WindowsDevSetupScripts convention.

> **Known gap.** Two flows are PowerShell-native and have no configuration
> file at all: Calm OS (`calm-os`) and Comfort Shell (`comfort-shell`). The
> fallback above resolves them to a path that doesn't exist, so the extension
> can't launch them today. Before this extension ships, teach it to run
> `windows.install` directly when `windows.configuration` is absent.

## Confirmation dialog

`winget configure` against a real DSC config can install packages, change
Expand Down
19 changes: 11 additions & 8 deletions src/manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@
# configuration: (optional) path to winget DSC configuration.winget the
# extension applies via `winget configure`. Defaults to
# "<dir of install>/configuration.winget" when omitted.
# PowerShell-native flows (calm-os, comfort-shell) have no
# DSC document and omit this key; the extension needs a
# script-launch path before it can offer them.
# build: shell command to build the hello world (run from repo
# root). "" to skip.
# run: shell command whose stdout is compared to "expected"
Expand Down Expand Up @@ -279,21 +282,21 @@ flows:

- id: calm-os
name: Calm OS
description: Distraction-free dev workstation — apps + OS settings + WSL, all in one DSC
description: Distraction-free dev workstation — apps + OS settings + WSL, in one PowerShell run
category: user-experience
tags: [user-experience, calm-os, distraction-free, taskbar, wsl, ubuntu]
icon: 🧘
onboardingUrl: https://dev.windows.com
# Heavy machine-state changes (Sudo, Recall off, Click To Do off, WSL +
# Ubuntu install with a forced reboot, etc.) — keep out of the automated
# matrix. The probe under src/tests/calm-os/probe.ps1 is for a human running
# the flow locally; it asserts that `git` resolves on PATH after the
# install (the apps module's first dep) as a fast smoke signal.
# PowerShell-native flow: there is no configuration.winget, so the
# `configuration` key is omitted (same shape as comfort-shell).
# Heavy machine-state changes (Sudo, Developer Mode, WSL + Ubuntu with a
# forced reboot) keep it out of the automated matrix. The probe under
# src/tests/calm-os/probe.ps1 is for a human running the flow locally; it
# asserts that `git` resolves on PATH afterwards as a fast smoke signal.
manual_test: true
os: [windows]
windows:
install: windows-dev-config/install.ps1
configuration: windows-dev-config/dev-config.winget
install: windows-dev-config/dev-config.ps1
build: ""
run: pwsh -NoProfile -File src/tests/calm-os/probe.ps1
expected: src/tests/calm-os/expected.txt
Expand Down
11 changes: 5 additions & 6 deletions src/tests/calm-os/probe.ps1
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# Smoke-test probe for the calm-os user-experience flow.
#
# After the master config has been applied, the apps module installs
# git via winget. The simplest signal a human can use to confirm the
# flow worked is: does `git --version` exit 0 after the run? If so,
# the apps module reached completion (git is the first dep in the
# chain). If not, something tripped during install and the user
# should look at the install transcript.
# After the flow has run, the packages phase has installed git via winget.
# The simplest signal a human can use to confirm the flow worked is: does
# `git --version` exit 0 afterwards? If so, the packages phase reached
# completion. If not, something tripped during install and the user should
# look at devconfig-log.txt next to dev-config.ps1.
#
# Output: `OK` if git is on PATH and `git --version` exits 0;
# throw otherwise (which the harness surfaces as a failure).
Expand Down
Loading