-1

I was looking for a solution to pin a shortcut or program to the task in win 10 with PS. I found Pin program to taskbar using PS in Windows 10. The VB Script works,

If WScript.Arguments.Count < 1 Then WScript.Quit
'----------------------------------------------------------------------
Set objFSO = CreateObject("Scripting.FileSystemObject")
objFile    = WScript.Arguments.Item(0)
sKey1      = "HKCU\Software\Classes\*\shell\{:}\\"
sKey2      = Replace(sKey1, "\\", "\ExplorerCommandHandler")
'----------------------------------------------------------------------
With WScript.CreateObject("WScript.Shell")
    KeyValue = .RegRead("HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer" & _
    "\CommandStore\shell\Windows.taskbarpin\ExplorerCommandHandler")

   .RegWrite sKey2, KeyValue, "REG_SZ"

    With WScript.CreateObject("Shell.Application")
        With .Namespace(objFSO.GetParentFolderName(objFile))
            With .ParseName(objFSO.GetFileName(objFile))
                .InvokeVerb("{:}")
            End With
        End With
    End With

    .Run("Reg.exe delete """ & Replace(sKey1, "\\", "") & """ /F"), 0, True
End With
'----------------------------------------------------------------------

I can invoke VB script from PS but a helpful person converted the script to PS

Param($Target)

$KeyPath1  = "HKCU:\SOFTWARE\Classes"
$KeyPath2  = "*"
$KeyPath3  = "shell"
$KeyPath4  = "{:}"
$ValueName = "ExplorerCommandHandler"
$ValueData =
    (Get-ItemProperty `
        ("HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\" + `
            "CommandStore\shell\Windows.taskbarpin")
    ).ExplorerCommandHandler

$Key2 = (Get-Item $KeyPath1).OpenSubKey($KeyPath2, $true)
$Key3 = $Key2.CreateSubKey($KeyPath3, $true)
$Key4 = $Key3.CreateSubKey($KeyPath4, $true)
$Key4.SetValue($ValueName, $ValueData)

$Shell = New-Object -ComObject "Shell.Application"
$Folder = $Shell.Namespace((Get-Item $Target).DirectoryName)
$Item = $Folder.ParseName((Get-Item $Target).Name)
$Item.InvokeVerb("{:}")

$Key3.DeleteSubKey($KeyPath4)
if ($Key3.SubKeyCount -eq 0 -and $Key3.ValueCount -eq 0) {
    $Key2.DeleteSubKey($KeyPath3)
}

However this PS script will not run unless the VB script has been ran at least one time. Is there a way to make the PS script work without having to run the VB script?

The error I get when trying to run the PS script without running the VB script at least once before it:

You cannot call a method on a null-valued expression.
At \\server\Utilities\TaskbarPin.ps1:41 char:5
+     $Key3 = $Key2.CreateSubKey($KeyPath3, $true)
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

You cannot call a method on a null-valued expression.
At \\server\Utilities\TaskbarPin.ps1:42 char:5
+     $Key4 = $Key3.CreateSubKey($KeyPath4, $true)
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

You cannot call a method on a null-valued expression.
At \\server\Utilities\TaskbarPin.ps1:43 char:5
+     $Key4.SetValue($KeyValue, $ValueData)
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

You cannot call a method on a null-valued expression.
At \\server\Utilities\TaskbarPin.ps1:50 char:5
+     $Key3.DeleteSubKey($KeyPath4)
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

I do not get an error after using the VB script once to do the task.

Drake
  • 93
  • 1
  • 3

2 Answers2

0

You should not have been impacted by this this way. The code works as designed, but you have to call the path the exe fully.

I just converted it to a function and it is successful with no other dependencies.

Function Add-AppToTaskbar
{
    [cmdletbinding()]

    Param
    (
        [string]$Target
    )

    $KeyPath1  = "HKCU:\SOFTWARE\Classes"
    $KeyPath2  = "*"
    $KeyPath3  = "shell"
    $KeyPath4  = "{:}"
    $ValueName = "ExplorerCommandHandler"
    $ValueData =
        (Get-ItemProperty `
            ("HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\" + `
                "CommandStore\shell\Windows.taskbarpin")
        ).ExplorerCommandHandler

    $Key2 = (Get-Item $KeyPath1).OpenSubKey($KeyPath2, $true)
    $Key3 = $Key2.CreateSubKey($KeyPath3, $true)
    $Key4 = $Key3.CreateSubKey($KeyPath4, $true)
    $Key4.SetValue($ValueName, $ValueData)

    $Shell = New-Object -ComObject "Shell.Application"
    $Folder = $Shell.Namespace((Get-Item $Target).DirectoryName)
    $Item = $Folder.ParseName((Get-Item $Target).Name)
    $Item.InvokeVerb("{:}")

    $Key3.DeleteSubKey($KeyPath4)
    if ($Key3.SubKeyCount -eq 0 -and $Key3.ValueCount -eq 0) 
    {$Key2.DeleteSubKey($KeyPath3)}
}

Add-AppToTaskbar -Target 'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe'

BTW, these pinned things live in two places on your system:

Here:

$env:USERPROFILE\AppData\Roaming\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar

Registry:

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband

Both are required.

Update based on OP's comment

I just ran this locally and remotely, both are successful. See results below. The local host I am using - WS2012R2 set as a workstation role I don't have any W10 systems in my lab. The earlier test was on a local W10 host.

Executed in the console host, ISE and VSCode.

PS C:\Windows\system32> $env:COMPUTERNAME
LabWS01

# PS Version
PS C:\Windows\system32> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      4.0
WSManStackVersion              3.0
SerializationVersion           1.1.0.1
CLRVersion                     4.0.30319.42000
BuildVersion                   6.3.9600.18968
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0}
PSRemotingProtocolVersion      2.2


# the current user profile pinned location filtered for notepad*

PS C:\Windows\system32> Get-ChildItem -Path "$env:USERPROFILE\AppData\Roaming\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar\Notepad*"

# Tested path to remote share

PS C:\Windows\system32> Test-path -Path '\\Server\ShareName\Add-AppToTaskbar.ps1'
True

# Ran the script from that remote share

PS C:\Windows\system32> \\Server\ShareName\Add-AppToTaskbar.ps1 'c:\windows\notepad.exe'

or this way...

Start-process -FilePath Powershell -ArgumentList '\\Server\ShareName\Add-AppToTaskbar.ps1 -Target C:\Windows\notepad.exe'

# Review pinned item location, filtered for notepad*

PS C:\Windows\system32> Get-ChildItem -Path "$env:USERPROFILE\AppData\Roaming\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar\Notepad*"


    Directory: C:\Users\Labuser001\AppData\Roaming\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---          8/9/2018   8:48 PM        791 Notepad.lnk

Shortcut shows pinned to taskbar.

So, this sounds environmental on your side. Now you can pin apps using GPO if this issue continues.

postanote
  • 15,138
  • 2
  • 14
  • 25
  • So I used "PS c:\>\\Server\Utilities\TaskbarPin.ps1 'c:\windows\notepad.exe'" and got the error, no matter how many times I try (verified on multiple W10 machines). Then I would use "PS c:\>wscript.exe \\server\utilities\TaskbarPin.vbs 'c:\windows\notepad.exe'" which will work. Finally I used "PS c:\>\\Server\Utilities\TaskbarPin.ps1 'c:\windows\notepad.exe'" again and it works. Very strange, I do not have this issue on my home computer. I will try the cmdlet approach and see if that works. Might be an issue trying to run it from the UNC path that "fixes" itself after running the VBS. – Drake Aug 09 '18 at 23:48
  • I just did this in my lab, locally and from a share on a remote server. See my update for you. – postanote Aug 10 '18 at 00:52
  • Thanks for the assistance. Unfortunately I cannot get the script to work without running the VBS first. I tried manually pinning and then running the script as well, which did not work. The only plausible reason I can come up with is that the VBS is creating something in the registry that the PS cannot create. I cannot use GPO as I do not have privileges to set GPO. I will just invoke the VBS from PS. I am doing other things from powershell as well, otherwise I would just use wscript. – Drake Aug 13 '18 at 13:52
0

I've modified your function so that selectively pins or unpins items to the taskbar. Previously, the problem is that the pin command was not exclusive, it would unpin the application if it was already pinned. With further detection of what was pinned in a binary registry value, it has been possible to determine that the item has already been pinned and it will not attempt to pin the item twice.

Set-AppPinTaskbarCsv is a function that was customized for our environment, I only include it as an example only, if someone wanted to roll this out in a login script to ensure the users have all of the apps they need pinned, it would need a good deal of modification and simplification. It has some functions which are not included that check group membership and reformat strings to expand variables, which are not required. After pinning the applications, it displays more reliably if explorer is restarted, and the Csv function will restart explorer if any items are pinned.

Function Set-PinTaskbar {
Param (
    [parameter(Mandatory=$True, HelpMessage="Target item to pin")]
    [ValidateNotNullOrEmpty()]
    [string] $Target
    ,
    [Parameter(Mandatory=$False, HelpMessage="Target item to unpin")]
    [switch]$Unpin
)
If (!(Test-Path $Target)) {
    Write-Warning "$Target does not exist"
    Break
}

$Reg = @{}
$Reg.Key1 = "*"
$Reg.Key2 = "shell"
$Reg.Key3 = "{:}"
$Reg.Value = "ExplorerCommandHandler"
$Reg.Data = (Get-ItemProperty ("HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\Windows.taskbarpin")).ExplorerCommandHandler
$Reg.Path1 = "HKCU:\SOFTWARE\Classes"
$Reg.Path2 = Join-Path $Reg.Path1 $Reg.Key1
$Reg.Path3 = Join-Path $Reg.Path2 $Reg.Key2
$Reg.Path4 = Join-Path $Reg.Path3 $Reg.Key3

If (!(Test-Path -LiteralPath $Reg.Path2)) {
    New-Item -ItemType Directory -Path $Reg.Path1 -Name [System.Management.Automation.WildcardPattern]::Escape($Reg.Key1)
}
If (!(Test-Path -LiteralPath $Reg.Path3)) {
    New-Item -ItemType Directory -Path ([System.Management.Automation.WildcardPattern]::Escape($Reg.Path2)) -Name $Reg.Key2
}
If (!(Test-Path -LiteralPath $Reg.Path4)) {
    New-Item -ItemType Directory -Path ([System.Management.Automation.WildcardPattern]::Escape($Reg.Path3)) -Name $Reg.Key3
}
Set-ItemProperty -Path ([System.Management.Automation.WildcardPattern]::Escape($Reg.Path4)) -Name $Reg.Value -Value $Reg.Data

$Shell = New-Object -ComObject "Shell.Application"
$Folder = $Shell.Namespace((Get-Item $Target).DirectoryName)
$Item = $Folder.ParseName((Get-Item $Target).Name)

# Registry key where the pinned items are located
$RegistryKey = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband"
# Binary registry value where the pinned items are located
$RegistryValue = "FavoritesResolve"
# Gets the contents into an ASCII format
$CurrentPinsProperty = ([system.text.encoding]::ASCII.GetString((Get-ItemProperty -Path $RegistryKey -Name $RegistryValue | Select-Object -ExpandProperty $RegistryValue)))
# Filters the results for only the characters that we are looking for, so that the search will function
[string]$CurrentPinsResults = $CurrentPinsProperty -Replace '[^\x20-\x2f^\x30-\x3a\x41-\x5c\x61-\x7F]+', ''

# Globally Unique Identifiers for common system folders, to replace in the pin results
$Guid = @{}
$Guid.FOLDERID_ProgramFilesX86 = @{
    "ID" = "{7C5A40EF-A0FB-4BFC-874A-C0F2E0B9FA8E}"
    "Path" = ${env:ProgramFiles(x86)}
}
$Guid.FOLDERID_ProgramFilesX64 = @{
    "ID" = "{6D809377-6AF0-444b-8957-A3773F02200E}"
    "Path" = $env:ProgramFiles
}
$Guid.FOLDERID_ProgramFiles = @{
    "ID" = "{905e63b6-c1bf-494e-b29c-65b732d3d21a}"
    "Path" = $env:ProgramFiles
}
$Guid.FOLDERID_System = @{
    "ID" = "{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}"
    "Path" = Join-Path $env:WINDIR "System32"
}
$Guid.FOLDERID_Windows = @{
    "ID" = "{F38BF404-1D43-42F2-9305-67DE0B28FC23}"
    "Path" = $env:WINDIR
}

ForEach ($GuidEntry in $Guid.Keys) {
    $CurrentPinsResults = $CurrentPinsResults -replace $Guid.$GuidEntry.ID,$Guid.$GuidEntry.Path
}

$Split = $CurrentPinsResults -split ('C:')

$SplitOutput = @()
# Process each path entry, remove invalid characters, test to determine if the path is valid
ForEach ($Entry in $Split) {
    If ($Entry.Substring(0,1) -eq '\') {
        # Get a list of invalid path characters
        $InvalidPathCharsRegEx = [IO.Path]::GetInvalidPathChars() -join ''
        $InvalidPathChars = "[{0}]" -f [RegEx]::Escape($InvalidPathCharsRegEx)
        $EntryProcessedPhase1 = "C:" + ($Entry -replace $InvalidPathChars)
        $EntryProcessedPhase2 = $null
        # Remove characters from the path until it is resolvable
        ForEach ($Position in $EntryProcessedPhase1.Length .. 1) {
            If (Test-Path $EntryProcessedPhase1.Substring(0,$Position)) {
                $EntryProcessedPhase2 = $EntryProcessedPhase1.Substring(0,$Position)
                Break
            }
        }
        # If the path resolves, add it to the array of paths
        If ($EntryProcessedPhase2) {
            $SplitOutput += $EntryProcessedPhase2
        }
    }
}

$PinnedItems = @()
$Shell = New-Object -ComObject WScript.Shell
ForEach ($Path in $SplitOutput) {
    # Determines if the entry in the registry is a link in the standard folder, if it is, resolve the path of the shortcut and add it to the array of pinnned items
    If ((Split-Path $Path) -eq (Join-Path $env:USERPROFILE "AppData\Roaming\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar")) {
        $Shell.CreateShortcut($Path).TargetPath
        $PinnedItems += $Shell.CreateShortcut($Path).TargetPath
    }
    Else {
        # If the link or executable is not in the taskbar folder, add it directly
        $PinnedItems += $Path
    }
}

# Unpin if the application is pinned
If ($Unpin.IsPresent) {
    If ($PinnedItems -contains $Target) {
        $Item.InvokeVerb("{:}")
        Write-Host "Unpinning application $Target"
    }
}
Else {
    # Only pin the application if it hasn't been pinned
    If ($PinnedItems -notcontains $Target) {
        $Item.InvokeVerb("{:}")
        Write-Host "Pinning application $Target"
    }
}

# Remove the registry key and subkeys required to pin the application
If (Test-Path $Reg.Path3) {
    Remove-Item -LiteralPath $Reg.Path3 -Recurse
}

}

Function Set-PinTaskbarCsv {
Param (
    [Parameter(Mandatory=$true)]
    $PinHashTable
    ,
    [Parameter(Mandatory=$true)]
    $UnpinHashTable
)
$Organization = "LIHC"
$RootRegistry = "HKCU:\Software\" + $Organization
$RootRegistryPinned = Join-Path $RootRegistry "Pinned"
# Unpin applications from taskbar
ForEach ($Entry in $UnpinHashTable.Keys) {
    $Location = Format-VariablesString -String $UnpinHashTable.$Entry.Location
    Add-Log "Taskbar app unpinned" $Location
    Set-PinTaskbar -Target $Location -Unpin
}

# Pin applications to taskbar
$Groups = @("Group1","Group2","Group3","Group4","Group5")
ForEach ($Entry in $PinHashTable.Keys) {
    $Entry
    $Location = Format-VariablesString -String $PinHashTable.$Entry.Location
    $ToTaskbar = [string]::IsNullOrWhiteSpace($PinHashTable.$Entry.Group1)
    ForEach ($Group in $Groups) {
        If (!([string]::IsNullOrWhiteSpace($PinHashTable.$Entry.$Group))) {
            $ToTaskbar = (Get-UserGroups -Username $env:USERNAME -Group $PinHashTable.$Entry.$Group) -or $ToTaskbar
        }
    }        
    If (!([string]::IsNullOrWhiteSpace($PinHashTable.$Entry.TestPath))) {
        $ToTaskbar = ((Test-Path $PinHashTable.$Entry.TestPath) -or (Test-Path $PinHashTable.$Entry.TestPath2)) -and $true
    }
    If ($ToTaskbar -and (Test-Path $Location) -and (!(Get-ItemProperty $RootRegistryPinned $Location -ErrorAction SilentlyContinue))) {
        #Set-AppPinTaskbar -Application $Location
        Set-PinTaskbar -Target $Location
        Add-Log "Taskbar app Pinned" $Location
        New-ItemProperty -Path $RootRegistryPinned -Name $Location 2>&1 > $null
        $Status = $true
    }
}
If ($Status) {
    Get-Process -Name explorer | Stop-Process
    Start-Process -FilePath explorer.exe
}

}