Skip to content

About

Hotkey toggle for the Windows main display, refresh rate, sound output, and microphone, organised into profiles - with a no-CLI settings GUI. Pure PowerShell, no dependencies.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

setup-switcher

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.


What it does

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.


Settings window (easiest — no command line)

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 couch profile 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).


Quick start (command line)

# 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.ps1

Configuring it

Everything 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

Refresh-rate safety net

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 reference

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).


Binding it to a key / button

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 / -Taskbar walks 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.

Taskbar-pinnable .exe launchers

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.ps1

This 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-run Build-Exe.ps1 any 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.zip

The 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.


System tray

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.


How it works (internals)

  • 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, if ConfirmRefreshSeconds is 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 legacy ChangeDisplaySettingsEx API is kept only as a fallback for making a display primary. (SDC_VALIDATE is 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 IMMDeviceEnumerator and set with the undocumented IPolicyConfig COM 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 labelled Endpoint (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.

Troubleshooting

  • "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 -List and confirm the target is listed as active. Check setup-switcher.log for 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 ConfirmRefreshSeconds so 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.log next to the script.

Development

# run the test suite (static parse + unit + integration self-tests)
powershell -NoProfile -ExecutionPolicy Bypass -File tests\Run-Tests.ps1

The 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.


Files

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).

About

Hotkey toggle for the Windows main display, refresh rate, sound output, and microphone, organised into profiles - with a no-CLI settings GUI. Pure PowerShell, no dependencies.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages