How can I ingest random-formatted dates in PowerShell - powershell

I have a login tracker log file that is an amalgamation of multiple sources. The sources (and there are many) use a variety of date formats. I am using the resultant $objList object to hand these over to a SQL Database. When I try to then use SQL Queries, I am missing data.
Here is a small slice of the raw input, from September 2007:
Logon;Username;Server01;10/09/2007 09:56:40
Logon;Username;Server02;10/09/2007 11:26:20
Logon;Username;Server03;9/11/2007 10:16:27 AM
Logon;Username;Server04;11/09/2007 12:28:45
Notice the 3rd one is American format, the others are European. I need a way of getting these things to ingest into a script in a consistent date format. There are literally hundreds of thousands of lines in this file, so it is not realistic to go through by hand and modify anything.
Here is what I have so far.
IF ($SplitUsr.Count -eq '4')
{
$varAction = $SplitUsr[0]
IF ($varAction -eq 'Logon')
{
$varActionx = $SplitUsr[0].Trim()
$varUser = $SplitUsr[1].Trim()
$varHostname = $SplitUsr[2].Trim()
$varTime = $SplitUsr[3].Trim()
try {$datetime = [dateTime]::Parse("$varTime",([Globalization.CultureInfo]::CreateSpecificCulture('en-GB')))}
catch [System.Management.Automation.MethodInvocationException]
{
$datetime = [dateTime]::Parse("$varTime",([Globalization.CultureInfo]::CreateSpecificCulture('en-US')))
}
$objLogon = New-Object PSObject
$objLogon | Add-Member -Membertype NoteProperty -Name "Entry" -Value $intCount
$objLogon | Add-Member -Membertype NoteProperty -Name "Logon" -Value '1'
$objLogon | Add-Member -Membertype NoteProperty -Name "User" -Value $varUser
$objLogon | Add-Member -Membertype NoteProperty -Name "Hostname" -Value $varHostname
$objLogon | Add-Member -Membertype NoteProperty -Name "Date" -Value $datetime
$objList += $objLogon
Unfortunately, this is parsing them into
10 September 2007 09:56:40
10 September 2007 11:26:20
09 November 2007 10:16:27
11 September 2007 12:28:45
You can see that the 3rd example, the one with the American formatting in the raw data, came out as November instead of the 11 September (inverting the 9 and 11).
The same thing is happening all over the place. When I look at the SQL entries for December, here's what I'm getting:
07 December 2007 09:53:33
07 December 2007 11:37:48
12 July 2007 13:25:02
07 December 2007 13:26:38
07 December 2007 15:04:56
You can see that the third one somehow got the 12 and 7 inverted. This is the problem I'm trying to resolve.
Any suggestions?
Edit: A few more samples:
Logon;Username;Server01;18/11/2008 11:19:08
Logon;Username;Server02;18/11/2008 11:21:46 AM
Logon;Username;Server03;18/11/2008 14:28:30
Logon;Username;Server04;19/11/2008 09:55:50
Logon;Username;Servername;19/11/2008 14:14:09
Logon;Username;Servername;19/11/2008 14:19:56
Logon;Username;Servername;20/11/2008 12:19:57 PM
Not all the AM/PM indicate American formatting, unfortunately.

This is the "KI" you was speaking of:
$dates = #( '10/09/2007 09:56:40',
'09/10/2007 11:26:20',
'10/09/2007 10:16:27 AM',
'10/09/2007 12:28:45' )
$cultureUS = [CultureInfo]::CreateSpecificCulture("en-US")
$cultureEU = [CultureInfo]::CreateSpecificCulture("en-GB")
$maxDays = 2 # Max. allowed difference between current date and former date in days
for( $i = 0; $i -lt $dates.Count; $i++ ) {
$currentDate = [DateTime]::Parse( $dates[ $i ],$cultureEU )
if( $i -gt 0 ) {
$diffPast = New-TimeSpan -Start $lastDate -End $currentDate
}
else {
$diffPast = New-TimeSpan -Start $currentDate -End $currentDate
}
if( $diffPast.Days -gt $maxDays ) {
# check if month of current date is day of last date => culture issue
if( $currentDate.Day -eq $lastDate.Month -or $currentDate.Month -eq $lastDate.Day ) {
$currentDate = [DateTime]::Parse( $dates[ $i ],$cultureUS )
}
}
$currentDate
$lastDate = $currentDate
}

Unfortunately, not all the AM/PM indicate American date formats.
Without additional information, you cannot solve your problem, because of inherent ambiguities:
9/11/2007 10:16:27 AM
It is impossible to tell whether this is an en-US (US) timestamp that refers to the 11th day of September (month first), or a en-GB (UK) timestamp that refers to 9th day of November (day first).
Only if either the first or the second component happens to be 13 or higher is en-US or en-GB implied, and only such timestamps would be handled correctly by the try / catch logic in your question.
If you provide an additional constraint that all dates must meet, a solution is possible.
For instance, if you know that all dates fall into a given month:
# The month that all log entries are expected to fall into:
$refMonth = 9 # September, for example.
# Create an array of the cultures to use for parsing:
[cultureinfo[]] $cultures = 'en-GB', 'en-US'
'11/9/2007 17:02:15',
'9/11/2007 05:02:44 PM',
'11/9/2007 05:03:01 PM' | ForEach-Object {
$ok = $false; [datetime] $dt = 0
foreach ($culture in $cultures) {
$ok = [datetime]::TryParse($_, $culture, 'None', [ref] $dt) -and $dt.Month -eq $refMonth
if ($ok) { break }
}
if (-not $ok) { Write-Error "Not recognized as a date in the expected month: $_" }
else { $date } # Output the resulting date.
}
The above yields the following, showing that all dates were parsed as month 9 (September) dates:
Tuesday, September 11, 2007 5:02:15 PM
Tuesday, September 11, 2007 5:02:44 PM
Tuesday, September 11, 2007 5:03:01 PM

Related

The months difference between dates using PowerShell

I need to have AD User Account Expiration Date and now how many months and date its remain until will be disabled.
I tried the code under but I am getting in the months 1 and I have less than one month I would like to have answer like 0 month and 27 days
$StartDate (DateNow)
2019-08-29 00:00:00
AccountExpirationDate
---------------------
2019-09-26 00:00:00
$ExpirDate = Get-ADUser test111 -Properties AccountExpirationDate | select AccountExpirationDate
AccountExpirationDate
---------------------
2019-09-26 00:00:00
$EndDate= $ExpirDate.AccountExpirationDate
2019-09-26 00:00:00
$StartDate = (GET-DATE)
2019-08-29 00:00:00
NEW-TIMESPAN –Start $StartDate –End $EndDate
Days : 27
Hours : 10
Minutes : 29
Seconds : 56
$monthdiff = $EndDate.month - $StartDate.month + (($EndDate.Year - $StartDate.year) * 12)
1
(Here I got the number 1 but I have less than one month)
I found no easy way to do this in PowerShell (as TimeSpan doesn't support month counting), hence I ended up with the following. Starting with the years of the two dates, take their difference and course correct if the start day hasn't passed in the current year. Then do the same with the months:
$StartDate = [DateTime]'2021-01-23'
$today = Get-Date
$daydiff = New-TimeSpan -Start $StartDate -End $today
$yeardiff = $today.year - $StartDate.year
If($yeardiff -gt 0 -And $StartDate.month -gt $today.month
-And $StartDate.day -gt $today.day) {
$yeardiff = $yeardiff -1
}
$monthdiff = $today.month - $StartDate.month + ($yeardiff * 12)
If($StartDate.day -gt $today.day) { $monthdiff = $monthdiff -1 }
Write-Host "$($daydiff.days) days | $($monthdiff) months"
Simplest solution I could work out.
I get the number of months between the start date and the monthly anniversary day in the current month. Then adjust:
$start_date = Get-Date '2022-02-27 21:00'
$end_date = Get-Date
# get the monthly anniversary of the $start_date in the current month
$this_month_anniversary = Get-Date ('{0}-{1}-{2} {3:d2}:{4:d2}' -f $end_date.Year, $end_date.Month, $start_date.Day, $start_date.Hour, $start_date.Minute)
# get the number days in the month, so we can get a denominator when figuring the percent of the way we are towards the next anniversay.
# Which month? If we're past this month's anniversary use the current month. If we haven't reached it yet, use the previous month
if ($end_date -gt $this_month_anniversary) {
$days_in_month = [DateTime]::DaysInMonth($end_date.Year, $end_date.Month)
} else {
$last_month = (Get-Date ('{0}-{1}-01' -f $end_date.Year, $end_date.Month)).AddDays(-1)
$days_in_month = [DateTime]::DaysInMonth($last_month.Year, $last_month.Month)
}
# get months between the start date and this month's anniversay, then
# adjust for the current month, this will be negative if the anniversay hasn't occured yet, otherwise positive
($this_month_anniversary.Month - $start_date.month + (($this_month_anniversary.Year - $start_date.year) * 12) +
(New-TimeSpan -Start $this_month_anniversary -End $end_date).TotalDays / $days_in_month)
I believe this is what you want, or can be tweaked to achieve it relatively easily.
$today = Get-Date;
$endOfYearDate = "12/31/$($today.Year)";
$endOfYear = Get-Date($endOfYearDate);
$monthsLeftInTheYear = ($endOfYear.Month - $today.Month);
$daysLeftInTheYear = ($endOfYear - $today);
$daysLefInTheYear.Days;
You can just do normal arithmetic on dates, but if there are no months, it will return $null not 0.
$today - date
$ExpirDate = Get-ADUser test111 -Properties AccountExpirationDate | select AccountExpirationDate
$diffday = $today - $expirDate
$diffday.days
$diffday.months
if ($diffday.months -eq $null)
{
$Diffday.months =0
}

Powershell - change file Date Created and Date Modified based on Filename

I have lots of file scanned PDF documents that have the file named with an included date. For example:
FileA_2017-10-15.pdf
FileB_2016-04-08.pdf
FileC_2018-01-30.pdf
some files also are formatted with an underscore at the end as well such as...
FileD_2018-01-30_1.pdf
FileE_2018-01-30_2.pdf
there are even a few that have two underscores before the date such as...
FileF_Example_2018-01-30_1.pdf
FileG_Example_2018-01-30_2.pdf
Unfortunately, the date they were scanned in is different than the actual date of the document. So the "Date Created" and "Date Modified" attributes are different than what is shown in the file name.
I would like a script that I could run to change the "Date Created" and "Date Modified" to match that of the filename.
I attempted this using someone else's script but I don't know enough about PowerShell to make it actually work. Note that I do not want to change the name of the file, only the time stamp.
$Directory = "C:\TestFolder"
$DateFormat = "yyyy-MM-dd"
foreach ($file in (Get-ChildItem $Directory)) {
$date_from_file=GetFileName::[datetime])
$file.CreationTime = $date_from_file
$file.LastAccessTime = $date_from_file
$file.LastWriteTime = $date_from_file
Write-Host ($file.Name + " - " + $date_from_file)
}
The code above can be scraped if something else has already been written since what I have doesn't work.
Edit
Wondering if it would also be possible to add to the script so that it could include files in sub-folders as well. Maybe it could be scripted in a way that would only consider the files in a folder if the Date Modified on the folder is today. I would like to run this on a parent folder that could potentially have many sub-folders and if those folders don't have a "Date Modified" of today, then it should skip the files in that folder. I was thinking that could speed up the process. Open to thoughts and thanks for the help!
You are quite near, you need
split the date part from filename and convert it to a [datetime]
I use a RegEx with a capture group anchored at the end $ of the BaseName
## Q:\Test\2019\05\19\SO_56211626.ps1
$Directory = "C:\TestFolder"
foreach ($file in (Get-ChildItem -Path $Directory -Filter *.pdf)){
if($File.BaseName -match '_(\d{4}-\d{2}-\d{2})(_\d)?$'){
$date_from_file= (Get-Date $Matches[1])
$file.CreationTime = $date_from_file
$file.LastAccessTime = $date_from_file
$file.LastWriteTime = $date_from_file
$file | Select-Object Name,CreationTime,LastAccessTime,LastWriteTime
}
}
Sample output:
> Q:\Test\2019\05\19\SO_56211626.ps1
Name CreationTime LastAccessTime LastWriteTime
---- ------------ -------------- -------------
FileA_2017-10-15.pdf 2017-10-15 00:00:00 2017-10-15 00:00:00 2017-10-15 00:00:00
FileB_2016-04-08.pdf 2016-04-08 00:00:00 2016-04-08 00:00:00 2016-04-08 00:00:00
FileC_2018-01-30.pdf 2018-01-30 00:00:00 2018-01-30 00:00:00 2018-01-30 00:00:00
An English locale (en-US) produces:
Name CreationTime LastAccessTime LastWriteTime
---- ------------ -------------- -------------
FileA_2017-10-15.pdf 10/15/2017 12:00:00 AM 10/15/2017 12:00:00 AM 10/15/2017 12:00:00 AM
FileB_2016-04-08.pdf 4/8/2016 12:00:00 AM 4/8/2016 12:00:00 AM 4/8/2016 12:00:00 AM
FileC_2018-01-30.pdf 1/30/2018 12:00:00 AM 1/30/2018 12:00:00 AM 1/30/2018 12:00:00 AM
[
edit - since the OP is getting very strange errors with my suggested fix - errors that i cannot reproduce with the sample data - i've changed this answer to the full suggested code.
edit 2 - added new file name variants and code to deal with them.
edit 3 - changed from splitting to a regex match since the sample data has changed yet again. [*sigh ...*]
]
you are not actually creating the datetime object that you need. the $date_from_file= line doesn't actually do anything other than create red error msgs ... [grin]
replace this line ...
$date_from_file=GetFileName::[datetime])
... with this line ...
$date_from_file = [datetime]::ParseExact($File.BaseName.Split('_')[-1], $DateFormat, $Null)
... and your $date_from_file variable will contain a proper [datetime] object that will work in your assignments.
i would likely change the sequence of those assignments to put the $file.LastAccessTime = $date_from_file LAST so that it doesn't get changed by the next line.
also, that value will change any time that the file is accessed, so it may not be worth changing. [grin]
here is the full script along with what it does -
what it does ...
sets the location & the date format to use
creates a set of test files from the OPs sample file names
gets the files from the source
converts the .BaseName into a [datetime] object
assigns the .CreationTime, .LastWriteTime, & .LastAccessTime values to the datetime from the file name
displays the changed values
here is the code ...
$Directory = $env:TEMP
$DateFormat = "yyyy-MM-dd"
# create some test files
$TestFileList = #(
'FileA_2017-10-15.pdf'
'FileB_2016-04-08.pdf'
'FileC_2018-01-30.pdf'
'FileD_2019-09-09_1.pdf'
'FileE_2015-05-05_2.pdf'
)
foreach ($TFL_Item in $TestFileList)
{
$Null = New-Item -Path $Directory -Name $TFL_Item -ItemType File -Force
}
$FileList = Get-ChildItem -LiteralPath $Directory -Filter '*.pdf' -File
foreach ($FL_Item in $FileList) {
# removed split, added regex match to work with ever-growing list of variant file names
$Null = $FL_Item.BaseName -match '_(?<DateString>\d{4}-\d{2}-\d{2})'
$DateString = $Matches.DateString
$date_from_file = [datetime]::ParseExact($DateString, $DateFormat, $Null)
$FL_Item.CreationTime = $date_from_file
$FL_Item.LastWriteTime = $date_from_file
$FL_Item.LastAccessTime = $date_from_file
# show the resulting datetime info
'=' * 20
$CurrentFileInfo = Get-Item -LiteralPath $FL_Item.FullName
$CurrentFileInfo.FullName
$CurrentFileInfo.CreationTime
$CurrentFileInfo.LastWriteTime
$CurrentFileInfo.LastAccessTime
}
screen output ...
====================
C:\Temp\FileA_2017-10-15.pdf
2017 October 15, Sunday 12:00:00 AM
2017 October 15, Sunday 12:00:00 AM
2017 October 15, Sunday 12:00:00 AM
====================
C:\Temp\FileB_2016-04-08.pdf
2016 April 08, Friday 12:00:00 AM
2016 April 08, Friday 12:00:00 AM
2016 April 08, Friday 12:00:00 AM
====================
C:\Temp\FileC_2018-01-30.pdf
2018 January 30, Tuesday 12:00:00 AM
2018 January 30, Tuesday 12:00:00 AM
2018 January 30, Tuesday 12:00:00 AM
====================
C:\Temp\FileD_2019-09-09_1.pdf
2019 September 09, Monday 12:00:00 AM
2019 September 09, Monday 12:00:00 AM
2019 September 09, Monday 12:00:00 AM
====================
C:\Temp\FileE_2015-05-05_2.pdf
2015 May 05, Tuesday 12:00:00 AM
2015 May 05, Tuesday 12:00:00 AM
2015 May 05, Tuesday 12:00:00 AM
i checked the files directly in explorer & they match the displayed values.
Thanks. I was stuck without this thread. I ended up with a variation that matched any filename with a correctly formatted date, thus:
# Call like:
# powershell -NoLogo -ExecutionPolicy Unrestricted -Sta -NonInteractive -WindowStyle Normal -File ".\Rename_files_selected_folders_ModifyDateStamps.ps1" -Folder "T:\files"
# 1. capture a commandline parameter 1 as a mandatory "Folder string" with a default value
param ( [Parameter(Mandatory=$true)] [string]$Folder = "T:\HDTV\autoTVS-mpg\Converted" )
[console]::BufferWidth = 512
$DateFormat = "yyyy-MM-dd"
write-output "Processing Folder: ",$Folder
# 2. Iterate the files
$FileList = Get-ChildItem -Recurse $Folder -Include '*.mp4','*.bprj','*.ts' -File
foreach ($FL_Item in $FileList) {
$ixxx = $FL_Item.BaseName -match '(?<DateString>\d{4}-\d{2}-\d{2})'
if($ixxx){
#write-output $FL_Item.FullName
$DateString = $Matches.DateString
$date_from_file = [datetime]::ParseExact($DateString, $DateFormat, $Null)
$FL_Item.CreationTime = $date_from_file
$FL_Item.LastWriteTime = $date_from_file
$FL_Item | Select-Object FullName,CreationTime,LastWriteTime
}
}
# https://stackoverflow.com/questions/56211626/powershell-change-file-date-created-and-date-modified-based-on-filename

Split a logfile

I have a log file with this:
Wed Oct 17 05:39:27 2018 : Resource = 'test04' cstep= 'titi04' time =18.751s
Wed Oct 17 05:40:31 2018 : Resource = 'test05' cstep= 'titi05' time =58.407s
Wed Oct 17 05:41:31 2018 : Resource = 'test06' cstep= 'titi06' time =3.400s
Wed Oct 17 05:42:31 2018 : Resource = 'test07' cstep= 'titi07' time =4.402s
I want split and want only the values greater than 5:
18.751
58.407
My script is in PowerShell and collects all values, not just values greater than 5:
$list = Get-Content "C:\Users\Desktop\slow_trans\log_file.txt"
$results = foreach ($line in $list) {
$line.Split('=')[3].Trim().TrimEnd('s')
}
$results
Results are
18.751
58.407
3.400
4.402
I want only
3.400
4.402
Changing the requirements on the fly is normally a no go,
so you don't deserve it.
Also the wording Superior 5 reminds me at a previous question from another user account..
Nevertheless here a script with a single pipe and datetime conversion.
## Q:\Test\2018\11\06\SO_53170145.ps1
Get-Content .\logfile.txt |
Where-Object {$_ -match '^(.*?) : .*time =([0-9\.]+)s'}|
Select-Object #{n='DT';e={([datetime]::ParseExact($Matches[1],'ddd MMM dd HH:mm:ss yyyy',[cultureinfo]::InvariantCulture).ToString('yyyy-MM-dd HH:mm:ss'))}},
#{n='val';e={[double]$Matches[2]}} |
Where-Object val -le 5
Sample output (decimal comma due to my German locale)
DT val
-- ---
2018-10-17 05:41:31 3,4
2018-10-17 05:42:31 4,402
the following casts the selected string as double and then returns only those which are less than 5
$results = Foreach ($line in $list) {
$val = [double]$line.Split('=')[3].Trim().TrimEnd('s')
if($val -lt 5) {
$val
}
}
Select-String is one option:
(Select-String -Path "TargetLog.txt" -Pattern ".*(?<time>\d+\.\d+)s").Matches |
ForEach-Object {
if([double]$_.Groups['time'].Value -lt 5.0) {$_.Value}
}
This will output the entire matching line:
Wed Oct 17 05:41:31 2018 : Resource = 'test06' cstep= 'titi06' time =3.400s
Wed Oct 17 05:42:31 2018 : Resource = 'test07' cstep= 'titi07' time =4.402s
If you only want the number from each line, change the if block to this:
{$_.Groups['time'].Value}

System.Object[] in CSV File

I have read all I can read but I just don't get what the problem is when exporting something from PowerShell to a CSV and getting System.Object[]
This is just a section of code I use to extract missing updates from servers that are managed by SCCM. The Switch Array is there because I need to have the SCCM Deployment Unique ID translated into a "Friendly" name I use to identify that patching collection.
This works fine and is displayed correcting on the screen (I just do this for testing the Switch array to ensure it is working by putting $Updates in the script). However, when I attempt to export to a CSVfile, I get the System.Object[] underneath the column titles.
I know I could pipe the first line, then select the objects in the $TargetedUpdates array, and export them without any problem. But this only gives me the SCCM Deployment Unique ID with the server. I need to "resolve" this to a friendly name that makes sense in the CSV file. How can this be accomplished?
$TargetedUpdates = Get-WmiObject -Query "Select * from CCM_TargetedUpdateEX1 where UpdateState = 0" -Namespace root\ccm\SoftwareUpdates\DeploymentAgent -Computer ifdpv02
ForEach-Object {
$MissingUpdates = $TargetedUpdates.RefAssignments.TrimEnd(";")
$MonthlyPatch = switch ($MissingUpdates){
"{0C3267EE-F343-4577-B1A3-C24FA0406DDF}" {"October 2014 Patching for Test\DEV Servers"}
"{D849903A-4594-4D72-9224-39DC2ABA22E}" {"October 2014 Patching for Production Servers"}
"{A3F0E8A2-FB2F-4045-8E22-7726007844E6}" {"October 2014 Patching for Manual Servers"}
"{DC3991B7-30EB-4529-AA63-537968A651D0}" {"October 2014 Patching for New Server Builds"}
"{7C263094-4DA3-4AB8-9F79-0C169EA18D6D}" {"October 2014 Patching for Manual Test Servers"}
"{39EDE4AD-71C9-4393-B849-498C6D677FFF}" {"October 2014 Patching for Test\SQL Servers"}
#**********************************************************************************************
#**********************************************************************************************
default {"This is a System Center Endpoint Protection Update"}  
}
$Updates = New-Object PSobject
$Updates | Add-Member NoteProperty Server ($TargetedUpdates.PScomputerName)
$Updates | Add-Member NoteProperty MonthlyPatch $MonthlyPatch
$Updates
$Updates | Export-Csv C:\temp\test.csv -NoTypeInformation
}
Invoke-Item C:\temp\test.csv
In short: You are not processing each update individually.
Unfortunately I am not familiar with the data structure of your WMI return to know if this is going to work. You need to send data to the foreach-object loop to process is the main thing.
$TargetedUpdates = Get-WmiObject -Query "Select * from CCM_TargetedUpdateEX1 where UpdateState = 0" -Namespace root\ccm\SoftwareUpdates\DeploymentAgent -Computer ifdpv02
$TargetedUpdates | ForEach-Object {
$MissingUpdates = $_.RefAssignments.TrimEnd(";")
$MonthlyPatch = switch ($MissingUpdates){
"{0C3267EE-F343-4577-B1A3-C24FA0406DDF}" {"October 2014 Patching for Test\DEV Servers"}
"{D849903A-4594-4D72-9224-39DC2ABA22E}" {"October 2014 Patching for Production Servers"}
#TRUNCATED AS IT IS TOO LONG**********************************************************************************************
default {"This is a System Center Endpoint Protection Update"}
}
$Updates = New-Object PSobject
$Updates | Add-Member NoteProperty Server ($TargetedUpdates.PScomputerName)
$Updates | Add-Member NoteProperty MonthlyPatch $MonthlyPatch
$Updates
} | Export-Csv C:\temp\test.csv -NoTypeInformation
We loop each update individually create an object for each. Those are then sent to the Export-CSV for output.
Another Approach
Instead of the switch in the loop you could use a hashtable that stores all your id and friendly names and then use a calculated property to make the object for you rather easily.
$friendlies = #{
"{0C3267EE-F343-4577-B1A3-C24FA0406DDF}" = "October 2014 Patching for Test\DEV Servers"
"{D849903A-4594-4D72-9224-39DC2ABA22E}" = "October 2014 Patching for Production Servers"
# Add more here obviously.
}
$server = "ifdpv02"
$TargetedUpdates = Get-WmiObject -Query "Select * from CCM_TargetedUpdateEX1 where UpdateState = 0" -Namespace root\ccm\SoftwareUpdates\DeploymentAgent -Computer $server
$TargetedUpdates | Select-Object #{Label="Server";Expression={$server}},
#{Label="MonthlyPatch";Expression={$friendlies[$_.RefAssignments.TrimEnd(";")]}} |
Export-Csv C:\temp\test.csv -NoTypeInformation
Caveat for both solution here:
Like I said earlier I do not know the data structure. If RefAssignments returns as array we need to add some more logic but it can be done.
I believe I solved the problem by changing my variable for $MonthlyPatchAssignment. Excuse me for the clutter, the code I will post here is much different than I originally posted. The key seems to be grabbing the variable in the pipeline ($.RefAssignments) and putting that into my Swich array. Before I was trying to take that same variable but make it equal to another variable (i.e. $MissingUpdates = $.RefAssignments.TrimEnd(";"))
Now I am getting the correct value instead of System.Object[] in my CSV file
$MonthlyPatchAssignment = switch ($_.RefAssignments.TrimEnd(";")) {
     
"{0C3267EE-F343-4577-B1A3-C24FA0406DDF}" {"October 2014 Patching for Test\DEV Servers"}
"{D849903A-4594-4D72-9224-39DC2ABA22E}" {"October 2014 Patching for Production Servers"}
"{A3F0E8A2-FB2F-4045-8E22-7726007844E6}" {"October 2014 Patching for Manual Servers"}
"{DC3991B7-30EB-4529-AA63-537968A651D0}" {"October 2014 Patching for New Server Builds"}
"{7C263094-4DA3-4AB8-9F79-0C169EA18D6D}" {"October 2014 Patching for Manual Test Servers"}
"{39EDE4AD-71C9-4393-B849-498C6D677FFF}" {"October 2014 Patching for Test\SQL Servers"}
default {"This is a System Center Endpoint Protection Update"}  
}
$NonCompliantDetail = New-Object PSobject
$NonCompliantDetail | Add-Member NoteProperty Server $($_.PScomputerName)
$NonCompliantDetail | Add-Member NoteProperty PatchName $MonthlyPatchAssignment
$NonCompliantDetail | Add-Member NoteProperty BullentinID $uBulletinID
$NonCompliantDetail | Add-Member NoteProperty Description $uTitle
$NonCompliantDetail | Export-Csv C:\Temp\sccm\"$FileNamePreface"_MissingUpdatesRAW.csv -NoTypeInformation -Append

PowerShell: Comparing dates

I am querying a data source for dates. Depending on the item I am searching for, it may have more than date associated with it.
get-date ($Output | Select-Object -ExpandProperty "Date")
An example of the output looks like:
Monday, April 08, 2013 12:00:00 AM
Friday, April 08, 2011 12:00:00 AM
I would like to compare these dates and return which one is set further out into the future.
As Get-Date returns a DateTime object you are able to compare them directly. An example:
(get-date 2010-01-02) -lt (get-date 2010-01-01)
will return false.
I wanted to show how powerful it can be aside from just checking "-lt".
Example: I used it to calculate time differences take from Windows event view Application log:
Get the difference between the two date times:
PS> $Obj = ((get-date "10/22/2020 12:51:1") - (get-date "10/22/2020 12:20:1 "))
Object created:
PS> $Obj
Days : 0
Hours : 0
Minutes : 31
Seconds : 0
Milliseconds : 0
Ticks : 18600000000
TotalDays : 0.0215277777777778
TotalHours : 0.516666666666667
TotalMinutes : 31
TotalSeconds : 1860
TotalMilliseconds : 1860000
Access an item directly:
PS> $Obj.Minutes
31
Late but more complete answer in point of getting the most advanced date from $Output
## Q:\test\2011\02\SO_5097125.ps1
## simulate object input with a here string
$Output = #"
"Date"
"Monday, April 08, 2013 12:00:00 AM"
"Friday, April 08, 2011 12:00:00 AM"
"# -split '\r?\n' | ConvertFrom-Csv
## use Get-Date and calculated property in a pipeline
$Output | Select-Object #{n='Date';e={Get-Date $_.Date}} |
Sort-Object Date | Select-Object -Last 1 -Expand Date
## use Get-Date in a ForEach-Object
$Output.Date | ForEach-Object{Get-Date $_} |
Sort-Object | Select-Object -Last 1
## use [datetime]::ParseExact
## the following will only work if your locale is English for day, month day abbrev.
$Output.Date | ForEach-Object{
[datetime]::ParseExact($_,'dddd, MMMM dd, yyyy hh:mm:ss tt',$Null)
} | Sort-Object | Select-Object -Last 1
## for non English locales
$Output.Date | ForEach-Object{
[datetime]::ParseExact($_,'dddd, MMMM dd, yyyy hh:mm:ss tt',[cultureinfo]::InvariantCulture)
} | Sort-Object | Select-Object -Last 1
## in case the day month abbreviations are in other languages, here German
## simulate object input with a here string
$Output = #"
"Date"
"Montag, April 08, 2013 00:00:00"
"Freidag, April 08, 2011 00:00:00"
"# -split '\r?\n' | ConvertFrom-Csv
$CIDE = New-Object System.Globalization.CultureInfo("de-DE")
$Output.Date | ForEach-Object{
[datetime]::ParseExact($_,'dddd, MMMM dd, yyyy HH:mm:ss',$CIDE)
} | Sort-Object | Select-Object -Last 1
Considering you want to include time also, I have included sample. I am putting datetime in the ISO8601, so it works in locale agnostic manner.
Monday, April 08, 2013 12:00:00 AM
Friday, April 08, 2011 12:00:00 AM
(Get-date "2013-04-08T00:00:00") -lt (Get-Date "2011-04-08T00:00:00")
False