Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
# PowerShell
# PowerShell

See this project for start menu layout changes
https://github.com/andyhoffman12/Start-Menu-Layout
200 changes: 97 additions & 103 deletions StartMenuLayout/Create-Start-Menu-Layout-XML.ps1
Original file line number Diff line number Diff line change
@@ -1,27 +1,54 @@
# Set file and folder names
$LayoutPath = "C:\StartMenu"
$LayoutXMLFile = "Layout.xml"
$StartAppsFile = "StartApps.csv"
$PublisherIDsFile = "PublisherIDs.txt"
#this is the Logon script.
Param(
# Working Folder for files. Should be the same location you define Group Policy to look for the "layout.xml", This will try and grab the value from the registry'
[Parameter(Mandatory = $false)]
$layoutPath = (Split-Path -path (Get-ItemPropertyValue -Path "HKCU:\Software\Policies\Microsoft\Windows\Explorer\" -Name StartLayoutFile)) + "\"
)
#set global Error actions
$ErrorActionPreference = "Stop"
$ErrorView = "CategoryView"

# Set the output location of the layout xml file
$XmlPath = "$LayoutPath\$LayoutXMLFile"
# Set file and folder names
#the layout path does not need to be set here unless the layout path gpo is not used or if the xml path is stored on a network drive used by multiple users(why wold you use this script then)
#$layoutPath = "C:\ProgramData\StartMenu\"

# Get the text files containing groups of apps to be pinned
# The files must be named GROUPNAME.1 and GROUPNAME.2
# where GROUPNAME = the desired display name of the group in the start menu
$GroupFiles = Get-ChildItem -Path $LayoutPath\* -Include *.1, *.2
if (!(Test-Path -path $layoutPath)) {
Write-Error "No usable layout path found"
Write-Error $error[0].exception
}
$layoutXMLFile = $layoutPath + "Layout.xml"
$tempLayoutXMLFile = $layoutPath + "Layout_temp.xml"

# Get the Name (file name + extension) and BaseName (file name only) properties of the group files
$Group1 = $GroupFiles | Where-Object {$_.Name -match ".1"} | Select-Object -Property Name,BaseName
$Group2 = $GroupFiles | Where-Object {$_.Name -match ".2"} | Select-Object -Property Name,BaseName
$availableStartAppsJson = $layoutPath + "AvailableStartApps.json"
$menuListJson = $layoutPath + "Menu.json"

# Load the valid start Apps list
try {
$availablestartApps = Get-Content -path $availableStartAppsJson | ConvertFrom-Json
}
catch {
Write-Warning "Unable to load StartApps from $availableStartAppsJson"
Write-Error $_
}
# Load the menu groups
try {
$menuGroups = Get-Content $menuListJson | ConvertFrom-Json
}
catch {
Write-Warning "!!!Unable to load Menu Groups from: $menuListJson"
Write-Error $_
}

function WriteShellXML {

# Write the required XML elements for the Start layout file
$writer.WriteStartElement("LayoutModificationTemplate", "http://schemas.microsoft.com/Start/2014/LayoutModification")
$writer.WriteAttributeString("Version", "1")

$writer.WriteStartElement("LayoutOptions")
$writer.WriteAttributeString("StartTileGroupCellWidth", "6")
$writer.WriteEndElement()

$writer.WriteStartElement("DefaultLayoutOverride")
$writer.WriteAttributeString("LayoutCustomizationRestrictionType", "OnlySpecifiedGroups")

Expand All @@ -31,109 +58,76 @@ function WriteShellXML {
$writer.WriteAttributeString("GroupCellWidth", "6")
}

function WriteTileXML ($Group) {
function WriteTileXML ($group) {
# Write the Start Group XML Element
$writer.WriteStartElement("start", "Group", "http://schemas.microsoft.com/Start/2014/StartLayout")
$writer.WriteAttributeString("Name", $group.name)

switch ($Group) {
#this looses the order of the apps from menu.json/$group.members
$confirmedStartApps = $availablestartApps | Where-Object { $_.Name -in $group.members }

1 {$GroupNumber = $Group1}
2 {$GroupNumber = $Group2}
$sortedConfirmedStartApps = @()
foreach ($member in $group.members) {
$sortedConfirmedStartApps += $confirmedStartApps | Where-Object { $_.name -eq $member }
}

# Get the list of apps for the designated group
$GroupApps = Get-Content -Path $LayoutPath\$($GroupNumber.Name)

# Set the group name to be displayed in the Start Menu
$StartGroupName = $GroupNumber.BaseName

# Write the Start Group XML Element
$writer.WriteStartElement("start", "Group", "http://schemas.microsoft.com/Start/2014/StartLayout")
$writer.WriteAttributeString("Name", $StartGroupName)

# Set loop counter
$Counter = 0

# Loop through the group apps list and write corresponding XML elements
foreach ($Item in $GroupApps) {

# Get the start app info for the pinned list item
$StartApp = $StartAppsCSV | Where-Object {$_.Name -eq $Item}

# Check for existence of start app
if ($StartApp) {

# Determine modern or desktop app, set corresponding tile and appID types
if (($PubIDs | ForEach-Object {$StartApp.AppID -match $_} ) -contains $true) {

$TileType = "Tile"
$AppIDType = "AppUserModelID"
}
else {

$TileType = "DesktopApplicationTile"
$AppIDType = "DesktopApplicationID"
}

# Determine column and row value of the tile
switch ($Counter) {

0 {$column = 0; $row = 0}
1 {$column = 2; $row = 0}
2 {$column = 4; $row = 0}
3 {$column = 0; $row = 2}
4 {$column = 2; $row = 2}
5 {$column = 4; $row = 2}
6 {$column = 0; $row = 4}
7 {$column = 2; $row = 4}
8 {$column = 4; $row = 4}
}

# Write XML elements and attributes for the tile
$writer.WriteStartElement("start", $TileType, "http://schemas.microsoft.com/Start/2014/StartLayout")
$writer.WriteAttributeString("Size", "2x2")
$writer.WriteAttributeString("Column", $column)
$writer.WriteAttributeString("Row", $row)
$writer.WriteAttributeString($AppIDType, $StartApp.AppID)
$writer.WriteEndElement()

# Increment loop counter
$Counter++
}
}
foreach ($confirmedApp in $sortedConfirmedStartApps) {
#$confirmedApp | select AppID, Name, TileType
$index = $sortedConfirmedStartApps.IndexOf($confirmedApp)

$row = [math]::Truncate($index / 3) * 2
$column = ($index % 3) * 2
Write-Output "row: $row column: $column $($confirmedApp.Name)"

#Write XML elements and attributes for the tile
$writer.WriteStartElement("start", $confirmedApp.TileType, "http://schemas.microsoft.com/Start/2014/StartLayout")
$writer.WriteAttributeString("Size", "2x2")
$writer.WriteAttributeString("Column", $column)
$writer.WriteAttributeString("Row", $row)
$writer.WriteAttributeString($confirmedApp.AppIDType, $confirmedApp.AppID)
$writer.WriteEndElement()
}
$writer.WriteEndElement()
}
# If Group 1 exists, proceed. Else, do nothing
if ($Group1) {

# Get Start Apps list
$StartAppsCSV = Import-Csv -Path $LayoutPath\$StartAppsFile

# Get Modern App publisher IDs
$PubIDs = Get-Content -Path $LayoutPath\$PublisherIDsFile

# Create a new XML writer settings object and configure settings
$settings = New-Object system.Xml.XmlWriterSettings
$settings.Indent = $true
$settings.OmitXmlDeclaration = $true
# Create a new XML writer settings object and configure settings
$settings = New-Object system.Xml.XmlWriterSettings
$settings.Indent = $true
$settings.OmitXmlDeclaration = $true

# Create a new XML writer
$writer = [system.xml.XmlWriter]::Create($XmlPath, $settings)
# Create a new XML writer
Try {
$writer = [system.xml.XmlWriter]::Create($tempLayoutXMLFile, $settings)

# Call function to write XML shell
WriteShellXML

# Call function to write tile XML elements
WriteTileXML -Group 1

# Only executed if Group 2 exists
if ($Group2) {

# Write additional end element for Group 1
$writer.WriteEndElement()

WriteTileXML -Group 2
$menuGroups.menuGroup | ForEach-Object {
WriteTileXML -group $_
}

# Flush the XML writer and close the file
$writer.Flush()
$writer.Close()
}
Catch {
Write-Warning "Atempted to write the xml but ran into an issue:"
Write-Error $_
}

if (Test-Path -path $layoutXMLFile) {
#compares the new and old XML
$FilesDifferent = Compare-Object $(Get-Content -LiteralPath $layoutXMLFile) $(Get-Content -LiteralPath $tempLayoutXMLFile )
Write-Output $FilesDifferent
}
else {
$FilesDifferent = $true
}

if ($FilesDifferent) {
Write-Verbose "Start Menu Layout Updated"
Copy-Item $tempLayoutXMLFile $layoutXMLFile -verbose -Force
if (!$?) { throw $error[0].exception }
}
else {
Write-Verbose "Start Menu Layout Not Updated"
}
35 changes: 30 additions & 5 deletions StartMenuLayout/Get-Apps-and-IDs.ps1
Original file line number Diff line number Diff line change
@@ -1,7 +1,32 @@
$LayoutPath = "C:\StartMenu"
$StartAppsFile = "StartApps.csv"
$PublisherIDsFile = "PublisherIDs.txt"
#this is the Logoff script
Param(
# Working Folder for files. Should be the same location you define Group Policy to look for the 'layout.xml, This will try and grab the value from the registry'
[Parameter(Mandatory = $false)]
$layoutPath = (Split-Path -path (Get-ItemPropertyValue -Path "HKCU:\Software\Policies\Microsoft\Windows\Explorer\" -Name StartLayoutFile)) + "\"
)

Get-StartApps | Export-Csv -Path $LayoutPath\$StartAppsFile
#layout path here is commented out as the information is coming from the windows registry for the layout.xml storage location.
#this works ok when using a layout that is stored localy on a computer. If its remote this will collide with other pc's
#to resolve any issues set the parameter on input or set hard coded here:
#$layoutPath = "C:\ProgramData\Startmenu"
$startAppsJson = "AvailableStartApps.json"

Get-AppxPackage | Select-Object -ExpandProperty PublisherID | Sort-Object | Get-Unique | Out-File -FilePath $LayoutPath\$PublisherIDsFile
$pubIDs = Get-AppxPackage | Select-Object -ExpandProperty PublisherID | Sort-Object | Get-Unique
$availableStartApps = Get-StartApps

$availableStartApps | Add-Member -NotePropertyName TileType -NotePropertyValue ""
$availableStartApps | Add-Member -NotePropertyName AppIDType -NotePropertyValue ""

foreach ($app in $availableStartApps) {
#finding if an item contains a publisher ID in APPID or not. Those with PublisherID's are a Tile Type application (ie probably from Microsoft Store)
if ($app.AppID | Select-String $pubIDs -Quiet) {
$app.TileType = "Tile"
$app.AppIDType = "AppUserModelID"
}
else {
$app.TileType = "DesktopApplicationTile"
$app.AppIDType = "DesktopApplicationID"
}
}

$availableStartApps | ConvertTo-Json | Out-File -FilePath $layoutPath\$startAppsJson
42 changes: 42 additions & 0 deletions StartMenuLayout/Menu.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"menuGroup": [
{
"name": "DSTI Common Apps",
"members": [
"Log Off",
"This PC",
"Internet Explorer",
"Google Chrome",
"Calculator",
"Word 2013",
"Excel 2013",
"PowerPoint 2013",
"Outlook 2013",
"OneNote 2013",
"Epicor 10",
"Epicor 10 MES",
"Skype",
"SysAid",
"Weather"
]
},
{
"name": "DSTI Job Specific",
"members": [
"Notepad",
"Remote Desktop Manager",
"Visual Studio Code",
"Wrike for Windows",
"PDF-XChange Editor",
"Epicor 10 - No SSO",
"Epicor 10 MES - TEST",
"Epicor 10 TEST",
"Amazon Chime",
"ShoreTel Communicator",
"DWG TrueView 2019 - English",
"Edrawings 2018 x64 Edition",
"Microsoft Edge"
]
}
]
}