Drive one PC as two setups, from your chair. setup-switcher flips your whole Windows machine between using it as a normal desk PC — monitor, high refresh rate, headphones — and driving it on a TV in another room — big screen, TV speakers — with a single hotkey. Flip back with the same key.
The point is a seamless switch. The same PC serves both places, but moving between them normally means a trip through Windows Settings to change the main display, then the sound output, then the refresh rate (and maybe the mic). setup-switcher does all of that together in one keypress — or one click from the system tray — so you can throw a film up on the TV and drop back to your desk setup without getting out of your chair. Each profile bundles a main display, refresh rate, sound output, and microphone; e.g. desk (monitor + 360 Hz + headphones) ⇄ TV (LG TV + 60 Hz + TV speakers).
No third-party software and nothing to install. It's plain PowerShell (with a little inline C# for the Windows display/audio APIs) plus an optional point-and-click settings window.
For each profile you choose any combination of:
- Main display — sets the Windows "Make this my main display" primary. All your displays stay on; only the main flag (taskbar / Start / where new windows open) moves.
- Refresh rate (optional) — sets that display's Hz via the modern display API, which can drive high-refresh DSC panels (e.g. 1440p @ 360 Hz). Some DSC links occasionally come up black on a refresh change; the switch can show a "Keep this? (reverts in N s)" prompt and auto-revert to the working mode if it isn't confirmed, so a bad change never leaves the screen dead until reboot — see Refresh-rate safety net.
- Sound output — sets the default playback device (Console, Multimedia, Communications).
- Microphone (optional) — sets the default recording device.
Pressing the hotkey toggles: it looks at which profile's display is currently the
main one and switches to the other. It's stateless, so it can never get "stuck". With more
than two profiles you can cycle through them (-Cycle) or pick any profile from the
system-tray menu.
Double-click Configure.vbs to open a settings window. With no typing or CLI you can:
- Point each profile at any connected display, refresh rate, sound output, and microphone — dropdowns are populated from your actual hardware
- Add or remove profiles with the + / − buttons, and rename by
double-clicking a name in the list — e.g. a
couchprofile that uses a third screen - Snapshot current setup — one click captures the display, refresh rate, sound and mic active right now as a new profile (then just rename it)
- Choose which two profiles the hotkey flips between
- Change the keyboard shortcut (Ctrl/Alt/Shift + a key) and install it in one click
- Add a desktop icon in one click
- Turn the system-tray icon on or off with a single checkbox — on runs it now and starts it with Windows; off stops it and removes it from startup (see System tray)
- Switch now / Toggle now to test immediately, then Save
Refresh rate and microphone are optional per profile — leave either on "(leave unchanged)" and the toggle won't touch it. Devices that share a name (three "Microphone"s, two "Speakers") are shown with their adapter — e.g. Microphone (HyperX Cloud III Wireless) — and matched unambiguously.
Everything is written back to config.psd1 and the Start-Menu shortcut — the same files
the command line uses, so the two stay in sync. Configure.cmd does the same but keeps a
console window visible (handy if something goes wrong).
# see your devices + the exact names to use
powershell -NoProfile -ExecutionPolicy Bypass -File setup-switcher.ps1 -List
# preview a toggle without changing anything
powershell -NoProfile -ExecutionPolicy Bypass -File setup-switcher.ps1 -DryRun
# toggle for real
powershell -NoProfile -ExecutionPolicy Bypass -File setup-switcher.ps1
# install the hotkey (default Ctrl+Alt+T)
powershell -ExecutionPolicy Bypass -File Install-Shortcut.ps1Everything lives in config.psd1. Edit it in any text editor (or use the
settings window, which writes this file for you).
Profiles = @{
desk = @{ Display = 'AW2725DF'; Hz = 360; Audio = 'Headphones'; Mic = 'HyperX' }
tv = @{ Display = 'LG TV'; Hz = 60; Audio = '1 - LG TV' }
couch = @{ Display = 'LG TV'; Hz = 120; Audio = '1 - LG TV' }
}
Toggle = @('desk', 'tv') # the 2-way hotkey pair
Cycle = @('desk', 'tv', 'couch') # optional order for -Cycle (defaults to all profiles)
ConfirmRefreshSeconds = 15 # optional refresh-rate safety net (0/omit = off)Per-profile keys:
| Key | Required | Matches |
|---|---|---|
Display |
yes | monitor EDID name (AW2725DF, LG TV) or PnP id (DELA252, GSM0001) |
Audio |
yes | playback device name (Headphones, 1 - LG TV) |
Hz |
no | a refresh rate the display supports at its current resolution |
Mic |
no | recording device name (HyperX, Logi C270) |
Top-level keys:
| Key | Default | Effect |
|---|---|---|
Toggle |
required | the two profile names the hotkey flips between |
Cycle |
all profiles, in order | the ordered list -Cycle rotates through |
NotifyOnSuccess |
$false |
pop a tray notification after a successful switch |
ConfirmRefreshSeconds |
0 (off) |
seconds to show the refresh-rate safety-net prompt; 0 (or omitted) disables it |
Changing the refresh rate on a high-refresh DSC panel (e.g. 1440p @ 360 Hz) occasionally
fails to retrain the DisplayPort link and the screen comes up black — the same risk the
Windows Settings → Refresh rate dropdown guards with its "Keep changes?" countdown. Set
ConfirmRefreshSeconds to mirror that: after a rate change, a prompt appears on the changed
display and, if you don't confirm within that many seconds (because the screen is blank, you
can't), the tool automatically reverts to the previous working mode. The change is
session-only — setup-switcher never writes it to Windows' saved display configuration,
because Windows auto-replays a saved layout on the next hotplug/wake with no prompt (exactly how
a marginal DSC mode gets re-applied and blacks the panel out). Set it to 0 (or leave it out) to
switch silently once your panel/cable is reliable. The settings window has no editor for this key
but preserves it across a Save.
If a display ever does get stuck black after a switch, re-running the switch with -Force
re-drives the link (a normal switch skips a display already "at" the target rate); pressing
Win + Ctrl + Shift + B (restart the graphics driver) or re-plugging the DisplayPort cable
also recovers it without a reboot.
All text matches are case-insensitive substrings. Run -List to see every valid
string. If a value matches two devices the script tells you and asks for something more
specific; for genuinely same-named devices the settings window stores the unique device
id automatically. Add as many profiles as you like; Toggle picks which two the hotkey
flips between, and -Apply <name> jumps to any profile directly.
| Command | Effect |
|---|---|
setup-switcher.ps1 |
Toggle between the two Toggle profiles (default). |
setup-switcher.ps1 -Apply tv |
Force a specific profile. |
setup-switcher.ps1 -Cycle |
Rotate to the next profile in Cycle (or all profiles in order). |
setup-switcher.ps1 -List |
List displays (with available refresh rates), sound outputs, and mics. |
setup-switcher.ps1 -Status |
Show current main display, sound, mic, and active profile. |
setup-switcher.ps1 -DryRun |
Resolve targets and print the plan; change nothing. |
setup-switcher.ps1 -Apply desk -Force |
Re-apply a profile even if the display looks "already at" the target rate — re-drives (recovers) a display stuck black after a refresh change. |
setup-switcher.ps1 -CheckAudio |
Non-destructive self-test: proves sound + mic switching work here. |
setup-switcher.ps1 -CheckDisplay |
Non-destructive self-test: shows what the display API resolves. |
Launchers: setup-switcher.cmd (visible, for reading output) and
setup-switcher.vbs (silent — what the hotkey uses).
Install-Shortcut.ps1 creates a Start-Menu shortcut to the silent launcher with a global
Shortcut key:
powershell -ExecutionPolicy Bypass -File Install-Shortcut.ps1 # Ctrl+Alt+T
powershell -ExecutionPolicy Bypass -File Install-Shortcut.ps1 -HotKey 'Ctrl+Alt+D' -Desktop
powershell -ExecutionPolicy Bypass -File Install-Shortcut.ps1 -Taskbar # pin (guided on Win11)
powershell -ExecutionPolicy Bypass -File Install-Shortcut.ps1 -Extras # add "jump to desk/tv"Other triggers:
- Taskbar + Win+number — pin the shortcut; its position from the left is
Win+1,Win+2, … (Windows 11 no longer lets apps pin silently, so the settings window /-Taskbarwalks you through the one right-click.) - Stream Deck / mouse macro / gamepad — point it at
wscript.exe "<path>\setup-switcher.vbs".
Windows only registers a
.lnk"Shortcut key" globally when the shortcut is in the Start Menu or on the Desktop, and there can be a short delay the first time.
Windows won't reliably pin a .lnk, .vbs, or PowerShell script to the taskbar, but it pins a
real .exe with no fuss. Build three tiny (~6 KB) launchers:
powershell -ExecutionPolicy Bypass -File Build-Exe.ps1This produces, next to the scripts:
| File | What it does |
|---|---|
Setup Switcher.exe |
One-click toggle (desk ⇄ tv) — pin this to the taskbar |
Setup Switcher Settings.exe |
Opens the settings window |
Setup Switcher Tray.exe |
Runs the system-tray icon (menu of every profile) |
Each .exe just starts the matching .ps1 hidden (no console flash) and carries the app
icon (setup-switcher.ico). Keep the .exe files in this folder — they resolve the scripts
next to themselves. To pin: right-click Setup Switcher.exe → Pin to taskbar (or drag it
onto the taskbar); its position from the left also becomes Win+1, Win+2, …
The
.exes are compiled with the C# compiler that ships with Windows (nothing to install). They're unsigned, so the first run may show a SmartScreen prompt (More info → Run anyway). Re-runBuild-Exe.ps1any time to rebuild them.
Handing it to someone else: a lone .exe won't run — it needs the .ps1 files beside it.
Bundle everything into a zip and attach that to a GitHub Release:
powershell -ExecutionPolicy Bypass -File Build-Exe.ps1 -Package # -> SetupSwitcher.zipThe zip ships a generic config.example.psd1 (not your personal config.psd1). The recipient
extracts the folder, copies config.example.psd1 to config.psd1 (or just opens the settings
window, which writes it for them), then pins Setup Switcher.exe.
setup-switcher-tray.ps1 — or Setup Switcher Tray.exe — puts an icon in the
notification area. Click it (left or right) for a menu of every profile — pick one to
switch to it — plus Toggle, Cycle, Settings…, Refresh, and a Start with
Windows toggle that adds/removes a login shortcut. A checkmark shows the active profile and
updates whenever the display changes. It's the easiest way to use more than two profiles
without memorising extra hotkeys.
Not everyone wants something running all the time, so the tray is opt-in: the settings window has a "Show a tray icon" checkbox that reflects whether it's on right now. Ticking it starts the tray immediately and every login; unticking it stops the running tray (cleanly, via a named stop signal) and removes the login shortcut. The checkbox and the tray's own Start with Windows menu item share the same login-shortcut, so the two always agree.
- Display + refresh — driven by the modern CCD API (
QueryDisplayConfig/SetDisplayConfig), the same driver-cooperative path the Windows Settings display page uses. Making a display primary shifts every source position so the target sits at (0,0). Changing Hz first checks the rate is one the driver actually enumerates for the current resolution, then applies it transiently — never written to Windows' display database — and, ifConfirmRefreshSecondsis set, waits for you to confirm the display came up, reverting automatically otherwise (the safety net for DSC links that come up black). Not saving is deliberate: Windows auto-replays a saved arrangement on the next hotplug/GPU-reset with no confirmation — precisely how a marginal DSC panel gets driven black. The legacyChangeDisplaySettingsExAPI is kept only as a fallback for making a display primary. (SDC_VALIDATEis deliberately not used to pre-check the rate: with an unresolved target mode it rubber-stamps any value — even 999 Hz — so the enumerated-rate list is the authoritative check instead.) - Sound + microphone — the default endpoint is read with
IMMDeviceEnumeratorand set with the undocumentedIPolicyConfigCOM interface. Windows ships two flavours of it on two coclasses; the tool tries the Win7 one, then the Vista one (IPolicyConfigVista), because newer Windows 11 builds only implement the latter. Devices are enumerated from the registry (active endpoints only) and labelledEndpoint (Adapter)so same-named devices stay distinguishable; matching also accepts a device GUID as an exact escape hatch. - Everything is inline C# inside
setup-switcher.ps1— no compiled binaries, no installed modules. The settings window is WPF built at runtime from PowerShell.
- "No connected display matches 'LG TV'" — the TV is off or its input is disconnected, so Windows dropped it. Turn it on / select its input and try again.
- Sound or mic didn't switch — run
-CheckAudio; it proves the switch path works here without changing anything. Then run-Listand confirm the target is listed as active. Checksetup-switcher.logfor the exact error. - A refresh rate didn't apply — it must be one the display supports at its current
resolution. The settings window only offers valid rates; from the CLI, check the rates
shown by
-List. The switch also refuses a rate the driver doesn't enumerate (it logs "…Hz unsupported…" and leaves the display untouched). - The screen went black after a switch (high-refresh DSC panel) — a DSC DisplayPort link
can fail to retrain on a refresh change. Set
ConfirmRefreshSecondsso the switch auto-reverts when the display doesn't come up (see Refresh-rate safety net). To recover a stuck display now: re-run with-Force, press Win + Ctrl + Shift + B, or re-plug the DisplayPort cable. If it also happens from the Windows Settings refresh dropdown, it's the cable/GPU-driver/panel (try a certified DP cable, the other DP port, or a driver update). - Hotkey does nothing — make sure the shortcut is in the Start Menu (the installer puts it there), give Windows a few seconds, or sign out/in once.
- Log — every run appends to
setup-switcher.lognext to the script.
# run the test suite (static parse + unit + integration self-tests)
powershell -NoProfile -ExecutionPolicy Bypass -File tests\Run-Tests.ps1The engine and installer early-return when dot-sourced, so the unit tests load their real
functions (device matching, cycle rotation, hotkey parsing) without touching hardware. The
integration tests drive the built-in -Validate / -Export self-tests of the settings
window, the tray app and the engine.
| File | Purpose |
|---|---|
setup-switcher.ps1 |
The engine — all logic + inline C#. |
config.psd1 |
Your editable profiles + toggle / cycle settings. |
config.example.psd1 |
A generic, commented starter config (shipped in the release zip). |
setup-switcher-gui.ps1 |
The settings window (WPF; no dependencies). |
setup-switcher-tray.ps1 |
The system-tray app (WinForms; menu of every profile). |
Configure.vbs / Configure.cmd |
Open the settings window. |
setup-switcher.vbs / .cmd |
Silent / visible launchers. |
Install-Shortcut.ps1 |
Creates the hotkey / desktop / taskbar shortcut(s). |
Build-Exe.ps1 |
Builds the taskbar-pinnable .exe launchers. |
setup-switcher.ico / New-Icon.ps1 |
The app icon and the script that generates it. |
tests/Run-Tests.ps1 |
Dependency-free test suite. |
setup-switcher.log |
Auto-created run log (gitignored). |