2021年编辑:
从本质上讲,我的函数所做的就是使用从 shell.application ComObject 调用的“pin”动词。这在 2020 年我写答案时曾经有效,但现在似乎已经过时了。
$Shell = New-Object -ComObject "Shell.Application"
$Folder = $Shell.Namespace((Get-Item $ShortcutPath).DirectoryName)
$Item = $Folder.ParseName((Get-Item $ShortcutPath).Name)
$Item.InvokeVerb("pin")
几年前有一个官方的 Windows API 可以做到这一点,但它也被删除了。
原答案
这是一个将执行以下操作的函数:
- 使用提供的完整路径创建临时快捷方式。
- 添加参数/图标/热键和描述(如果有)
- 调用临时快捷方式上的固定动词来创建固定项目。
固定的项目将引用您的应用程序,而不是临时快捷方式(无论如何,该快捷方式已被删除)
使用时只需填写参数即可(仅Path为必填项)
使用所有参数和泼溅的示例
$PinParams = @{
Path = 'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe'
Arguments = '-incognito'
Name = 'Chrome Incognito'
Description = 'Launch Chrome (Incognito)'
Hotkey = 'ALT+CTRL+K'
IconLocation = 'C:\Windows\system32\shell32.dll,22'
RunAsAdmin = $true
}
New-PinnedItem @PinParams
简单的例子
New-PinnedItem -Arguments '-incognito' -Path 'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe'
仅支持所有项目的名称$env:Path
/ 安装的应用程序
New-PinnedItem -Path 'notepad.exe' # Works because c:\windows\system32 is in $env:path
New-PinnedItem -Path 'chrome.exe' # Works because install path in installed appliation
New-PinnedItem -Path 'chrome' # Automatically assume *.exe if no extension provided
支持启动Powershell命令
# Internet options CPL
$inetcpl = @{
Command = { Start-Process inetcpl.cpl }
Name = 'inetcpl'
IconLocation = 'C:\Windows\system32\shell32.dll,99'
}
# Win + R
New-PinnedItem @inetcpl
$runcmd = @{
Command = { $obj = New-Object -ComObject Shell.Application; $obj.FileRun() }
Name = 'Run'
IconLocation = 'C:\Windows\system32\shell32.dll,25'
}
New-PinnedItem @runcmd
#Multiline will automatically be converted to single line behind the scene.
New-PinnedItem -name 'test' -Command {
Write-Host 'test'
pause
} -WindowStyle Normal
--
功能定义
Function New-PinnedItem {
[CmdletBinding()]
param (
[ValidateScript( { $_.IndexOfAny([System.IO.Path]::GetInvalidFileNameChars()) -eq -1 })]
[Parameter(ParameterSetName = 'Path')]
[Parameter(Mandatory, ParameterSetName = 'Command')]
[String]$Name,
[Parameter(Mandatory, ParameterSetName = 'Path')]
[ValidateNotNullOrEmpty()]
[String]$Path,
[Parameter(Mandatory, ParameterSetName = 'Command')]
[scriptblock]$Command,
[ValidateSet('Normal', 'Minimized', 'Maximized')]
[String]$WindowStyle = 'Normal',
[String]$Arguments,
[String]$Description,
[String]$Hotkey,
[String]$IconLocation,
[Switch]$RunAsAdmin,
[String]$WorkingDirectory,
[String]$RelativePath
)
$pinHandler = Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\Windows.taskbarpin" -Name "ExplorerCommandHandler"
New-Item -Path "HKCU:Software\Classes\*\shell\pin" -Force | Out-Null
Set-ItemProperty -LiteralPath "HKCU:Software\Classes\*\shell\pin" -Name "ExplorerCommandHandler" -Type String -Value $pinHandler
if ($PSCmdlet.ParameterSetName -eq 'Command') {
#$Path = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
$Path = "powershell.exe"
$Arguments = ('-NoProfile -Command "&{{{0}}}"' -f ($Command.ToString().Trim("`r`n") -replace "`r`n", ';'))
if (!$PsBoundParameters.ContainsKey('WindowStyle')) {
$WindowStyle = 'Minimized'
}
}
$NoExtension = [System.IO.Path]::GetExtension($path) -eq ""
if (!(Test-Path -Path $Path)) {
if ($NoExtension) {
$Path = "$Path.exe"
}
$Found = $False
$ShortName = [System.IO.Path]::GetFileNameWithoutExtension($path)
# testing against installed programs (Registry)
$loc = Get-ChildItem HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall
$names = ($loc | foreach-object { Get-ItemProperty $_.PsPath }).Where( { ![String]::IsNullOrWhiteSpace($_.InstallLocation) })
$InstallLocations1, $InstallLocations2 = $names.Where( { $_.DisplayName -Like "*$ShortName*" }, 'split')
$InstallLocations1 = $InstallLocations1 | Select -ExpandProperty InstallLocation
$InstallLocations2 = $InstallLocations2 | Select -ExpandProperty InstallLocation
Foreach ($InsLoc in $InstallLocations1) {
if (Test-Path -Path "$Insloc\$path") {
$Path = "$Insloc\$path"
$Found = $true
break
}
}
if (! $found) {
$Result = $env:Path.split(';').where( { Test-Path -Path "$_\$Path" }, 'first')
if ($Result.count -eq 1) { $Found = $true }
}
# Processing remaining install location (less probable outcome)
if (! $found) {
Foreach ($InsLoc in $InstallLocations2) {
if (Test-Path -Path "$Insloc\$path") {
$Path = "$Insloc\$path"
$Found = $true
exit for
}
}
}
if (!$found) {
Write-Error -Message "The path $Path does not exist"
return
}
}
if ($PSBoundParameters.ContainsKey('Name') -eq $false) {
$Name = [System.IO.Path]::GetFileNameWithoutExtension($Path)
}
$TempFolderName = "tmp$((48..57 + 97..122| get-random -Count 4 |% {[char][byte]$_}) -join '')"
$TempFolderPath = "$env:temp\$TempFolderName"
$ShortcutPath = "$TempFolderPath\$Name.lnk"
[Void](New-Item -ItemType Directory -Path $TempfolderPath)
if ($Path.EndsWith(".lnk")) {
Copy-Item -Path $Path -Destination $ShortcutPath
$obj = New-Object -ComObject WScript.Shell
$link = $obj.CreateShortcut($ShortcutPath)
}
else {
$obj = New-Object -ComObject WScript.Shell
$link = $obj.CreateShortcut($ShortcutPath)
$link.TargetPath = $Path
}
switch ($WindowStyle) {
'Minimized' { $WindowstyleID = 7 }
'Maximized' { $WindowstyleID = 3 }
'Normal' { $WindowstyleID = 1 }
}
$link.Arguments = $Arguments
$Link.Description = $Description
if ($PSBoundParameters.ContainsKey('IconLocation')) { $link.IconLocation = $IconLocation }
$link.Hotkey = "$Hotkey"
$link.WindowStyle = $WindowstyleID
if ($PSBoundParameters.ContainsKey('WorkingDirectory')) { $link.WorkingDirectory = $WorkingDirectory }
if ($PSBoundParameters.ContainsKey('RelativePath')) { $link.RelativePath = $RelativePath }
$link.Save()
if ($RunAsAdmin) {
$bytes = [System.IO.File]::ReadAllBytes($ShortcutPath)
$bytes[0x15] = $bytes[0x15] -bor 0x20 #set byte 21 (0x15) bit 6 (0x20) ON
[System.IO.File]::WriteAllBytes($ShortcutPath, $bytes)
}
$Shell = New-Object -ComObject "Shell.Application"
$Folder = $Shell.Namespace((Get-Item $ShortcutPath).DirectoryName)
$Item = $Folder.ParseName((Get-Item $ShortcutPath).Name)
$Item.InvokeVerb("pin")
Remove-Item -LiteralPath "HKCU:Software\Classes\*\shell\pin\" -Recurse
Remove-item -path $ShortcutPath
Remove-Item -Path $TempFolderPath
[void][System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$shell)
[void][System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$obj)
}
总之,根据您的需要,您可以这样称呼它:
New-PinnedItem -Path 'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe' -Arguments '--proxy-server=192.168.1.2:8080'
其他注意事项
看起来固定某些东西时需要考虑两件事。
其他参数对于 PIN 操作无关紧要。
使用同一组完整路径和参数调用的任何 PIN 操作都将与其他引脚以及引脚(如果未找到)或取消固定(如果找到)进行比较,而不考虑名称/图标位置/热键/等...
请注意,如果您使用该函数来固定已打开的项目(例如:Chrome),如果路径/参数匹配,则将在当前实例上执行固定/取消固定操作,这意味着它可能看起来好像不起作用但如果您查看打开的应用程序的固定状态(或关闭它),您应该会看到行为从取消固定更改为固定或固定更改为取消固定(如果已固定)
补充笔记
引脚数据存储在 2 个位置
HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband
$env:APPDATA\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar
您可以通过使用它们轻松交换 2 个或更多引脚的任务栏。
这是一段代码,用于以十六进制/字符串形式查看最喜欢的数据
$Bytes = Get-ItemPropertyValue -LiteralPath "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband\" -name 'Favorites'
# Data as Hex
[System.BitConverter]::ToString($bytes)
# A look at the data
[System.Text.Encoding]::UTF8.GetString($Bytes)
参考
[MS-SHLLINK]:Shell 链接 (.LNK) 二进制文件格式 https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-shllink/16cb4ca1-9339-4d0c-a68d-bf1d6cc0f943?redirectedfrom=MSDN
创建以管理员身份运行快捷方式 https://stackoverflow.com/a/29002207/934946