如何使用 PowerShell 可靠地将项目复制到 mtp 设备?

Posted

技术标签:

【中文标题】如何使用 PowerShell 可靠地将项目复制到 mtp 设备?【英文标题】:How to reliably copy items with PowerShell to an mtp device? 【发布时间】:2019-09-01 19:54:37 【问题描述】:

我希望能够将项目(文件、文件夹)从 Windows PC 复制到 MTP 设备。我想使用 PowerShell 来编写脚本。

我找到了这个帖子:shell32 copyhere not working neither in .Net nor powershell script,但那里的答案无助于理解解决方案(并且是由提出该问题的同一个人给出的)。下面是一个最小的代码示例:

param ($itemName)

$shell = New-Object -com Shell.Application

$sourceFolder = $shell.Namespace($(Get-Location).toString()).self
$item = $sourceFolder.GetFolder.Items() | where  $_.Name -eq $itemName 

$mtpDevice = $shell.NameSpace(0x11).items() | where  $_.name -eq 'DUMMY_MODEL' 
$destination = $mtpDevice.GetFolder.items() | where  $_.Name -eq 'Card' 
$destinationFolder=$shell.Namespace($destination).self

$destinationFolder.GetFolder.CopyHere($item)

$shell.open($destinationFolder)
Start-Sleep -s 1

我假设要复制的项目 ($itemName) 存在于 Windows 机器上。我假设 mtp 设备在 Windows 资源管理器中被视为“DUMMY_MODEL”,并且它包含一个空的***文件夹“Card”。

我希望这条线

$destinationFolder.GetFolder.CopyHere($item)

应该做的工作。但事实并非如此。为了让它工作,我需要以编程方式打开目标文件夹窗口并使用睡眠。为什么?上面提到的线程说它是为了让复制线程完成。为什么不打开窗户就不能完成?这可以在不以编程方式打开窗口的情况下完成吗?即使我打开窗口并进行睡眠,复制也不能 100% 可靠地工作。为什么?

【问题讨论】:

Here's another example 使用 Items() 集合进行可靠复制。就个人而言,我是 Beyond Compare 文件管理的粉丝。我每天在家和工作中使用它。版本 4 通过使用 mtp://[phone name]/Card 语法支持 MTP。 Beyond Compare可以用于脚本和无人值守运行吗?可以不购买就永久使用(我的意思是合法使用)? 你提到的例子只做mtp->pc传输,但我有兴趣在相反的方向传输。 我更新了我的帖子,添加了一个更简单的脚本以适应您的需求。题外话:Beyond Compare 具有启动同步的命令行选项,但我对无人值守执行不是很熟悉,无法提供示例。它不是免费的,但它的价值远远超过我为永久许可证支付的 40 美元。 【参考方案1】:

这是基于我对没有很好记录的 Shell.Application 对象的有限了解。如果有人知道的更好,请随时纠正。

Shell.Application COM 对象是 Windows Explorer shell 的副本,它异步执行文件操作。为每个“复制”操作创建一个新线程,$shell 实例管理这些线程并接收事件 - 完成/失败/需要用户输入/等。 当脚本终止时,$shell 被清理,无法接收事件。它创建的复制线程将异常终止,就像您在将文件从一个驱动器复制到另一个驱动器时关闭计算机一样。

请注意CopyHere doesn't raise a completed event。这使得在脚本中捕获失败或等待完成变得困难。理想情况下,您将使用 Copy-Item 中内置的 Powershell 而不是 Shell,但 MTP 设备可能无法使用。

快速解决方法可能是像链接的答案一样添加System.Console.ReadKey(),或者如果您想在无人看管的情况下运行,则延长睡眠持续时间。

编辑:您可以确认目标路径中每个文件的存在,而不是等待:$destinationFolder.GetFolder.Items() 包含目标中的文件列表。来自this thread WMI 也是一种选择,但示例很少。

编辑 2:这是一个从硬盘复制到手机并确认已完成的简单脚本:

param([string]$phoneName = 'Nexus 5X', #beyond compare path: 'mtp://Nexus 5X'
    [string]$sourcePath = 'C:\Temp\',
    [string]$targetPath = '\Card\DCIM\',
    [string]$filter='(.jpg)|(.mp4)$'
)

$Shell = New-Object -ComObject Shell.Application
$PhoneObject = $shell.NameSpace(17).self.GetFolder.items() | where  $_.name -eq $phoneName  #gets the phone special folder

$SourceFolder = $Shell.NameSpace($sourcePath).self.GetFolder()
$DestFolder = $Shell.NameSpace((Join-path $PhoneObject.Path $targetPath)).self.GetFolder()

foreach($Item in $SourceFolder.Items() | ?$_.Name -match $filter)
    $DestFolder.CopyHere($Item)
    Do 
        $CopiedFile = $null 
        $CopiedFile = $DestFolder.Items() | ?$_.Name -eq $Item.Name
    While( ($CopiedFile -eq $null) -and ($null -eq (Sleep -Milliseconds 100)) )#skip sleeping if it's already copied
    Write-Host "Copied $($item.Name)"

【讨论】:

这个答案比链接线程中的答案要好得多。您的意思是定期轮询$destinationFolder.GetFolder.Items(),直到所需的项目出现在目的地(mtp 设备)? 是的,这就是我的意思 - Do-While 循环定期轮询。 $PhoneObject 有一个 Path 属性,其中嵌入了难以解析的 GUID,但可以与 $targetPath 结合以获取 COM FolderItem 对象。调整此脚本以从您的手机复制或移动非常容易。如果满足您的需求,请接受此答案。 感谢您的接受。您能否测试更新的脚本以确保它适用于嵌套文件夹和大量数据?它应该比以前的版本快得多,因为如果文件已经被复制,它就不会休眠。 我在 $DestFolder 赋值行中有一个错误:“对象...没有名称为 GetFolder() 的方法”我必须使用不带大括号的 GetFolder。

以上是关于如何使用 PowerShell 可靠地将项目复制到 mtp 设备?的主要内容,如果未能解决你的问题,请参考以下文章

powershell 使用凭证将项目复制到UNC路径

如何以异步方式有效地将变量从 Matlab 传递到 GPU?

PowerShell 复制项目到远程 - 自定义端口

如何使用纯函数可靠地将 POSIX 纪元秒转换为 EST/EDT 时间

如何使用PowerShell将选择查询的输出从DataSet复制到日志文件?

GitHub API:无法可靠地将文件添加到存储库