Extracting date from a file name in PowerShell - powershell

I have written a PowerShell script to extract the date from filenames and delete based on that.
Sample file name: TBTT_UAT_01012021_000019.csv
Code to extract date :
$fileDateText = ($filename -split '_')[4]
$fileDate = [System.DateTime]::ParseExact($fileDateText, "dd/MM/yyyy", [System.Globalization.CultureInfo]::InvariantCulture)
But I am getting the following error when I run the script, as it recognizes the date before CSV:
String '000019.csv' was not recognized as a valid DateTime.: FormatException
Can someone advise, please?
Thanks,

I think you may be looking at the wrong array element returning from your split. Also the string you are giving in the overloads for .ParseExact() may be off. This seemed to work in my tests:
$fileDate = ("TBTT_UAT_01012021_000019.csv" -split '_')[2]
[DateTime]::ParseExact($fileDate, "ddMMyyyy", [System.Globalization.CultureInfo]::InvariantCulture)
Returned: Friday, January 1, 2021 12:00:00 AM
If you want to get more granular about the time we'll have to cut up the string file name differently.

Issue has been fixed by changing from:
[System.DateTime]::ParseExact($fileDateText, "dd/MM/yyyy", [System.Globalization.CultureInfo]::InvariantCulture)
to
$fileDate = [DateTime]::ParseExact("$fileDateText", 'ddMMyyyy',[CultureInfo]::InvariantCulture)

if ($filename -match '(?<Timestamp>\d{8})') {
[DateTime]::ParseExact($Matches.Timestamp, 'ddMMyyyy', [CultureInfo]::InvariantCulture)
}
I gravitate toward regexes with named extraction groups rather than doing string manipulation. The syntax is a bit more fiddly, but the resulting script ends up being more resilient.

Related

PowerShell get latest string date

I have a list with dates inside
$date = #('05-28-2020','09-30-2021')
I want to find the most recent date
I tried to convert string before comparat it :
[datetime]::ParseExact($date[0],'MM_dd_yyyy', $null)
But not working
Any ideas ?
You can directly cast your date-time strings to [datetime] - no need for a ParseExact() call - given that your input format is recognized by the invariant culture that PowerShell always uses in casts.
System.Linq.Enumerable.Max<T>() allows you to find the maximum in a [datetime]-typed enumerable.
Therefore:
$date = #('05-28-2020','09-30-2021') # Note: The #(...) enclosure isn't strictly needed.
[Linq.Enumerable]::Max([datetime[]] $date)
On a US-English system, the above yields Thursday, September 30, 2021 12:00:00 AM, i.e. the default output formatting of the most recent date in the input, [datetime] '09-30-2021'.
As for what you tried:
As has been mentioned, the only immediate problem with your ParseExact() call was that you used _ instead of -.
Also note that passing $null as the third argument implies that the culture currently in effect is used during parsing (as reflected in [cultureinfo]::CurrentCulture; this won't matter with numeric formatting sequences such as MM, but it would with symbolic ones such as MMM - and potentially even with unquoted placeholders : and /.
switch your _ to - and it will work:
$date = #('05-28-2020','09-30-2021')
$max = '01-01-1999'
$date | ForEach-Object {
if($_ -ge $max){
$max = $_
}
}
This should do the work.

Convert a string to datetime in PowerShell

I am using PowerShell to try and convert a string to a datetime. It should be easy, right?
I am getting the string from a CSV import, and it comes in the format of Jul-16. I have tried multiple ways of getting it into the format I want which is yyyy-MM-dd and I am currently at the following.
$invoice = $object.'Invoice Month'
$invoice = "01-" + $invoice
$invoice = [datetime]::parseexact($invoice, 'yyyy-MM-dd', $null)
But I get the error:
String was not recognized as a valid DateTime.
Am I missing something?
ParseExact is told the format of the date it is expected to parse, not the format you wish to get out.
$invoice = '01-Jul-16'
[datetime]::parseexact($invoice, 'dd-MMM-yy', $null)
If you then wish to output a date string:
[datetime]::parseexact($invoice, 'dd-MMM-yy', $null).ToString('yyyy-MM-dd')
You can simply cast strings to DateTime:
[DateTime]"2020-7-16"
or
[DateTime]"Jul-16"
or
$myDate = [DateTime]"Jul-16";
And you can format the resulting DateTime variable by doing something like this:
'{0:yyyy-MM-dd}' -f [DateTime]'Jul-16'
or
([DateTime]"Jul-16").ToString('yyyy-MM-dd')
or
$myDate = [DateTime]"Jul-16";
'{0:yyyy-MM-dd}' -f $myDate
You need to specify the format it already has, in order to parse it:
$InvoiceDate = [datetime]::ParseExact($invoice, "dd-MMM-yy", $null)
Now you can output it in the format you need:
$InvoiceDate.ToString('yyyy-MM-dd')
or
'{0:yyyy-MM-dd}' -f $InvoiceDate
Chris Dents' answer has already covered the OPs' question but seeing as this was the top search on google for PowerShell format string as date I thought I'd give a different string example.
If like me, you get the time string like this 20190720170000.000000+000
An important thing to note is you need to use ToUniversalTime() when using [System.Management.ManagementDateTimeConverter] otherwise you get offset times against your input.
PS Code
cls
Write-Host "This example is for the 24hr clock with HH"
Write-Host "ToUniversalTime() must be used when using [System.Management.ManagementDateTimeConverter]"
$my_date_24hr_time = "20190720170000.000000+000"
$date_format = "yyyy-MM-dd HH:mm"
[System.Management.ManagementDateTimeConverter]::ToDateTime($my_date_24hr_time).ToUniversalTime();
[System.Management.ManagementDateTimeConverter]::ToDateTime($my_date_24hr_time).ToUniversalTime().ToSTring($date_format)
[datetime]::ParseExact($my_date_24hr_time,"yyyyMMddHHmmss.000000+000",$null).ToSTring($date_format)
Write-Host
Write-Host "-----------------------------"
Write-Host
Write-Host "This example is for the am pm clock with hh"
Write-Host "Again, ToUniversalTime() must be used when using [System.Management.ManagementDateTimeConverter]"
Write-Host
$my_date_ampm_time = "20190720110000.000000+000"
[System.Management.ManagementDateTimeConverter]::ToDateTime($my_date_ampm_time).ToUniversalTime();
[System.Management.ManagementDateTimeConverter]::ToDateTime($my_date_ampm_time).ToUniversalTime().ToSTring($date_format)
[datetime]::ParseExact($my_date_ampm_time,"yyyyMMddhhmmss.000000+000",$null).ToSTring($date_format)
Output
This example is for the 24hr clock with HH
ToUniversalTime() must be used when using [System.Management.ManagementDateTimeConverter]
20 July 2019 17:00:00
2019-07-20 17:00
2019-07-20 17:00
-----------------------------
This example is for the am pm clock with hh
Again, ToUniversalTime() must be used when using [System.Management.ManagementDateTimeConverter]
20 July 2019 11:00:00
2019-07-20 11:00
2019-07-20 11:00
MS doc on [Management.ManagementDateTimeConverter]:
https://learn.microsoft.com/en-us/dotnet/api/system.management.managementdatetimeconverter?view=dotnet-plat-ext-3.1
$invoice = "Jul-16"
[datetime]$newInvoice = "01-" + $invoice
$newInvoice.ToString("yyyy-MM-dd")
There you go, use a type accelerator, but also into a new var, if you want to use it elsewhere, use it like so: $newInvoice.ToString("yyyy-MM-dd")as $newInvoice will always be in the datetime format, unless you cast it as a string afterwards, but will lose the ability to perform datetime functions - adding days etc...
Hope below helps!
PS C:\Users\aameer>$invoice = $object.'Invoice Month'
$invoice = "01-" + $invoice
[datetime]$Format_date =$invoice
Now type is converted. You can use method or can access any property.
Example :$Format_date.AddDays(5)
It's very easy; in my case it works with;
Input:
$Date = '29-07-2022'
DateFormat Convertion:
[datetime]::parseexact($date, 'dd-MM-yyyy', $null).ToString('dd-MMMM-yyyy')
Output:
I had a different but related need to convert a number (seconds) into days/hours/seconds etc.
$seconds = 41414141
New-Timespan -seconds $seconds

Change date format from "yyyymmdd" to "mm/dd/yyyy"

I've tried a lot of different ways and I can't seem to get it right.
Here is the code of what I have tried so far...
[String]$dateValue = '20161212'
[String]$dateStamp = $dateValue -f (Get-Date)
[String]$dateStamp2 = ([datetime]::parseexact($dateValue, "yyyyMMdd", [System.Globalization.CultureInfo]::InvariantCulture)).Date
[String]$dateStamp3 = ([datetime]::FromFileTime($dateValue)).ToString('g')
Write-Host '$dateStamp = ' $dateStamp
Write-Host '$dateStamp2 = ' $dateStamp2
Write-Host '$dateStamp3 = ' $dateStamp3
Current Code Output
$dateStamp = 20161212
$dateStamp2 = 12/12/2016 00:00:00
$dateStamp3 = 12/31/1600 5:00 PM
Desired Code Output
$dateStamp = 12/12/2016
Any Ideas?
Once you have a datetime object it's easy to convert it to whatever string format you need. You are so close with your second attempt. Adding ToString allows you to specify a string format.
([datetime]::parseexact($dateValue, "yyyyMMdd", [System.Globalization.CultureInfo]::InvariantCulture)).ToString("dd/MM/yyyy")
Given that you have a culture-invariant string as your input and that you want a fixed output format, you may as well perform string parsing, without the need to convert to an intermediate [datetime] instance:
> '20161213' -replace '\d{2}(\d{2})(\d{2})(\d{2})', '$2/$3/$1'
12/13/16
Note that I've changed the day to be different from the month to better highlight the reformatting that takes place.
Generally, though, the [datetime]-based method demonstrated in Nick's helpful answer gives you the most flexibility.

Datetime compare doesn't match

I want to compare a date which I get from a xml file with a "lastwritetime" of a file. Powershell ISE shows the same for both but my if does not return true. I tried some datetime casts but this doesn't help as well.
My XML:
<meta>
<log path="D:\tomcat.log">
<lastwrite>08/03/2015 13:44:09</lastwrite>
</log>
</meta>
My powershell script:
[xml]$log_meta = Get-Content "D:\log_meta.xml"
$node = $log_meta.meta.log | where {$_.path -eq "D:\tomcat.log"}
(ls "D:\tomcat.log").LastWriteTime
$node.lastwrite
if((ls "D:\tomcat.log").LastWriteTime -eq $node.lastwrite){
"Date and time is the same"
}
Third line displays: (german date format)
Montag, 3. August 2015 13:44:09
Fourth line displays:
08/03/2015 13:44:09
But my if does not return true.
$node.lastwrite is a string (you read it from file) while LastWriteTime is DateTime. You need to convert them both to date, or both to DateTime.
If you convert both to DateTime, you'll need to round file time to the nearest second. On NTFS, file time has 100-nanosecond precision.
It's probably better to convert file time to string in the same format.
$writeTimeString = (ls "D:\tomcat.log").LastWriteTime.ToString("MM/dd/yy HH:mm:ss")
Okay, it's working now. I converted both to strings.
(ls "D:\tomcat.log").LastWriteTime.ToString("dd/MM/yyyy HH:mm:ss")
I hoped I can work with datetimes, but maybe that's not possible. Thank you!

extract filename and data from file

I have hourly log files from the past couple of months and i would like to export the data to MS Charts.
I've managed to get the data out, but having problems with getting the date on the x axis of the Chart.
The file name of the log file contains the date and i've tried using the creatation date or last write time. The closest i've got is to count the number of logs then divide by 24, but the dates it generates does not match up with the data.
Any ideas please?
Everything works as expected, but i cant get the X Axis to display the correct dates. in theory, it should calculate the 11 June as the start date, and the end date is yesterday's date.
the format of the file it reads from is
HealthCheck Wed 10 Sep 2014 - 05.00 AM.log
Ideally, I would like to get the date from the file name. I dont want to rely on calculations on when the file was written as this is prone to errors.
[void][Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms.DataVisualization")
$Farm = "XAccess"
#gets files from yesterday
$Date = Get-Date #-Uformat %x
$Date = ($Date).adddays(-1)
$Date = $Date.ToString("M/d/yyyy")
$files = get-childitem "c:\$Farm*.log" | Where{$_.LastWriteTime -lt $date}
$ActiveSessions = Select-String -path $files '(?<=^"*Total Active Sessions: )\d+(?=)'|
ForEach-Object {$_.Matches[0].Value}
#Calculates numbers of days
$datapoints = $ActiveSessions.count/24
#== Creates Chart ==#
# create chart object
$Chart = New-object System.Windows.Forms.DataVisualization.Charting.Chart
$Chart.Width = 1600
$Chart.Height = 800
# create a chartarea to draw on and add to chart
$ChartArea = New-Object System.Windows.Forms.DataVisualization.Charting.ChartArea
$Chart.ChartAreas.Add($ChartArea)
[void]$Chart.Series.Add("Data")
$Chart.ChartAreas["ChartArea1"].AxisX.Interval = 24
$Chart.ChartAreas["ChartArea1"].AxisX.LabelStyle.Angle = -40
$Chart.ChartAreas["ChartArea1"].AxisY.Interval = 5
$Chart.ChartAreas["ChartArea1"].AxisY.title = "Active Sessions"
$chart.Series.Add('ChartArea1')
# add a data point for each server
foreach ($session in $ActiveSessions)
{
$dp1 = new-object System.Windows.Forms.DataVisualization.Charting.DataPoint(0, $session)
$dp1.AxisLabel = (get-date).adddays(-"$datapoints").tostring("ddd dd MMM")
$datapoints = $datapoints - 0.0416666666666667
$Chart.Series["Data"].Points.Add($dp1)
}
# set the title to the date and time
$title = new-object System.Windows.Forms.DataVisualization.Charting.Title
$Chart.Titles.Add( $title )
$Chart.Titles[0].Font = "Arial,13pt"
$Chart.Titles[0].Text = "Year to Date sessions for $Farm"
# save the chart to a file
$Chart.SaveImage("C:\$Farm Farm.png","png")
Ok, so you want the date from the file. No problem, we can do that. Let's start with how best to do it... We have options (as usual) to choose from. Wait, I'm getting ahead of myself. Where do we want to define it? Well, simplest place I can think of is anyplace that you are looping through the files in question and getting other data, so we can keep everything properly associated. So, it looks like the lines that you assign $ActiveSessions is going to be our best bet.
Now, back to how to get the date. We could use the SubString method, but that just seems messy to me when we have a defined format for the text. Personally I'd rather do a regex match. So what I see in the file name is a three letter day abbreviation, then a 1-2 digit day, a three letter month, and a four digit year. After that there's a hyphen, and two digit hour, period, two digit minute, and the AM/PM designator.
So, as far as [datetime] formats go that's ddd d MMM yyyy - hh.mm tt. We'll get back to that. How to extract the date? Oh right, a regex. Here's how I'd get that:
([regex]"\w{3} \d{1,2} \w{3} \d{4} - \d{2}\.\d{2} (?:AM|PM)").matches($_.Filename).value
That declares the pattern as a regex object, and then uses it's Matches() method, and get the value of the match. So we have the date, now to actually make it usable. Here's where we get back to that DateTime format. We can use the [DateTime]::ParseExact() method to get the date time, even with the strange formatting. Now we are going to be getting it in the ForEach loop, and are extracting the date from the FileName property of the object that Select-String is feeding the loop. Here's what it will look like:
[datetime]::ParseExact(([regex]"\w{3} \d{1,2} \w{3} \d{4} - \d{2}\.\d{2} (?:AM|PM)").matches($_.Filename).value,"ddd d MMM yyyy - hh.mm tt",$null)
Ok, that actually gives us a nice usable datetime object. So that ForEach loop is already spitting back the active sessions, and $ActiveSessions is an array of strings. Let's change that a little and make it an array of objects, and each object will have two properties now, Sessions and Date. So the inside of the ForEach loop has to make an object with those properties. Easiest way (with PowerShell v3 or higher, I'll show you the hard way if you are running an old version of PowerShell and need me to) is:
[PSCustomObject][Ordered]#{
'Sessions'=$_.Matches[0].Value
'Date'=[datetime]::ParseExact(([regex]"\w{3} \d{1,2} \w{3} \d{4} - \d{2}\.\d{2} (?:AM|PM)").matches($_.Filename).value,"ddd d MMM yyyy - hh.mm tt",$null)
}
That makes the whole $ActiveSessions = line look like:
$ActiveSessions = Select-String -path $files '(?<=^"*Total Active Sessions: )\d+(?=)'|
ForEach-Object {[PSCustomObject][Ordered]#{
'Sessions'=$_.Matches[0].Value
'Date'=[datetime]::ParseExact(([regex]"\w{3} \d{1,2} \w{3} \d{4} - \d{2}\.\d{2} (?:AM|PM)").matches($_.Filename).value,"ddd d MMM yyyy - hh.mm tt",$null)
}
}
Only thing that leaves is to change where that variable is referenced:
foreach ($session in $ActiveSessions)
{
$dp1 = new-object System.Windows.Forms.DataVisualization.Charting.DataPoint(0, $session.session)
$dp1.AxisLabel = $Session.Date.tostring("ddd dd MMM")
$Chart.Series["Data"].Points.Add($dp1)
}
That should do it for you.
I tried using regex but could not get pass the error i mentioned.
so after a bit more research, i decided exporting the creation date of the file and the data i needed into a CSV file, then creating the chart from that. So this is the code i used to get the data in to csv:
$files = get-childitem "c:\$farm*.log" | sort CreationTime
Foreach ($file in $files)
{
$FileCreation = $file.CreationTime.Date.ToString('ddd dd MMM yyyy')
$ActiveSessions = Select-String -path $file '(?<=^"*Total Active Sessions: )\d+(?=)' | ForEach-Object {$_.Matches[0].Value}
Add-Content d:\licInUse.csv "$FileCreation,$ActiveSessions"
}
# processing the Data
$Processes = Import-Csv -path d:\licInUse.csv -Delimiter ',' -Header "Date","Count"
$DateNames = #(foreach($Date in $Processes){$Date.Date})
$SessionCount = #(foreach($Date in $Processes){$Date.Count})
then used the following to plot the data
$Chart.Series["Data"].Points.DataBindXY($DateNames, $SessionCount)