PowerShell访问便携式媒体设备(MTP)文件系统 5


背景

佳能相机,usb数据线接win7系统以后,每次都要进入比较深的目录下,而且每次都要手动复制/粘帖照片和视频文件到本地硬盘上。麻烦。(另外,不喜欢用读卡器+存储卡的方式)。
如果能做个软件,运行下就全自动copy到d盘某个目录下,是不是很方便呢?这功能,脚本或编程,不知道能否实现呢?简单点说就是:(读取相机储存卡里的相片,并保存到电脑的某个磁盘的某个目录里面,并以当时的日期作为文件夹名称保存)

需求

  1. 手工运行1次软件,首先软件会在d盘根目录下建立1个新目录。目录名字就是:年-月-日,比如:2014-06-27
  2. 软件会进行检测,如果没有满足条件,就直接退出。(软件检测过程,是有难度的吧?因为没有盘符)
  3. 如果有满足条件的照片/视频,不需要提示(不需要键盘/鼠标参与,不需要弹出对话框),就直接copy所有的照片/视频文件,可以显示文件copy复制过程界面,复制文件完成后退出。复制过程,尽量避免出现软件界面,避免用鼠标/键盘操作选择才能复制。
  4. 判断是否存在DCIM这个目录?如果存在,就搜索里面是否有照片/视频,并执行下面操作
    a、copy DCIM目录下所有照片和视频文件,到d盘下的新目录(/y参数是指覆盖前不提示)。
    b、copy完毕,删除DCIM源目录下的所有照片/视频文件。运行过程中,尽量别弹出交互式菜单/选择项目啥的。
    c、DCIM目录下还有别的不同级别的子目录,这些子目录下面才会有照片和视频文件,文件扩展名主要是jpg avi mov mp4 mts

知识点分析

  1. 该问题的主要难点是以MTP方式连接的设备没有盘符,不能使用简单的copy命令。不过,此问题由@codecook和@小楼在Powershell从数码相机复制图片思路 一文中完美解决:使用shell.application即可
  2. 为了增强脚本的复用性,准备简单实现Get-ShellItem,Get-ChildShellItem,Copy-ShellItem,Remove-ShellItem,Move-ShellItem 5个函数,这样即使脱离了这个需求,本篇的脚本仍然有意义。
  3. 调用过程中可能会大量使用Shell.Application 对象,觉得将它作为全局变量。
  4. 默认的 NameSpace()方法只支持目录,不支持文件文件。否则会抛出异常:使用“1”个参数调用“NameSpace”时发生异常:“未指定的错误 (异常来自 HRESULT:0x80004005 (E_FAIL))”。 为了增强兼容性和保持一致性,如果传入一个文件路径,可以尝试通过文件路径获取目录,然后再从目录的子项中获取对应的Shell项。
  5. Get-ChildShellItem只有支持递归才会有意义,但是直接递归非常容易引发系统堆栈层数不足的异常,所以采用用户自定义栈。
  6. 如果不想复制文件和移动文件时出现进度条,Copyhere和MoveHere中的参数16改成4

ShellItem源脚本

# 
# 获取 Shell.Application 代理 
#  
function Get-ShellProxy 
{ 
  if( -not $global:ShellProxy) {  
  $global:ShellProxy = new-object -com Shell.Application 
  } 
  $global:ShellProxy 
} 
 
# 
# 查看 Shell 项 
#  
function Get-ShellItem 
{ 
 param($Path=17) 
 $shell=Get-ShellProxy 

 # 默认的 NameSpace()方法只支持目录,不支持文件 
 # 为了增强兼容性和保持一致性,如果传入一个文件路径,可以尝试通过文件路径获取目录,然后再从目录的子项中获取Shell项 
 trap [System.Management.Automation.MethodInvocationException] 
 { 
  $Path = $Path.ToString() 
  $dir = $Path.Substring( 0 ,$Path.LastIndexOf('\') ) 
  return $shell.NameSpace($dir).items()  |  
   where { (-not $_.IsFolder ) -and  $_.path -eq $Path} |
   select -First 1
  continue 
 } 
 $shell.NameSpace($Path).self 
} 
 
# 
# 查看 Shell 子项,支持递归和过滤
#  
function Get-ChildShellItem 
{ 
 param(
  $Path=17,
  [switch]$Recurse,
  $Filter=$null) 

 #内部过滤器
 Filter myFilter
 {
   if($Filter){ 
   $_ | where { $_.Name -match $Filter } 
   }
   else{
   $_ 
   }
 }

 $shellItem = Get-ShellItem $Path
 $shellItem | myFilter
 # 如果是目录 
 if( $shellItem.IsFolder ){  
  # 如果指定递归 
  if($Recurse) { 
   $shellItem | myFilter
   $stack=New-Object System.Collections.Stack
   # 当前目录压入堆栈 
   $stack.Push($shellItem)
   while($stack.Count -gt 0)
   {
     # 访问栈顶元素
     $top = $stack.Pop()
     $top | myFilter

     # 访问栈顶元素的子元素
     $top.GetFolder.items() | foreach {
       if( $_.IsFolder )
       { $stack.Push($_) }
       else { $_ | myFilter }
     }
   }
  } 
  else{ 
  $shellItem.GetFolder.items() | myFilter }
  } 
} 

# 
# 复制Shell项
# 
function Copy-ShellItem
{
 param($Path,$Destination)
 $shell=Get-ShellProxy
 $shell.NameSpace($Destination).Copyhere($Path,16)
}

# 
# 删除Shell项
# 
function Remove-ShellItem
{
 param($Path)
 $ShellItem = Get-ShellItem $Path
 $ShellItem.InvokeVerb('delete')
}

# 
# 移动Shell项
# 
function Move-ShellItem
{
 param($Path,$Destination)
 $shell=Get-ShellProxy
 $shell.NameSpace($Destination).MoveHere($Path,16)
}

简单测试

Get-ShellItem 获取我的电脑

PS> Get-ShellItem


Application  : System.__ComObject
Parent       : System.__ComObject
Name         : 这台电脑
Path         : ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}
GetLink      :
GetFolder    : System.__ComObject
IsLink       : False
IsFolder     : True
IsFileSystem : False
IsBrowsable  : False
ModifyDate   : 1899/12/30 0:00:00
Size         : 0
Type         : 系统文件夹

Get-ChildShellItem 获取我的电脑下的通用文件夹和驱动器

S> Get-ChildShellItem | select name,path

Name               Path
----               ----
这台电脑           ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}
音乐               I:\Users\非苔\Music
下载               I:\Users\非苔\Downloads
图片               I:\Users\非苔\Pictures
视频               I:\Users\非苔\Videos
文档               I:\Users\非苔\Documents
桌面               I:\Users\非苔\Desktop
A820t              ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\\?\us...
系统 (C:)          C:\
程序 (D:)          D:\
学习 (E:)          E:\
DVD RW 驱动器 (F:) F:\
娱乐 (G:)          G:\
win8 (I:)          I:\

将我手机中的外置存储卡中的DCIM下的照片和视频按日期复制在D:\DICM目录下

A820T是什么东西,是我的联想手机啦。

PowerShell复制数码相机中的照片和视频

PowerShell复制数码相机中的照片和视频

$des = 'D:\DICM'
$phone = Get-ChildShellItem | where { $_.name -eq 'a820t' }
Get-ChildShellItem -Path "$($phone.Path)\外置存储卡\DCIM" -Filter '(.jpg)|(.3gp)$' | foreach {
 #获取照片创建日期
 $datestr = $_.Parent.GetDetailsOf($_,3)
 $datestr = ([datetime]$datestr).ToString('yyyy-MM-dd')

 #新建日期文件夹
 $dir = Join-Path $des $datestr
 if( -not (Test-Path $dir) ) {
  mkdir $dir
 }
 
 # 复制文件
 Copy-ShellItem -Path $_ -Destination $dir
 # 或者移动文件
 # Move-ShellItem -Path $_ -Destination $dir
}
本文链接: https://www.pstips.net/access-file-system-against-mtp-connection.html
请尊重原作者和编辑的辛勤劳动,欢迎转载,并注明出处!

关于 Mooser Lee

我是一个Powershell的爱好者,创建了PowerShell中文博客,热衷于Powershell技术的搜集和分享。本站部分内容来源于互联网,不足之处敬请谅解,并欢迎您批评指正。

回复 Q1503560704 取消回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

5 条评论 “PowerShell访问便携式媒体设备(MTP)文件系统