Powershell Source Code Encoding / hidden characters [duplicate] - powershell

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}'
)

Related

Powershell Registry String to Workable Date

Cannot convert string format to date using parse exact.
Registry contains the following string value:
2022-10-18T12:40:25
I need to convert this string to a date field in order to count number of days since (compared to today).
$startdate =
Get-ItemProperty -Path 'HKLM:SOFTWARE\Adobe\Acrobat Distiller\DC\Installer\' |
select-object 'Dummy'
[datetime]::parseexact($startdate,'dd/MM/yyyy HH:mm',$null)
$today = (([datetime]::Now))
$x = New-TimeSpan -Start $startdate -End $today
"$($x.days) $("days have passed since") $($startdate)"
Cannot find an overload for "parseexact" and the argument count: "3".
At line:2 char:1
+ [datetime]::parseexact($startdate,'dd/MM/yyyy HH:mm',$null)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodCountCouldNotFindBest
New-TimeSpan : Cannot bind parameter 'Start'. Cannot convert value "#{dummy=2022-10-18T12:40:25}" to type "System.DateTime". Error: "Cannot convert the "#{dummy=2022-10-18T12:40:25}" value of type
"Selected.System.Management.Automation.PSCustomObject" to type "System.DateTime"."
At line:5 char:26
+ $x = New-TimeSpan -Start $startdate -End $today
+ ~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [New-TimeSpan], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.NewTimeSpanCommand
days have passed since #{dummy=2022-10-18T12:40:25}
Your primary problem is that you need to use -ExpandProperty, if you use Select-Object, or you can use simple property access:
Select-Object (select) by default returns a [pscustomobject] instance that has the requested properties - even when you're only asking for a single property. To get only that property's value, use the -ExpandProperty parameter instead of the (possibly positionally implied) -Property parameter - see this answer for details and alternatives, notably the ability to simply use (...).SomeProperty
Therefore, the simplest solution is:
$startdate = (
Get-ItemProperty 'HKLM:SOFTWARE\Adobe\Acrobat Distiller\DC\Installer'
).Dummy
Or, in PSv5+, using Get-ItemPropertyValue:
$startdate =
Get-ItemPropertyValue 'HKLM:SOFTWARE\Adobe\Acrobat Distiller\DC\Installer' Dummy
As for then parsing the resulting string into a [datetime] instance:
The format string you're passing to ::ParseExact(), doesn't match your input string, and TheMadTechnician's answer shows how to fix that (see this answer's bottom section for further considerations).
However, you can more simply use a [datetime] cast, which recognizes your string format as-is, irrespective of what culture is currently in effect:
[datetime] '2022-10-18T12:40:25'
In essence, PowerShell translates the above into the following ::Parse() call:
[datetime]::Parse('2022-10-18T12:40:25', [cultureinfo]::InvariantCulture)
In general, for full robustness when using format strings, it is best to escape :, /, and . (if present) with \, so as if they are to be interpreted literally, i.e by only matching literally during parsing and being included literally when formatting output (by default, they're considered placeholders for the target culture's separators):
[datetime]::ParseExact(
'2022-10-18T12:40:25',
'yyyy-MM-ddTHH\:mm\:ss', # \-escaping ensures literal matching
[cultureinfo]::InvariantCulture
)
Note: In this particular case, [cultureinfo]::InvariantCulture alone would be sufficient for robustness, given that this culture uses : as the time separator.
You get an error because your second argument doesn't match the format of the datetime.
[datetime]::parseexact($startdate,'yyyy-MM-ddTHH:mm:ss',$null)

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}'
)

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}'
)

How to extract a certain part of a string in powershell

I want to extract the last 4 digits before ".txt" from this string:
09/14/2017 12:00:27 - mtbill_post_201709141058.txt 7577_Delivered: OK
Those represent the time at which that log was created and I want to display it as 10:58. I read from a file that has multiple lines similar to the one displayed.
Get-Content file.txt | ForEach-Object {
$splitUp = $_ -split "_"
$SC=$splitUp[2] -split "_"
Write-Host $SC
$len = $SC.Length
$folder2 = $SC.Substring($len - 12, 42)
}
I tried separating the string by "_" and then counting the characters in the obtained string and tried separating by the "Substring" command, but I receive the following error.
Exception calling "Substring" with "2" argument(s): "StartIndex cannot
be less than zero. Parameter name: startIndex"
At line:6 char:5
+ $folder2 = $SC.Substring($len - 12, 42)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CategoryInfo : NotSpecified: (:) [], MethodInvocationException
FullyQualifiedErrorId : ArgumentOutOfRangeException
You can use a regex "lookahead".
What you are searching for is a set of four digits followed by ".txt":
$string = "09/14/2017 12:00:27 - mtbill_post_201709141058.txt 7577_Delivered: OK"
$regex = "\d{4}(?=\.txt)"
[regex]::matches($string, $regex).value
There's probably a more elegant solution:
$String = '09/14/2017 12:00:27 - mtbill_post_201709141058.txt 7577_Delivered: OK'
$String -Match '.*(?=\.txt)' | Out-Null
$Match = $Matches[0][-4..-1] -Join ''
$Time = [DateTime]::ParseExact($Match, 'HHmm',[CultureInfo]::InvariantCulture)
$Time.ToShortTimeString()
Uses RegEx to get all of the string before the .txt
Uses the Array Index to get the characters from 4th to last to the last character and joins them together as a single string.
Casts the value as a DateTime object using ParseExact to interpret it as 24 hour time code
Outputs the Short Date value of that DateTime object.
Just do it with Substring and IndexOf:
$string="09/14/2017 12:00:27 - mtbill_post_201709141058.txt 7577_Delivered: OK"
$string.Substring($string.IndexOf('.txt')-4, 4)

Converting CSV date string to another format

I got a CSV file with a date column with the format dd/MM/YYY HH:mm:ss, e.g. 14/11/2016 00:00:00.
I'm trying to batch convert the formating on that column to an ISO-8601 date format: YYYY-mm-dd HH:mm:ss
I have tried: ( I have edited I had an error on input string format...)
Import-Csv someCSV.csv | % {
$_.'[Date]' = ([datetime]::ParseExact(($_.'[Date]'),"dd/MM/YYYY HH:mm:ss").ToString('yyyy-MM-dd HH:mm:ss'))
} | Export-Csv 'C:\testBis.csv' -NoTypeInformation
I'm getting an error:
Cannot find an overload for "ParseExact" and the argument count: "2".
At line:1 char:69
+ ... Date]' ; ([datetime]::ParseExact(($_.'[Date]'),"dd/MM/YYY HH ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodCountCouldNotFindBest
I've had a look and tryed with Try parse and sever few ideas but nothing seam to work.
I dont know what I'm going wrong. any suggestions?
The pattern for matching the year must be yyyy (with lowercase y), there is no comma in your sample date, and you need to provide culture information for ParseExact(). I would also recommend to escape the forward slashes, otherwise they'll match whatever date separator is configured in the computer's regional settings. And you need to echo the modified record back to the pipeline, otherwise there'd be nothing to export.
$culture = [Globalization.CultureInfo]::InvariantCulture
Import-Csv someCSV.csv | ForEach-Object {
$_.'[Date]' = [DateTime]::ParseExact($_.'[Date]', 'dd\/MM\/yyyy HH:mm:ss', $culture).ToString('yyyy-MM-dd HH:mm:ss')
$_ # <-- feed modified record back into pipeline
} | Export-Csv 'C:\testBis.csv' -NoType
DateTime.ParseExact requires at least three parameters. The third parameter is the culture to use. You can pass the invariant culture [System.Globalization.CultureInfo]::InvariantCulture, eg:
([datetime]::ParseExact(($_.'[Date]'),"dd/MM/YYYY HH:mm:ss",[System.Globalization.CultureInfo]::InvariantCulture)
or a cleaner version:
$inv = [System.Globalization.CultureInfo]::InvariantCulture
$fmtFrom = "dd/MM/YYY HH:mm:ss"
$fmtTo = "yyyy-MM-dd HH:mm:ss"
Import-Csv someCSV.csv | % {
$_.'[Date]' = ([datetime]::ParseExact(($_.'[Date]'),$fmtFrom, $inv).ToString($fmtTo))
} | Export-Csv 'C:\testBis.csv' -NoTypeInformation
I removed the , from the format string because you mentioned that the actual format is dd/MM/YYY HH:mm:ss, not dd/MM/YYY, HH:mm:ss