Trying to determine if AD accounts have been modified in the last 2 hours.
If I manually do a Get-ADUser and then compare $ObjDelta = ((Get-Date) - ($i.Modified)) I can successfully check the "Hours" value.
Days : 0 Hours : 2 Minutes : 45 Seconds : 10 Milliseconds : 321 Ticks : 99103217697 TotalDays : 0.114702798260417 TotalHours : 2.75286715825 TotalMinutes : 165.172029495 TotalSeconds : 9910.3217697 TotalMilliseconds : 9910321.7697
Yet when I put it in a script with
$Output = Foreach ($i in $Imported) {
$ObjDelta = ((Get-Date) - ($i.Modified))
If ($ObjDelta.Hours -gt "1") {
<Do things here>
}
I get a running error on each $i of
Multiple ambiguous overloads found for "op_Subtraction" and the argument count: "2". At line:1 char:1
- $ObjDelta = ((Get-Date) - ($i.Modified))
CategoryInfo : NotSpecified: (:) [], MethodException FullyQualifiedErrorId : MethodCountCouldNotFindBest
I have confirmed that the "Modified" value is populated on these accounts.
Any thoughts?
tl;dr
As you've discovered yourself, you need to convert
$i.Modifiedto a[datetime]instance explicitly, using the rules of the current culture, given that the data came from a CSV presumably created withExport-Csv:Note:
The above assumes that the CSV data was created while the same culture as your current one was in effect. If not, see the next section for a solution.
Only if your current culture happens to be
en-US(US-English) would[datetime] $i.Modified, i.e. a simple cast be sufficient, for the reasons explained in the next section.Background information:
As js2010's helpful answer explains, a subtraction operation (
-) with a[datetime]instances as the LHS requires as its RHS either another[datetime]instance or a[timespan]instance. In the former case the result is a time span (i.e. a[timespan]instance), in the latter a new point in time (i.e. a different[datetime]instance).The reason that
(Get-Date) - $i.Modifieddidn't work in your case is that you loaded your data from a CSV file, presumably withImport-Csv, and CSV data is inherently untyped, or, more accurately, invariably[string]-typed.While PowerShell generally attempts automatic conversions to suitable operand types, it cannot do so in the case at hand, because it is possible to convert a string to either of the two supported RHS types, which results in the error message complaining about ambiguous overloads you saw.[1]
PowerShell supports convenient from-string conversions simply using a cast to the target type (placing a type literal such as
[datetime]before the value to convert), which translate into calls to the static::Parse()method behind the scenes (if exposed by the target type).For instance,
[datetime] '1970/01/01'is translated to[datetime]::Parse('1970/01/01', [cultureinfo]::InvariantCulture)Note the use of
[cultureinfo]::InvariantCulture: PowerShell by design, if available, requests use of this - as the name suggests - invariant culture, which is based on, but distinct from, the US-English culture.By contrast:
Passing only a string to
[dateteime]::Parse()uses the current culture's formats; e.g.,[datetime]::Parse('6.12')is interpreted as 12 June (month-first with cultureen-US(US-English) in effect, and as 6 December (day-first) in a culture such asfr-FR(French).The solution above therefore only works if the CSV data was created with the same (or at least a compatible) culture in effect as the one in effect when the data is read. If this assumption doesn't hold, you'll have to parse the date/time strings with an explicit format string that matches the data, using
[datetime]::ParseExact(); e.g.:Curiously - and regrettably - when passing strings as arguments to binary cmdlets (as opposed to cmdlet-like scripts and functions written in PowerShell), it is also the current culture that is used, so that
Get-Date 6.12exhibits the same culture-dependent behavior as[datetime]::Parse('6.12').This is a known inconsistency that will not be fixed, however, so as to preserve backward compatibility - see GitHub issue #6989.
In the code at the top, this behavior is taken advantage of; however, you may prefer use of
[datetime]::Parse($i.Modified)to avoid ambiguity.The reason that culture-sensitive parsing of
$i.Modifiedis necessary in your case (Get-Date $i.Modifiedor[datetime]::Parse($i.Modified)) is the - unfortunate - behavior ofExport-Csv(and its in-memory counterpart,ConvertTo-Csv) to invariably use the current culture when stringifying dates ([datetime]) and (fractional) numbers (typically,[double]):This hampers the portability of CSV data generated this way.
Note what while there is a
-UseCultureswitch, the only culture-sensitive aspect it controls is the separator character: by default, it is always,(i.e., culture-insensitive, ironically); with-UseCultureit is the current culture's list separator, such as;in the French culture.GitHub issue #20383, in the context of discussing improvements to the stringification of complex objects, summarizes the culture-related problems of the CSV cmdlets; ideally, by default they would be culture-invariance consistently and comprehensively, with (consistent and comprehensive) culture-sensitivity only coming into play on demand, with
-UseCulture; sadly, this is again not an option if backward compatibility must be maintained.The only way to avoid culture-sensitivity is to generate string representations of the property (column) values explicitly, in the simplest case via using a
[string]cast, which stringifies with the invariant culture; e.g.:The above yields culture-invariant CSV data; e.g.:
When such culture-invariant data is parsed, regular PowerShell casts can then be used for from-string conversion (e.g.
[datetime] $i.Modifiedor[double] $i.Ratio)[1] Note that during overload resolution (for the call to the
op_Subtraction()method call underlying the operator in this case), PowerShell generally only consults the type of the arguments, not also their content.