Pin and Unpin from Quick Access using PowerShell

CaptnDork

New Member
Messages
6
Reaction score
0
I am curious if anyone has had any luck unpinning items from Quick Access using PowerShell? I found the following NTLite post and have been trying to figure out the proper code to UNPIN items. SOLVED - AutomaticDestinations Edits For Quick Access

I don't seem to have an issue pinning using:
Code:
$Path = "$($env:USERPROFILE)\Music"
$QuickAccess = New-Object -ComObject shell.application
$TargetObject = $QuickAccess.Namespace("shell:::{679f85cb-0220-4080-b29b-5540cc05aab6}").Items() | where {$_.Path -eq "$Path"}
$QuickAccess.Namespace("$Path").Self.InvokeVerb(“pintohome”)

However, I can't seem to get items to UNPIN. I did find the following StackOverflow post that Quick Access contains two "sections": Pinned Items and Frequent Folders. Music and Videos are in the second section, unlike the rest (Desktop, Downloads, Documents, Pictures). So the verb to invoke changes goes from unpinfromhome to removefromhome. Is it possible programmatically add folders to the Windows 10 Quick Access panel in the explorer window?

I have included a text file with the PowerShell code I'm using - just in case I have something messed up in there.

I'd also like to pin/unpin items (shortcuts?) to the taskbar, but I'm wondering if I'll end up with the same problem? (Haven't tried yet - stuck on this, first.)

Any help would be greatly appreciated!
 

Attachments

My impression is the answer depends on which Windows version are you're running. Some folder items expose Verbs better than others.
Maybe you can list your Windows version, and see if someone knows the answer.
 
garlin Thanks for the response. I am running Win 11 Pro, ver. 10.0.22631 build 22631 (23H2). My NTLite install will be installing the same version.
 
Some quick testing on W11 23H2:
- Pinning or unpinning folders is really slow, because you're sending events thru Explorer.
- Pinning an already pinned folder, makes Explorer unpin it. It's a toggle function.
- There's no API to sort folders in alphabetical order. If you unpin a Folder in the middle of the list, re-pinning it doesn't restore the original position.
- This doesn't touch frequently accessed folders, which is a function of the jumplists.

Code:
$TestFolder = "C:\Users\GARLIN\Downloads\TEST FOLDER"

if (-not (Test-Path $TestFolder)) {
    New-Item $TestFolder -ItemType Directory | Out-Null
}

$objShell = New-Object -ComObject Shell.Application

for ($i = 0; $i -lt 10; $i++) {
    # My Videos
    $objShell.Namespace("shell:::{A0953C92-50DC-43bf-BE83-3742FED03C9C}").Self.InvokeVerb("UnpinToHome")
    # My Music
    $objShell.Namespace("$($env:USERPROFILE)\Music").Self.InvokeVerb("UnpinToHome")
    # Recycle Bin
    $objShell.Namespace("shell:::{645FF040-5081-101B-9F08-00AA002F954E}").Self.InvokeVerb("PinToHome")
    # Test Folder
    $objShell.Namespace($TestFolder).Self.InvokeVerb("PinToHome")
    Start-Sleep 3

    # My Videos
    $objShell.Namespace("shell:::{A0953C92-50DC-43bf-BE83-3742FED03C9C}").Self.InvokeVerb("PinToHome")
    # My Music
    $objShell.Namespace("$($env:USERPROFILE)\Music").Self.InvokeVerb("PinToHome")
    # Recycle Bin
    $objShell.Namespace("shell:::{645FF040-5081-101B-9F08-00AA002F954E}").Self.InvokeVerb("UnpinToHome")
    # Test Folder
    $objShell.Namespace($TestFolder).Self.InvokeVerb("UnpinToHome")
    Start-Sleep 3
}

Remove-Item $TestFolder
 
Some quick testing on W11 23H2:
- Pinning or unpinning folders is really slow, because you're sending events thru Explorer.
- Pinning an already pinned folder, makes Explorer unpin it. It's a toggle function.
- There's no API to sort folders in alphabetical order. If you unpin a Folder in the middle of the list, re-pinning it doesn't restore the original position.
- This doesn't touch frequently accessed folders, which is a function of the jumplists.

Code:
$TestFolder = "C:\Users\GARLIN\Downloads\TEST FOLDER"

if (-not (Test-Path $TestFolder)) {
    New-Item $TestFolder -ItemType Directory | Out-Null
}

$objShell = New-Object -ComObject Shell.Application

for ($i = 0; $i -lt 10; $i++) {
    # My Videos
    $objShell.Namespace("shell:::{A0953C92-50DC-43bf-BE83-3742FED03C9C}").Self.InvokeVerb("UnpinToHome")
    # My Music
    $objShell.Namespace("$($env:USERPROFILE)\Music").Self.InvokeVerb("UnpinToHome")
    # Recycle Bin
    $objShell.Namespace("shell:::{645FF040-5081-101B-9F08-00AA002F954E}").Self.InvokeVerb("PinToHome")
    # Test Folder
    $objShell.Namespace($TestFolder).Self.InvokeVerb("PinToHome")
    Start-Sleep 3

    # My Videos
    $objShell.Namespace("shell:::{A0953C92-50DC-43bf-BE83-3742FED03C9C}").Self.InvokeVerb("PinToHome")
    # My Music
    $objShell.Namespace("$($env:USERPROFILE)\Music").Self.InvokeVerb("PinToHome")
    # Recycle Bin
    $objShell.Namespace("shell:::{645FF040-5081-101B-9F08-00AA002F954E}").Self.InvokeVerb("UnpinToHome")
    # Test Folder
    $objShell.Namespace($TestFolder).Self.InvokeVerb("UnpinToHome")
    Start-Sleep 3
}

Remove-Item $TestFolder

Ok, I thought I understood when I started testing the toggle, but now I'm just confused. Your code uses both UnpinToHome and PinToHome; I assume you got results from the UnpinToHome? UnpinFromHome, UnpinTohome and RemoveFromHome doesn't seem to have an effect on my system. However, the toggle using PinToHome does work to pin/unpin. :rolleyes:

Also, do you know if it's possible to find out if something is already pinned, so it doesn't get unpinned?
 
You can retrieve the array list of currently pinned Folders:
Code:
($objShell.Namespace("shell:::{679f85cb-0220-4080-b29b-5540cc05aab6}").Items() | where { $_.IsFolder -eq $true }).Name

Desktop
Downloads
Documents
Pictures
Videos
Music
 
You can retrieve the array list of currently pinned Folders:
Code:
($objShell.Namespace("shell:::{679f85cb-0220-4080-b29b-5540cc05aab6}").Items() | where { $_.IsFolder -eq $true }).Name

Desktop
Downloads
Documents
Pictures
Videos
Music

After some more testing I found I was using the wrong CLSID. Instead of using {679F85CB-0220-4080-B29B-5540CC05AAB6} "Quick access," I should have been using {3936E9E4-D92C-4EEE-A85A-BC16D5EA0819} "Frequent Folders." I don't why my PC is not using Quick Access; even if I manually pin a user folder, or any folder, it still only shows in Frequent Folders. Now the pin AND unpin both work appropriately.

Hopefully this info will be helpful to others. If I can get an updated script written, that uses both CLSIDs, I'll post it here.
 
Hello,
3 PowerShell 5.1 functions to manage Quick Accesses :

JavaScript:
function Get-QuickAccessItem {
    <#
    .SYNOPSIS
        Retrieves the list of pinned items in Quick Access.

    .DESCRIPTION
        This function returns all items pinned in Quick Access, providing the following details for each:
          - Its Name (as displayed in Quick Access),
          - Its Full Path,
          - A flag `IsSystem` indicating if it's a system folder,
          - A flag `IsBroken` indicating an invalid path.

    .EXAMPLE
        Get-QuickAccessItem
    .EXAMPLE
        Get-QuickAccessItem | Where-Object { -not $_.IsSystem }

    .AUTHOR
        Jean-Philippe SOSSON
    .VERSION
        25.03.22.1801
    #>

    [CmdletBinding()]
    param ()

    try {
        # Detecting standard system paths for the current user
        Write-Verbose " Detecting standard system paths..."

        # Use COM to retrieve the exact paths of system folders
        $ShellApp = New-Object -ComObject Shell.Application
        $SystemPaths = @{
            Desktop       = $ShellApp.Namespace('shell:Desktop').Self.Path
            Documents     = $ShellApp.Namespace('shell:Personal').Self.Path
            Pictures      = $ShellApp.Namespace('shell:My Pictures').Self.Path
            Music         = $ShellApp.Namespace('shell:My Music').Self.Path
            Videos        = $ShellApp.Namespace('shell:My Video').Self.Path
            Downloads     = $ShellApp.Namespace('shell:Downloads').Self.Path
        }

        Write-Verbose " Detected system paths:"
        $SystemPaths.GetEnumerator() | ForEach-Object { Write-Verbose "  - $($_.Key): $($_.Value)" }

        # Load pinned Quick Access items
        Write-Verbose " Loading pinned Quick Access items..."
        $PinnedNamespace = $ShellApp.Namespace("shell:::{679f85cb-0220-4080-b29b-5540cc05aab6}")
        if (-not $PinnedNamespace) {
            throw "Unable to load pinned items in Quick Access. Ensure that Quick Access is properly configured."
        }

        $PinnedItems = $PinnedNamespace.Items()

        # Analyze each Quick Access item
        $Results = foreach ($PinnedItem in $PinnedItems) {
            $Path = $PinnedItem.Path
            $IsSystem = $false

            # Check if the path EXACTLY matches a known system folder
            foreach ($SystemPath in $SystemPaths.Values) {
                if ($Path -eq $SystemPath) { # Strict comparison
                    Write-Verbose "✅ '$Path' identified as system."
                    $IsSystem = $true
                    break
                }
            }

            # Construct an output object for each item
            [PSCustomObject]@{
                Name     = $PinnedItem.Name
                Path     = $Path
                IsSystem = $IsSystem
                IsBroken = if (Test-Path $Path) { $false } else { $true }
            }
        }

        return $Results
    } catch {
        # Error handling
        Write-Error "❌ An error occurred: $($_.Exception.Message)"
    }
}

function Remove-QuickAccessItem {
    <#
    .SYNOPSIS
        Removes one or more Quick Access items by name or path.

    .DESCRIPTION
        This function removes Quick Access items based on their name, path, or invalid paths.
        If `-Force` is used, no confirmation is required, and the function returns an array
        of the removed items.

    .PARAMETER Name
        Name(s) or patterns of the target items (default: `*` for "all").

    .PARAMETER Path
        Full path(s) of the target items.

    .PARAMETER IncludeSystem
        Include system items (e.g., Desktop, Downloads, etc.).

    .PARAMETER OnlyBrokenPaths
        Remove only items pointing to invalid paths.

    .PARAMETER Force
        Deletes items without requesting confirmation and directly returns a table of removed items.

    .EXAMPLES
        Remove-QuickAccessItem -Name "Videos"
        Remove-QuickAccessItem -Path "C:\Users\Support Technician\Videos"
        Remove-QuickAccessItem -IncludeSystem -OnlyBrokenPaths
        Remove-QuickAccessItem -Name "Downloads" -Force

    .AUTHOR
        Jean-Philippe SOSSON
    .VERSION
        25.03.22.1811
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param (
        [string[]]$Name = '*',       # Pattern(s) of target item names
        [string[]]$Path,             # Full path(s) of the target items
        [switch]$IncludeSystem,      # Include system items
        [switch]$OnlyBrokenPaths,    # Remove only invalid paths
        [switch]$Force               # Skip confirmation and directly return table
    )

    begin {
        # Initialize COM object
        $ShellApp = New-Object -ComObject Shell.Application
        $QuickAccessFolder = $ShellApp.Namespace("shell:::{679f85cb-0220-4080-b29b-5540cc05aab6}")

        if (-not $QuickAccessFolder) {
            Write-Error "❌ Unable to access Quick Access. Check your system configuration."
            return
        }

        # Get Quick Access items
        $QuickAccessItems = Get-QuickAccessItem
    }

    process {
        # Filter target items based on parameters
        $TargetItems = @()
        if ($OnlyBrokenPaths) {
            $TargetItems = $QuickAccessItems | Where-Object {
                $_.IsBroken -and ($IncludeSystem -or -not $_.IsSystem)
            }
        } elseif ($Path) {
            $TargetItems = $QuickAccessItems | Where-Object {
                $_.Path -in $Path -and ($IncludeSystem -or -not $_.IsSystem)
            }
        } elseif ($Name) {
            $TargetItems = $QuickAccessItems | Where-Object {
                $_.Name -like $Name -and ($IncludeSystem -or -not $_.IsSystem)
            }
        }

        # Check if any items were found
        if (-not $TargetItems) {
            Write-Verbose "⚠️ No matching items found."
            return
        }

        # If Force is enabled, no confirmation, just delete items
        if ($Force) {
            foreach ($Item in $TargetItems) {
                try {
                    $QuickAccessFolder.Items() | Where-Object { $_.Path -eq $Item.Path } |
                        ForEach-Object { $_.InvokeVerb("unpinfromhome") }
                    Write-Verbose "✅ Removed: '$($Item.Name)'"
                } catch {
                    Write-Error "❌ Failed: '$($Item.Name)' - $($_.Exception.Message)"
                }
            }
            return $TargetItems # Return array of removed items
        }

        # show all TargetItems in console for deleting confirmation
        $TargetItems | Format-Table -AutoSize | Out-String | Write-Host

        # Non-Force mode: Ask for confirmation and process each item
        if ($PSCmdlet.ShouldContinue("Do you really want to delete these Quick Accesses ?", "CONFIRMATION")) {
            foreach ($Item in $TargetItems) {
                try {
                    $QuickAccessFolder.Items() | Where-Object { $_.Path -eq $Item.Path } |
                        ForEach-Object { $_.InvokeVerb("unpinfromhome") }
                    Write-Host -ForegroundColor Green "✅ Removed: '$($Item.Name)'"
                } catch {
                    Write-Error "❌ Failed: '$($Item.Name)' - $($_.Exception.Message)"
                }
            }
        } else {
            Write-Host -ForegroundColor Yellow "Action canceled by the user."
        }
    }
}

function Add-QuickAccessItem {
    <#
    .SYNOPSIS
        Pins an item to Windows Explorer Quick Access.

    .DESCRIPTION
        This function pins the specified item (file or folder) to Windows Explorer's Quick Access,
        and the function returns :
        - Its Name (as displayed in Quick Access),
        - Its Full Path,
        - A flag `IsSystem` indicating if it's a system folder,
        - A flag `IsBroken` indicating an invalid path.

        If the item is already pinned to Quick Access, it skips the addition and returns the existing item.

    .PARAMETER Path
        The full path to the item you want to pin to Quick Access.

    .EXAMPLE
        Add-QuickAccessItem -Path "C:\MyFolder"

    .AUTHOR
        Jean-Philippe SOSSON
    .VERSION
        25.03.22.1837
    #>
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string]$Path
    )

    Begin {
        # Check if the path exists
        if (-not (Test-Path -Path $Path)) {
            Write-Error "❌ The path '$Path' does not exist."
            return
        }
    }

    Process {
        try {
            # Check if the item is already in Quick Access
            $ExistingItem = Get-QuickAccessItem | Where-Object { $_.Path -eq $Path }
            if ($ExistingItem) {
                Write-Verbose "ℹ️ The item '$Path' is already pinned to Quick Access."
                return $ExistingItem
            }

            # Create the Shell object
            $Shell = New-Object -ComObject Shell.Application

            # Get the folder or file object
            $Item = Get-Item -Path $Path

            # Get the namespace for the item
            $Namespace = $Shell.Namespace($Item.Parent.FullName)
            if (-not $Namespace) {
                throw "Unable to access the parent folder of '$Path'."
            }

            # Get the item in the namespace
            $ShellItem = $Namespace.ParseName($Item.Name)
            if (-not $ShellItem) {
                throw "Unable to access the item '$Path'."
            }

            # Pin the item to Quick Access
            $ShellItem.InvokeVerb("pintohome")

            Write-Verbose "✅ The item '$Path' has been successfully added to Quick Access."
            # Return the Quick Access item details
            return Get-QuickAccessItem | Where-Object { $_.Path -eq $Path }
        }
        catch {
            Write-Error "❌ An error occurred while adding the item to Quick Access: $($_.Exception.Message)"
            return
        }
    }
}
 
Last edited:
Back
Top