How to extract a certain part of a string in powershell - 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)

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

Cannot find an overload for "ToString" and the argument count: "1"

i'm not understanding what i'm doing wrong here since i seem to do the same thing but only one works.
i have a text file with a number list that i want to process (round the values):
39.145049
40.258140
41.400803
42.540093
43.664530
and here my script:
$a = get-content "input.txt"
$b = $a -join ','
$b | % {$_.ToString("#.###")}
this results in the following error:
Cannot find an overload for "ToString" and the argument count: "1".
At D:\script.ps1:9 char:9
+ $b | % {$_.ToString("#.###")}
+ ~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodCountCouldNotFindBest
however if i take the result after joining which is:
39.145049,40.258140,41.400803,42.540093,43.664530
and create the following script:
$b = 39.145049,40.258140,41.400803,42.540093,43.664530
$b | % {$_.ToString("#.###")}
it works just fine and outputs:
39.145
40.258
41.401
42.54
43.665
where am i going wrong on this one?
This happens as the inputs are not of the same type.
$b1 = $a -join ','
$b2 = 39.145049,40.258140,....
$b1.GetType().Name
String
$b2.GetType().Name
Object[]
As the input in the first case is a single string, foreach loop doesn't process it as a collection of decimal values but a single string. Thus,
$b | % {$_.ToString("#.###")}
Is going to do (as pseudocode):
'39.145049,40.258140,41.400803,42.540093,43.664530'.ToString("#.###")
Whilst the array version is doing
39.145049.ToString("#.###")
40.258140.ToString("#.###")
41.400803.ToString("#.###")
Powershell's able to figure out in the later case that the values are numbers. In the first case, it's just a string and thus the automatic conversion doesn't work.
What actually works in the first case is to cast the values as nubmers. Like so,
$a | % {$([double]$_).ToString("#.###")}
39,145
40,258
41,401
42,54
43,665

Weird PowerShell Exec Output Capture Behavior

I'm writing a simple PowerShell script that handles the output of mkvinfo. It captures the output of mkvinfo, stores in a variable $s and does some post-processing on $s. The strange part is while $s has content, I can't extract a substring from it.
The error message I'm getting was:
Exception calling "Substring" with "1" argument(s): "startIndex cannot be larger than length of string.
Parameter name: startIndex"
This is a sample code:
$filePath = $folder + $file.name
$mkvinfoExe = "C:\mkvinfo.exe"
$s = & $mkvinfoExe $filePath
$s | out-host
$s.Substring($s.Length-1) | out-host
Are you sure $s is a string and not an array? If it is an array, $s.Length will be the number of elements in the array and you could get the error that you are getting.
For example:
PS > $str = #("this", "is", "a")
PS > $str.SubString($str.Length - 1)
Exception calling "Substring" with "1" argument(s): "startIndex cannot be larger than length of string.
Parameter name: startIndex"
At line:1 char:1
+ $str.SubString($str.Length - 1)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ArgumentOutOfRangeException
Just found out because mkvinfo outputs multiple lines, $s is actually a String array (or List?). Switching to $s[0].Substring($s[0].Length-1) solves it.