user2214162
user2214162

Reputation: 79

PowerShell: unexpected output from a less-than comparison

Editor's note:

The original form of this question had a well-defined problem that stemmed from accidentally -le-comparing a [string] LHS with a [double] RHS (annotations added):

$c = Get-Date (Get-Date).ToUniversalTime() -UFormat %s
$epochseconds=[math]::Round($c)  # $epochseconds is now a [double]

$d = get-childitem C:\scripts\PALO\* -recurse | Select-String -pattern "expiry-epoch"

$e = $d -split "epoch"  # -split always returns *strings*
$certtime = $e[1]       # $certtime is now a [string]
$epochtime = $epochseconds - 2505600  # $epochtime is a [double]

ForEach ($i in $d){
    If ($certtime -le $epochtime) {  # LHS is [string], RHS is [double]
        Write-Output $i
    }
}

The OP's later revision (see below), by replacing the original code, accidentally eliminated this problem by first applying a subtraction to the [string] LHS (during which an implicit conversion to a number happens), rendering the existing answer inapplicable.


[The OP's later revision, which should be a *new* question.]

I am writing a script that is supposed to notify me if the expiry-epoch time of a certificate is within 30 days of expiration. However, if one file does not match the IF statement then I get no output, if all files match the IF statement then I get the appropriate output.

PS C:\scripts> $certtime
 1560350005

PS C:\scripts> $epochtime
1520858749

I just updated the code:

$c = Get-Date (Get-Date).ToUniversalTime() -UFormat %s
$epochtimes=[math]::Round($c)
$d = get-childitem C:\scripts\PALO\* -recurse | Select-String -pattern "expiry-epoch"
$e=$d -split "epoch"
$certtime=$e[1]
$certexp = $certtime - 2592000

ForEach ($i in $d){
  If ($certexp -le $epochtime) {
   Write-Output $i
  }
}

Upvotes: 1

Views: 332

Answers (1)

mklement0
mklement0

Reputation: 439812

Note:
* This answer is based on the original code in the question.
* There's an additional problem with the original code not addressed below: $d is expected to contain multiple items, yet only the 1st item's epoch time is extracted with $certtime=$e[1]; this must be moved into the foreach loop: $certtime = [double] ($i -split "epoch")[1]

  • The -split operator always returns strings.
  • Typically, it is the LHS of PowerShell operators determines the data type used in the operation[1] .
    • In the case at hand, the LHS of -le being a [string] means that the RHS is quietly converted to a string, whatever its original type.

Thus, given that $certtime is a string, $certtime -le $epochtime performs lexical comparison, which is not your intent.

The solution is to cast to the desired numerical data type:

# Use [double], because $epochtime is of type [double] in your code.
$certtime = [double] $e[1]

Or, directly in the context of your conditional:

if ([double] $certtime -le $epochtime) ...

[1] There are exceptions: - and / convert both operands from strings to numbers on demand; e.g., ' 10' - '2' yields [int] 8; by contrast, this does not happen with + and *, which have string-specific semantics (concatenation and replication).
For other LHS types, +, -, * and / only succeed if the given type custom-defines these operators via operator overloading.
Without overloading, the behavior of non-string-coercing operands such as -eq, -lt, -gt, ... depends on whether the LHS type implements interfaces such as IEquatable and IComparable.
Finally, with the collection-based -in and -contains operators, it is the individual elements of the collection-valued operand that drive the coercion - see this answer.

Upvotes: 2

Related Questions