Summary: Microsoft PFE, Chris Wu, discusses using Windows PowerShell to configure Jump Lists.
Microsoft Scripting Guy, Ed Wilson, is here. Welcome to the weekend. Today we have a special treat in the form of a guest blogger. The Scripting Wife and I had the good fortune to meet Chris Wu in Montreal, Canada when we were there doing a Windows PowerShell workshop for Microsoft Premier Customers. Here is a bit about Chris…
Chris Wu started his career at Microsoft in 2002, first as a support engineer in the Microsoft Global Technical Support Center in China to support the various components of the base operating system. Now he works as a premier field engineer in Canada, and he specializes in platform and development support. During the course of troubleshooting, performance tuning, and debugging, he has created many utilities to ease and facilitate the process by leveraging various programming languages, like C, C++, and C#. And Windows PowerShell has become his new favorite.
Take it away Chris…
One of the many features that I like about Windows 7 is the much improved taskbar. Specifically, I am a big fan of Jump Lists, which is a feature that enables users open favorite documents, pictures, websites, and utilities associated with an application. All this is accessible through a right-click on the application’s taskbar icon—even without the application being started first.
As serious Windows PowerShell users, just like me, you might have been tempted to pin the Windows PowerShell ISE to the taskbar. You would then end up with the disappointment of having nothing on its Jump List, as shown here.
Not only the application fails to create shortcuts to Windows PowerShell Console application or “Run as Administrator” mode, but also it won’t populate frequently used script files. (Remember that .ps1 documents are associated with Notepad instead of Windows PowerShell applications, unless another scripting environment is installed.) So it is time to change it—by using Windows PowerShell scripts, of course.
Windows 7 provides Windows Shell APIs that allow applications to alter Jump Lists (and to achieve many other Windows Shell features). Unfortunately, these APIs are written in native code without .NET implementation. Technically, it’s possible to wrap a needed API in C# code and embed it into a Windows PowerShell script, but this approach is not my intention (and it is probably out of my capability). Lucky for .NET programmers and Windows PowerShell scripters, Microsoft has already released Windows API Code Pack for Microsoft .NET Framework, which will make our lives much easier.
As far as a Jump List is concerned, only two precompiled DLLs from the Windows API Code Pack are needed. So download the current release, and then in the Binaries folder, extract Microsoft.WindowsAPICodePack.dll and Microsoft.WindowsAPICodePack.Shell.dll to a folder, for example, C:\Tools. And now it’s time to have fun!
Add-Type -Path “c:\tools\Microsoft.WindowsAPICodePack.dll”
Add-Type -Path “c:\tools\Microsoft.WindowsAPICodePack.Shell.dll”
$JumpList = [Microsoft.WindowsAPICodePack.Taskbar.JumpList]::CreateJumpList()
$Link = new-object Microsoft.WindowsAPICodePack.Taskbar.JumpListLink `
-ArgumentList”powershell.exe”,”PS Console”
$JumpList.AddUserTasks($Link)
$JumpList.Refresh()
The following image shows the script and its associated output.
Well, this list might still lack some elements to be called appealing, but it’s indeed an achievement, considering that we made it with merely six lines of code. To get the most out of a Jump List, one needs to dig into the details of the taskbar classes and their members. And there are great resources available on Internet, including:
Within the features that are provided by the Shell APIs, these can be rather easily achieved as follows:
- Associate an icon to a Jump List item
- Use a separator
- Create custom-named categories to organize items
And here is the code snippet:
Add-Type -Path “c:\tools\Microsoft.WindowsAPICodePack.dll”
Add-Type -Path “c:\tools\Microsoft.WindowsAPICodePack.Shell.dll”
$JumpList = [Microsoft.WindowsAPICodePack.Taskbar.JumpList]::CreateJumpList()
$Link = new-object Microsoft.WindowsAPICodePack.Taskbar.JumpListLink `
-ArgumentList “powershell.exe”,”PS Console”
$Link.IconReference = new-object Microsoft.WindowsAPICodePack.Shell.IconReference `
-ArgumentList “powershell.exe,0”
$Links = ,$Link
$Links += New-Object Microsoft.WindowsAPICodePack.Taskbar.JumpListSeparator
$Link = new-object Microsoft.WindowsAPICodePack.Taskbar.JumpListLink `
-ArgumentList “C:\Tools”,”Tools”
$Link.IconReference = new-object Microsoft.WindowsAPICodePack.Shell.IconReference `
-ArgumentList “shell32.dll,3”
$Links += $Link
$JumpList.AddUserTasks($Links)
$Category = new-object Microsoft.WindowsAPICodePack.Taskbar.JumpListCustomCategory -ArgumentList “Utilities”
$Link = new-object Microsoft.WindowsAPICodePack.Taskbar.JumpListLink -ArgumentList “notepad.exe”, “Notepad”
$Category.AddJumpListItems(@($Link))
$JumpList.AddCustomCategories($Category)
$JumpList.Refresh()
The script and its associated output are shown here:
You must have noticed redundancy in the code snippet. Indeed, this is a golden opportunity to show the power of pipeline processing in Windows PowerShell. While doing so, I also added support for command parameters, document icons, and custom categories. So here we have a function called Set-JumpList:
function Set-JumpList {
Param (
[string] $DllFolder = “”
)#End Param
Begin {
“Microsoft.WindowsAPICodePack.dll”,”Microsoft.WindowsAPICodePack.Shell.dll” |
foreach-object {
if ($DllFolder) { Add-Type -Path “$DllFolder\$_” -ErrorAction Stop }
else { Add-Type -Path (get-command $_ -TotalCount 1 -ErrorAction Stop).Path -ErrorAction Stop }
}
$JumpList = [Microsoft.WindowsAPICodePack.Taskbar.JumpList]::CreateJumpList()
$JumpList.ClearAllUserTasks()
$Category = $null
}#End Begin
Process {
$Name = ([string]$_.Name).Trim()
$Path = [Environment]::ExpandEnvironmentVariables(([string]$_.Path).Trim())
$Icon = [Environment]::ExpandEnvironmentVariables(([string]$_.Icon).Trim())
$Parameter = ([string]$_.Parameter).Trim()
if($Path -and ($Name -notmatch “^%%”)) { # Try to resolve Path
if (Test-Path $Path) { $Path = (Get-Item $Path).FullName }
else { $Path = (Get-Command $Path -TotalCount 1 -ErrorAction SilentlyContinue).Path }
}
if (($Name -notmatch “^%%”) -and !$Icon -and $Path) { # Try to locate the Icon reference from registry
try {
if ((Get-Item $Path).PSIsContainer) { $Icon = (Get-ItemProperty “Registry::HKEY_CLASSES_ROOT\Folder\DefaultIcon”).”(default)” }
else { $Icon = (Get-ItemProperty (“Registry::HKEY_CLASSES_ROOT\” + (Get-ItemProperty (“Registry::HKEY_CLASSES_ROOT\”+(Get-Item $Path).Extension)).”(default)” + “\DefaultIcon”)).”(default)” }
if ($Icon -match “^%1”) { $Icon = “$Path,0” }
$Icon = $Icon.Replace(‘”‘,”)
if ($Icon -notmatch “,”) { $Icon += “,0” }
} catch {}
}#End if
if ($Name -eq “%%”) { # Separator
if ( $Category ) { # Cannot have separator inside a custom category
$JumpList.AddCustomCategories($Category)
$Category = $null
}
else { $JumpList.AddUserTasks((New-Object Microsoft.WindowsAPICodePack.Taskbar.JumpListSeparator)) } # Add a separator
}
elseif ($Name -match “^%%”) { # New Category
if ( $Category ) { $JumpList.AddCustomCategories($Category) } # Inside a custom category already, registry previous first
$Category = new-object Microsoft.WindowsAPICodePack.Taskbar.JumpListCustomCategory -ArgumentList $Name.Substring(2)
}
elseif ( $Path -and $Name ) { # Add an item
$Link = new-object Microsoft.WindowsAPICodePack.Taskbar.JumpListLink -ArgumentList $Path, $Name
if ( $Icon ) { $Link.IconReference = new-object Microsoft.WindowsAPICodePack.Shell.IconReference -ArgumentList $Icon }
if ( $Parameter ) { $Link.Arguments = $Parameter }
if ( $Category ) { $Category.AddJumpListItems(@($Link)) }
else { $JumpList.AddUserTasks($Link) }
}#End if
}#End Process
End {
if ($Category ) { $JumpList.AddCustomCategories($Category) }
$JumpList.Refresh()
}#End End
}#End function Set-JumpList
This function expects objects from the pipeline that have properties like Name, Path, Icon, and Parameter. These arguments are used to create items in the task list. Special name “%%” is reserved to create a separator in the “Tasks” section, and “%%categoryname” type of expressions can be used to create a custom-named category in the Jump List, and the items that follow will be added to this category.
Personally, I would use the ConvertFrom-Csv cmdlet to create custom objects and pipe them to the function. I’m using “|” as delimiter because the icon definition retrieved from registry sometimes contains a comma.
@”
Notepad|notepad
Calculator|calc
%%
PS Console|powershell
Command Prompt|cmd
%%Files
Config Script|C:\scripts\Config.ps1
ToDo|notepad|C:\scripts\todo.txt
%%Folders
Tools|c:\tools
“@ | ConvertFrom-Csv -Header Name,Path,Parameter,Icon -Delimiter “|” | Set-JumpList -Dll c:\tools
The -DllFolder parameter in the previous code snippet can be omitted if the two DLLs are located in one of the directories listed in the $env:Path environment variable.
So here we have a very well customized Jump List. And did I mention that after it is created, the Jump List is persistent, regardless of the running status of the application itself? Make sure that you pin the Windows PowerShell ISE to the taskbar. Then you can always find this list by right-clicking the application icon from the taskbar—even without ISE running. You can see this in the following image.
Cheers!
~Chris
Thank you, Chris, for sharing a cool script and technique. The entire script can be found on the Scripting Guys Script Repository.
Join us tomorrow for a special report about the Scripting Games by Bartek Bielawski.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy
Is it possible to alter Jump Lists for another app than Windows PowerShell ISE itself (pinned to Taskbar, of course)?
I have tried this using CreateJumpListForIndividualWindow method with no success although the $JumpList variable looks like as if it was initialized using the CreateJumpList() method.
Thank you .