Last month I have been involved in a large Windows deployment project. One of the requirements was to be able to deliver Windows 7 computers with the Windows Powershell icon pinned to the taskbar.
That was not a difficult problem to solve, and, even if Windows 7 is now old stuff (especially after Windows 8.1 came out both on computers and Windows Phones) this project was nevertheless a good occasion to learn some Powershell basics that I feel like sharing with you here today.
The first thing to understand is that the Windows Taskbar is a user-oriented feature that Microsoft doesn't want system administrators or program installers to modify in place of the end-user. In other words, since most users don't want programs putting junk in their taskbar, Windows doesn't support you doing as such. That's the historical reason why I suppose there is no existing API to my knowledge to modify the Windows Taskbar.
Happily enough, the Shell.Application COM object comes to the rescue. Ok, I agree that's a bit old method of interacting with the Desktop by mimicking mouse-clicking, and some of you will point out that this is so VBScript. This is nonetheless the only possible way to achieve what we have been tasked for. And, for those that are curious, it's good to know that all the code I'll show below works against Windows 8 systems. What else?
Let's begin. The Shell.Application COM object is a powerful object that allow interaction with the Windows Shell. It provides a set of automation objects that can be used to access many of the Shell's features and dialog boxes.
For our task, we are going to use PowerShell to create this Shell.Application object and through it we will manipulate the Explorer programmatically.
All COM objects are created through the command: New-Object -ComObject. There really are a lot of options and possibilities for New-Object -ComObject. You could for instance open Outlook (with Outlook.Application), or Word (with Word.Application), or Excel (with Excel.Application). For our purpose we specifically need a Shell.Application type of object. Let's see how that's done.
$Shell = New-Object -ComObject Shell.ApplicationOnce you have established this kind of connection with the Windows Shell, you can perfom most of the interaction with Windows Explorer as you'd do with your keyboard and mouse.
You could for instance open the Windows Explorer and browse the C:\ folder:
$Shell.open("C:\")Or you could open the Windows Help:
$Shell.Help()
As you should know, Open() and Help() are methods of the Shell object. There are other useful methods, which can be investigated with Get-Member:
$Shell | Get-Member TypeName: System.__ComObject#{866738b9-6cf2-4de8-8767-f794ebe74f4e} Name MemberType Definition ---- ---------- ---------- AddToRecent Method void AddToRecent (Variant, string) BrowseForFolder Method Folder BrowseForFolder (int, string, int, Variant) CanStartStopService Method Variant CanStartStopService (string) CascadeWindows Method void CascadeWindows () ControlPanelItem Method void ControlPanelItem (string) EjectPC Method void EjectPC () Explore Method void Explore (Variant) ExplorerPolicy Method Variant ExplorerPolicy (string) FileRun Method void FileRun () FindComputer Method void FindComputer () FindFiles Method void FindFiles () FindPrinter Method void FindPrinter (string, string, string) GetSetting Method bool GetSetting (int) GetSystemInformation Method Variant GetSystemInformation (string) Help Method void Help () IsRestricted Method int IsRestricted (string, string) IsServiceRunning Method Variant IsServiceRunning (string) MinimizeAll Method void MinimizeAll () NameSpace Method Folder NameSpace (Variant) Open Method void Open (Variant) RefreshMenu Method void RefreshMenu () ServiceStart Method Variant ServiceStart (string, Variant) ServiceStop Method Variant ServiceStop (string, Variant) SetTime Method void SetTime () ShellExecute Method void ShellExecute (string, Variant, Variant, Variant, Variant) ShowBrowserBar Method Variant ShowBrowserBar (string, Variant) ShutdownWindows Method void ShutdownWindows () Suspend Method void Suspend () TileHorizontally Method void TileHorizontally () TileVertically Method void TileVertically () ToggleDesktop Method void ToggleDesktop () TrayProperties Method void TrayProperties () UndoMinimizeALL Method void UndoMinimizeALL () Windows Method IDispatch Windows () WindowsSecurity Method void WindowsSecurity () WindowSwitcher Method void WindowSwitcher () Application Property IDispatch Application () {get} Parent Property IDispatch Parent () {get}Between them you can see methods like ToggleDesktop(), which corresponds to pressing Win+D, or MinimizeAll(), which corresponds to Win+M. In our case we want first use the NameSpace method to create and return a Folder object for the folder where Windows Powershell resides: C:\Windows\System32\WindowsPowershell\v1.0\
The NameSpace method accepts as a parameter the path of the folder you work on as well as one of the ShellSpecialFolderConstants values. Here some examples:
(new-object -comobject shell.application).NameSpace(0x21).Self.Path C:\Users\administrator\AppData\Roaming\Microsoft\Windows\Cookiesor
(new-object -comobject shell.application).NameSpace(0x25).Self.Path C:\Windows\System32In the previous example 0x25 is an hex code referring to the Windows System folder, usually c:\Windows\System32.
Unfortunately for the moment there is no special folder hex code for the Windows Powershell folder as far as I know. So the full path must be hard-coded in the string:
(New-Object -ComObject shell.application).Namespace('C:\Windows\System32\WindowsPowershell\v1.0\')Let's see what the available methods are for the returned Folder object:
(New-Object -ComObject shell.application).Namespace('C:\Windows\System32\WindowsPowershell\v1.0\') | Get-Member TypeName: System.__ComObject#{a7ae5f64-c4d7-4d7f-9307-4d24ee54b841} Name MemberType Definition ---- ---------- ---------- CopyHere Method void CopyHere (Variant, Variant) DismissedWebViewBarricade Method void DismissedWebViewBarricade () GetDetailsOf Method string GetDetailsOf (Variant, int) Items Method FolderItems Items () MoveHere Method void MoveHere (Variant, Variant) NewFolder Method void NewFolder (string, Variant) ParseName Method FolderItem ParseName (string) Synchronize Method void Synchronize () Application Property IDispatch Application () {get} HaveToShowWebViewBarricade Property bool HaveToShowWebViewBarricade () {get} OfflineStatus Property int OfflineStatus () {get} Parent Property IDispatch Parent () {get} ParentFolder Property Folder ParentFolder () {get} Self Property FolderItem Self () {get} ShowWebViewBarricade Property bool ShowWebViewBarricade () {get} {set} Title Property string Title () {get}Before any of these methods can be executed, we must use ParseName to create an object that represents an item inside this folder.
(New-Object -ComObject shell.application).Namespace('C:\Windows\System32\WindowsPowershell\v1.0\').parsename('powershell.exe') Application : System.__ComObject Parent : System.__ComObject Name : powershell.exe Path : C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe GetLink : GetFolder : IsLink : False IsFolder : False IsFileSystem : True IsBrowsable : False ModifyDate : 27/09/2013 04:13:50 Size : 471040 Type : ApplicationThe returned object has four methods:
ExtendedProperty Method Variant ExtendedProperty (string) InvokeVerb Method void InvokeVerb (Variant) InvokeVerbEx Method void InvokeVerbEx (Variant, Variant) Verbs Method FolderItemVerbs Verbs ()As you can see there are two ways to call linked verbs: InvokeVerb() and Verbs().
InvokeVerb() is the direct method to work on a FolderItem object. To pin or unpin items to the taskbar it takes as value the verbs as they are described under HKEY_CLASSES_ROOT\CLSID\{90AA3A4E-1CBA-4233-B8BB-535773D48449}.
That's because the action of pinning and unpinning items is managed by the 'Pin to Taskbar' handler which has the very secret CLSID {90AA3A4E-1CBA-4233-B8BB-535773D48449}. The two shown verbs are:
taskbarpin;taskbarunpin
So the code to pin or unpin is the following:
(New-Object -ComObject shell.application).Namespace('C:\Windows\System32\WindowsPowershell\v1.0\').parsename('powershell.exe').invokeverb('TaskbarPin')
(New-Object -ComObject shell.application).Namespace('C:\Windows\System32\WindowsPowershell\v1.0\').parsename('powershell.exe').invokeverb('TaskbarUnPin')The alternative is to use the Verbs() method. The listed actions match what you would get by right clicking on an item in Windows Explorer:
(New-Object -ComObject shell.application).Namespace('C:\Windows\System32\WindowsPowershell\v1.0\').parsename('powershell.exe').verbs() | select Name Name ---- &Open Run as &administrator Scan for Viruses... Pin to Tas&kbar Pin to Start Men&u Restore previous &versions Cu&t &Copy Create &shortcut &Delete Rena&me P&ropertiesPlease note that the ampersand (&) that appears in some verbs is not a typo. Some verbs actually have the ampersand because it's used for the hot key of the context menu.
The oneliner here is longer than the one that leverages InvokeVerb():
((new-object -com "Shell.Application").Namespace('C:\Windows\System32\WindowsPowershell\v1.0\').Parsename('powershell.exe').Verbs() | ? {$_.Name -eq 'Pin to Tas&kbar'}).doit()To make it more readable and understandable, let's split it up:
$shell = new-object -com "Shell.Application" $folder = $shell.Namespace('C:\Windows\System32\WindowsPowershell\v1.0\') $item = $folder.Parsename('powershell.exe') $verb = $item.Verbs() | ? {$_.Name -eq 'Pin to Tas&kbar'} if ($verb) {$verb.DoIt()}Now you might be wondering what the 'DoIt' line is for. Well, it's a bit of running in circles but in simple words, DoIt() executes a verb on the FolderItem associated with the verb. Nothing else.
So to resume, the script does the pinning in three steps:
- Use the Verbs() method of a FolderItem object to get a list of verbs applicable to the target.
- Filter the verbs down to the one with a name matching the expected action.
- Execute the DoIt() method to invoke the verb.
When you execute the code above, it's just like Powershell is doing the clicking for you and your pinned apps on the taskbar get saved in both locations below:
In the registry under:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband
In the hidden TaskBar folder:
C:\Users\Username\AppData\Roaming\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar
Let's end this post with a few information on the Shell.Application. If you want to find out all the possible ComObjects on your PC with Powershell, just check in the registry under HKLM:\Software\Classes. We can perform a bit of pattern matching to filter down to the objects that matches the naming syntax of ComObjects with '^\w+\.\w+$', which can be read like two groups of words separated by a dot.
Here's the code to find them all:
Get-ChildItem HKLM:\Software\Classes -ErrorAction SilentlyContinue | ? { $_.PSChildName -match '^\w+\.\w+$' -and (Test-Path -Path "$($_.PSPath)\CLSID") } | Select-Object -ExpandProperty PSChildNameShell.Application is one of them:
Get-ChildItem HKLM:\Software\Classes | ? PSChildName -Match 'Shell.Application' Hive: HKEY_LOCAL_MACHINE\Software\Classes Name Property ---- -------- Shell.Application (default) : Shell Automation Service
Now that we know where it is stored in the Windows Registry, we can also see its CLSID, which is {13709620-C279-11CE-A49E-444553540000}, as you can see opening the key. This value can be used to perform the pinning in another way that I am going to show you.
First of all we have to convert the CLSID to a type, which is done like this:
$Type= [Type]::GetTypeFromCLSID('13709620-C279-11CE-A49E-444553540000')Then we will leverage the [System.Activator] class, which is generally used to create types of objects. Its CreateInstance method create an instance of the object. This is usually used in .NET Late Binding, as explained here, but I think it's worth having a look:
$Object = [Activator]::CreateInstance($Type)Then we can proceed like seen before defining the path of the file, its name and the verb to apply:
[Activator]::CreateInstance([Type]::GetTypeFromCLSID('13709620-C279-11CE-A49E-444553540000')).Namespace('C:\Windows\System32\WindowsPowershell\v1.0\').ParseName('powershell.exe').InvokeVerb('taskbarpin')That's kind of a long one-liner, and unfortunately there is no way of making it shorter as far as I can tell. You should now be able to read it, because you know the basics concepts I have just shown you. I hope you learned something. Stay tuned for more.
Really awesome post, do you have any pointers why i cant get it to work in Windows 10?
ReplyDelete+1
ReplyDeleteI finally found this tutorial. Thanks man. It takes me more than one month to find this tutorial. I was searching for how to pin in taskbar but got no luck and finally found it. Thanks
ReplyDelete