Published in Articles on by Michiel van Oosterhout ~ 5 min read

The Windows 11 taskbar, like the Start menu, may have apps pinned to it. The pinned apps apear to the right of the taskbar items (the icons that launch the Start menu, search, task view, widgets, and chat). A custom set of pinned apps on the taskbar can be maintained via a registry-based group policy for Windows Explorer.

Pinned apps in the Windows 11 taskbar
Pinned apps in the Windows 11 taskbar

Registry-based group policy

Group Policy for Windows is a way to manage Windows clients that are joined to an Active Directory domain, by distributing configuration from central location. But a stand-alone Windows computer can also be configured using local group policies.

Registry-based group policies are policies that, when applied, will result in changes to the Windows Registry database (see Implementing Registry-based Policy). Local registry-based group policies for the computer and for the local users are stored in 2 Registry.pol files in the $Env:SystemRoot\System32\GroupPolicy\Machine and User directories respectively. These are binary files (see Registry Policy File Format), and Microsoft provides a tool (LGPO.exe) that can "import and apply policy settings". This tool can be downloaded as part of the Microsoft Security Compliance Toolkit 1.0 (includes documentation).

Even if we were to use this tool to set a local registry-based group policy and then apply it, it would require us to know the mapping of the registry-based group policy to the actual registry keys and values. We might as well use Windows PowerShell to create or update the registry. The only downside is that changing the registry keys used by registry-based policies (e.g. HKCU:\Software\Policies) requires an elevated Windows PowerShell session.

Start Layout policy

The Start Layout policy can be used to modify the taskbar's pinned apps. The policy has 2 parts, Start Layout File (a text box to specify the path to an XML file) and Reapply layout at every logon (a check box). The policy itself and the parts map to the following registry values under HCKU:Software\Policies:

  • Microsoft\Windows\Explorer,LockedStartLayout,REG_DWORD
  • Microsoft\Windows\Explorer,StartLayoutFile,REG_EXPAND_SZ
  • Microsoft\Windows\Explorer,ReapplyStartLayoutEveryLogon,REG_DWORD

The schema of the XML file is described in Customize the Taskbar on Windows 11 (for group policy) and Customize the Taskbar (for OEMs).

Script

The Windows PowerShell script below will create values in the registry in a way that is similar to applying the Start Layout policy. This script must be run from an elevated Windows PowerShell session. After running this script you have to sign out and sign back in for Windows Explorer to check the registry.

# Ensure the directory for to store the layout file exists
$path = "$Env:LOCALAPPDATA\Microsoft\Windows\Shell"
$directory = New-Item -Force -ItemType Directory -Path $path

# XML namespace URIs
$rootNamespaceUri = "http://schemas.microsoft.com/Start/2014/LayoutModification"
$defaultlayoutNamespaceUri = "http://schemas.microsoft.com/Start/2014/FullDefaultLayout"
$taskbarNamespaceUri = "http://schemas.microsoft.com/Start/2014/TaskbarLayout"

# The path to the Start Layout File
$fileName = "LayoutModification.xml"
$filePath = Join-Path $path $fileName

# The XML document
$document = [xml]::new()

if (-not (Test-Path $filePath))
{
    $declaration = $document.CreateXmlDeclaration("1.0", "utf-8", $null)
    $document.AppendChild($declaration) | Out-Null

    $root = $document.CreateElement("LayoutModificationTemplate", $rootNamespaceUri)
    $root.SetAttribute("xmlns:defaultlayout", $defaultlayoutNamespaceUri)
    $root.SetAttribute("xmlns:taskbar", $taskbarNamespaceUri)
    $root.SetAttribute("Version", "1")
    $document.AppendChild($root) | Out-Null

    $customTaskbarLayoutCollectionn = $document.CreateElement("CustomTaskbarLayoutCollection", $rootNamespaceUri)
    $root.AppendChild($customTaskbarLayoutCollectionn) | Out-Null

    $taskbarLayout = $document.CreateElement("defaultlayout", "TaskbarLayout", $defaultlayoutNamespaceUri)
    $customTaskbarLayoutCollectionn.AppendChild($taskbarLayout) | Out-Null

    $taskbarPinList = $document.CreateElement("taskbar", "TaskbarPinList", $taskbarNamespaceUri)
    $taskbarLayout.AppendChild($taskbarPinList) | Out-Null

    Write-Host "Creating initial taskbar layout modification file..." -NoNewline
    $document.Save($filePath) | Out-Null
    Write-Host " OK"
}
else
{
    Write-Host "Loading initial taskbar layout modification file..." -NoNewline
    $document.Load($filePath)
    Write-Host " OK"

    $taskbarPinList = $document.GetElementsByTagName("TaskbarPinList", $taskbarNamespaceUri)[0]
}

# List of apps to pin
$pinnedApps = @(
    @{ Type = "DesktopApp"; Id = "Microsoft.Windows.Explorer"; DisplayName = "File Explorer" }
    @{ Type = "UniversalWindowsApp"; Id = "Microsoft.MicrosoftEdge_8wekyb3d8bbwe!MicrosoftEdge"; DisplayName = "Edge" }
    @{ Type = "UniversalWindowsApp"; Id = "Microsoft.WindowsTerminal_8wekyb3d8bbwe!App"; DisplayName = "Windows Terminal" }
)

# Pin each app by adding the pinned app's type-specific XML element to the taskbar pin list element
$pinnedApps | ForEach-Object {
    $displayName = $_.DisplayName
    $id = $_.Id

    switch ($_.Type)
    {
        "DesktopApp" {
            $taskbarPin = $document.CreateElement("taskbar", "DesktopApp", $taskbarNamespaceUri)
            $taskbarPin.SetAttribute("DesktopApplicationID", $id)
        }
        "Link" {
            $taskbarPin = $document.CreateElement("taskbar", "DesktopApp", $taskbarNamespaceUri)
            $taskbarPin.SetAttribute("DesktopApplicationLinkPath", $id)
        }
        "UniversalWindowsApp" {
            $taskbarPin = $document.CreateElement("taskbar", "UWA", $taskbarNamespaceUri)
            $taskbarPin.SetAttribute("AppUserModelID", $id)
        }
    }

    Write-Host "Pinning [$displayName]..." -NoNewline
    $taskbarPinList.AppendChild($taskbarPin) | Out-Null
    Write-Host " OK"
}

# Save the Start Layout File
$document.Save($filePath)

# Create the registry-based policy's registry values
$policyPath = "HKCU:\Software\Policies\Microsoft\Windows\Explorer"
if (-not (Test-Path -Path $policyPath))
{
    # Ensuring the registry key exists
    $key = New-Item -Force -ItemType Directory -Path $policyPath
}

# Force the (re)creation of the registry values
$value = New-ItemProperty -Force -Name "LockedStartLayout" -Path $policyPath -PropertyType "DWord" -Value 1
$value = New-ItemProperty -Force -Name "StartLayoutFile" -Path $policyPath -PropertyType "ExpandString" -Value $filePath
$value = New-ItemProperty -Force -Name "ReapplyStartLayoutEveryLogon" -Path $policyPath -PropertyType "DWord" -Value 1

The script uses the types in the System.Xml namespace to create the Start Layout File if the file does not exist yet. Apps are then pinned by adding <taskbar:DesktopApp> and <taskbar:UWA> child nodes to the <taskbar:TaskbarPinList> element. This technique lends itself to pinning newly installed apps in the future.

Summary

Apps can be pinned to the taskbar via a registry-based local group policy that references an XML file. By adding the registry keys directly we can get the same result we'd get from manually applying the local group policy. The XML file can be updated later when you want to pin newly installed apps.