Reputation: 11
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?
Upvotes: 1
Views: 835
Reputation: 437197
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 with Export-Csv
:
$ObjDelta = (Get-Date) - (Get-Date $i.Modified) # or: [datetime]::Parse($i.Modified)
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.
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 with Import-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 culture en-US
(US-English) in effect, and as 6 December (day-first) in a culture such as fr-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.:
# Parse as day.month.year
[datetime]::ParseExact('6.12.2023', 'd\.M\.yyyy', $null)
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 of Export-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.:
[pscustomobject] @{ PropA = 'string'; PropB = Get-Date; PropC = 1.5 } |
ForEach-Object {
$oht = [ordered] @{} # helper hashtable for constructing an all-strings clone of the object
foreach ($p in $_.psobject.Properties) {
# Use PowerShell's [string] cast, which uses *culture-invariant* stringification
$oht[$p.Name] = [string] $p.Value
}
# Construct and output the all-strings clone.
[pscustomobject] $oht
} |
ConvertTo-Csv
The above yields culture-invariant CSV data; e.g.:
"PropA","PropB","PropC"
"string","12/06/2023 15:04:02","1.5"
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.
Upvotes: 1
Reputation: 27428
The second argument can be [timespan] or [datetime]:
[datetime]::op_Subtraction
OverloadDefinitions
-------------------
static datetime op_Subtraction(datetime d, timespan t)
static timespan op_Subtraction(datetime d1, datetime d2)
(get-date) - [datetime]'12/3'
Days : 1
Hours : 0
Minutes : 1
Seconds : 24
Milliseconds : 542
Ticks : 864845423929
TotalDays : 1.00097849991782
TotalHours : 24.0234839980278
TotalMinutes : 1441.40903988167
TotalSeconds : 86484.5423929
TotalMilliseconds : 86484542.3929
Upvotes: 2