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.Modified
to 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.Modified
didn'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.12
exhibits 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.Modified
is necessary in your case (Get-Date $i.Modified
or[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
-UseCulture
switch, the only culture-sensitive aspect it controls is the separator character: by default, it is always,
(i.e., culture-insensitive, ironically); with-UseCulture
it 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.Modified
or[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.