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

0

1 Answer 1

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()}
        }
    }
}
Sign up to request clarification or add additional context in comments.

9 Comments

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 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..
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 :
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
@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.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.