Restoration

How many of you out there have laptops, tablets, or surface pros?
I bet a lot of you do.
How many of you out there have a different resolution when you are docked and connected to an external monitor or two than when you are mobile?
Again I’m going to bet most of you.
wrench-512
Last question. How many of you are tired of repositioning all of your windows once you come back from working on your mobile device to a docked scenario?

I’m willing to bet most of you. I know I was. But what do we do about it? Let me show you what I did about it, maybe it will help you.

PowerShell to the rescue!

Welcome back to another episode of how PowerShell and a little bit of Windows API can help save you a lot of frustration. Let me start off with a little explanation. I work as a web developer and I’ve gotten a bit of reputation for creating little tools to make my life easier rather than doing something repetitively. I am a strong believer in automation and prefer to automate my tasks whenever possible. The same is true of me when it comes to setting up my system after docking.

The Environment

I work in an open space environment, almost the entire company sits in one large space so that there can be open collaboration between the many different departments and people without the physical barriers that exist in a cubical farm. There are many times during the day when the other developers and I need retreat to one of our meeting rooms to has hash something out. When I return and dock my computer all of my windows are the same sizes and positions that they were prior to docking. This means all of my items are all crammed in the upper left corner rather than nicely spread across the vast real estate that I enjoy when my laptop is in its dock. This is also a problem if I have been working at home and come back to the office the next day.

The Solution

The solution for me was to come up with a way of automatically setup the programs that I am likely to have open on my desktop. Here are my requirements.

  • If the program is not already open the script should open it.
  • The program should move to its pre-defined position.

Let’s get started on the code. We need the SetWindowPos function from the User32.dll (Win32API) file. Since PowerShell does not support Platform Invocation (P/Invoke) we are going to have to jump through a small hoop to get things running.

# Helper functions for building the class
$script:nativeMethods = @();
function Register-NativeMethod([string]$dll, [string]$methodSignature) {
$script:nativeMethods += [PSCustomObject]@{ Dll = $dll; Signature = $methodSignature; }
}

function Add-NativeMethods() {
$nativeMethodsCode = $script:nativeMethods | % { "
[DllImport(`"$($_.Dll)`")]
public static extern $($_.Signature);
" }

Add-Type @"
using System;
using System.Runtime.InteropServices;
public static class NativeMethods {
$nativeMethodsCode
}
"@
}

# Add methods here
Register-NativeMethod "user32.dll" "bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags)"

# This builds the class and registers them (you can only do this one-per-session, as the type cannot be unloaded?)
Add-NativeMethods

This next bit of code will provide access to the local processes that we need. The process has all kinds of good things on it.  What we are most interested with are the process name and if it has a title. I’ve created the function below that will give us that information. There are some applications like Chrome or Opera that will open a process per tab, but only one of those processes will have the Main Window Title.

function GetWindowProcess() {
Param([Parameter(Mandatory=$true, HelpMessage="The name of the process in memory (ps-list).`n")]
[string] $processName,
[Parameter(Mandatory=$false, HelpMessage="True if you want to only affect the process with a Main Window Title (default false).`n")]
[bool] $useMainWindowTitle = $false)

if ($useMainWindowTitle) {
return (Get-Process | Where { $_.MainWindowTitle -and $_.Name -eq $processName })
}
else {
return Get-Process -Name $processName -ErrorAction SilentlyContinue
}
}

Now since I will be setting this information up for more than just one program, I’ve written a method called StartAndRepositionApplication. This will start up the application (if it is not currently running) and then reposition the application by setting it’s window position.

function StartAndRepositionApplication() {
Param([Parameter(Mandatory=$true, HelpMessage="The name english name to be used for the process.`n")]
[string] $name,
[Parameter(Mandatory=$true, HelpMessage="The name of the process in memory (ps-list).`n")]
[string] $processName,
[Parameter(Mandatory=$true, HelpMessage="The path to the process executable.`n")]
[string] $pathToExecutable,
[Parameter(Mandatory=$false, HelpMessage="The working directory (if specified).`n")]
[string] $workingDirectory = $env:USERPROFILE,
[Parameter(Mandatory=$true, HelpMessage="The starting left coordinate.`n")]
[int] $left,
[Parameter(Mandatory=$true, HelpMessage="The starting top coordinate.`n")]
[int] $top,
[Parameter(Mandatory=$true, HelpMessage="The width of the window once complete.`n")]
[int] $width,
[Parameter(Mandatory=$true, HelpMessage="The height of the window once complete.`n")]
[int] $height,
[Parameter(Mandatory=$false, HelpMessage="True if you want to only affect the process with a Main Window Title (default false).`n")]
[bool] $useMainWindowTitle = $false)

[System.Diagnostics.Process] $proc = GetWindowProcess -processName $processName -useMainWindowTitle $useMainWindowTitle
if ($proc -eq $null) {
Write-Host (' -> The process ' + $name + ' is not running, starting it up...')
Start-Process -FilePath $pathToExecutable -WorkingDirectory $workingDirectory
[System.Threading.Thread]::Sleep(2 * 1000)
$proc = GetWindowProcess -processName $processName -useMainWindowTitle $useMainWindowTitle
if ($proc -eq $null) {
throw 'Unable to start the process'
}
}
[bool] $winPosResult = [NativeMethods]::SetWindowPos($proc.MainWindowHandle, [System.IntPtr]::Zero, $left, $top, $width, $height, 0)
Write-Host (' -> ' + $name + ' Start: ' + $winPosResult)
}

I like to start and position several applications when I come to work.

Now that we have all of our helper functions defined above, Here is how I have leveraged them to automate my life.

# Set the registry values for Visual Studio 2013
Set-ItemProperty -Path HKCU:\Software\Microsoft\VisualStudio\12.0 -Name 'MainWindow' -Value '75 0 1844 1040 1'

StartAndRepositionApplication -name 'Firefox' -processName 'firefox' `
-pathToExecutable 'C:\Program Files (x86)\Mozilla Firefox\firefox.exe' -workingDirectory 'C:\Program Files (x86)\Mozilla Firefox' `
-left 1915 -top 0 -width 1800 -height 950

StartAndRepositionApplication -name 'Microsoft Outlook 2013' -processName 'OUTLOOK' `
-pathToExecutable 'C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Microsoft Office 2013\Outlook 2013.lnk' `
-left 1921 -top 200 -width 1650 -height 840

StartAndRepositionApplication -name 'Pidgin' -processName 'pidgin' `
-pathToExecutable 'C:\Program Files (x86)\Pidgin\pidgin.exe' `
-workingDirectory 'C:\Program Files (x86)\Pidgin' `
-left 3540 -top 0 -width 300 -height 1048 -useMainWindowTitle $true

StartAndRepositionApplication -name 'Console 2' -processName 'Console' `
-pathToExecutable 'C:\Program Files (x86)\Console2\Console.exe' `
-workingDirectory 'C:\Program Files (x86)\Console2' `
-left 1915 -top 150 -width 880 -height 900

StartAndRepositionApplication -name 'Opera' -processName 'opera' `
-pathToExecutable 'C:\Program Files (x86)\Opera\launcher.exe' `
-workingDirectory 'C:\Program Files (x86)\Opera' `
-left 80 -top 0 -width 1845 -height 1045 -useMainWindowTitle $true

StartAndRepositionApplication -name 'Visual Studio 2013', -processName 'devenv' `
-pathToExecutable 'C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\devenv.exe' `
-left 75 -top 0 -width 1844 -height 1040
PS C:\WINDOWS\system32> C:\Development\_RestoreWindowPositions.ps1
 -> Firefox Start: True
 -> Microsoft Outlook 2013 Start: True
 -> Pidgin Start: True
 -> Console 2 Start: True
 -> The process Opera is not running, starting it up...
 -> Opera Start: True
 -> The process Visual Studio 2013 is not running, starting it up...
 -> Visual Studio 2013 Start: True

PS C:\WINDOWS\system32>

Now all of my applications are both started and positioned for me where I want them. Ready to get to work. Any questions?

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s