在 Windows 10 中使用 PS 将程序(带参数)固定到任务栏

MKA*_*NET 4 powershell windows-10

我可以使用以下代码将程序固定到 Windows 10 任务栏(感谢这个StackOverflow 问题)。但是,如果我尝试向程序添加命令行参数,如下例所示,它不起作用。代码似乎假定目标可执行文件没有任何参数。

$Target = "`"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe`" --proxy-server=192.168.1.2:8080"
Run Code Online (Sandbox Code Playgroud)
$Target = "`"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe`" --proxy-server=192.168.1.2:8080"
Run Code Online (Sandbox Code Playgroud)

Sag*_*pre 6

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")
Run Code Online (Sandbox Code Playgroud)

几年前有一个官方的 Windows API 可以做到这一点,但它也被删除了。

原答案

这是一个将执行以下操作的函数:

  • 使用完整路径提供创建临时快捷方式。
  • 添加参数/图标/热键和描述(如果有)
  • 在临时快捷方式上调用 pin 动词以创建固定项目。

固定的项目将引用您的应用程序,而不是临时快捷方式(当时已被删除)

使用时填写参数即可(Only Path为必填项)

使用所有参数 & splatting 的示例

$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 
Run Code Online (Sandbox Code Playgroud)

简单的例子

New-PinnedItem -Arguments '-incognito' -Path 'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe'
Run Code Online (Sandbox Code Playgroud)

仅支持$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
Run Code Online (Sandbox Code Playgroud)

支持启动 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
Run Code Online (Sandbox Code Playgroud)

——

功能定义

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)
}
Run Code Online (Sandbox Code Playgroud)

总而言之,根据您的需要,您可以这样称呼它:

New-PinnedItem -Path 'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe' -Arguments '--proxy-server=192.168.1.2:8080'
Run Code Online (Sandbox Code Playgroud)

其他注意事项

固定某些东西时,看起来有两件重要的事情需要考虑。

  • 应用程序的完整路径
  • 传下来的论据

其他参数对 PIN 操作无关紧要。使用相同的完整路径和参数集调用的任何 PIN 操作都将与其他引脚进行比较,无论是引脚(如果未找到)还是未固定(如果找到),而不考虑名称 / 图标位置 / 热键 / 等...

请注意,如果您使用该函数来固定已经打开的项目(例如:Chrome),如果路径/参数匹配,则将在当前实例上执行固定/取消固定操作,这意味着它可能看起来好像没有工作但是如果您查看打开的应用程序的 pin 状态(或关闭它),您应该看到行为从 unpinned 变为 pinned 或 pinned 变为 unpinned(如果已经固定)

补充说明

引脚数据存储在 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)
Run Code Online (Sandbox Code Playgroud)

参考

[MS-SHLLINK]:Shell Link (.LNK) 二进制文件格式

创建以管理员身份运行快捷方式