我希望能够将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
我假定Windows计算机上存在要复制的项目($ itemName)。我假设mtp设备在Windows资源管理器中被视为“ DUMMY_MODEL”,并且其中包含一个空的顶级文件夹“ Card”。
我希望那一行
$destinationFolder.GetFolder.CopyHere($item)
应该做的工作。但事实并非如此。为了使其正常工作,我需要以编程方式打开目标文件夹窗口并使用sleep。为什么?上面提到的线程说这是为了完成复制线程。为什么不打开窗户就无法完成?可以在不以编程方式打开窗口的情况下完成此操作吗?即使我打开窗户并入睡,复制也无法100%可靠地进行。为什么?
答案 0 :(得分:0)
这是基于我对Shell.Application对象的有限了解,而该文档没有很好的记录。如果有人知道更好,请随时进行纠正。
Shell.Application COM对象是Windows Explorer Shell的副本,该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)"
}