我知道如何使用 Windows Script Host 创建快捷方式 (关联)。但我需要的是创建快捷方式并放置在特定位置没有必须使用鼠标手动放置它们。可以通过脚本(例如 PowerShell 等)实现吗?
原因是我喜欢将 Windows 安装脚本化,以便安装脚本可以受到变更控制(例如,Git)。
答案1
这相当复杂,因为使用 Explorer 视图(如桌面)需要使用几个不支持脚本的 COM 对象,但可以在 PowerShell 中通过在某些嵌入式 C# 中声明本机函数和结构的 .NET 表示来完成。基于这篇 Raymond Chen 的文章,我写了这个脚本:
Param(
[Parameter(Mandatory)][string]$ShortcutTitle,
[Parameter(Mandatory)][int]$IconX,
[Parameter(Mandatory)][int]$IconY,
[Parameter(Mandatory)][string]$TargetPath,
[Parameter()][string]$Arguments = '',
[Parameter()][string]$WorkingDirectory = ''
)
# Declarations of COM interfaces, functions, and structures for interfacing with the shell
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public struct POINT {
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
public struct STRRET {
int uType;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 260)]
byte[] cStr;
}
public class PInvoke {
[DllImport("Shlwapi.dll")]
public static extern int StrRetToStrW(ref STRRET pstr, IntPtr pidl, [MarshalAs(UnmanagedType.LPWStr)] ref string ppsz);
}
[ComImport]
[Guid("6d5140c1-7436-11ce-8034-00aa006009fa")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ComServiceProvider {
[return: MarshalAs(UnmanagedType.IUnknown)]
Object QueryService(ref Guid guidService, ref Guid riid);
}
[ComImport]
[Guid("000214e2-0000-0000-c000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IShellBrowser {
IntPtr GetWindow();
void UnusedContextSensitiveHelp();
void UnusedInsertMenusSB();
void UnusedSetMenuSB();
void UnusedRemoveMenusSB();
void UnusedSetStatusTextSB();
void UnusedEnableModelessSB();
void UnusedTranslateAcceleratorSB();
void UnusedBrowseObject();
void UnusedGetViewStateStream();
void UnusedGetControlWindow();
void UnusedSendControlMsg();
[return: MarshalAs(UnmanagedType.IUnknown)]
Object QueryActiveShellView();
void UnusedOnViewWindowActive();
void UnusedSetToolbarItems();
}
[ComImport]
[Guid("1af3a467-214f-4298-908e-06b03e0b39f9")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IFolderView2 {
void UnusedGetCurrentViewMode();
void UnusedSetCurrentViewMode();
[return: MarshalAs(UnmanagedType.IUnknown)]
Object GetFolder(ref Guid riid);
IntPtr Item(int iItemIndex);
int ItemCount(int uFlags);
void UnusedItems();
void UnusedGetSelectionMarkedItem();
void UnusedGetFocusedItem();
POINT GetItemPosition(IntPtr pidl);
void UnusedGetSpacing();
void UnusedGetDefaultSpacing();
void UnusedGetAutoArrange();
void UnusedSelectItem();
void SelectAndPositionItems(int cidl, [MarshalAs(UnmanagedType.LPArray)] IntPtr[] apidl, [MarshalAs(UnmanagedType.LPArray)] POINT[] apt, int dwFlags);
void UnusedSetGroupBy();
void UnusedGetGroupBy();
void UnusedSetViewProperty();
void UnusedGetViewProperty();
void UnusedSetTileViewProperties();
void UnusedSetExtendedTileViewProperties();
void UnusedSetText();
void SetCurrentFolderFlags(int dwMask, int dwFlags);
int GetCurrentFolderFlags();
void UnusedGetSortColumnCount();
void UnusedSetSortColumns();
void UnusedGetSortColumns();
void UnusedGetItem();
void UnusedGetVisibleItem();
void UnusedGetSelectedItem();
void UnusedGetSelection();
void UnusedGetSelectionState();
void UnusedInvokeVerbOnSelection();
void UnusedSetViewModeAndIconSize();
void UnusedGetViewModeAndIconSize();
void UnusedSetGroupSubsetCount();
void UnusedGetGroupSubsetCount();
void UnusedSetRedraw();
void UnusedIsMoveInSameFolder();
void UnusedDoRename();
}
[ComImport]
[Guid("000214e6-0000-0000-c000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IShellFolder {
void UnusedParseDisplayName();
void UnusedEnumObjects();
void UnusedBindToObject();
void UnusedBindToStorage();
void UnusedCompareIDs();
void UnusedCreateViewObject();
void UnusedGetAttributesOf();
void UnusedGetUIObjectOf();
STRRET GetDisplayNameOf(IntPtr pidl, int uFlags);
void UnusedSetNameOf();
}
"@
# Find the desktop window
$shellWindowsType = [type]::GetTypeFromCLSID('9ba05972-f6a8-11cf-a442-00a0c90a8f39')
$shellWindows = [activator]::CreateInstance($shellWindowsType)
$csidlDesktop = 0
$empty = $null
$desktopWindow = $shellWindows.FindWindowSW([ref]$csidlDesktop, [ref]$empty, 8, 0, 1)
$shellBrowser = [ComServiceProvider].GetMethod('QueryService').Invoke($desktopWindow, @([guid]'4c96be40-915c-11cf-99d3-00aa004ae837', [guid]'000214e2-0000-0000-c000-000000000046'))
$folderView = [IShellBrowser].GetMethod('QueryActiveShellView').Invoke($shellBrowser, @())
# Uncheck "auto arrange icons" so icons can be moved
[IFolderView2].GetMethod('SetCurrentFolderFlags').Invoke($folderView, @(1, 0))
# Note how many icons were displayed before so we can know when the new one appeared
$iconCount = [IFolderView2].GetMethod('ItemCount').Invoke($folderView, @(0))
# Determine the path for the new shortcut
$wshShell = New-Object -ComObject WScript.Shell
$desktopPath = $wshShell.SpecialFolders('Desktop')
$lnkPath = "$desktopPath\$ShortcutTitle.lnk"
If (-not (Test-Path $lnkPath)) {
# If it doesn't already exist, create it with the WScript API
$shortcut = $wshShell.CreateShortcut($lnkPath)
$shortcut.TargetPath = $TargetPath
$shortcut.Arguments = $Arguments
$shortcut.WorkingDirectory = $WorkingDirectory
$shortcut.Save()
# Refresh the desktop and wait for Explorer to add an icon for the new shortcut
$desktopWindow.Refresh()
$newIconCount = $iconCount + 1
$tries = 0
While ($iconCount -lt $newIconCount -and $tries -lt 30) {
$iconCount = [IFolderView2].GetMethod('ItemCount').Invoke($folderView, @(0))
$tries += 1
Start-Sleep -Milliseconds 100
}
}
# Get a representation of the folder that allows reading item names
$shellFolder = [IFolderView2].GetMethod('GetFolder').Invoke($folderView, @([guid]'000214e6-0000-0000-c000-000000000046'))
# Iterate over the icons to find the new one
0..($iconCount - 1) | % {
# Get the ID and name of this icon
$pidl = [IFolderView2].GetMethod('Item').Invoke($folderView, @([int]$_))
$strret = [IShellFolder].GetMethod('GetDisplayNameOf').Invoke($shellFolder, @($pidl, 0x1000))
$name = ''
If ([PInvoke]::StrRetToStrW([ref]$strret, $pidl, [ref]$name) -eq 0) {
If ($name -eq $ShortcutTitle) {
# If it's the one we made, set its position
$point = [POINT]::new()
$point.x = $IconX
$point.y = $IconY
[IFolderView2].GetMethod('SelectAndPositionItems').Invoke($folderView, @(1, [IntPtr[]]@($pidl), [POINT[]]@($point), 128))
# No need to look at the remaining icons - exit the script
Break
}
}
}
WScript.Shell
如果快捷方式不存在,它将使用经典脚本组件来创建快捷方式,然后使用 shell COM API 来查找并重新定位该快捷方式。
例如,如果将这个大脚本保存为positionedshortcut.ps1
,则以下命令将在 (1000, 400) 附近创建一个名为“Friendly Shell”的有点愚蠢的快捷方式,该快捷方式会启动带有问候语的命令提示符。如果已存在同名的快捷方式,它将被移回该位置,但其他情况不会改变。
powershell -ExecutionPolicy Bypass -c .\positionedshortcut.ps1 -ShortcutTitle 'Friendly Shell' -IconX 1000 -IconY 400 -TargetPath 'C:\Windows\System32\cmd.exe' -Arguments '/k echo Hi there!'
要设置其他快捷键属性,请$shortcut
像平常一样操作对象。新对象的坐标$point
以像素为单位;如果您需要适应不同的屏幕尺寸,您可以从脚本获取屏幕分辨率也。