robocopy 在创建文件夹时添加隐藏符号

robocopy adds hidden symbol when creating folders

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

$copy_from = "G:\DCIM0MSDCF\"
$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

要创建正确的文件夹名称应该修复什么?

使用来自 COM 对象 returns 本地化结果的 GetDetailsOf() 方法,这导致你的函数在我的荷兰机器上 returning 日期 'dd-MM-yyyy HH:mm' 格式(周围有不可见的字符)。

IMO 更好的方法是使用 System.Drawing.Imaging.Metafile 获取日期,将 exif 数据读取为 null-terminated 字节数组,并使用以下函数从中解析日期作为 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
    }
}

上面returns

的简短解释

两种方法 return 拍摄图像的日期 DateTime object,而不是 string . 该对象具有 .Year.Month.Day 等属性。它还有各种方法,例如 .AddDays().ToShortDateString().ToString() 等等更多

如果您按照您的评论执行 $datetaken = ($datetaken -split ' ')[0],则您要求 PowerShell 隐式 使用 默认值[=59] 将其转换为字符串=] ToString() 方法。
您可以在代码中使用该 ToString() 方法,只要您在方括号之间为它提供所需的格式化字符串即可。

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

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

$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 strings and custom format specifiers