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

How to reliably copy items with PowerShell to an mtp device?

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

我找到了这个线程: 但那里的答案无助于理解解决方案(并且是由提出该问题的同一个人给出的)。下面是一个最小的代码示例:

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 Explorer 中被视为 "DUMMY_MODEL",并且它包含一个空的顶级文件夹 "Card".

我希望这条线

$destinationFolder.GetFolder.CopyHere($item)

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

这是基于我对没有详细记录的 Shell.Application 对象的有限了解。如果哪位知道的更好,欢迎指正。

Shell.Application COM 对象是 Windows Explorer shell 的副本,后者异步执行文件操作。为每个 'copy' 操作创建一个新线程,$shell 实例管理这些线程并接收事件 - completed/failed/needs 用户 input/etc。 当脚本终止时,$shell 被清除并且无法接收事件。它创建的复制线程将异常终止,就像您在将文件从一个驱动器复制到另一个驱动器时关闭了计算机一样。

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

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

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

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

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)"
}