Hey, Scripting Guy! How Can I Change My Desktop Monitor Resolution via Windows PowerShell?

Hey, Scripting Guy! How Can I Change My Desktop Monitor Resolution via Windows PowerShell?

  • Comments 10
  • Likes

 

Hey, Scripting Guy! QuestionHey, Scripting Guy! I need to be able to use a Windows PowerShell 2.0 script to change the resolution on my desktop monitor. Is this possible?

-- AS

 

Hey, Scripting Guy! AnswerHello AS,

Microsoft Scripting Guy Ed Wilson here, I have been looking for a script to change my monitor resolution for years! When I used to travel all over the world and taught scripting workshops to Microsoft Premier customers, I always shifted my laptop resolution down to 1024 x 768 to ensure the screen was large enough for students in the back of the room to read – a real important consideration when a person will be reading code for 40 solid hours! Anyway, it simply was impossible to do this via script until recently. This is because Windows PowerShell 2.0 makes it relatively easy to call native code from within Windows PowerShell. I was talking to my good friend Andy Schneider about writing a guest article, and I had a great idea; I would let Andy write a Windows PowerShell script using the Win32 API, and I would write the same script using DirectX. The problem is that the DirectX methodology is no longer supported (I found that out after spending several hours troubleshooting my script). So here is the best part of a great idea--guest blogger week continues with Andy Schneider. Let’s let Andy introduce himself and carry on with today’s blog.

Photo of Andy Schneider

About Andy Schneider: I am a systems engineer at Avanade, Inc. I am on Avanade’s Internal IT team that works with Compute Utility and Storage infrastructure. I’ve been a professional in the IT industry since 1999. I remember the first time I started playing with VBScript. I ran into my boss’s office exclaiming “Holy Schnikees! Look what we can do with this stuff!” Seeing what I could do with automation, my view of IT changed forever. Now I am pretty much a pure Windows PowerShell fanatic. Windows PowerShell has provided me with a fairly smooth glide path into the world of development with .NET and C#. I’m not a developer, but I have a strong passion for bridging the world of development and systems engineering together by being able to interface with developers and other systems engineers. Learning Windows PowerShell has really given me the tools I need to become more educated about the world of development. You can find my blog at http://get-powershell.com/.

There are two factors that encourage me to increase my skillset when it comes to writing Windows PowerShell code. The first is pure fascination and learning for the sake of learning. The second factor for me is a real problem that can either be solved only with a new addition to my skill set or can be solved much more easily with the new skill set. In this blog post, I would like to talk about how I was able to start using some new skills and eventually developed a solution for the problem I was having. Just keep in mind, no matter where you are in your skill set with Windows PowerShell; the glide path up to the next level is not very steep.

I am pretty comfortable with Windows PowerShell and using .NET directly in Windows PowerShell. However, I am an IT Pro, not a developer. I am willing to crack open some C# code every once in a while, but I really only know just enough to be dangerous. In this case, I wanted to figure out how to use inline C# and the Win32 API to script some things that could only be accessed using P-Invoke. There were a couple things I was looking at, but for this article, I will talk about using P-Invoke to change the screen resolution.

The first step in figuring out how something works is to find out how someone else has already solved the problem, or has come close to solving it for you. Bing is your friend! I spent 30 to 45 minutes one night poking around until I found this article up on C-Sharp Corner. Attached to this article is some source code written in C# that I used as my starting point. In this solution was a file called resolution.cs which pretty much had all the code I needed.

Once I had the code, there were a few things I wanted to change. The first was to remove the dependency on Winforms since this was going to be used in a PowerShell console window. I pulled out all the MessageBox.Show() method calls. The second thing I wanted to change was how the ChangeResolution() method was being called. I changed it to a static method that returned a string. This way, I didn’t have to create a new instance of the object every time I called the method.

It took me a while to figure out how this was all pieced together. In order to go between managed .NET code in C# and the Win32 API, there are a few crazy hoops we have to jump through. Lucky for me, someone has already jumped through them. The first was the creation of the DEVMODE struct. DEVMODE was a parameter type that the ChangeDisplaySettings function was using. The second piece that was interesting to me was the use of [DllImport(“user32.dll”)]. This is telling the compiler to expose some function in the Win32 API using unmanaged code. The two functions used in this script are EnumDisplaySettings() and ChangeDisplaySettings().Once I had the C# code nailed down, wrapping it into a PowerShell Advanced Function was fairly straightforward.

 

Function Set-ScreenResolution {
param (
[Parameter(Mandatory=$true,
           Position = 0)]
[int]
$Width,
[Parameter(Mandatory=$true,
           Position = 1)]
[int]
$Height
)
$pinvokeCode = @"
using System;
using System.Runtime.InteropServices;
namespace Resolution
{
    [StructLayout(LayoutKind.Sequential)]
    public struct DEVMODE1
    {
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string dmDeviceName;
        public short dmSpecVersion;
        public short dmDriverVersion;
        public short dmSize;
        public short dmDriverExtra;
        public int dmFields;
        public short dmOrientation;
        public short dmPaperSize;
        public short dmPaperLength;
        public short dmPaperWidth;
        public short dmScale;
        public short dmCopies;
        public short dmDefaultSource;
        public short dmPrintQuality;
        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;
        public int dmICMMethod;
        public int dmICMIntent;
        public int dmMediaType;
        public int dmDitherType;
        public int dmReserved1;
        public int dmReserved2;
        public int dmPanningWidth;
        public int dmPanningHeight;
    };
    class User_32
    {
        [DllImport("user32.dll")]
        public static extern int EnumDisplaySettings(string deviceName, int modeNum, ref DEVMODE1 devMode);
        [DllImport("user32.dll")]
        public static extern int ChangeDisplaySettings(ref DEVMODE1 devMode, int flags);
        public const int ENUM_CURRENT_SETTINGS = -1;
        public const int CDS_UPDATEREGISTRY = 0x01;
        public const int CDS_TEST = 0x02;
        public const int DISP_CHANGE_SUCCESSFUL = 0;
        public const int DISP_CHANGE_RESTART = 1;
        public const int DISP_CHANGE_FAILED = -1;
    }
    public class PrmaryScreenResolution
    {
        static public string ChangeResolution(int width, int height)
        {
            DEVMODE1 dm = GetDevMode1();
            if (0 != User_32.EnumDisplaySettings(null, User_32.ENUM_CURRENT_SETTINGS, ref dm))
            {
                dm.dmPelsWidth = width;
                dm.dmPelsHeight = height;
                int iRet = User_32.ChangeDisplaySettings(ref dm, User_32.CDS_TEST);
                if (iRet == User_32.DISP_CHANGE_FAILED)
                {
                    return "Unable To Process Your Request. Sorry For This Inconvenience.";
                }
                else
                {
                    iRet = User_32.ChangeDisplaySettings(ref dm, User_32.CDS_UPDATEREGISTRY);
                    switch (iRet)
                    {
                        case User_32.DISP_CHANGE_SUCCESSFUL:
                            {
                                return "Success";
                            }
                        case User_32.DISP_CHANGE_RESTART:
                            {
                                return "You Need To Reboot For The Change To Happen.\n If You Feel Any Problem After Rebooting Your Machine\nThen Try To Change Resolution In Safe Mode.";
                            }
                        default:
                            {
                                return "Failed To Change The Resolution";
                            }
                    }
                }
            }
            else
            {
                return "Failed To Change The Resolution.";
            }
        }
        private static DEVMODE1 GetDevMode1()
        {
            DEVMODE1 dm = new DEVMODE1();
            dm.dmDeviceName = new String(new char[32]);
            dm.dmFormName = new String(new char[32]);
            dm.dmSize = (short)Marshal.SizeOf(dm);
            return dm;
        }
    }
}
"@
Add-Type $pinvokeCode -ErrorAction SilentlyContinue
[Resolution.PrmaryScreenResolution]::ChangeResolution($width,$height)
}

Again, the point of going through this is not to provide a definitive guide on how to interop with the Win32 API. I know next to nothing about it. The key here that I want to share is to not be intimidated by technology we don’t understand. This was a long process for me to figure all of it out, but the process brought up a bunch of questions and answers, and eventually allowed me to solve the problem with a new set of tools at my disposal.

Where ever you are in your skill set with PowerShell, what is your next step? Maybe you have been using the interactive shell and now you could write a function. Maybe you’ve written a function and can start using advanced functions. Maybe you’ve got a chunk of functions and could start experimenting with Modules. Maybe you’re really comfortable with Windows PowerShell and want to start playing with C#. Whatever the case may be, don’t be afraid to step it up a notch and go for it!

The complete Set-ScreenResolution.ps1 script is available in the Script Repository.

AS that is all there is to using Windows PowerShell 2.0 to change the monitor resolution. Guest blogger week will continue tomorrow when we will talk about … wait a minute.

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post them on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • <p>Very valuable script indeed!</p> <p>Thanks a lot.</p>

  • <p>Hey, Scripting Guy! It&#39;s possible to set the best resolution ?</p> <p>Regards</p> <p>Jon</p>

  • <p>@Jon,</p> <p>Yes, this is definitely possible. I actually had some VB.NET code about 3 years ago that would enumerate the highest resolution supported by the monitor that was attached to a computer. I&#39;ll poke around and see if I can find the code... if I can.. i don&#39;t think porting it to PS would be very hard.</p> <p>-Andy</p>

  • <p>@Jon,</p> <p>I think the .NET framework added some functionality since I last started playing with this. It looks like you can use the following code to get the maximum resolution for your monitor</p> <p>$screen = [System.Windows.Forms.Screen]::PrimaryScreen</p> <p>$height = $screen.bounds.Size.Width &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</p> <p>$width = $screen.bounds.Size.Height</p>

  • <p>Brilliant. I will incorporate this into a Microsoft Deployment Toolkit Task Sequence to automatically adjust the screen resolution to its maximum size once a new windows 7 image is built (which it doesn&#39;t seem to do at present). Cheers.</p>

  • <p>Nice script.</p> <p>I am new to .net and Powershell so I wold like som hints to incorporate the &quot;best resolution&quot; code.</p>

  • <p>Any hints on how to make this target a secondary display? I was looking thru the code and the comments and saw that using this code allows me to see the secondary device&#39;s name but I dont know how to modify the script.</p> <p>$screen = [System.Windows.Forms.Screen]::AllScreens</p> <p>$screen</p> <p>BitsPerPixel : 32</p> <p>Bounds &nbsp; &nbsp; &nbsp; : {X=0,Y=0,Width=1920,Height=1080}</p> <p>DeviceName &nbsp; : \\.\DISPLAY1</p> <p>Primary &nbsp; &nbsp; &nbsp;: True</p> <p>WorkingArea &nbsp;: {X=0,Y=0,Width=1920,Height=1040}</p> <p>BitsPerPixel : 32</p> <p>Bounds &nbsp; &nbsp; &nbsp; : {X=1920,Y=0,Width=1024,Height=768}</p> <p>DeviceName &nbsp; : \\.\DISPLAY2</p> <p>Primary &nbsp; &nbsp; &nbsp;: False</p> <p>WorkingArea &nbsp;: {X=1920,Y=0,Width=1024,Height=768}</p>

  • <p>I need this script to run on 100 remote machines. Unfortunately, it&#39;s not set up to take a &quot;computername&quot; parameter. Anybody know how this might be accomplished? I&#39;ve tried various incarnations of invoke-command with no success.</p>

  • <p>Monitor resolution is set per-user and not per-computer. &nbsp;Each user decides their own preferred resolution.</p>

  • <p>how to apply this screen change for secondary monitor?</p>