Microsoft provides a way to manage and enforce a customized Start Menu layout (pinned tiles) in Windows 10:
Documentation Link: https://docs.microsoft.com/en-us/windows/configuration/customize-and-export-start-layout
This blog post will assume that the reader is familiar with the high-level steps involved:
- Manually configuring the Start Menu layout on a Windows 10 system
- Using the Export-StartLayout PowerShell cmdlet to generate a layout XML file
- Applying a policy to machines in your organization so they use the layout XML file
This process works fine, but it’s a static “set it and forget it” approach that doesn’t handle configuration changes or differences very well. I’ve attempted to come up with a more dynamic approach with the following features:
- Read in a group (or two) of apps to be pinned (can be different per system)
- Dynamically generate the layout XML file
- Only write entries for apps that are present/installed on the system
- Write updated layout XML file before logon (prevents issues with the layout file being locked in-use)
- Works for both Modern and Desktop apps
So, to get a better idea of how this works, start by using the Export-StartLayout command, and look at the exported XML file in Notepad:
Notice that for desktop application tiles, it uses DesktopApplicationLinkPath to specify the location of the .lnk or .url file to pin. This means that you must know/maintain the exact location of these items for the Start Menu to be able to display them correctly. Fortunately, you can use the DesktopApplicationID instead. The Microsoft doc I linked to earlier has an “Important” note mentioning this:
So, how do I find the DesktopApplicationID of the items I want to pin? The answer is, via another PowerShell cmdlet called Get-StartApps. If you look under the hood of that cmdlet in
you’ll find that what it’s really doing is enumerating the items found in a “virtual” folder named AppsFolder:
This location can’t be browsed to normally via Windows Explorer, but you can view it by entering shell:AppsFolder into a run command line or explorer bar. This folder essentially contains all the apps available for pinning, both Desktop and Modern.
In summary, by using DesktopApplicationID in the layout XML instead of DesktopApplicationLinkPath, you don’t have to know the location of the items you want to pin. You just need to know the names of the apps, and Get-StartApps will give you the associated app IDs.
Another thing to note in the layout XML is that the entries for Modern apps require different attributes than the Desktop apps. If I’m creating the layout file dynamically, how do I determine the difference between Modern and Desktop apps so I know which attributes to use for which line? Unfortunately, Get-StartApps doesn’t have an explicit property that distinguishes between Modern and Desktop apps. However, the AppID for a Modern app will contain the publisher ID. Example:
If I have a list of the publisher IDs, I can check to see if an AppID contains one, and then I’ll know which XML attributes to write. A list of unique publisher IDs can be obtained with the following PowerShell command:
Get-AppxPackage | Select-Object -ExpandProperty PublisherID | Sort-Object | Get-Unique
The only other information I need to know is the tile size, column, and row values. To greatly simplify the logic involved, I decided to go with a three-by-three group of medium size tiles, meaning that the tile size is the same for all nine tiles: 2×2. That makes the column and row values easy to determine as well.
Now that I know how to dynamically generate the pinned app entries in the layout XML, how do I provide a list (or two) of apps to pin? The answer is to obtain the desired app names from Get-StartApps, and create a simple text file with the app names listed in the order in which you want them to be pinned. Example:
This list of apps
Will result in this Start Menu layout:
Notice that the text file name (Enterprise Apps) determines the name of the group on the Start Menu. Also, the file extension (.1) means that it is the first group of apps that should be pinned. If I create another list of apps with a .2 extension like this:
The resulting Start Menu layout would look like this:
If only the first file exists on the system, only that list of apps is pinned. The group names and app lists are completely customize-able per system.
If an app on the list isn’t found, it is simply skipped, and no line is written for it in the XML. So, for example, a system could be missing three of the nine apps in a group, and the top six spots will still be used, leaving no gaps.
If you put all the related files in the same folder that I’m using as the location in my scripts, it will look like this:
At this point, you should have everything you need to dynamically create the layout XML…but there are a few remaining issues:
- It’s not always the case, but typically I’ve found that a layout XML file that’s already in place can’t be modified while a user is logged on because the file will be locked in-use
- Even if the layout XML is modified, the user wouldn’t see the changes until they log off/log on again (or until explorer.exe is killed/restarted, which doesn’t seem like a very clean workaround outside of testing.)
- A user needs to be logged on for the Get-StartApps and Get-AppxPackage cmdlets to return the full list of available apps and publisher IDs. Running these command as the computer/SYSTEM account will result in only returning the apps that are provisioned for all users.
To work around these issues, I used a two-stage approach:
- A logoff script that runs Get-StartApps and Get-AppxPackage while a user is still logged on, and exports the content into files.
- A startup script that reads the App list and publisher IDs from the exported files, and writes the layout XML file before the user logs back on
Consider the following scenario:
You want to deploy a new app to a certain department in your organization and pin its tile to the Start Menu on those systems. With my process in place, you could automate a step in the app install sequence that simply adds the app name to one of the app list text files. You could then call for the system to restart on completion of the install sequence. The new app gets picked up and written to the layout XML file automatically, and the tile is ready for the user when they log back on. Conversely, you could remove a pinned app on uninstallation without leaving a blank tile in its place.
I have some other ideas that I’ve left out of the scripts for now for the sake of simplicity, but I still want to mention them:
- Add a registry property value check that determines whether a system should have a fully locked down Start Menu, or partially locked down which would add LayoutCustomizationRestrictionType=”OnlySpecifiedGroups” to the layout XML file.
- Create a subfolder for the PublisherIDs and StartApps files that has write permissions for normal users. This will allow the logoff script to run successfully, while the app list and layout xml files can remain in a protected area only accessible to administrators.
The PowerShell scripts can be grabbed from my GitHub page:
Create-Start-Menu-Layout-XML.ps1 is meant to be used as a startup script in group/local policy, and Get-Apps-and-IDs.ps1 is meant to be used as a logoff script. Also, don’t forget that the file path in your Start Layout policy must match the path you use in these scripts:
I don’t have this widely deployed at the moment, but throughout my testing on Windows 10 1607 and 1703, it has seemed to work well and doesn’t add a noticeable amount of time to the logoff/logon/restart process. I’m curious to see what kind of feedback I get from the community. Let me know if you have any ideas for improvement.
Thanks for reading!