Tuesday, May 18, 2010

Episode #95: I Screen, You Screen, We All Screen for...

Ed's Tan, Rested, and Ready:

I'm back from vacation, and wanted to thank my fellow CLKF'ers for holding down the fort while I was away. Tim and Hal did a bang up job responding to the hundreds of thousands of e-mails from adoring fans, managing the hordes of Bodacious Research Assistants on the 83rd floor of Kung Fu Towers (our skyscraper that holds the world-wide headquarters of our blog and the infrastructure necessary to support it), and dealing with any IT issues that came up in our shop while I was absent. Tim mentioned to me that one of these issues dealt with a user whose GUI was giving him problems. He was complaining that the program didn't fit on his screen. Hmmmm... probably an issue with the screen resolution.

We can check the screen resolution with cmd.exe of a remote system using every Window user's best friend at the commandline, wmic, thusly:
C:\> wmic /node:IPaddr /user:Admin /password:Password desktopmonitor
get screenwidth, screenheight


ScreenHeight ScreenWidth
600 800
So, we can see that this GUI was a little tiny by modern standards. Tim provided some verbal coaching to the user about how to change this, and... voila! Problem solved.

Even one's best friends can be annoying sometimes, and wmic certainly has its frustrating parts. Note how we asked for screenwidth followed by screenheight, but wmic gave them to us backwards? That's because wmic always returns attributes in alphabetical order by attribute name (screenheight is alphabetically before screenwidth). The alphabetical fetish is hard coded into wmic, and there's no way around it using wmic by itself. That's why I usually manual alphabetize the attributes I ask for in my wmic commands. It makes me feel like my computer is doing what I want. I ask for them alphabetically, and it gives them to me alphabetically. You see, one doesn't use cmd.exe... it uses you.

But, who wants to look at screen resolutions listed backwards (600X800)? Clearly, alphabetical order here is lame. We've gotta reverse that, which we can do with our other little buddy, the cmd.exe FOR /F loop, a quirky little parser dude:

C:\> for /f "skip=1 tokens=1,2" %i in ('"wmic /node:IPaddr /user:Admin 
/password:password desktopmonitor get screenheight, screenwidth"')
do @echo %jX%i
1024X768
Here, I'm running a FOR /F loop to parse the output of my wmic command. I set my parsing options to skip down 1 line (because I want to bypass the column titles), and tokenize around the first and second columns of my output. My iterator variable will be %i, and because I have two tokens, %j will be automagically allocated. I then include my wmic command, which is inside of single quote double quotes (' "). The single quote tells the FOR loop I'll be executing a command. The double quotes lets me use a command that has special characters in it, such as a comma, without having to resort to the funky ^ character to escape it. It reads a little nicer this way. Just a little.

Note that in my command, I have alphabetized my requested attributes, my standard practice with wmic, so that I can more easily keep in my head their order when dealing with them in the body of my parsing loop. Finally, in the body of the loop (after the "do"), I turn off command display (@) and echo out my variables, reversed, with an X in between (for resolution). So, we see %jX%i, or 1024X768 in this example.

Whew! That's ugly... but it is easily extensible for all kinds of wmic madness.

Furthermore, remember that we can replace our /node:IPaddr with /node:@filename, having a file with one IP address or machine name per line, and we can pull information from a bunch of boxes about their screen resolution.

Unfortunately, there is no way to alter the screen resolution at the cmd.exe command line using only built-in tools. The wmic desktopmonitor alias has no callable methods, nor does the desktop alias. There are some great third party tools for doing so, like Display Changer, which is free for personal and educational use.

Tim is pale, tired, and slow

Those silly people and their GUI's. All sorts of problems with color and resolution. Ironically, the problem was found via the command line since the user wasn't able to determine the resolution he was running.

Here is the PowerShell version of the command. It is very similar to Ed's command, except it will (usually) prompt for credentials via a dialog box (GUI). The credentials are stored in a secure string. A secure string is encrypted in memory and zero'ed when no longer used.

PS C:\> $cred = Get-Credential
PS C:\> Get-WmiObject win32_desktopmonitor -ComputerName GuiMachine -Credential $cred |
select screenwidth, screenheight

screenwidth screenheight
----------- ------------
800 600
One noticeable difference between wmic and Get-WmiObject (alias gwmi) is that the full class name has to be used in PowerShell. This means that you typically have to type Win32_ (case insensitive) before the class name.

We can shorten this command to one line as well as use aliases and shortened parameter names.

PS C:\> gwmi win32_desktopmonitor -comp GuiMachine -cred (Get-Credential) | select screen*
screenheight screenwidth
------------ -----------
600 800
Let's take a step back and look at the properties of the $cred variable that holds our credentials.

PS C:\> $cred
UserName Password
-------- --------
sillyuser System.Security.SecureString
Hrm, can we see what the password contains?

PS C:\> ConvertFrom-SecureString $cred.Password
01000000d08c9ddf0115d1118c7a00c04fc297be01000000477d77c
aaec31c478b9568787c422fb10000000002000000000003660000c0
00000010000000232a3a9ecb092c10661956b28dee0f63000000000
4800000a0000000100000009d240c479361e0156ba4b63f995270de
18000000521e807650133832cfe5fc675cf3c7b8f71d4a5b0d4fa1f
114000000da5bfe8edf24c21b17a326989a82dd83ad1fb69c
Nope. There is a way, but it can only be decrypted by the same user on the same machine. This is a much safer option than typing the clear text password on the command line. If you want, you can read the details on DPAPI, but we will go into this more in a future episode.

We can even save the credentials in a file and import them for later use. First, export:

PS C:\> ConvertFrom-SecureString $cred.Passord | Out-File encryptedpass.txt
Then import the password, and recreate the credential.

PS C:\> $pass = ConvertTo-SecureString (cat encryptedpass.txt)
PS C:\> $cred = New-Object System.Management.Automation.PSCredential
-ArgumentList "myuser",$pass
You can even use another key to encrypt the exported file by using the -key parameter.

The only goofy thing with Get-Credential is that it pops up a dialog box to prompt for the credentials, silly GUI's. You can edit the registry to change the behavior so it prompts on the command line.

PS C:\> Set-ItemProperty HKLM:\SOFTWARE\Microsoft\PowerShell\1\ShellIds
-Name ConsolePrompting -Value True
Now we see the prompt on the command line.

PS C:\> PS C:\> $cred = Get-Credential
Supply values for the following parameters:
Credential
User: myuser
Password for user myuser: ****************
If we wanted to get the resolution on a number of machines we can use the following command.

PS C:\> Get-Content servers.txt |
% { gwmi win32_desktopmonitor -comp $_ -cred $cred } |
select SystemName, ScreenWidth, ScreenHeight

SystemName ScreenWidth ScreenHeight
---------- ----------- ------------
Machine1 800 600
Machine2 640 480
Machine3 1440 900
Machine4 1024 768
Let's see how easy this is for Hal...
Hal Isn't Sure Which End Is Up:

It turns out there are a couple of answers to the "What's my screen resolution?" question on a typical Unix system running some X Windows based display. First there's the old, reliable xdpyinfo command. To tell you just how old this command is, I can remember that one of the first shell scripts I ever wrote back in the 1980's parsed the output of xdpyinfo when setting up my default windowing environment. xdpyinfo dumps out a ton of information-- some useful and some not so much-- but here's a quick idiom for grabbing the screen resolution from the output:

$ xdpyinfo | awk '/dimensions:/ {print $2}'
1920x1200

And, yes, that's "width x height" unlike the Windows "standard" ordering. Crazy Unix people, what will they think of next?

However, the modern mechanism for interacting with your display(s) is the xrandr command. Short for "X Rotate and Resize", xrandr lets you query the current state of the display but, as you might guess from the command name, is really designed to allow you to manipulate the display from the command line or from within a shell script.

You can output the current display info with "xrandr -q":

$ xrandr -q
Screen 0: minimum 320 x 200, current 1920 x 1200, maximum 8192 x 8192
VGA1 connected 1920x1200+0+0 (normal left inverted right x axis y axis) 519mm x 324mm
1920x1200 60.0*+
1280x1024 75.0
1024x768 75.1 60.0
800x600 75.0 60.3
640x480 75.0 60.0
720x400 70.1
LVDS1 connected (normal left inverted right x axis y axis)
1024x768 50.0 + 85.0 75.0 70.1 60.0 40.0
832x624 74.6
800x600 85.1 72.2 75.0 60.3 56.2
640x480 85.0 72.8 75.0 60.0 59.9
720x400 85.0
640x400 85.1
640x350 85.1
0x0 0.0

This is the output from my laptop in its configuration in my office, where I have it connected to an external display ("VGA1" in the xrandr output) in addition to its internal video display ("LVDS1" for Laptop Video Display System). You can see all of the supported resolutions for each display. The "*" marks the active display(s)-- here I'm only using my external monitor at 1920x1200 just like we saw in the xdpyinfo output.

But the power of xrandr is its ability to completely control how your displays are set up. For example, here's the xrandr command I use when I'm teaching and I want my laptop display and the external projector to be showing the exact same image:

xrandr --output LVDS1 --mode 1024x768 --output VGA1 --mode 1024x768 --same-as LVDS1

But the two displays don't have to be showing the same image:

xrandr --output VGA1 --auto --output LVDS1 --auto --right-of VGA1

Here the "--auto" after each display means "choose the highest available resolution": 1920x1200 in the case of my external monitor and 1024x768 for my laptop display. And note that instead of "--same-as" I'm using "--right-of" to position the laptop display virtually to the right of my external monitor (where it sits physically on my desk). The upshot is that I can drag windows off the right-hand side of my external monitor and they'll show up on my laptop display. It's kind of cool, but my laptop display is really too small to be of much use when I'm working at my desk. By the way, there's also "--left-of", "--above", and "--below" positioning options, just like you might expect.

If I want to reset things to my default desktop environment-- laptop display off and external monitor at max resolution-- all I need to do is:

xrandr --output LVDS1 --off --output VGA1 --auto

But suppose this was a desktop machine with dual displays. Personally, I prefer to run my dual displays in "portrait" mode (more code in my display windows that way):

xrandr --output VGA1 --auto --rotate right \
--output VGA2 --auto --rotate right --right-of VGA1

The "--rotate" option handles orienting the display into portrait mode, and you can go either "right" or "left", depending how your monitor mount swivels. There's even "--rotate inverted" which I suppose might be useful if you're trying to display from a projector suspended upside-down from the ceiling (though most projectors these days have an internal setting to deal with that).

I have to say that xrandr is one of the coolest things to happen in X Windows for a while. It used to be much more painful to manipulate display configurations. But now it's totally straightforward.