powershell: stuck with easy DateTime script - powershell

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.

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.

Running Cmdlet with equality comparision operator

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 .

Odd Result Date/Time Format - Powershell

I'm passing a string that represents a date i.e. 20180625 to my Powershell script.
I'm then taking the string parameter, which is called $currentDate and formatting it as follows:
$date = [datetime]::ParseExact($currentDate,"yyyyMMdd",$null)
However, when I write the $date variable out it's displaying as 6/29/2018 12:00:00 AM.
I'm doing this because I need to get the day of the year for my script:
$dayofyear = ($date).dayofyear
Which works. I just expected the $date to be in the yyyyMMdd format. Just curious as to why this is happening.
The format parameter for ParseExact tells the parser what format the date you are giving it is in. The object you get back is a DateTime object not a string. To get the string in the format that you want, use the .ToString() method then give if the format that you want the string to be in.
As an example:
$currentDate = '20180629'
$date = [datetime]::ParseExact($currentDate,"yyyyMMdd",$null)
$dayOfYear = $date.DayOfYear
$date.ToString('yyyyMMdd')
$date is an object of type [datetime] which contains an exact measure of time in ticks. For instance, a timespan of 1 day would be 864000000000 ticks. Thus it is not possible to have $null values in a lesser field (864 ticks would only be a few milliseconds). $date prints to the console with a default formatting, which can be changed. However, since each field down to -Milliseconds is populated as 0, when that default format does contain fields such as -hours, they will be displayed as the minimum value (in this case, 12am exactly).

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

Powershell Variable issue and changing date format

I have a very odd thing happening in Powershell.
Here is the code:
add-pssnapin windows.serverbackup
$wbs = Get-Wbsummary
$lastbackuptime = $wbs.lastBackupTime
$solution = "Windows Server Backup"
$surl = "https://REDACTED:1338/REDACTED.asp?querysolution=$solution&querylastbackuptime=$lastbackuptime"
write-host $surl
write-host $lastbackuptime
Here is what is output when I run this
https://REDACTED:1338/REDACTED.asp?querysolution=Windows Server Backup&querylastbackuptime=05/07/2013 05:04:12
07/05/2013 05:04:12
Why is powershell swapping the date around when made as part of another variable but not when I output the variable on its own?!
This is a special case with casting a datetime object. When you simply print the date as a string using write-host, that will be equal to running $lastbackuptime.toString(). This method uses the culture of you're computer. In my case, the culture in Region settings for Windows is Norway, so I get the "european" dateformat: dd/mm/yyyy.
However, when you include $lastbackuptime inside a string, it performs a cast to a string-object. In PowerShell(or .Net) it was decided that when casting a datetime-object, it should use a standard format to convert it to string so that the code would run the same no matter what culture the computer was configured with. That's why casting gives you the US format, while toString() and Write-Host gives the "european" format
Ex:
[16:07:43] PS-ADMIN C:\> $d.tostring()
07.05.2013 16:04:17
[16:13:05] PS-ADMIN C:\> write-host $d
07.05.2013 16:04:17
[16:13:12] PS-ADMIN C:\> [string]$d
05/07/2013 16:04:17
To specify the format your datetime should be displayed, you can do something like this:
[16:14:28] PS-ADMIN C:\> $d.tostring("dd/MM/yyyy")
07.05.2013
[16:14:34] PS-ADMIN C:\> "examplestring $($d.tostring("dd/MM/yyyy"))"
examplestring 07.05.2013
Read more here
Check your regional settings, specifically the Short Date & Long Date formats.
On my system, Short Date is MM/dd/yyyy and and Long Date is dddd, MMMM dd,yyyy. Then, running a simplified version of your example:
>$lastbackuptime = get-date;
>$lastbackuptime
Tuesday, May 07, 2013 10:07:42
>$url="http://whatever/redacted.asp?time=$lastbackuptime";
>$url
http://whatever/redacted.asp?time=05/07/2013 10:07:42
When used on its own, the Long Date format is used in returning the date, but when concatenated with (or expanded inside) another string the Short Date format is used.