3

Context

I'm aiming to gather info about the user's Java Runtime Environment in a script for use in checking that it meets certain requirements. I start with a variable named JRE that's cast as a list of strings, which becomes populated with info gathered about the Java Runtime Environment present (if any):

[List[String]] $Script:JRE = ($(Invoke-Expression -Command 'java --version' | Select-Object -Last 1) `
   -replace '(?:[\(\)]|Server VM|build)', '' `
   -replace '(?:(\d{2}\-Bit)|([\d\.\-\+]+)|(?:, (.+),) )', "`n`$1`n`$2`n`$3`n" `
   -replace ' {2,}', '' `
   -replace "`n+", "`n" `
) | Split-String -Separator "`n" # This works fine, but it doesn't make me feel fine.

Example result:

PS> $JRE[0..4]
OpenJDK
64-Bit
20.0.1+9-29
mixed mode
sharing

I then test against the contents of JRE

  • Is Java actually present? ($null -ne $JRE) -and ($JRE[0] -match '(java|OpenJDK)')
  • Is it reasonably up-to-date? [Version]$JRE[2].Substring(0, $JRE[2].IndexOf('+')) -ge [Version]'20.0.0'
  • Is it the 64-bit build? $JRE[1] -match '64'

…and keep the results of these tests confined within a single variable, which is a strongly-typed dictionary named JavaIs:

[Dictionary[String, Boolean]] $Script:JavaIs = New-Object -TypeName 'Dictionary[String, Boolean]'

I need to populate JavaIs, and I decided to go with ForEach-Object for accomplishing this. Here's where my question comes into play.

Trials, Expectations, Results

The Parameter Way: ❌

What doesn't work is using the InputObject parameter to pass the hashtable to ForEach-Object. The result is that JavaIs receives a single, empty KV pair:

ForEach-Object -InputObject @{
   Extant = ($null -ne $JRE) -and ($JRE[0] -match '(java|OpenJDK)')
   Recent = [Version]$JRE[2].Substring(0, $JRE[2].IndexOf('+')) -ge [Version]'20.0.0'
   x86_64 = $JRE[1] -match '64' 
}.GetEnumerator() -Process {
   $JavaIs.Add($_.Key, $_.Value)
}

# For what it's worth, swapping the parameter order makes no difference:
ForEach-Object -Process { 
   $JavaIs.Add($_.Key, $_.Value)
} -InputObject @{
   Extant = ($null -ne $JRE) -and ($JRE[0] -match '(java|OpenJDK)')
   Recent = [Version]$JRE[2].Substring(0, $JRE[2].IndexOf('+')) -ge [Version]'20.0.0'
   x86_64 = $JRE[1] -match '64' 
}.GetEnumerator()
PS> $JavaIs

Key Value
--- -----
    False

The Pipeline Way: ✔

What does work is providing the hashtable via pipe:

@{
   Extant = ($null -ne $JRE) -and ($JRE[0] -match '(java|OpenJDK)')
   Recent = [Version]$JRE[2].Substring(0, $JRE[2].IndexOf('+')) -ge [Version]'20.0.0'
   x86_64 = $JRE[1] -match '64' 
}.GetEnumerator() | ForEach-Object -Process {
   $JavaIs.Add($_.Key, $_.Value)
}
PS> $JavaIs

Key    Value
---    -----
x86_64  True
Extant  True
Recent  True

I'm perfectly okay with pipelining, but this piqued my curiosity. Why does using -InputObject fail in this scenario?

Software Version Architecture
PowerShell 7.3.4 x86_64
Windows 10.0.19045 x86_64
  • 1
    As an aside: [`Invoke-Expression` (`iex`) should generally be avoided](https://stackoverflow.com/a/51252636/45375); except in unusual circumstances, [don't use it to invoke an external program or PowerShell script / command](https://stackoverflow.com/a/57966347/45375). In your case, invoke `java --version` directly. – mklement0 May 15 '23 at 03:09
  • 1
    Your question is in effect a duplicate of https://stackoverflow.com/q/72942282/45375 (which relates to `Where-Object`, but analogously applies to `ForEach-Object`), but it cannot be marked as such, because the answer there is neither up-voted nor accepted. The short of it is: using `-InputObject` explicitly, with an _argument_ - rather than implicitly _via the pipeline_ - is rarely meaningful; think of `-InputObject` as an implementation detail meant to facilitate pipeline input only. – mklement0 May 15 '23 at 03:17

0 Answers0