Running Cmdlet with equality comparision operator - powershell

Running the following command in Powershell 7.0.3 returns true
[datetime]'2020-08-09' -eq 'August 9, 2020'
But, I want to replace the right side of the command with Get-Date. I wonder how I can do it. If I'm not wrong (Please correct me if I am), we can do it in bash using the by enclosing the command in ticks(`).
I've tried the Invoke-Command cmdlet but I get the same error:
> [datetime]'2020-08-09' -eq Invoke-Command Get-Date
ParserError:
Line |
1 | [datetime]'2020-08-09' -eq Invoke-Command Get-Date
| ~
| You must provide a value expression following the '-eq' operator.
Can someone help me out with it or point me in the right direction?

To compare a datetime object with another, you need to make sure that other part is also a datetime object.
This is why you need to put brackets around the right-hand side of the equasion, so the command in there is evaluated first.
As Lee_Dailey mentioned, the result of [datetime]'2020-08-09' is a date object with the time part set to 0:00:00 (midnight).
As example of how it will work is:
[datetime]'2020-08-09' -eq (Get-Date).Date
Another thing to consider is the fact that constructing the date using [datetime]'2020-08-09' will leave you with a datetime object that has its .Kind property set to Unspecified.
If you are comparing this to the resulting datetime object that stores a UTC date (= has its .Kind property set to 'Utc)', the equasion will still go wrong (unless your current timezone IS actually UTC)
We can fake this by doing
[datetime]'2020-08-09' -eq (Get-Date).Date.ToUniversalTime()
Which in my timezone would return False.
P.S. It is wrong to compare a datetime object to a formatted date string.
It may have worked in your case, because apparently the .ToString() method of a datetime object happens to format in 'yyyy-MM-dd' on your system. On different locales however comparing like this will fail .

Related

Where-object clause of Get-ChildItem only works when I use date functions in specific application

I have code to find files containing specific words that were written to the folder today. The following code works as expected:
##Define the directory to search
$Crmrpts = '\\1.1.1\crmrpts'
##Define the report names to search for. Full names not wildcard
$Values =
#(
'Customer',
'Product',
'Sales',
'Returns',
'Inventory'
)
##Gci the folder defined above looking for file names matching the reports to search for above
$Files = gci $Crmrpts '*.csv' | Where-Object {
$v = $_.Name
$t = $_.LastWriteTime.Date
$Values | Where-Object { $v.Contains($_) -and $t.Equals((Get-Date).Date) }
}
The important variable here is $t and the section $t.Equals((Get-Date).Date)
Since I am looking for LastWriteTime.Date specifically, I assume I can change my search criteria to be a date, like so:
$Values | Where-Object { $v.Contains($_) -and $t.Equals("12/13/2020")
But this doesn't work. However, if I run another piece of independent code using the same components, it does work:
gci '\\1.1.1\crmrpts' | ? {$_.Name -like '*Customers*' -and $_.LastWriteTime.Date -eq "12/13/2020"}
Why does the above line work, containing $_.LastWriteTime.Date -eq "12/13/2020" but that same piece of logic does not work in my original script above?
The only way I can get my original script to work for a date other than current date is like this:
$Values | Where-Object { $v.Contains($_) -and $t.Equals((Get-Date).Date.AddDays(-1))
The .Net DateTime.Equals() method can only compare a datetime object with another datetime object.
That is why $t.Equals("12/13/2020") won't work, because you are telling the method to compare a datetime object to a string.
$_.LastWriteTime.Date -eq "12/13/2020" does work, because PowerShell conveniently converts the given date string to a datetime object before comparing.
Here, the conversion to a DateTime object works culture invariant as mklement0 commented.
However, if you have a different locale, like me (NL Dutch), you cannot rely on creating dates using the invariant "MM/dd/yyy" format all the time..
Especially, something like Get-Date '12/13/2020' will give me an exception because here the current locale settings (for me "dd/MM/yyyy") are used.
In short:
always try and compare a datetime object to another datetime object
don't assume that every machine in the world accepts the same date string format
tl;dr
Use a [datetime] cast to force the method argument to be of that type:
t.Equals([datetime] "12/13/2020")
$t.Equals("12/13/2020")
As Theo's helpful answer explains, the [datetime] type's .Equals() method only works as you intended if you pass a [datetime] instance - passing a string representation of a date is not automatically converted to [datetime].[1]
By contrast, PowerShell's -eq operator does perform implicit conversion of the RHS, namely to the type of the LHS operand.
Therefore, $_.LastWriteTime.Date -eq "12/13/2020" is implicitly the same as:
$_.LastWriteTime.Date -eq [datetime] "12/13/2020" and that's why it worked.
Note that this implicit conversion is culture-invariant; that is, it works the same irrespective of what culture is in effect in your session (as reflected in $PSCulture and by Get-Culture).
PowerShell casts and automatic type conversions, including stringification in expandable strings ("..."), are generally culture-invariant (based on System.Globalization.CultureInfo.InvariantCulture, which is based on the US-English culture).
Notable exceptions:
Implicit type conversions in the context of -as, the conditional type conversion operator, are unexpectedly culture-sensitive.
This inconsistency is discussed in GitHub issue #8129.
Implicit type conversions in the context of binding to cmdlet parameters are also unexpectedly culture-sensitive.
This inconsistency, which notably does not affect written-in-PowerShell functions and scripts - is discussed in GitHub issue #6989; however, a decision was made not to fix this so as not to break backward compatibility.
Also note that PowerShell's for-display output formatting and the use of the -f operator (e.g., '{0:N2}' -f 1.2) are culture-sensitive by design.
For a comprehensive discussion of culture invariance vs. sensitivity in PowerShell, see this answer.
[1] Note that while PowerShell generally performs implicit type conversions also when calling .NET methods, in this case PowerShell's method-overload resolution algorithm chooses the [object] based .ToEquals() overload (bool Equals(System.Object value)), which therefore passes the string argument as-is - and [datetime] itself makes no attempt to perform automatic conversions, as is typical. It is PowerShell that provides automatic conversions in many cases, which is generally a great benefit, but has its pitfalls.

powershell: stuck with easy DateTime script

Currently having a really really hard time with something that looks very easy to fix but for some reason just doesnt work.
I have an Excel file with a column called "Date" with uh...dates in it with the following format; 21-3-2021. All I want is to use these dates to set an AD account Expiration date.
So this is what I did:
import-CSV ".\Example.csv" | % {
$User = $._username
$ExpirationDate = [datetime]::Parse($_.Date).ToString('dd/MM/yyyy')
if ($_.Date -ne '') {
Set-ADAccountExpiration $User $ExpirationDate
}
else {
Clear-ADAccountExpiration $user
}
Exception calling "Parse" with "1" argument(s): "String was not recognized as a valid DateTime."
Please, if someone can tell me what I am doing wrong I would be really happy.
This is driving me nuts!
The single-argument [datetime]::Parse() overload - e.g.,
[datetime]::Parse('21-3-2021') - implicitly uses the date/time formats of the current culture[1], as reflected in $PSCulture and Get-Culture, and it seems that that current culture in effect for you doesn't recognize a date string such as '21-3-2021'.
A simple solution is to explicitly specify a culture in which such a date format is valid and pass that culture as the second argument:
# The German culture supports such a format.
# Sample output is from my en-US (US English system).
PS> [datetime]::Parse('21-3-2021', [cultureinfo] 'de-DE')
Sunday, March 21, 2021 12:00:00 AM
If you had a truly exotic format not recognized by any of the predefined cultures (which isn't the case here), you could use the [datetime]::ParseExact() method:
PS> [datetime]::ParseExact('21-3-2021', 'd\-M\-yyyy', [cultureinfo]::InvariantCulture)
Sunday, March 21, 2021 12:00:00 AM
Note:
The - instances are \-escaped to ensure that they are matched verbatim in the input; by default, they are treated as abstract placeholders for any of the date-component separators supported by the target culture.
The specific culture context passed as the 3rd argument - the invariant culture here - would only matter if the input string contained abstract placeholders and/or culture-specific month names, such as "März" for March; you can pass $null to use the current culture.
[1] Note that if you cast a string to a type in PowerShell - e.g., [datetime] '1/13' - the invariant culture is always used for parsing; in other words: [datetime] '1/13' is equivalent to:
[datetime]::Parse('1/13', [cultureinfo]::InvariantCulture).
The invariant culture is derived from the US-English culture and is designed to be both independent of the current culture and to be stable over time, which makes it suitable for programmatic use - see this answer for more.

How do I get the format of creationtime.month to be double digits instead of single?

I have a script that takes the creation date of a file and injects it into an xml tag. Everything works but the formatting of the date shows 2019-4-3 and I need the format to be 2019-04-03.
I have tried the 'tostring(MM)' and that has not worked for me. Anyone have any ideas? Here is a snippet of the code where I think the issue is. I can post the whole script if anyone wants to see it.
$filename="C:\Users\Public\file.xml"
$filename2="C:\Users\Public\file2.xml"
$month = (Get-ChildItem $filename).CreationTime.Month
$day = (Get-ChildItem $filename).CreationTime.Day
$year = (Get-ChildItem $filename).CreationTime.Year
$hour = (Get-ChildItem $filename).CreationTime.Hour
$min = (Get-ChildItem $filename).CreationTime.Minute
$sec= (Get-ChildItem $filename).CreationTime.Second
$output=[string]$year+"-"+[string]$month+"-"+[string]$day+"T"+[string]$hour+":"+[string]$min+":"+[string]$sec+":00z"
Write-Host $output
The output of the dates are single digit and I need them double digits. Any ideas?
The following should work assuming you don't actually need each part of the datetime as a separate variable:
$filename="C:\Users\Public\file.xml"
(Get-ChildItem $filename).creationtime.toString('yyyy-MM-ddTHH:mm:ss')+':00z'
I don't know why the toString() method did not work for you previously unless you didn't quote contents inside the parentheses. The method doesn't always require an input argument, but with what you are trying it requires a string to be passed to it.
You are constructing this the hard way. Formatting dates and times as strings is fully explained here: https://ss64.com/ps/syntax-dateformats.html
A simpler way to do this, and faster since right now you are getting the file info 6 times right now, would be to use the built-in [datetime] method ToString().
(Get-Item $filename).CreationTimeUtc.ToString("yyyy-MM-ddTHH:mm:ss.00Z")
Notice that I used the CreationTimeUtc property instead of CreationTime, since the "Z" at the end of your string that you are creating indicates that this is the time in UTC (the "Z" stands for Zulu time, as explained in Wikipedia).
To complement TheMadTechnician's helpful answer:
Except for the T separating the date and time parts and a sub-second component, your format is the same as the standard u date/time format:
PS> (Get-Item /).CreationTimeUtc.ToString('u') -replace ' ', 'T'
2018-11-30T10:47:40Z
If you need an explicit zero-valued sub-second component, append -replace 'Z$', '.00Z' - note the use of ., not :, as the separator, because . is normally used.

Importing Data From CSV in Powershell Providing UK Date in CSV is Less Than 7 Days Away

so I've got a CSV file with 5 headings: name, collectionDate, location, equipmentNotes, manager.
Now my collectionDates are all in UK format so DD/MM/YYYY. I only want to import data from the CSV with collection dates that are 7 days or less in the future.
So today is 17/08, therefore I would only want to pull data from the CSV that is dated between 17/08 to 24/08.
Although Powershell sort of handles datetime objects in UK format if you tell it to, I seem to be unable to then manipulate the date to add on 7 days.
Here is my current code:
$today = Get-Date
$thisWeek = $today.AddDays(7)
$thisWeekString = $thisWeek.ToString()
$dateParts = $thisweekString -split " "
$weekFinal = $dateParts[0]
$import = Import-Csv #("\\location\EmailCSV.csv") | Where-Object {$_.collectionDate -lt $weekFinal}
Powershell correctly adds 7 days to the datetime to make it 24/08, and then when converting it to a string and removing the time from it, it correctly sets the variable as 24/08/2018. But when I then go to compare them in the Import cmdlet, it just returns all data in the CSV, rather than dates less than 24/08.
I also know Powershell can compare these, because if I create a separate variable $otherDate with 24/08/2018 in it, and then create an if statement that checks if $weekFinal is greater or less than $otherDate, it correctly runs the statement when true.
I've tried using both Where and Where-Object in the Import-Csv cmdlet, but both have returned the same results.
How do I get Powershell to correctly compare $_.collectionDate and $weekFinal to filter the imported data from the csv?
It is easiest to perform all calculations and comparisons using [datetime] instances - avoid string operations (except for converting a string representation of a date to a [datetime] instance).
First, express today's date and 1 week from now as a [datetime] instance without a time-of-day component:
$today = (Get-Date).Date # Today's date, without a time component (midnight)
$oneWeekFromToday = $today.AddDays(7)
Then use [datetime]::Parse() to convert the invariably string-typed CSV column value of interest to a [datetime] instance so you can perform proper date-comparison.
$import = Import-Csv \\location\EmailCSV.csv | Where-Object {
$thisDate = [datetime]::Parse($_.collectionDate)
# Process this row if its date falls between today and 1 week from now, inclusively.
$thisDate -ge $today -and $thisDate -le $oneWeekFromToday
}
Important: use [datetime]::Parse('...'), not a [datetime] '...' cast, because only [datetime]::Parse() respects the current culture's date and time formats; by design, PowerShell's casts and string interpolation always use the invariant culture, irrespective of the current culture - for more information, see this answer of mine.
Try casting the strings to [datetime] in your comparison. When using a sample set of data, I was able to get the expected results.
$import = Import-Csv #("\\location\EmailCSV.csv") | Where-Object {[datetime]$_.collectionDate -lt [datetime]$weekFinal}

Powershell Convert Date format in a CSV

I have a CSV file with the following data
"Date","Time","Name","SourceIP","DestinationIP"
"Sep 1","03:55:57","mmt-5","172.16.48.158","22.22.22.22"
"Sep 1","03:55:57","mmt-5","172.16.48.158","22.22.22.22"
"Sep 1","03:55:57","mmt-5","172.16.48.158","22.22.22.22"
"Sep 1","03:55:57","mmt-5","172.16.48.158","22.22.22.22"
I would like to convert the date into something more usable like 09-01-2016 (the year would be the current year).
How can I accomplish this?
That's easy using the [DateTime] class's ParseExact method. You supply the string that is a date, you tell it how the date is formatted, and you provide a culture or provider or something, but I always just use $null.
[datetime]::ParseExact("Sep 1","MMM d",$null)
That comes out to:
Thursday, September 1, 2016 12:00:00 AM
So you could just do something like:
$Array | ForEach{[datetime]::ParseExact($_.Date,"MMM d",$null)}
And that would convert each entry's Date property to a valid [datetime] object. Then you just format it however you want:
$Array | ForEach{[datetime]::ParseExact($_.Date,"MMM d",$null).ToString("M-d-yyyy")}
That would output:
9-1-2016
Or for the exact thing you asked for use "MM-dd-yyyy" to get 09-01-2016.
Edit: Thanks to wOxxOm for educating me about the third parameter's necessity when dealing with non-localized date formats! So, if this needs to be localized for other cultures, you will need to include that last parameter. That can be done as such:
$Culture = [cultureinfo]::GetCultureInfoByIetfLanguageTag('en-US')
$Array | ForEach{[datetime]::ParseExact($_.Date,"MMM d",$Culture).ToString("MM-dd-yyyy")}
Edit2: Ok, to replace your current Date field with this information you could pass the array to the Select command, and create a new Date property on the fly, and discard the original, then pass that to Export-CSV:
$Array | Select *,#{l='Date';e={[datetime]::ParseExact($_.Date,"MMM d",$null).ToString("M-d-yyyy")}} -Exclude Date | Export-CSV D-Sample-2.csv -NoType