Skip to content

Instantly share code, notes, and snippets.

@edwinclement08
Created December 26, 2025 11:28
Show Gist options
  • Select an option

  • Save edwinclement08/b0a8af0449a8441a43f3ef8620e5b290 to your computer and use it in GitHub Desktop.

Select an option

Save edwinclement08/b0a8af0449a8441a43f3ef8620e5b290 to your computer and use it in GitHub Desktop.
Swapping Monitor horizontal orientation for Win11(2 side by side monitors usecase).
param(
[switch]$DryRun,
[switch]$UseUnicode,
[switch]$Apply
)
# Usage:
# powershell -NoProfile -ExecutionPolicy Bypass -File "monitor_swap.ps1" -Apply
# Two Add-Type signatures: ANSI (original) and Unicode (alternate) — use -UseUnicode to pick the Unicode one for testing.
$SignatureAnsi = @"
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DEVMODE {
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string dmDeviceName;
public short dmSpecVersion;
public short dmDriverVersion;
public short dmSize;
public short dmDriverExtra;
public int dmFields;
public int dmPositionX;
public int dmPositionY;
public int dmDisplayOrientation;
public int dmDisplayFixedOutput;
public short dmColor;
public short dmDuplex;
public short dmYResolution;
public short dmTTOption;
public short dmCollate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string dmFormName;
public short dmLogPixels;
public short dmBitsPerPel;
public int dmPelsWidth;
public int dmPelsHeight;
public int dmDisplayFlags;
public int dmDisplayFrequency;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DISPLAY_DEVICE {
public int cb;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string DeviceName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string DeviceString;
public int StateFlags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string DeviceID;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string DeviceKey;
}
public class User32 {
[DllImport("user32.dll")]
public static extern bool EnumDisplayDevices(string lpDevice, int iDevNum, ref DISPLAY_DEVICE lpDisplayDevice, int dwFlags);
[DllImport("user32.dll")]
public static extern int EnumDisplaySettings(string lpszDeviceName, int iModeNum, ref DEVMODE lpDevMode);
[DllImport("user32.dll")]
public static extern int ChangeDisplaySettingsEx(string lpszDeviceName, ref DEVMODE lpDevMode, IntPtr hwnd, int dwFlags, IntPtr lParam);
public const int ENUM_CURRENT_SETTINGS = -1;
public const int CDS_UPDATEREGISTRY = 0x01;
public const int DM_POSITION = 0x20;
}
"@
$SignatureUnicode = $SignatureAnsi -replace 'CharSet = CharSet.Ansi','CharSet = CharSet.Unicode'
# select signature
$Signature = if ($UseUnicode) { $SignatureUnicode } else { $SignatureAnsi }
# add the P/Invoke types only if they are not already loaded
if (-not ('User32' -as [type])) {
try {
Add-Type -TypeDefinition $Signature -ErrorAction Stop
}
catch {
Write-Error "Failed to Add-Type User32: $_"
throw
}
}
# --- Monitor Re-arrangement Logic ---
# Diagnostic helper: test Marshal sizes and EnumDisplayDevices for indices 0..9
function Test-EnumDisplayDevices {
Write-Host "--- Diagnostics: Marshal.SizeOf and EnumDisplayDevices test ---"
$dd = New-Object DISPLAY_DEVICE
$dm = New-Object DEVMODE
$dd.cb = [System.Runtime.InteropServices.Marshal]::SizeOf($dd)
$dm.dmSize = [System.Runtime.InteropServices.Marshal]::SizeOf($dm)
Write-Host "DISPLAY_DEVICE.cb = $($dd.cb)"
Write-Host "DEVMODE.dmSize = $($dm.dmSize)"
for ($i=0; $i -lt 10; $i++) {
$d = New-Object DISPLAY_DEVICE
$d.cb = [System.Runtime.InteropServices.Marshal]::SizeOf($d)
$ok = [User32]::EnumDisplayDevices($null, $i, [ref]$d, 0)
if ($ok) {
Write-Host "$i -> OK Name='$($d.DeviceName)' StateFlags=0x$('{0:X}' -f $d.StateFlags)"
} else {
Write-Host "$i -> FALSE"
}
}
Write-Host "--- End diagnostics ---"
}
# Run diagnostics if requested
if ($DryRun) {
Test-EnumDisplayDevices
}
# 1. Get all active monitors
$monitors = New-Object System.Collections.Generic.List[PSObject]
for ($i=0; $i -lt 10; $i++) {
$device = New-Object DISPLAY_DEVICE
# Use full namespace here:
$device.cb = [System.Runtime.InteropServices.Marshal]::SizeOf($device)
if ([User32]::EnumDisplayDevices($null, $i, [ref]$device, 0)) {
if ($device.StateFlags -band 0x1) { # 0x1 = Attached to desktop
$mode = New-Object DEVMODE
$mode.dmSize = [System.Runtime.InteropServices.Marshal]::SizeOf($mode)
[User32]::EnumDisplaySettings($device.DeviceName, [User32]::ENUM_CURRENT_SETTINGS, [ref]$mode)
$monitors.Add([PSCustomObject]@{
Name = $device.DeviceName
X = $mode.dmPositionX
W = $mode.dmPelsWidth
Mode = $mode
})
}
}
}
# If P/Invoke found nothing, fall back to managed enumeration so the script can at least list screens
$EnumerationMethod = 'PInvoke'
# Try to obtain a DEVMODE for a given device name via EnumDisplaySettings.
function Try-GetDevMode {
param([string]$DeviceName)
$mode = New-Object DEVMODE
$mode.dmSize = [System.Runtime.InteropServices.Marshal]::SizeOf($mode)
try {
$ret = [User32]::EnumDisplaySettings($DeviceName, [User32]::ENUM_CURRENT_SETTINGS, [ref]$mode)
if ($ret -ne 0) {
return $mode
}
else {
return $null
}
}
catch {
Write-Host "EnumDisplaySettings failed for '$DeviceName': $_"
return $null
}
}
if ($monitors.Count -eq 0) {
Write-Host "P/Invoke enumeration returned no devices - falling back to managed enumeration (System.Windows.Forms.Screen)"
try {
Add-Type -AssemblyName System.Windows.Forms -ErrorAction Stop
foreach ($s in [System.Windows.Forms.Screen]::AllScreens) {
# Try to obtain a DEVMODE for the managed screen DeviceName so we can apply changes later.
$mode = Try-GetDevMode $s.DeviceName
if ($mode) {
Write-Host "Managed screen $($s.DeviceName) -> obtained DEVMODE via EnumDisplaySettings"
}
else {
Write-Host "Managed screen $($s.DeviceName) -> no DEVMODE available via EnumDisplaySettings"
}
$monitors.Add([PSCustomObject]@{
Name = $s.DeviceName
X = $s.Bounds.X
W = $s.Bounds.Width
Mode = $mode
})
}
$EnumerationMethod = 'Managed'
}
catch {
Write-Error "Managed fallback failed to load System.Windows.Forms: $_"
}
}
# 2. Identify Left and Right based on X position
if ($monitors.Count -ge 2) {
$sorted = $monitors | Sort-Object X
$leftMonitor = $sorted[0]
$rightMonitor = $sorted[1]
} else {
$sorted = @()
$leftMonitor = $null
$rightMonitor = $null
}
# output current arrangement
Write-Host "Current Monitor Arrangement:"
$monitors | ForEach-Object {
Write-Host "$($_.Name): X=$($_.X), W=$($_.W)"
}
if ($leftMonitor -and $rightMonitor) {
Write-Host "Moving $($leftMonitor.Name) to the right of $($rightMonitor.Name)..."
# 3. Calculate New Position
# The new X for the left monitor is the Right monitor's X + its Width
$newX = $rightMonitor.X + $rightMonitor.W
$devMode = $leftMonitor.Mode
Write-Host "Using DEVMODE for $($leftMonitor.Name): $($devMode.dmPositionX), $($devMode.dmPelsWidth)"
# what are the keys available in leftMonitor?
$canApply = $true
if (-not $devMode) {
Write-Host "No DEVMODE available for $($leftMonitor.Name) (managed fallback supplied). Cannot perform P/Invoke apply."
$canApply = $false
}
if ($canApply) {
$devMode.dmPositionX = $newX
$devMode.dmFields = [User32]::DM_POSITION
}
Write-Host "Proposed new X for $($leftMonitor.Name): $newX"
if ($DryRun -or -not $Apply) {
Write-Host "Dry-run or not requested to apply. Use -Apply to perform the ChangeDisplaySettingsEx call."
}
if ($Apply) {
if (-not $canApply) {
Write-Error "Cannot apply because no DEVMODE was available for the target monitor. Aborting apply."
}
else {
# 4. Apply
$res = [User32]::ChangeDisplaySettingsEx($leftMonitor.Name, [ref]$devMode, [IntPtr]::Zero, [User32]::CDS_UPDATEREGISTRY, [IntPtr]::Zero)
if ($res -eq 0) { Write-Host "Success!" -ForegroundColor Green }
else { Write-Error "Failed with code $res" }
}
}
}
else {
Write-Host "Not enough monitors found to swap (need >= 2)."
}
# Try to obtain a DEVMODE for a given device name via EnumDisplaySettings.
function Try-GetDevMode {
param([string]$DeviceName)
$mode = New-Object DEVMODE
$mode.dmSize = [System.Runtime.InteropServices.Marshal]::SizeOf($mode)
try {
$ret = [User32]::EnumDisplaySettings($DeviceName, [User32]::ENUM_CURRENT_SETTINGS, [ref]$mode)
if ($ret -ne 0) {
return $mode
}
else {
return $null
}
}
catch {
Write-Host "EnumDisplaySettings failed for '$DeviceName': $_"
return $null
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment