1

Here's my code

# $index is 12 (for exif 'date taken')
$value = $dir.GetDetailsof($file, $index)
$value = $value.Trim()
if ($value -and $value -ne '') {
    return [DateTime]::ParseExact($value, "g", $null)
}
return $null

this always throws the following exception

Exception calling "ParseExact" with "3" argument(s): "String was not recognized as a valid DateTime."
At REDACTEDPATH.ps1:64 char:16
+         return [DateTime]::ParseExact($value, "g", $null)
+                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
   + FullyQualifiedErrorId : FormatException

If I use return [DateTime]::ParseExact("09/11/2019 19:16", "g", $null) the code works as expected

If I go back to using the variable and inspect $value at a breakpoint on the return line, it looks like 09/11/2019 19:16

If I look at the exif data of the file via the Windows property dialogue, the datetime looks like 09/11/2019 19:16

If I change the code to return [DateTime]::ParseExact($value, "dd\MM\yyyy HH:mm", $null) it throws the same exception, but if I try return [DateTime]::ParseExact("09/11/2019 19:16", "dd\MM\yyyy HH:mm", $null) it works. So it seems as though what I'm picking up in $value isn't what I think it is, but I can't see where it differs from my expectation.

Even inserting Write-Host "-$value-" just after the trim line shows exactly what I'd expect to see: -‎09/‎11/‎2019 ‏‎19:16-

EDIT here's an image of variable inspection at the breakpoint

mccrispy
  • 11
  • 4

1 Answers1

1

This exif property looks like a normal string, but it has a trailing zero (0x00) character that confuses ParseExact unless we include the zero in the ParseExact pattern

Try

[datetime]::ParseExact($value,"yyyy:MM:dd HH:mm:ss`0", $null)

On further inspection using a Hex editor, I could see your string is loaded with weird (invisible) characters. Preceeding each of the digit parts, there is a sequence of characters E2 80 8E and right after the space, where the time part begins it has E2 80 8F E2 80 8E..

Code point UTF-8 hex Name
U+200E e2 80 8e LEFT-TO-RIGHT MARK
U+200F e2 80 8f RIGHT-TO-LEFT MARK

I have no idea what kind of file you are taking this strange dat from, but this may help parsing out the date from that:

[datetime]::ParseExact($value -replace '[^\d/ :]+', 'dd/MM/yyyy HH:mm', $null)

Of course, given the example date you show, there is no telling whether the pattern is dd/MM/yyyy HH:mm or MM/dd/yyyy HH:mm, so you will have to find that out by trying to parse a date from more files..

The regex pattern:

[^\d/\ :]     Match any single character NOT present in the list below
              A “digit” (any decimal number in any Unicode script)
              A single character from the list “/ :”
   +          Between one and unlimited times, as many times as possible, giving back as needed (greedy)

P.S. To get the 'Date Taken' from an image file's Exif data, I usually use below helper function (simplified thanks to mklement0):

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

    Begin {
        Add-Type -AssemblyName 'System.Drawing'
    }
    Process {
        try {
            $image = [System.Drawing.Image]::FromFile($Path)

            # get the 'DateTimeOriginal' property (ID = 36867) from the image metadata
            # Tag Dec  TagId Hex  TagName           Writable  Group    Notes
            # -------  ---------  -------           --------  -----    -----
            # 36867    0x9003     DateTimeOriginal  string    ExifIFD  (date/time when original image was taken)
            # see: https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html
            #      https://learn.microsoft.com/en-us/dotnet/api/system.drawing.imaging.propertyitem.id
            #      https://learn.microsoft.com/nl-nl/windows/win32/gdiplus/-gdiplus-constant-property-item-descriptions#propertytagexifdtorig

            # get the date taken as an array of (20) bytes
            $exifDateBytes = $image.GetPropertyItem(36867).Value
            # transform to string, but beware that this string is Null terminated, so cut off the trailing 0 character
            # or incorporate it in the pattern for ParseExact: "yyyy:MM:dd HH:mm:ss`0"
            $exifDateString = [System.Text.Encoding]::ASCII.GetString($exifDateBytes).TrimEnd("`0")
            # return the parsed date
            [datetime]::ParseExact($exifDateString, "yyyy:MM:dd HH:mm:ss", $null) 
        }
        catch{
            Write-Warning -Message "Could not read Exif data from '$Path': $($_.Exception.Message)"
        }
        finally {
            if ($image) {$image.Dispose()}
        }
    }
}
Theo
  • 57,719
  • 8
  • 24
  • 41
  • I copied your snippet and then edited it to dd\MM\yyyy HH:mm`0 but it still threw the exact same error. If there's a trailing 0, why doesn't it show up when I write-host the $value? – mccrispy Aug 05 '22 at 12:13
  • @mccrispy Because you cannot see a null character. BTW, You say you changed the pattern to use backslashes.. in your question it shows forward slashed, but in my experience, the format for `DateTaken` uses colons.. – Theo Aug 05 '22 at 12:25
  • You're quite right, those slashes were my bad - a typo. all slashes are FORWARD. I get that I can't see the null character, so I've modified the Trim so that it removes [char]0x00, but that still doesn't work. As to the / vs : issue: yes, the "raw" EXIF uses : as separators, but I inspected the variable and it uses /, not : – mccrispy Aug 05 '22 at 12:47
  • I can't insert an image in comments, so I've added an image of the "variable inspector" up in the original post. Of course I don't have the rep points to post the image there either, so it's a link – mccrispy Aug 05 '22 at 12:56
  • @mccrispy Please see my edit. – Theo Aug 05 '22 at 14:08
  • thanks for the code example. When I have a bit more time over the weekend I'll incorporate it and see whether that resolves my problem. – mccrispy Aug 05 '22 at 14:22
  • Good to know that you can use [`System.Drawing.Image.GetPropertyItem`](https://learn.microsoft.com/en-us/dotnet/api/system.drawing.image.getpropertyitem) to retrieve extended document properties from image files, specifically (shouldn't that be `[System.Drawing.Image]::FromStream`, though? Wouldn't `::FromFile` be simpler, or is there a technical reason for using a stream). Note that the trailing ``"`0"`` only occurs when you use the .NET API, whereas the invisible left-to-right and right-to-left formatting chars. only occur when you use the `Shell.Application` COM API. – mklement0 Aug 05 '22 at 18:53
  • As for _why_ these invisible formatting chars. are there: the linked duplicate offers _somewhat_ of an explanation, but it's not satisfying. – mklement0 Aug 05 '22 at 18:54
  • 1
    @mklement0 Just tried with `[System.Drawing.Image]::FromFile($Path)` and that works as well. Can't remember why I used FromStream now.. I have now updated that for a simplified version. – Theo Aug 06 '22 at 19:54