tags:

views:

3731

answers:

8

I would like to be able to do "Extend my Windows desktop onto this monitor" via code. A PowerShell script would be idea. I have googled around a far amount and WMI seems the way forward but as I have zero knowledge in WMI I thought I would try here.

Hope someone can help!

Cheers,

Zi

+3  A: 

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.
;
VonC
Please don't put this into production code
Paul Betts
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.
Jeffrey Hantin
A: 

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"
halr9000
A: 

VonC,

That works a treat! Thanks for the heads up... I actually read that article but didn't bother trying it coz what I was really after was the learning how to do it using WMI :) But you have solved my initial problem and for that I salute you my friend!

Zi

Zi Makki
Please add this as a comment.
DMan
A: 

halr9000,

I got as far as the line below:

$priDescProperty = $devDescProperty | Where-Object { $_.$devDescStr -match $ControllerName }

But could not find anything with “attach.todesktop”… I think I am missing a trick :)

Zi Makki
It's possible that is a registry key specific to the Nvidia card in my box. :( Look around in regedit in the path I have in the script. Maybe you can find the right option. I wish this weren't so hard...it shouldn't be.
halr9000
P.S. I think your reply to my answer should be done as a comment, not by adding a new answer. -StackOverFlow Nazi
halr9000
At 21 rep you can't post comments ...
Joey
A: 

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}")
abunetta
A: 

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. I created a a snippet here if you would like to make changes to it

loraderon
A: 
public static 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;
    }
}
Wikan
A: 

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.

MemphiZ