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

and*_*jtp 7 windows powershell mtp

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

我发现这个线程:shell32 copyhere 在 .Net 和 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
Run Code Online (Sandbox Code Playgroud)

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

我希望这条线

$destinationFolder.GetFolder.CopyHere($item)
Run Code Online (Sandbox Code Playgroud)

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

Ric*_*oss 3

这是基于我对 Shell.Application 对象的有限了解,该对象没有很好的记录。如果有哪位朋友知道的比较清楚,欢迎指正。

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

请注意,CopyHere 不会引发已完成事件。这使得在脚本中捕获失败或等待完成变得困难。理想情况下,您可以使用 Copy-Item 内置的 Powershell 而不是 Shell,但这对于 MTP 设备可能无法实现。

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

编辑:您可以确认目标路径中每个文件是否存在,而无需等待:$destinationFolder.GetFolder.Items()包含目标中的文件列表。从这个线程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)"
}
Run Code Online (Sandbox Code Playgroud)