· 4 years ago · Feb 20, 2021, 05:40 AM
1<#
2 .SYNOPSIS
3 Pin pre-configured shortcuts to Start
4
5 .PARAMETER ControlPanel
6 Pin the "Control Panel" shortcut to Start
7
8 .PARAMETER DevicesPrinters
9 Pin the "Devices & Printers" shortcut to Start
10
11 .PARAMETER PowerShell
12 Pin the "Windows PowerShell" shortcut to Start
13
14 .EXAMPLE
15 PinToStart -Tiles ControlPanel, DevicesPrinters, PowerShell
16
17 .NOTES
18 Current user
19#>
20function PinToStart
21{
22 [CmdletBinding()]
23 param
24 (
25 [Parameter(Mandatory = $true)]
26 [ValidateSet("ControlPanel", "DevicesPrinters", "PowerShell")]
27 [string[]]
28 $Tiles
29 )
30
31 # Extract string from shell32.dll using its' number
32 # https://github.com/Disassembler0/Win10-Initial-Setup-Script/issues/8#issue-227159084
33 $Signature = @{
34 Namespace = "WinAPI"
35 Name = "GetStr"
36 Language = "CSharp"
37 MemberDefinition = @"
38[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
39public static extern IntPtr GetModuleHandle(string lpModuleName);
40[DllImport("user32.dll", CharSet = CharSet.Auto)]
41internal static extern int LoadString(IntPtr hInstance, uint uID, StringBuilder lpBuffer, int nBufferMax);
42public static string GetString(uint strId)
43{
44 IntPtr intPtr = GetModuleHandle("shell32.dll");
45 StringBuilder sb = new StringBuilder(255);
46 LoadString(intPtr, strId, sb, sb.Capacity);
47 return sb.ToString();
48}
49"@
50}
51 if (-not ("WinAPI.GetStr" -as [type]))
52 {
53 Add-Type @Signature -Using System.Text
54 }
55
56 # Extract the "Devices and Printers" string from shell32.dll
57 $DevicesPrinters = [WinAPI.GetStr]::GetString(30493)
58
59 # Create the old-style "Devices and Printers" shortcut in the Start menu
60 $Shell = New-Object -ComObject Wscript.Shell
61 $Shortcut = $Shell.CreateShortcut("$env:APPDATA\Microsoft\Windows\Start menu\Programs\System Tools\$DevicesPrinters.lnk")
62 $Shortcut.TargetPath = "control"
63 $Shortcut.Arguments = "printers"
64 $Shortcut.IconLocation = "$env:SystemRoot\system32\DeviceCenter.dll"
65 $Shortcut.Save()
66
67 Start-Sleep -Seconds 3
68
69 # Export the current Start layout
70 $StartLayout = "$PSScriptRoot\StartLayout.xml"
71 Export-StartLayout -Path $StartLayout -UseDesktopApplicationID
72
73 $Parameters = @(
74 # Control Panel hash table
75 @{
76 # Special name for Control Panel
77 Name = "ControlPanel"
78 Size = "2x2"
79 Column = 0
80 Row = 0
81 AppID = "Microsoft.Windows.ControlPanel"
82 },
83 # "Devices & Printers" hash table
84 @{
85 # Special name for "Devices & Printers"
86 Name = "DevicesPrinters"
87 Size = "2x2"
88 Column = 2
89 Row = 0
90 AppID = "Microsoft.AutoGenerated.{7FF3FDB0-CFD9-F944-4722-A9E766EDE23F}"
91 },
92 # Windows PowerShell hash table
93 @{
94 # Special name for Windows PowerShell
95 Name = "PowerShell"
96 Size = "2x2"
97 Column = 4
98 Row = 0
99 AppID = "{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\WindowsPowerShell\v1.0\powershell.exe"
100 }
101 )
102
103 # Valid columns to place tiles in
104 $ValidColumns = @(0, 2, 4)
105 [string]$StartLayoutNS = "http://schemas.microsoft.com/Start/2014/StartLayout"
106
107 # Add pre-configured hastable to XML
108 function Add-Tile
109 {
110 param
111 (
112 [string]
113 $Size,
114
115 [int]
116 $Column,
117
118 [int]
119 $Row,
120
121 [string]
122 $AppID
123 )
124
125 [string]$elementName = "start:DesktopApplicationTile"
126 [Xml.XmlElement]$Table = $xml.CreateElement($elementName, $StartLayoutNS)
127 $Table.SetAttribute("Size", $Size)
128 $Table.SetAttribute("Column", $Column)
129 $Table.SetAttribute("Row", $Row)
130 $Table.SetAttribute("DesktopApplicationID", $AppID)
131
132 $Table
133 }
134
135 [xml]$xXML. = Get-Content -Path $StartLayout -Encoding UTF8 -Force
136
137 foreach ($Tile in $Tiles)
138 {
139 $Parameter = $Parameters | Where-Object -FilterScript {$_.Name -eq $Tile}
140 $Group = $XML.LayoutModificationTemplate.DefaultLayoutOverride.StartLayoutCollection.StartLayout.Group | Where-Object -FilterScript {$_.Name -eq "Sophia Script"}
141
142 # If "Sophia Script" group exists in Start
143 if ($Group)
144 {
145 $DesktopApplicationID = ($Parameters | Where-Object -FilterScript {$_.Name -eq $Tile}).AppID
146
147 if (-not ($Group.DesktopApplicationTile | Where-Object -FilterScript {$_.DesktopApplicationID -eq $DesktopApplicationID}))
148 {
149 # Calculate current filled columns
150 $CurrentColumns = @($Group.DesktopApplicationTile.Column)
151 # Calculate current free columns and take the first one
152 $Column = (Compare-Object -ReferenceObject $ValidColumns -DifferenceObject $CurrentColumns).InputObject | Select-Object -First 1
153 # If filled cells contain desired ones assign the first free cell
154 if ($CurrentColumns -contains $Parameter.Column)
155 {
156 $Parameter.Column = $Column
157 }
158 $Group.AppendChild((Add-Tile @Parameter)) | Out-Null
159 }
160 }
161 else
162 { # Create the "Sophia Script" group
163 [Xml.XmlElement]$Group = $XML.CreateElement("start:Group", $StartLayoutNS)
164 $Group.SetAttribute("Name","Sophia Script")
165 $Group.AppendChild((Add-Tile @Parameter)) | Out-Null
166 $XML.LayoutModificationTemplate.DefaultLayoutOverride.StartLayoutCollection.StartLayout.AppendChild($Group) | Out-Null
167 }
168 }
169
170 $XML.Save($StartLayout)
171
172 # Temporarily disable changing the Start menu layout
173 if (-not (Test-Path -Path HKCU:\SOFTWARE\Policies\Microsoft\Windows\Explorer))
174 {
175 New-Item -Path HKCU:\SOFTWARE\Policies\Microsoft\Windows\Explorer -Force
176 }
177 New-ItemProperty -Path HKCU:\SOFTWARE\Policies\Microsoft\Windows\Explorer -Name LockedStartLayout -Value 1 -Force
178 New-ItemProperty -Path HKCU:\SOFTWARE\Policies\Microsoft\Windows\Explorer -Name StartLayoutFile -Value $StartLayout -Force
179
180 Start-Sleep -Seconds 3
181
182 # Restart the Start menu
183 Stop-Process -Name StartMenuExperienceHost -Force -ErrorAction Ignore
184
185 Start-Sleep -Seconds 3
186
187 # Enable changing the Start menu layout
188 Remove-ItemProperty -Path HKCU:\SOFTWARE\Policies\Microsoft\Windows\Explorer -Name LockedStartLayout -Force -ErrorAction Ignore
189 Remove-ItemProperty -Path HKCU:\SOFTWARE\Policies\Microsoft\Windows\Explorer -Name StartLayoutFile -Force -ErrorAction Ignore
190
191 Get-Item -Path $StartLayout | Remove-Item -Force -ErrorAction Ignore
192
193 Stop-Process -Name StartMenuExperienceHost -Force -ErrorAction Ignore
194
195 Start-Sleep -Seconds 3
196}