Trouble with [datetime]::parseexact - powershell

I know I must be missing something simple, but I've stared at it for a while and can't seem to see it.
I have the following powershell code
.".\Get-FileMetaDataReturnObject.ps1"
$BaseDirectory = "C:\Users\me\Desktop\Photo Test"
$Folders = get-childitem -path $BaseDirectory -Directory
foreach ($folder in $folders)
{
Write-Host "Folder name:$($folder.Name)"
$picture = Get-ChildItem -Path "$BaseDirectory\$($folder.Name)\"
$picMetaData = Get-FileMetaData -folder "$BaseDirectory\$($folder.Name)\"
$picDate = $picMetaData | Select 'Date Taken'
$picDateTaken = $picDate[0].'Date taken'
Write-Host $picDateTaken
$dateTime = [datetime]::parseexact($picDateTaken, "M/d/yyyy h:mm tt", [System.Globalization.CultureInfo]::InvariantCulture)
$dateStr = $dateTime.ToString('yyyy-MM-dd')
Write-Host $dateStr
}
When I run it I get the following error
Folder name:Deruta - Umbria, September 4, 2012
‎9/‎4/‎2012 ‏‎4:12 PM
Exception calling "ParseExact" with "3" argument(s): "String was not recognized as a valid DateTime."
At C:\Users\me\Desktop\Picture Folder Rename\PhotoFolderRename.ps1:18 char:5
+ $dateTime = [datetime]::parseexact($picDateTaken, "M/d/yyyy h:mm ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : FormatException
The meta data script is found here
I'm really not sure what I've screwed up with the date time parsing, so any help would be appreciated.

Comparing "9/‎4/‎2012 ‏‎4:12 PM".Length (20) with "9/4/2012 4:12 PM".Length (16) shows us something fishy is going on, and indeed this string is not what it appears to be; there are U+200E LEFT-TO-RIGHT MARK and U+200F RIGHT-TO-LEFT MARK control characters in there, which are invisible (or not, depending on your console settings). Replacing these out will make the string parseable:
$picDateTaken = $picDateTaken -replace "\u200e|\u200f", ""
Replacing control characters in general would be more involved, and I'm not sure if there isn't a better/more general way to get the metadata in an invariant format, but as long as you know exactly what you're dealing with this is good enough.

Related

PowerShell Function doesn't work consistently [duplicate]

I have a script which is using the EXIF data from a JPG file to find the DateTaken value. Once found, place the data in $Year and $Month variables.
$objShell = New-Object -ComObject Shell.Application
$folders = (Get-ChildItem G:\ServerFolders\Photos\UnSorted\ -Directory -Recurse -force).FullName
foreach ($Folder in $folders) {
$objfolder = $objShell.Namespace($folder)
foreach ($file in $objFolder.Items()) {
if ($objfolder.GetDetailsOf($file, 156) -eq ".jpg") {
$yeartaken = ($objFolder.GetDetailsOf($File, 12)).Split("/")[2].Split(" ")[0]
$month = $objFolder.GetDetailsOf($File, 12).Split("/")[1]
$monthname = (Get-Culture).DateTimeFormat.GetMonthName($month)
Write-Host $file.Name
}
}
}
So, if the file has a DateTaken as 06/10/2016, $yeartaken is 2016 and $month is 10
I then to Get-Culture to convert the 10 into October. This doesn't work because it's seeing $month as a string.
Cannot convert argument "month", with value: "‎10", for "GetMonthName" to
type "System.Int32": "Cannot convert value "‎10" to type "System.Int32".
Error: "Input string was not in a correct format.""
At line:1 char:3
+ (Get-Culture).DateTimeFormat.GetMonthName($month)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument
I've tried to convert it to a Integer value using casting or converting, but for some reason it won't convert.
PS> [int]$Test = $month
Cannot convert value "‎10" to type "System.Int32". Error: "Input string was
not in a correct format."
At line:1 char:1
+ [int]$Test = $month
+ ~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : MetadataError: (:) [], ArgumentTransformationMetadataException
+ FullyQualifiedErrorId : RuntimeException
A better approach to working with dates is to convert the date string to an actual DateTime object, which provides all the information you're looking for:
$culture = [Globalization.CultureInfo]::InvariantCulture
$pattern = 'dd\/MM\/yyyy'
$datestring = $objFolder.GetDetailsOf($File, 12).Split(' ')[0]
$datetaken = [DateTime]::ParseExact($datestring, $pattern, $culture)
$year = $datetaken.Year
$month = $datetaken.Month # month (numeric)
$monthname = $datetaken.ToString('MMMM') # month name
Assuming that the date is followed by a time in the format HH:mm:ss you could extend the code to handle the time as well:
$culture = [Globalization.CultureInfo]::InvariantCulture
$pattern = 'dd\/MM\/yyyy HH:mm:ss'
$datestring = $objFolder.GetDetailsOf($File, 12)
$timestamp = [DateTime]::ParseExact($datestring, $pattern, $culture)
$year = $timestamp.Year
$month = $timestamp.Month # month (numeric)
$monthname = $timestamp.ToString('MMMM') # month name
$hour = $timestamp.Hour
...
The date-and-time string returned by $objFolder.GetDetailsOf($File, 12) contains invisible control characters.
You can strip them as follows, after which your code should work:
# Remove the formatting control characters from the string.
$dateTimeStr = $objFolder.GetDetailsOf($File, 12) -replace '\p{Cf}'
# Note: The date format shown is directly recognized by
# PowerShell's [datetime] casts, which use the invariant culture.
$dateTaken = [datetime] $dateTimeStr
$monthname = (Get-Culture).DateTimeFormat.GetMonthName($dateTaken.Month)
\p{Cf} matches characters in the Format Unicode category, which comprises any character that "affects the layout of text or the operation of text processes, but is not normally rendered."
Specifically, in my test file's metadata I found multiple instances of Unicode control characters U+200E (LEFT-TO-RIGHT MARK) and U+200F (RIGHT-TO-LEFT MARK).
As for why these invisible control characters are present (information from this forum post, courtesy of mclayton):
The characters that you are seeing (along with some others, such as nulls) are embedded in BSTRs to allow the calling function to correctly display the string for any locale. It includes such things as the left to right marker that you saw, so that the calling application knows that the characters must be grouped and displayed left to right.
However, it's not quite clear why an explicit direction marker is needed for the default direction, left-to-right, and why seemingly each date component is preceded by one, and the time component even by two; here's an example of what should just be date string 9/5/2015 11:32 AM, with the invisible control characters visualized as PowerShell (Core) 7+ Unicode escape sequences (via Debug-String):
`u{200e}9/`u{200e}5/`u{200e}2015·`u{200f}`u{200e}11:32·AM
As an aside, on a general note: Ansgar Wiecher's answer shows more robust date-time string parsing techniques.
Self-contained sample:
The following example extracts the "Date Taken" field from file test.jpg located in the current folder and converts it to a [datetime] instance - note the need to use .ParseName() in order to pass an object representing the target file that the .GetDetailsOf() method understands:
[datetime] (
($folder = (New-Object -ComObject Shell.Application).
Namespace($PWD.ProviderPath)).
GetDetailsOf($folder.ParseName('test.jpg'), 12) -replace '\p{Cf}'
)

Powershell Source Code Encoding / hidden characters [duplicate]

I have a script which is using the EXIF data from a JPG file to find the DateTaken value. Once found, place the data in $Year and $Month variables.
$objShell = New-Object -ComObject Shell.Application
$folders = (Get-ChildItem G:\ServerFolders\Photos\UnSorted\ -Directory -Recurse -force).FullName
foreach ($Folder in $folders) {
$objfolder = $objShell.Namespace($folder)
foreach ($file in $objFolder.Items()) {
if ($objfolder.GetDetailsOf($file, 156) -eq ".jpg") {
$yeartaken = ($objFolder.GetDetailsOf($File, 12)).Split("/")[2].Split(" ")[0]
$month = $objFolder.GetDetailsOf($File, 12).Split("/")[1]
$monthname = (Get-Culture).DateTimeFormat.GetMonthName($month)
Write-Host $file.Name
}
}
}
So, if the file has a DateTaken as 06/10/2016, $yeartaken is 2016 and $month is 10
I then to Get-Culture to convert the 10 into October. This doesn't work because it's seeing $month as a string.
Cannot convert argument "month", with value: "‎10", for "GetMonthName" to
type "System.Int32": "Cannot convert value "‎10" to type "System.Int32".
Error: "Input string was not in a correct format.""
At line:1 char:3
+ (Get-Culture).DateTimeFormat.GetMonthName($month)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument
I've tried to convert it to a Integer value using casting or converting, but for some reason it won't convert.
PS> [int]$Test = $month
Cannot convert value "‎10" to type "System.Int32". Error: "Input string was
not in a correct format."
At line:1 char:1
+ [int]$Test = $month
+ ~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : MetadataError: (:) [], ArgumentTransformationMetadataException
+ FullyQualifiedErrorId : RuntimeException
A better approach to working with dates is to convert the date string to an actual DateTime object, which provides all the information you're looking for:
$culture = [Globalization.CultureInfo]::InvariantCulture
$pattern = 'dd\/MM\/yyyy'
$datestring = $objFolder.GetDetailsOf($File, 12).Split(' ')[0]
$datetaken = [DateTime]::ParseExact($datestring, $pattern, $culture)
$year = $datetaken.Year
$month = $datetaken.Month # month (numeric)
$monthname = $datetaken.ToString('MMMM') # month name
Assuming that the date is followed by a time in the format HH:mm:ss you could extend the code to handle the time as well:
$culture = [Globalization.CultureInfo]::InvariantCulture
$pattern = 'dd\/MM\/yyyy HH:mm:ss'
$datestring = $objFolder.GetDetailsOf($File, 12)
$timestamp = [DateTime]::ParseExact($datestring, $pattern, $culture)
$year = $timestamp.Year
$month = $timestamp.Month # month (numeric)
$monthname = $timestamp.ToString('MMMM') # month name
$hour = $timestamp.Hour
...
The date-and-time string returned by $objFolder.GetDetailsOf($File, 12) contains invisible control characters.
You can strip them as follows, after which your code should work:
# Remove the formatting control characters from the string.
$dateTimeStr = $objFolder.GetDetailsOf($File, 12) -replace '\p{Cf}'
# Note: The date format shown is directly recognized by
# PowerShell's [datetime] casts, which use the invariant culture.
$dateTaken = [datetime] $dateTimeStr
$monthname = (Get-Culture).DateTimeFormat.GetMonthName($dateTaken.Month)
\p{Cf} matches characters in the Format Unicode category, which comprises any character that "affects the layout of text or the operation of text processes, but is not normally rendered."
Specifically, in my test file's metadata I found multiple instances of Unicode control characters U+200E (LEFT-TO-RIGHT MARK) and U+200F (RIGHT-TO-LEFT MARK).
As for why these invisible control characters are present (information from this forum post, courtesy of mclayton):
The characters that you are seeing (along with some others, such as nulls) are embedded in BSTRs to allow the calling function to correctly display the string for any locale. It includes such things as the left to right marker that you saw, so that the calling application knows that the characters must be grouped and displayed left to right.
However, it's not quite clear why an explicit direction marker is needed for the default direction, left-to-right, and why seemingly each date component is preceded by one, and the time component even by two; here's an example of what should just be date string 9/5/2015 11:32 AM, with the invisible control characters visualized as PowerShell (Core) 7+ Unicode escape sequences (via Debug-String):
`u{200e}9/`u{200e}5/`u{200e}2015·`u{200f}`u{200e}11:32·AM
As an aside, on a general note: Ansgar Wiecher's answer shows more robust date-time string parsing techniques.
Self-contained sample:
The following example extracts the "Date Taken" field from file test.jpg located in the current folder and converts it to a [datetime] instance - note the need to use .ParseName() in order to pass an object representing the target file that the .GetDetailsOf() method understands:
[datetime] (
($folder = (New-Object -ComObject Shell.Application).
Namespace($PWD.ProviderPath)).
GetDetailsOf($folder.ParseName('test.jpg'), 12) -replace '\p{Cf}'
)

Unable to convert a string to an integer variable from DateTaken attribute on a JPG file

I have a script which is using the EXIF data from a JPG file to find the DateTaken value. Once found, place the data in $Year and $Month variables.
$objShell = New-Object -ComObject Shell.Application
$folders = (Get-ChildItem G:\ServerFolders\Photos\UnSorted\ -Directory -Recurse -force).FullName
foreach ($Folder in $folders) {
$objfolder = $objShell.Namespace($folder)
foreach ($file in $objFolder.Items()) {
if ($objfolder.GetDetailsOf($file, 156) -eq ".jpg") {
$yeartaken = ($objFolder.GetDetailsOf($File, 12)).Split("/")[2].Split(" ")[0]
$month = $objFolder.GetDetailsOf($File, 12).Split("/")[1]
$monthname = (Get-Culture).DateTimeFormat.GetMonthName($month)
Write-Host $file.Name
}
}
}
So, if the file has a DateTaken as 06/10/2016, $yeartaken is 2016 and $month is 10
I then to Get-Culture to convert the 10 into October. This doesn't work because it's seeing $month as a string.
Cannot convert argument "month", with value: "‎10", for "GetMonthName" to
type "System.Int32": "Cannot convert value "‎10" to type "System.Int32".
Error: "Input string was not in a correct format.""
At line:1 char:3
+ (Get-Culture).DateTimeFormat.GetMonthName($month)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument
I've tried to convert it to a Integer value using casting or converting, but for some reason it won't convert.
PS> [int]$Test = $month
Cannot convert value "‎10" to type "System.Int32". Error: "Input string was
not in a correct format."
At line:1 char:1
+ [int]$Test = $month
+ ~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : MetadataError: (:) [], ArgumentTransformationMetadataException
+ FullyQualifiedErrorId : RuntimeException
A better approach to working with dates is to convert the date string to an actual DateTime object, which provides all the information you're looking for:
$culture = [Globalization.CultureInfo]::InvariantCulture
$pattern = 'dd\/MM\/yyyy'
$datestring = $objFolder.GetDetailsOf($File, 12).Split(' ')[0]
$datetaken = [DateTime]::ParseExact($datestring, $pattern, $culture)
$year = $datetaken.Year
$month = $datetaken.Month # month (numeric)
$monthname = $datetaken.ToString('MMMM') # month name
Assuming that the date is followed by a time in the format HH:mm:ss you could extend the code to handle the time as well:
$culture = [Globalization.CultureInfo]::InvariantCulture
$pattern = 'dd\/MM\/yyyy HH:mm:ss'
$datestring = $objFolder.GetDetailsOf($File, 12)
$timestamp = [DateTime]::ParseExact($datestring, $pattern, $culture)
$year = $timestamp.Year
$month = $timestamp.Month # month (numeric)
$monthname = $timestamp.ToString('MMMM') # month name
$hour = $timestamp.Hour
...
The date-and-time string returned by $objFolder.GetDetailsOf($File, 12) contains invisible control characters.
You can strip them as follows, after which your code should work:
# Remove the formatting control characters from the string.
$dateTimeStr = $objFolder.GetDetailsOf($File, 12) -replace '\p{Cf}'
# Note: The date format shown is directly recognized by
# PowerShell's [datetime] casts, which use the invariant culture.
$dateTaken = [datetime] $dateTimeStr
$monthname = (Get-Culture).DateTimeFormat.GetMonthName($dateTaken.Month)
\p{Cf} matches characters in the Format Unicode category, which comprises any character that "affects the layout of text or the operation of text processes, but is not normally rendered."
Specifically, in my test file's metadata I found multiple instances of Unicode control characters U+200E (LEFT-TO-RIGHT MARK) and U+200F (RIGHT-TO-LEFT MARK).
As for why these invisible control characters are present (information from this forum post, courtesy of mclayton):
The characters that you are seeing (along with some others, such as nulls) are embedded in BSTRs to allow the calling function to correctly display the string for any locale. It includes such things as the left to right marker that you saw, so that the calling application knows that the characters must be grouped and displayed left to right.
However, it's not quite clear why an explicit direction marker is needed for the default direction, left-to-right, and why seemingly each date component is preceded by one, and the time component even by two; here's an example of what should just be date string 9/5/2015 11:32 AM, with the invisible control characters visualized as PowerShell (Core) 7+ Unicode escape sequences (via Debug-String):
`u{200e}9/`u{200e}5/`u{200e}2015·`u{200f}`u{200e}11:32·AM
As an aside, on a general note: Ansgar Wiecher's answer shows more robust date-time string parsing techniques.
Self-contained sample:
The following example extracts the "Date Taken" field from file test.jpg located in the current folder and converts it to a [datetime] instance - note the need to use .ParseName() in order to pass an object representing the target file that the .GetDetailsOf() method understands:
[datetime] (
($folder = (New-Object -ComObject Shell.Application).
Namespace($PWD.ProviderPath)).
GetDetailsOf($folder.ParseName('test.jpg'), 12) -replace '\p{Cf}'
)

What am I not getting about [DateTime]::ParseExact()?

$timeinfo = "01-‎06‎-2017 ‏‎12:34"
$template = "dd-MM-yyyy HH:mm"
[DateTime]::ParseExact($timeinfo, $template, $null)
results in:
Exception calling "ParseExact" with "3" argument(s): "String was not recognized
as a valid DateTime."
At line:3 char:1
+ [DateTime]::ParseExact($timeinfo, $template, $null)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : FormatException
I can't tell what is wrong here? Why is my string not a valid datetime when I specify in the template how it should read it?
You have some strange characters in your $timeinfo variable. When copying and pasting, I am getting this:
You can see this by pressing the right or left arrow and going through the string; it pauses at the odd character.
Changing to $timeinfo = "01-06-2017 12:34", your code works as expected. Copy and paste this string to test.
Edit - Using a Unicode converter, it looks like this is LRM control character
You probably copy and paste the code because there are incorrect characters in the $timedate, try copy and paste this:
$timeinfo = "01-06-2017 12:34"
$template = "dd-MM-yyyy HH:mm"
[DateTime]::ParseExact($timeinfo, $template, $null)

If condition not working with DateTime entries

I am not able to get the correct report with the below part of code.
Here's what I am doing:
Importing .csv file into variable which contains UserName, LastSucessSync (date), Model.
With below foreach loop this will format the .html report and provide the values Green or Red.
Issue is, when Date is blank in .csv file it still shows GREEN or some times RED.
It also throws the below error:
Cannot convert value "" to type "System.DateTime". Error: "String was not
recognized as a valid DateTime."
At C:\MobileStats.ps1:87 char:4
+ if([datetime]$Entry.LastSuccessSync -lt (Get-Date).AddDays(-14) -or $Entry.LastS ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvalidCastParseTargetInvocationWithFormatProvider
Rest of all is fine. Just I want to mark it as RED when date is blank or 14 days before.
PowerShell Code:
foreach ($Entry in $Result) {
if ([datetime]$Entry.LastSuccessSync -lt (Get-Date).AddDays(-14) -or $Entry.LastSuccessSync -like "") {
$Cdrivecolor="RED"
} else {
$Cdrivecolor="Green"
}