拖放到Powershell脚本

Drag and Drop to a Powershell script

我以为我对此有一个答案,但是我越玩它,就越把它视为Powershell的设计缺陷。

我想拖放(或使用"发送至"机制)将多个文件和/或文件夹作为数组传递给Powershell脚本。

测试脚本

1
2
3
4
5
6
#Test.ps1
param ( [string[]] $Paths, [string] $ExampleParameter )
"Paths"
$Paths
"args"
$args

尝试#1

我使用以下命令行创建了快捷方式,并将一些文件拖到该快捷方式上。文件作为单独的参数遇到,这些参数首先在位置上与脚本参数匹配,其余部分放在$ args数组中。

尝试#1的捷径

1
powershell.exe -noprofile -noexit -file c:\\Test.ps1

包装脚本

我发现我可以使用包装脚本来做到这一点...

1
2
#TestWrapper.ps1
& .\\Test.ps1 -Paths $args

包装脚本的快捷方式

1
powershell.exe -noprofile -noexit -file c:\\TestWrapper.ps1

批处理文件包装脚本

它通过批处理文件包装脚本工作...

1
2
3
4
5
6
7
8
9
REM TestWrapper.bat
SET args='%1'
:More
SHIFT
IF '%1' == '' GOTO Done
SET args=%args%,'%1'
GOTO More
:Done
Powershell.exe -noprofile -noexit -command"& {c:\\test.ps1 %args%}"

尝试的答案

凯斯·希尔(Keith Hill)提出了使用以下快捷命令行的绝妙建议,但是它没有正确传递参数。带有空格的路径在到达Test.ps1脚本时被分开。

1
powershell.exe -noprofile -noexit -command"& {c:\\test1.ps1 $args}"

有没有人找到一种无需额外脚本就能做到这一点的方法?


我意识到这个问题已经有好几年了,但是由于它仍然是许多与通过Explorer拖放运行PowerShell有关的Google查询的最高结果,我认为我会在今天发布我的想法希望它能帮助别人。

我希望能够将文件拖放到PowerShell(ps1)脚本上,却找不到任何好的解决方案(没有多余的脚本或快捷方式的东西)。我开始在注册表中查看文件关联,然后想出了一些似乎可以正常工作的东西。

首先,我们需要为.PS1文件添加一个DropHandler条目,以便资源管理器知道PS1文件应接受拖放操作。使用此注册表更改来执行此操作。您可能必须创建ShellEx和DropHandler子项。

1
2
HKEY_CLASSES_ROOT\\Microsoft.PowerShellScript.1\\ShellEx\\DropHandler\\
(Default) = {60254CA5-953B-11CF-8C96-00AA00B8708C}

此删除处理程序是Windows脚本宿主使用的处理程序。这是我的首选,因为它支持长文件名。如果遇到空格或其他问题,可以尝试使用标准Shell32可执行文件(.exe,.bat等)放置处理程序:{86C86720-42A0-1069-A2E8-08002B30309D}

这两个放置处理程序都可以通过简单地调用文件类型(称为Shell键的(Default)值)的默认动词(双击文件时会发生这种情况)来工作。对于PS1文件,这是打开的。默认情况下,此动词在记事本中显示PowerShell脚本-不太有用。

通过修改PS1文件的"打开"动词来更改此行为:

1
2
HKEY_CLASSES_ROOT\\Microsoft.PowerShellScript.1\\Shell\\Open\\Command\\
(Default) ="C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" -NoExit -File"%1" %*

打开后,它将在PowerShell中运行脚本,并将所有参数(删除的文件)传递给它。我将其设置为在脚本完成后保持打开状态,但是您可以通过删除-NoExit选项来更改此设置。

而已。我没有做任何广泛的测试,但是到目前为止,它似乎运行得很好。我可以删除单个文件/文件夹以及组。参数列表中文件的顺序并不总是您所期望的(资源管理器如何对选定文件进行排序的怪癖),但除此之外似乎很理想。您还可以在Shell:Sendto中创建PS1文件的快捷方式,从而允许您使用"发送到"菜单传递文件。

这两个都是REG文件格式的变化:

1
2
3
4
5
6
7
Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\\Microsoft.PowerShellScript.1\\ShellEx\\DropHandler]
@="{60254CA5-953B-11CF-8C96-00AA00B8708C}"

[HKEY_CLASSES_ROOT\\Microsoft.PowerShellScript.1\\Shell\\Open\\Command]
@="\"C:\\\\Windows\\\\System32\\\\WindowsPowerShell\\\\v1.0\\\\powershell.exe\" -NoExit -File \"%1\" %*"

笔记:

  • 这些更改的结果是,在资源管理器中双击PS1文件将执行脚本。要进行编辑而不是打开,则必须使用右键菜单。就像一个建议(从痛苦的经验中吸取的教训:)一样,如果您的脚本执行了破坏性的操作,则可以考虑使用确认/健全性检查防护。

  • 通过拖放操作运行的脚本将具有默认的开始工作目录C:\\Windows\\system32\\。另一个要小心的理由。

  • 请记住,除非使用签名脚本,否则您将需要更改执行策略(Set-ExecutionPolicy)。

  • 如果您已调整Windows 10上.PS1文件的处理方式,则需要删除注册表项HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\.ps1\\UserChoice,它将覆盖默认处理方式。


将文件或文件夹传递到Powershell脚本的最简单方法是包装器脚本,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
@echo off
rem the name of the script is drive path name of the Parameter %0
rem (= the batch file) but with the extension".ps1"
set PSScript=%~dpn0.ps1
set args=%1
:More
shift
if '%1'=='' goto Done
set args=%args%, %1
goto More
:Done
powershell.exe -NoExit -Command"& '%PSScript%' '%args%'"

您要做的就是

制作.bat文件的副本

为它提供与脚本文件相同的名称,但扩展名为.bat

例如" hello.ps1" <->" hello.bat"

将文件/文件夹拖放到批处理文件上,它将把它们传递给脚本。

示例脚本代码可能如下所示:

1
2
3
"hello world"
$args
$args.Gettype().Fullname


自此以来,一直以来的秘密一直是参数属性" ValueFromRemainingArguments"。

捷径

1
powershell.exe -noexit -file c:\\Test.ps1

Test.ps1

1
2
3
4
5
6
7
8
[CmdletBinding()]
param (
    [Parameter(ValueFromRemainingArguments=$true)]
    $Path
)

'Paths'
$Path

更改此快捷方式,然后尝试:

1
powershell.exe -noprofile -noexit -command"& {c:\\test1.ps1 $args}"


对于那些正在寻找快速好用的解决方案以将n单个文件拖放到需要字符串参数的Powershell脚本中的用户,可以将其用作快捷方式:

1
powershell.exe -noprofile -noexit -command"& {.\\myscript.ps1 ($args -join ' ')}"

如果您的文件路径具有多个连续的空格,它可能会中断,但这对我来说效果很好,因为我从未遇到过需要使用此技术的此类文件。


不幸的是,$args会将您的参数拆分为空格,从而对包含它们的文件名不正确地起作用。但是,您仍然可以在.ps1内部处理$args,而无需使用外部包装程序脚本!

由于您在快捷方式上拖放的所有内容(或选择"发送到"的内容)都将作为空格连接的绝对路径列表传递到脚本,因此可以使用regex和pipe解析所有出现的绝对路径的$args它去foreach:

1
2
3
4
5
6
$pattern = '([a-zA-Z]:\\\\(((?![<>:"/\\\\|?*]).)+((?<![ .])\\\\)?)*)(\\s|$)'

Select-String"$pattern" -input"$args" -AllMatches | Foreach {$_.matches} | ForEach-Object {
    $path ="$($_.Groups.Groups[1].Value)"
    # ...
}

其中每个$path都是一条绝对路径(包括那些内部有空格的路径)

绝对文件模式是从这里获取的。

我将其包装在括号中(以使第一个组与实际路径匹配),并在末尾添加一个(\\s|$)组,即"空白或字符串结尾",因为快捷方式参数将用单个空格连接;需要$来匹配参数列表中最后一个出现的路径。