Windows – How to pin .lnk files to taskbar in PowerShell

powershellshortcutsstart-menutaskbarwindows 10

I have seen many similar questions posted on stackoverflow.com, but this is different, and before you are wondering, why don't I post the question on stackoverflow.com? Because I am currently banned from asking questions there, and I currently don't have enough time to edit all those questions.

A brief description of what I want to do: I want to pin elevated cmd, powershell, pwsh, python etc. to taskbar programmatically, and of course I know how to create shortcuts using explorer.exe and pin them, but I consider the GUI method to be tiresome and tedious, having to click many times to get a simple job done, and I want to pin many programs…

I have found this:

How to create a Run As Administrator shortcut using Powershell

An example:

$WshShell = New-Object -comObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut("$Home\Desktop\PowerShell.lnk")
$Shortcut.TargetPath = "C:\Windows\System32\WindowsPowerShell\v1.0\PowerShell.exe"
$Shortcut.Save()
$bytes = [System.IO.File]::ReadAllBytes("$Home\Desktop\PowerShell.lnk")
$bytes[0x15] = $bytes[0x15] -bor 0x20
[System.IO.File]::WriteAllBytes("$Home\Desktop\PowerShell.lnk", $bytes)

The example will create a shortcut to default PowerShell named "PowerShell" on desktop with admin rights.

Now, to wrap it in a function:

function admin-shortcut {
  PARAM (
    [Parameter(ValueFromPipeline=$true, Mandatory=$true, Position=0)] [system.string] $path,
    [Parameter(ValueFromPipeline=$true, Mandatory=$true, Position=1)] [system.string] $name
    )
    $WshShell = New-Object -comObject WScript.Shell
    $Shortcut = $WshShell.CreateShortcut("$Home\Desktop\$name.lnk")
    $Shortcut.TargetPath = $path
    $Shortcut.Save()
    $bytes = [System.IO.File]::ReadAllBytes("$Home\Desktop\$name.lnk")
    $bytes[0x15] = $bytes[0x15] -bor 0x20
    [System.IO.File]::WriteAllBytes("$Home\Desktop\$name.lnk", $bytes)
}

I have seen many posts on stackoverflow.com about methods to pin programs directly to taskbar, but none of them solves my problem as I want to give the pinned shortcuts Administrator privileges, none of the methods I have seen does it.

So I Googled how to create an admin shortcut using PowerShell, and found the method mentioned above, now I only need to figure out how to pin the .lnk files to taskbar, unfortunately all Google search results of "how to pin shortcuts to taskbar" talks about pinning the program directly, none of them involves .lnk files, and I already explained why they won't work in this case.

So do you have any ideas? Any help will be appreciated.


Outdated methods that no longer work on Windows 10:

Method #1

$shell = new-object -com "Shell.Application"  
$folder = $shell.Namespace((Join-Path $env:SystemRoot System32\WindowsPowerShell\v1.0))
$item = $folder.Parsename('powershell_ise.exe')
$item.invokeverb('taskbarpin');

Method #2

$shell = new-object -com "Shell.Application"  
$folder = $shell.Namespace('C:\Windows')    
$item = $folder.Parsename('notepad.exe')
$verb = $item.Verbs() | ? {$_.Name -eq 'Pin to Tas&kbar'}
if ($verb) {$verb.DoIt()}

Method #3

$sa = new-object -c shell.application
$pn = $sa.namespace($env:windir).parsename('notepad.exe')
$pn.invokeverb('taskbarpin')

I pasted all these codes into PowerShell, no error messages showed up, but these commands literally do nothing, I had even restarted explorer.exe, still no change observed…


But no, wait! All is not lost, I can drag the resultant shortcut to taskbar to pin it to taskbar, and I can right click and pin to start, but imagine dragging 2 dozens of them… If I can figure out what exactly these actions do, I can replicate them with commands…


I have opened %Appdata%\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar

Folder and found some (non-UWP) app icons that I have pinned are indeed there,

and I have found some app icons that I have added to start menu here:

%AppData%\Microsoft\Internet Explorer\Quick Launch\User Pinned\StartMenu

However, I have confirmed simply copying the shortcuts there wouldn't work.

Found this:https://docs.microsoft.com/en-us/windows/configuration/configure-windows-10-taskbar, it is useful.


I have found a method to do this, since all I want is to add lines to the xml file, I can use this:

[string[]]$xmlconfig=get-content $template | % {
    $_
    if ($_ -match $pattern) {$line}
}

I know this is stupid, that's why I didn't post it as an answer, but it does get the job done, don't laugh at me as I am still learning, I am currently researching how to properly manipulate .xml files…

I tried:

[xml]$template=Get-Content $templatefile
$template.SelectNodes("//DesktopApp")

But it doesn't work, it also doesn't work on xml objects converted from .admx files, however .ChildNodes works, and it is not shown by Get-Member… I think treating it like plain text file is the best method now, otherwise I have to use something like this:

$template.LayoutModificationTemplate.CustomTaskbarLayoutCollection.TaskbarLayout.TaskbarPinList.DesktopApp.DesktopApplicationLinkPath

I have already solved it, I will post it as an answer now, I didn't pursue the registry key, and I no longer want to pin programs to start menu as I don't normally use it…

Best Answer

Minimum system requirement

I don't know, I am using PowerShell 7.2 preview 2 x64 on Windows 10 20H2, I have tested the script and confirmed it is working, and found a minor bug and fixed it; But I have tested my code on PowerShell 5.1.19041.610 and confirmed my code won't work on it, so maybe you need at least PowerShell Core 6? I don't really know, but it is advised to use the latest version of PowerShell.


I solved it via:

  1. Save as pintotaskbar.ps1:
    param(
      [parameter(mandatory=$true)] [validatenotnullorempty()]$execs
    )
    
    if (!(test-path $home\pinnedshortcuts)) {new-item $home\pinnedshortcuts -type "directory" > $null}
    $execs  = $execs -split "\|"
    
    foreach ($exec in $execs) {
      $path = ($exec -split "::")[0]
      $name = ($exec -split "::")[1]
    
      if ($path -notmatch "[C-Zc-z]:(\\[^(<>:`"\/\\|?*\x00-\x1f\x7f)]+)+") {$path=where.exe $path}
    
      $shortcutpath         = "$home\desktop\$name.lnk"
      $wshshell             = new-object -comobject wscript.shell
      $shortcut             = $wshshell.createshortcut($shortcutpath)
      $shortcut.targetpath  = $path
      $shortcut.save()
    
      $bytes                = [system.io.file]::readallbytes($shortcutpath)
      $bytes[0x15]          = $bytes[0x15] -bor 0x20
      [system.io.file]::writeallbytes($shortcutpath,$bytes)
    
      copy-item -path $shortcutpath -destination $home\pinnedshortcuts
    }
    
    $template         = get-content "$psscriptroot\template.xml"
    $pinnedshortcuts  = (get-childitem -path $home\pinnedshortcuts -filter "*.lnk").fullname | %{"`t`t<taskbar:DesktopApp DesktopApplicationLinkPath=`"{0}`" />" -f $_}
    $template         = $template | % {$_;if ($_ -match "<taskbar:taskbarpinlist>") {$pinnedshortcuts}}
    
    $template | out-file  -path "$home\pinnedshortcuts\pinnedshortcuts.xml"
    import-startlayout    -layoutpath $home\pinnedshortcuts\pinnedshortcuts.xml -mountpath c:\
    get-process           -name "explorer" | stop-process & explorer.exe
    

  2. Save template.xml in the same directory as pintotaskbar.ps1:
    <?xml version="1.0" encoding="utf-8"?>
    <LayoutModificationTemplate
        xmlns="http://schemas.microsoft.com/Start/2014/LayoutModification"
        xmlns:defaultlayout="http://schemas.microsoft.com/Start/2014/FullDefaultLayout"
        xmlns:start="http://schemas.microsoft.com/Start/2014/StartLayout"
        xmlns:taskbar="http://schemas.microsoft.com/Start/2014/TaskbarLayout"
        Version="1">
      <CustomTaskbarLayoutCollection>
        <defaultlayout:TaskbarLayout>
          <taskbar:TaskbarPinList>
          </taskbar:TaskbarPinList>
        </defaultlayout:TaskbarLayout>
      </CustomTaskbarLayoutCollection>
    </LayoutModificationTemplate>
    

  3. # Accepts a string like:
      # cmd::Command Prompt|pwsh::PowerShell 7|python::Python
    
    .\pintotaskbar.ps1 "cmd::Command Prompt|pwsh::PowerShell 7|python::Python"
    

I have found a strange issue, using the above example will generate a working shortcut named "Command Prompt pwsh" that points to %comspec%, I got rid of this by deleting [string] type specifier of $execs and it magically went away, I still have no idea how it happened...

Related Question