【问题标题】:robocopy adds hidden symbol when creating foldersrobocopy 在创建文件夹时添加隐藏符号
【发布时间】:2020-10-04 23:57:35
【问题描述】:

我所做的是使用 powershell ps1 文件和 Windows PowerShell ISE 将照片文件从 SD 卡复制到 HDD。 我从图像 exif 中获取拍摄日期并将其添加到目标路径。 问题是 robocopy 创建文件夹并添加了我不想拥有的奇怪前缀。 结果,我可以看到两个同名“2020”的子文件夹,一个是手动创建的,另一个是 robocopy 创建的。 仅当我使用 CMD 列出文件夹时才会看到此前缀。 在 output.log 和 powershell 中没有看到的前缀。

$copy_from = "G:\DCIM\100MSDCF\"
$copy_to = "C:\Photos\"

function GetDateTaken {
  param (
    [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
    [Alias('FullName')]
    [String]
    $Path
  )
  begin {
    $shell = New-Object -COMObject Shell.Application
  }
  process {
    $returnvalue = 1 | Select-Object -Property Name, DateTaken, Folder
    $returnvalue.Name = Split-Path $path -Leaf
    $returnvalue.Folder = Split-Path $path
    $shellfolder = $shell.Namespace($returnvalue.Folder)
    $shellfile = $shellfolder.ParseName($returnvalue.Name)
    $returnvalue.DateTaken = $shellfolder.GetDetailsOf($shellfile, 12)

    $returnvalue.DateTaken
  }
}

$file = Get-ChildItem -Path $copy_from -recurse -include ('*.jpg','*.arw')

$i = 0
$jpg = 0
$arw = 0

$logifile = 'output.log'

if ([System.IO.File]::Exists($logifile)) {
    Clear-Content $logifile
    Write-Host ("Logfile cleaned: $logifile")
} else {
    try {
        New-Item -Path . -Name $logifile | Out-Null
        Write-Host ("New logfile created: $logifile")
    }
    catch {
        "Failed to create $logifile"
    }
}

foreach ($file in $file) {

    if ($file.extension -eq '.JPG') { $jpg++ }
    if ($file.extension -eq '.ARW') { $arw++ }
    $i++

    $datetaken = ($file.fullname | GetDateTaken).Split(' ')[0]
    $datetaken_Day = $datetaken.Split('.')[0]
    $datetaken_Month = $datetaken.Split('.')[1]
    $datetaken_Year = $datetaken.Split('.')[2]

    $TargetPath = "$copy_to$datetaken_Year\$datetaken_Month\$datetaken_Day\"

    Write-Host ("$i. " + $file.Name + "   `tDate taken: " + $datetaken)
    
    robocopy $copy_from $TargetPath $file.Name /ts /fp /v /np /unilog+:$logifile | Out-Null

}

Write-Host ("`nTotal: " + $i + " files (" + $jpg + " JPG files, " + $arw + " ARW files)")

如果写$TargetPath = $copy_to + $datetaken_Year + "\" + $datetaken_Month + "\" + $datetaken_Day + "\"没有帮助。

如果我将 /fat 选项设置为 robocopy,则无济于事。

但是,例如,当我手动设置年份时,一切正常$datetaken_Year = 2020

为了创建正确的文件夹名称应该解决什么问题?

【问题讨论】:

  • 您没有显示$copy_to 的填充方式。
  • 添加了完整的脚本
  • 这个foreach ($file in $file) { 不是它应该的样子。 [grin] $Collection$CurrentItem otta 有不同的名称。
  • 通过阅读您的代码,似乎不需要的字符可能来自数据源,而不是 robocopy。您是否尝试通过 Format-Hex 发送该信息以查看实际情况?
  • foreach 有效,这不是问题。以十六进制格式输出显示不需要的字符 C:\Photos\?2020\ 我不知道为什么或如何摆脱它。尝试添加.Trim(),没有帮助。

标签: windows powershell robocopy


【解决方案1】:

使用 COM 对象中的 GetDetailsOf() 方法会返回本地化结果,这会导致我的荷兰机器上的函数以“dd-MM-yyyy HH:mm”格式返回日期(周围有不可见的字符)。

IMO 更好的方法是使用 System.Drawing.Imaging.Metafile 获取日期,将 exif 数据读取为空终止字节数组,并使用以下函数将日期解析为 DateTime 对象:

function Get-ExifDate {
    # returns the 'DateTimeOriginal' property from the Exif metadata in an image file if possible
    [CmdletBinding(DefaultParameterSetName = 'ByName')]
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0, ParameterSetName = 'ByName')]
        [Alias('FullName', 'FileName')]
        [ValidateScript({ Test-Path -Path $_ -PathType Leaf})]
        [string]$Path,
    
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0, ParameterSetName = 'ByObject')]
        [System.IO.FileInfo]$FileObject
    )

    Begin {
        Add-Type -AssemblyName 'System.Drawing'
    }
    Process {
        # the function received a path, not a file object
        if ($PSCmdlet.ParameterSetName -eq 'ByName') {
            $FileObject = Get-Item -Path $Path -Force -ErrorAction SilentlyContinue
        }
        # Parameters for FileStream: Open/Read/SequentialScan
        $streamArgs = @(
            $FileObject.FullName
            [System.IO.FileMode]::Open
            [System.IO.FileAccess]::Read
            [System.IO.FileShare]::Read
            1024,     # Buffer size
            [System.IO.FileOptions]::SequentialScan
        )
        try {
            $stream = New-Object System.IO.FileStream -ArgumentList $streamArgs
            $metaData = [System.Drawing.Imaging.Metafile]::FromStream($stream)

            # get the 'DateTimeOriginal' property (ID = 36867) from the metadata
            # Tag Dec  TagId Hex  TagName           Writable  Group    Notes
            # -------  ---------  -------           --------  -----    -----
            # 36867    0x9003     DateTimeOriginal  string    ExifIFD  (date/time when original image was taken)

            # get the date taken as an array of bytes
            $exifDateBytes = $metaData.GetPropertyItem(36867).Value
            # transform to string, but beware that this string is Null terminated, so cut off the trailing 0 character
            $exifDateString = [System.Text.Encoding]::ASCII.GetString($exifDateBytes).TrimEnd("`0")
            # return the parsed date
            return [datetime]::ParseExact($exifDateString, "yyyy:MM:dd HH:mm:ss", $null) 
        }
        catch{
            Write-Warning -Message "Could not read Exif data from '$($FileObject.FullName)'"
        }
        finally {
            If ($metaData) {$metaData.Dispose()}
            If ($stream)   {$stream.Close()}
        }
    }
}

另一种选择是下载并解压缩ExifTool
(你可以从here下载zip文件)

然后像这样使用它:

$exifTool = 'Path\To\Unzipped\ExifTool.exe'  # don't forget to 'Unblock' after downloading
$file     = 'Path\To\The\ImageFile'          # fullname

# retrieve all date tags in the file
# -s2 (or -s -s) return short tag name add the colon directly after that
$allDates = & $exifTool -time:all -s2 $file  

# try to find a line with tag 'DateTimeOriginal', 'CreateDate' or 'ModifyDate'
# which will show a date format of 'yyyy:MM:dd HH:mm:ss'
# and parse a DateTime object out of this string
$dateTaken = switch -Regex ($allDates) {
    '^(?:DateTimeOriginal|CreateDate|ModifyDate):\s(\d{4}:\d{2}:\d{2} \d{2}:\d{2}:\d{2})' {
        [datetime]::ParseExact($matches[1], 'yyyy:MM:dd HH:mm:ss', $null)
        break
    }
}

以上返回内容的简短说明

这两种方法都将拍摄图像的日期作为DateTime 对象返回,而不是字符串。 该对象具有.Year.Month.Day 等属性。它还具有.AddDays().ToShortDateString().ToString() 等各种方法。

如果您按照您的评论执行 $datetaken = ($datetaken -split ' ')[0],则您要求 PowerShell隐式使用 default ToString() 方法将其转换为字符串。
你可以在你的代码中使用 ToString() 方法,如果你在括号之间给它你需要的格式化字符串,无论如何你喜欢。

例如,如果您执行 $dateTaken.ToString('yyyy\\MM\\dd'),如果 $dateTaken 是今天,您将得到一个字符串 2020\10\08,它可以作为文件路径的一部分。

在您的代码中,您可以这样做:

$TargetPath = Join-Path -Path $copy_to -ChildPath $dateTaken.ToString('yyyy\\MM\\dd')
# if that path does not exist yet, create it
if (!(Test-Path -Path $TargetPath -PathType Container)) {
    $null = New-Item -Path $TargetPath -ItemType Directory
}

然后继续将文件复制到现在存在的 $TargetPath

请查看您可以在 DateTime 对象上使用的所有 standard format stringscustom format specifiers

【讨论】:

  • @Sergei 你可能想试试ExifTool。它是一款免费软件,看起来可以处理很多很多格式。
  • @Sergei 我已经使用 ExifTool 添加了替代代码。我自己没有任何 .ARW 文件,所以请告诉我们这是否也适用于这些文件。
  • 谢谢。好消息是它适用于 ARW 和 JPG。不幸的是,我无法了解如何拆分返回的日期。 $datetaken 在控制台“08.10.2020 2:08:51”中返回一个不错的日期。但如果我这样做 $datetaken.Split(' ')[0] 我会收到错误“方法调用失败,因为 [System.DateTime] 不包含名为 'Split' 的方法。”
  • write-host ($datetaken | Format-Hex) - 不起作用。错误:“Format-Hex:无法将 System.DateTime 类型的输入转换为十六进制。要查看其字符串表示的十六进制格式,请先将其通过管道传输到 Out-String cmdlet,然后再将其传输到 Format-Hex。” write-host ($datetaken | out-string | Format-Hex) 以其他格式返回日期。
  • @Sergei 我在回答中添加了额外的解释。主要的是返回的$dateTaken 变量不是string,而是object,你可以用任何你需要的方式格式化。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-12
  • 1970-01-01
  • 2015-09-30
  • 2017-04-05
  • 1970-01-01
相关资源
最近更新 更多