"Extend my Windows desktop onto this monitor" programmatically
Asked Answered
M

9

31

I would like to be able to set "Extend my Windows desktop onto this monitor" via code. A PowerShell script would be ideal. WMI seems the way forward but I have zero knowledge in WMI.

Mazman answered 8/11, 2008 at 19:11 Comment(0)
L
56

Windows 7, 8 and 10 are supposed to come with a small program that does exactly this: displayswitch.exe. This page lists the following parameters:

displayswitch.exe /internal  Disconnect projector (same as "Show only on 1" from the Display Properties dialog)
displayswitch.exe /clone     Duplicate screen
displayswitch.exe /extend    Extend screen
displayswitch.exe /external  Projector only (disconnect local) (same as "Show only on 2" from the Display Properties dialog)

For a one-click solution to the problem posed, simply create a *.bat-file containing the single line

call displayswitch.exe /extend

and save it to your desktop.

In Windows 11 22H2, displayswitch.exe does not seem to take parameters. Please see this link for more information. The suggested solution is to use a copy of the Windows-10-version of displayswitch.exe instead of the inbuilt version.

Lettering answered 2/8, 2014 at 12:10 Comment(4)
In Powershell no need for call.Glandule
Wonder how this can work on being plugged into onboard + dedicated graphics. Or I guess you'd have an issue with anything more then 3 monitors.Martinelli
In my version of Win10 this just brings up the Projector panel. It doesn't actually switch the display types. I have to run Powershell first. This is my batch file that works: powershell -Command "displayswitch.exe /clone" Knighthead
On Windows 11 23H2 it is working for me. I am using build 22631. In powershell ran the commands .\DisplaySwitch.exe /extend , and .\DisplaySwitch.exe /cloneOscan
C
7

I've made a cleaner version that does not use sendkeys.

public class DisplayHelper
{
    [DllImport("user32.dll")]
    static extern DISP_CHANGE ChangeDisplaySettings(uint lpDevMode, uint dwflags);
    [DllImport("user32.dll")]
    static extern bool EnumDisplayDevices(string lpDevice, uint iDevNum, ref DISPLAY_DEVICE lpDisplayDevice, uint dwFlags);

    enum DISP_CHANGE : int
    {
        Successful = 0,
        Restart = 1,
        Failed = -1,
        BadMode = -2,
        NotUpdated = -3,
        BadFlags = -4,
        BadParam = -5,
        BadDualView = -1
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    struct DISPLAY_DEVICE
    {
        [MarshalAs(UnmanagedType.U4)]
        public int cb;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string DeviceName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceString;
        [MarshalAs(UnmanagedType.U4)]
        public DisplayDeviceStateFlags StateFlags;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceID;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceKey;
    }

    [Flags()]
    enum DisplayDeviceStateFlags : int
    {
        /// <summary>The device is part of the desktop.</summary>
        AttachedToDesktop = 0x1,
        MultiDriver = 0x2,
        /// <summary>The device is part of the desktop.</summary>
        PrimaryDevice = 0x4,
        /// <summary>Represents a pseudo device used to mirror application drawing for remoting or other purposes.</summary>
        MirroringDriver = 0x8,
        /// <summary>The device is VGA compatible.</summary>
        VGACompatible = 0x16,
        /// <summary>The device is removable; it cannot be the primary display.</summary>
        Removable = 0x20,
        /// <summary>The device has more display modes than its output devices support.</summary>
        ModesPruned = 0x8000000,
        Remote = 0x4000000,
        Disconnect = 0x2000000
    }

    public static void EnableSecondaryDisplay()
    {
        var secondaryIndex = 1;
        var secondary = GetDisplayDevice(secondaryIndex);
        var id = secondary.DeviceKey.Split('\\')[7];

        using (var key = Registry.CurrentConfig.OpenSubKey(string.Format(@"System\CurrentControlSet\Control\VIDEO\{0}", id), true))
        {
            using (var subkey = key.CreateSubKey("000" + secondaryIndex))
            {
                subkey.SetValue("Attach.ToDesktop", 1, RegistryValueKind.DWord);
                subkey.SetValue("Attach.RelativeX", 1024, RegistryValueKind.DWord);
                subkey.SetValue("DefaultSettings.XResolution", 1024, RegistryValueKind.DWord);
                subkey.SetValue("DefaultSettings.YResolution", 768, RegistryValueKind.DWord);
                subkey.SetValue("DefaultSettings.BitsPerPel", 32, RegistryValueKind.DWord);
            }
        }

        ChangeDisplaySettings(0, 0);
    }

    private static DISPLAY_DEVICE GetDisplayDevice(int id)
    {
        var d = new DISPLAY_DEVICE();
        d.cb = Marshal.SizeOf(d);
        if (!EnumDisplayDevices(null, (uint)id, ref d, 0))
            throw new NotSupportedException("Could not find a monitor with id " + id);
        return d;
    }
}

I have only tested this on a newly installed computer.

Cleopatra answered 17/3, 2010 at 13:57 Comment(1)
VGACompatible should be 0x10, not 0x16Surah
C
4

This sort of operation is not directly accessible from PowerShell in the sense that there is not a .NET interface to these settings. A lot of core OS stuff is unmanaged code which can only be manipulated via win32 API calls. While you may be on to something with WMI, I searched for a while and wasn't able to find a satisfactory WMI class which is able to manipulate this setting.

The next step would be to modify the registry directly. It looks like the setting lies under HKLM:\system\CurrentControlSet\control\video--somewhere. I believe it's the one called "Attach.ToDesktop".

This is a partial solution, so I'm marking as community wiki answer.

I'm not certain this is the right registry key, and I don't have a system on which I can test multi-monitor at the moment. The purpose of this is to determine which is the primary controller, and then it outputs the value of the Attach.ToDesktop key.

param ( 
    $ControllerName = "$( throw 'ControllerName is a mandatory parameter' )"
)
$regPath = "HKLM:\system\CurrentControlSet\control\video"
$devDescStr = "Device Description"

Set-Location -path $regPath
$regSubKey = Get-ChildItem -recurse -include 0000
$devDescProperty = $regSubKey | Get-ItemProperty -name $devDescStr -erroraction SilentlyContinue 
$priDescProperty = $devDescProperty | Where-Object { $_.$devDescStr -match $ControllerName }
Set-Location -path $priDescProperty.PSPath
Get-ItemProperty -path . -name "Attach.ToDesktop"
Carriecarrier answered 8/11, 2008 at 19:11 Comment(0)
S
3

Here's another solution, in C# (via how to set primary monitor for Windows-7, in C#):

[Flags]
public enum SetDisplayConfigFlags : uint
{
    SDC_TOPOLOGY_INTERNAL = 0x00000001,
    SDC_TOPOLOGY_CLONE = 0x00000002,
    SDC_TOPOLOGY_EXTEND = 0x00000004,
    SDC_TOPOLOGY_EXTERNAL = 0x00000008,
    SDC_APPLY = 0x00000080
}

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern long SetDisplayConfig(uint numPathArrayElements,
    IntPtr pathArray, uint numModeArrayElements, IntPtr modeArray, SetDisplayConfigFlags flags);

static void CloneDisplays() {
    SetDisplayConfig(0, IntPtr.Zero, 0, IntPtr.Zero, SetDisplayConfigFlags.SDC_TOPOLOGY_CLONE | SetDisplayConfigFlags.SDC_APPLY);
}

static void ExtendDisplays() {
    SetDisplayConfig(0, IntPtr.Zero, 0, IntPtr.Zero, SetDisplayConfigFlags.SDC_TOPOLOGY_EXTEND | SetDisplayConfigFlags.SDC_APPLY);
}

static void ExternalDisplay() {
    SetDisplayConfig(0, IntPtr.Zero, 0, IntPtr.Zero, SetDisplayConfigFlags.SDC_TOPOLOGY_EXTERNAL | SetDisplayConfigFlags.SDC_APPLY);
}

static void InternalDisplay() {
    SetDisplayConfig(0, IntPtr.Zero, 0, IntPtr.Zero, SetDisplayConfigFlags.SDC_TOPOLOGY_INTERNAL | SetDisplayConfigFlags.SDC_APPLY);
}
Soulsearching answered 8/9, 2021 at 2:34 Comment(0)
A
2

One first possible solution is... through the GUI (but without user interaction)

VB script (also described here but in Autoit language):

Option Explicit
Dim WshShell, Dummy, Splash

On Error Resume Next

Set WshShell = WScript.CreateObject("WScript.Shell")

'Main
Call DoIt
WScript.Quit

Sub DoIt
wshshell.Run("%systemroot%\system32\control.exe desk.cpl,@0,3")

' Give Display Properties time to load
WScript.Sleep 1000
WshShell.SendKeys "2"
WScript.Sleep 10
WshShell.SendKeys "%E"
WScript.Sleep 500
WshShell.SendKeys "%A"
WshShell.SendKeys "{TAB}"
WshShell.SendKeys "{TAB}"
WshShell.SendKeys "{TAB}"
WshShell.SendKeys "{TAB}"
WshShell.SendKeys "{TAB}"
WshShell.SendKeys "{TAB}"
WshShell.SendKeys "{TAB}"
WshShell.SendKeys "{TAB}"
WshShell.SendKeys "{TAB}"
WshShell.SendKeys "{TAB}"
WshShell.SendKeys "{ENTER}"
End Sub 'DoIt

In Autoit, that would be:

;
; — toggle-screen.au3
;

; exec cpanel app `display settings`
Run(”C:\WINDOWS\system32\control.exe desk.cpl,@0,3?”)

; wait for window to be active
WinWaitActive(”Display Settings”)

; select 2nd display
Send(”{TAB}”)
Send(”{DOWN}”)

; work back to the ‘extend desktop’ control
Send(”+{TAB}”)
Send(”+{TAB}”)
Send(”+{TAB}”)
Send(”+{TAB}”)
Send(”+{TAB}”)
Send(”+{TAB}”)
Send(”+{TAB}”)
Send(”+{TAB}”)
Send(”+{TAB}”)

; toggle ‘extend desktop’ control and apply
Send(”{SPACE}”)
Send(”{ENTER}”)

; wait for window to be active
WinWaitActive(”Display Settings”)

; accept
Send(”{TAB}”)
Send(”{ENTER}”)

;
; — E.O.F.
; 
Aggrandize answered 8/11, 2008 at 19:11 Comment(1)
Icky! I know there are some miserable cases where all you can do is GUI robotics but somehow I don't think this is one of them.Virago
B
2

2 lines in autohotkey

2nd display on:

RunWait C:\Windows\System32\DisplaySwitch.exe /extend

2nd display off:

RunWait C:\Windows\System32\DisplaySwitch.exe /internal

-

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
; #Warn  ; Enable warnings to assist with detecting common errors.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
#Persistent 

Any1stKeyUWantToTurnOn::RunWait C:\Windows\System32\DisplaySwitch.exe /extend
Any2stKeyUWantToTurnOff::RunWait C:\Windows\System32\DisplaySwitch.exe /internal

or

You can check and try out my tool on github / BNK3R-Boy / DisplaySwitch. I published it right now.

Bloodroot answered 18/4, 2018 at 18:33 Comment(0)
M
1

Here is my AutoIt-Script for switching monitors as my ATI graphics card doesn't allow me to have 3 monitors active at the same time. I have 2 monitors attached and a TV. This script is doing what VonC's script does but in a more effective and faster way.

Run("C:\WINDOWS\system32\control.exe desk.cpl", "C:\Windows\system32\")
WinWait("Screen Resolution")
ControlCommand("Screen Resolution", "", "ComboBox1", "SetCurrentSelection", "SAMSUNG")

if (ControlCommand("Screen Resolution", "", "ComboBox3", "GetCurrentSelection", "") = "Disconnect this display") Then
    ControlCommand("Screen Resolution", "", "ComboBox1", "SetCurrentSelection", "2")
    ControlCommand("Screen Resolution", "", "ComboBox3", "SetCurrentSelection", "3")
    ControlCommand("Screen Resolution", "", "ComboBox1", "SetCurrentSelection", "0")
    ControlCommand("Screen Resolution", "", "ComboBox3", "SetCurrentSelection", "1")
    ControlClick("Screen Resolution", "", "Button4")
    WinWait("Display Settings")
    ControlClick("Display Settings", "", "Button1")
Else
    ControlCommand("Screen Resolution", "", "ComboBox3", "SetCurrentSelection", "3")
    ControlCommand("Screen Resolution", "", "ComboBox1", "SetCurrentSelection", "2")
    ControlCommand("Screen Resolution", "", "ComboBox3", "SetCurrentSelection", "1")
    ControlClick("Screen Resolution", "", "Button4")
    WinWait("Display Settings")
    ControlClick("Display Settings", "", "Button1")
EndIf

Just replace "SAMSUNG" with your third monitors/tvs name and you're all set! As you surely know you can convert it to an executable which runs on any machine even without AutoIt installed.

Mihe answered 14/8, 2010 at 0:1 Comment(0)
I
1

windows key + P button will do the same thing

Intricacy answered 25/7, 2018 at 15:37 Comment(1)
Sure, but the question is to do that in code, not interactively.Cattery
D
0

I had to made some small modifications to get VonC's script to work on my machine. It is now a little more generic.

;
; — toggle-screen2.au3
;

#include <WinAPI.au3>
; exec cpanel app `display settings`
Run(_WinAPI_ExpandEnvironmentStrings("%windir%") & "\system32\control.exe desk.cpl,@0,3?")

; wait for window to be active
WinWaitActive("Display Properties")

; select 2nd display
Send("!d")
Send("{DOWN}")

; toggle the ‘extend desktop’ checkbox
Send("!e")

; close the dialog
Send("{ENTER}")
Dooryard answered 21/5, 2009 at 7:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.