-
-
Save tresf/9bf55330421860e923fcbad8f606f53a to your computer and use it in GitHub Desktop.
# 2019 Tres Finocchiaro, Ed Resleff - CC0/Public Domain | |
# - Searches the system for Java | |
# - Looks for specified files or features (e.g. Commercial Features) | |
# Whether or not to show script debbuging information | |
$SHOW_DEBUG = $True; | |
# List of possible Java Runtime locations (including JDK) search locations | |
# Also traverses through 32-bit registry hive (WOW6432Node) | |
$JRE_SEARCH = @{ | |
# Windows-only | |
"HKLM:\SOFTWARE\JavaSoft\Java Runtime Environment" = $null; | |
"HKLM:\SOFTWARE\JavaSoft\Java Development Kit" = $null; | |
"HKLM:\SOFTWARE\JavaSoft\JDK" = $null; | |
# Mac-only | |
"/usr/libexec/java_home" = $null; | |
"/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home" = $null; | |
# Universal | |
"PATH" = $null; | |
"JAVA_HOME" = $null; | |
}; | |
# Detect system-specific variables | |
If([System.Environment]::OSVersion.Platform -eq "Unix") { | |
$SLASH = "/"; | |
$UNIX = $True; | |
$NEWLINE = "`n"; | |
$SUFFIX = ""; | |
} Else { | |
$SLASH = "\"; | |
$UNIX = $False; | |
$NEWLINE = "`r`n"; | |
$SUFFIX = ".exe"; | |
} | |
# Wildcard for matching files in JavaHome\bin\, useful for inferring certain commercial features | |
$JAVA_MATCH = @("*.exe"); | |
$jre_found = @(); | |
# Make a human readable title | |
Function Titlize($title) { | |
Write-Host "" | |
Write-Host "".PadRight($title.Length + 4, "="); | |
Write-Host " $title"; | |
Write-Host "".PadRight($title.Length + 4, "="); | |
} | |
Function Debug($item, $color) { | |
If($SHOW_DEBUG) { | |
If($color -ne "" -and $color -ne $null) { | |
Write-Host $item -ForegroundColor $color; | |
} Else { | |
Write-Host $item; | |
} | |
} | |
} | |
# Explodes a string array into a human-readble format | |
Function Explode($arr) { | |
$exploded = " [$NEWLINE"; | |
$arr | % { $exploded += " =>'$_'$NEWLINE"}; | |
$exploded += " ]$NEWLINE"; | |
$exploded; | |
} | |
# Gets the Java directory based on a registry key or environmental variable | |
Function GetJavaHome($key) { | |
Debug "Looking for Java in $key..."; | |
# Continue on errors to avoid early script termination. Change to "Stop" for debugging. | |
$ErrorAction = "SilentlyContinue" | |
If($UNIX -and $key -like "*/java_home" -and (Test-Path $key)) { | |
# Use java_home utility on MacOS | |
Debug " - $key was provided, calling it directly to find Java..."; | |
$javaHome = &$key; | |
} ElseIf($UNIX -and $key -like "*/Internet Plug-Ins/*") { | |
# Passthru for common MacOS directory | |
Debug " - $key was provided, looking there..."; | |
$javaHome = $key; | |
} ElseIf($key -notlike "HK*:\*") { | |
# Treat as environmental variable | |
Debug " - $key doesn't match known pattern; Assuming env var..."; | |
If ($key -eq "PATH") { | |
$javaHome = (Get-Command java -ErrorAction $ErrorAction).Source; | |
Debug " - PATH provided; Using absolute path: $javaHome"; | |
If($javaHome -ne "") { | |
$javaHome = Split-Path -parent (Split-Path -parent $javaHome); | |
Debug " - Calculated parent directory: $javaHome"; | |
} | |
} Else { | |
$javaHome = (Get-Item "env:$key" -ErrorAction $ErrorAction).Value; | |
Debug " - $key provided; Using underlying value: $javaHome"; | |
} | |
} Else { | |
# Treat as registry key | |
Debug " - $key is probably a registry key, traversing..."; | |
$ver = (Get-ItemProperty -Path $key -Name CurrentVersion -ErrorAction $ErrorAction).CurrentVersion; | |
Debug " - Registry shows CurrentVersion=$ver"; | |
If($ver -ne "") { | |
Debug " - Looking at $key\$ver JavaHome..."; | |
$javaHome = (Get-ItemProperty -Path "$key\$ver" -Name JavaHome -ErrorAction $ErrorAction).JavaHome; | |
Debug " - Registry shows JavaHome=$javaHome"; | |
} Else { | |
Debug " - CurrentVersion is blank; Cannot use."; | |
} | |
# Set repeast flag for 32-bit nodes | |
# PowerShell sucks with recursion, so we have to recurse last | |
$try32bit = ($key -notlike "*WOW6432Node*"); | |
} | |
# Sanitize before adding it to the list | |
# TODO: "/usr" may cause issues with macOS by propting to install JRE | |
if ($javaHome -ne "" -and $javaHome -ne $null -and (Test-Path $javaHome)) { | |
Debug " - Appending $javaHome to list of valid locations"; | |
$obj = New-Object -TypeName PSObject; | |
$obj | Add-Member -MemberType NoteProperty -Name Path -Value $javaHome; | |
$obj | Add-Member -MemberType NoteProperty -Name Version -Value (GetJavaVersion $javaHome); | |
$obj; | |
} Else { | |
Debug " - JavaHome is empty or points to a missing location. Skipping."; | |
} | |
If($try32bit) { | |
Debug " - Done but checking the 32-bit registry, just incase..."; | |
GetJavaHome $key.Replace("HKLM:\SOFTWARE\","HKLM:\SOFTWARE\WOW6432Node\"); | |
} | |
} | |
# Tries to parse the Java version from a path, returns as a System.Version object | |
Function GetJavaVersion($value) { | |
# Parse java -version on Unix (can probably be used on Windows, needs testing | |
# stderr needs to be redirected to stdout | |
Debug "Calling java -version to parse version info..."; | |
$info = &$value$($SLASH)bin$($SLASH)java -version 2>&1; | |
# assume it's first value in quotes, e.g. java version "11.0.0" | |
Debug " - Found the following:"; | |
Debug "$info" yellow; | |
Debug " - Assuming version is in double quotes, splitting:"; | |
$parts = "$info" -Split '"'; | |
Debug (Explode $parts) green; | |
$part = $parts[1]; | |
Debug " - Grabbing item at index 1:"; | |
Debug " $part" cyan; | |
Debug " - Splitting $part on any non-digits to separate parts:"; | |
$digits = "$part" -Split "\D+" | Where({ $_ -ne "" }); | |
Debug (Explode $digits) green; | |
If($digits[0] -eq "1") { | |
Debug " - Stripping first digit because it's in old 1.x format..."; | |
# User newer Java formatting (8.0 instead of 1.8) | |
$unused,$digits=$digits; # drop first element | |
Debug (Explode $digits) green; | |
} | |
Debug " - Joining parts with periods and casting to [System.Version]:"; | |
$ver = [System.Version]($digits -Join "."); | |
Debug " $ver" magenta; | |
$ver; | |
} | |
# Calls jcmd and returns a custom property | |
Function CheckCustomProperty($jcmd, $proc, $property) { | |
If((&$jcmd $proc help) -contains "$property") { | |
&$jcmd $proc $property; | |
} Else { | |
Write-Warning "`"$property`" was not found calling `"$jcmd`" $proc help" | |
} | |
} | |
$JRE_SEARCH.Keys | % { $jre_found += (GetJavaHome $_) } | |
Titlize "Java found in the following locations" | |
# Remove duplicates | |
$jre_found = ($jre_found |Sort-Object -Property Path -Unique) | |
# Sort by version | |
$jre_found = ($jre_found |Sort-Object -Property Version -Descending) | |
$jre_found|Format-Table; | |
Debug "Looking for highest JDK version..."; | |
$best = "NOT_FOUND"; | |
$jre_found | % { | |
$jcmdPath = "$($_.Path)$($SLASH)bin$($SLASH)jcmd$($SUFFIX)"; | |
If(Test-Path $jcmdPath) { | |
Debug "- JDK found $_" yellow; | |
$best = $_; | |
Return; | |
} Else { | |
Debug "- This does not appear to be a JDK $_"; | |
} | |
}; | |
Write-Host "`nUsing `"$($best.Path)`"" | |
# List all files in \bin with "java" in the name | |
$jre_found | % { | |
$dir = $_.Path; | |
$files = @(); | |
$JAVA_MATCH | % { | |
Titlize "Matching `"$_`" in `"$dir`""; | |
$files += (Get-ChildItem -Path $dir$($SLASH)bin -Filter $_ -Recurse | %{ $_.BaseName }); | |
Write-Host $files; | |
} | |
}; | |
# Call jcmd from the highest JDK directory found | |
$jcmd = "$($best.Path)$($SLASH)bin$($SLASH)jcmd"; | |
Titlize "Running `"$jcmd`" to get processes found" | |
# Take all PIDs that aren't spawned from jcmd itself :) | |
$pids = (& $jcmd -l).Split("`n") -notmatch "JCmd" | |
$procs = @(); | |
$pids | % { | |
# Get the PID of the Java process | |
$proc = "$_".Split()[0]; | |
# Counter for displaying additional features | |
$feat = 0; | |
# Build an object containing the process info | |
$obj = New-Object -TypeName PSObject | |
# Get the PID | |
$obj | Add-Member -MemberType NoteProperty -Name PID -Value $proc; | |
# Get the path to the running JAR | |
$obj | Add-Member -MemberType NoteProperty -Name Process -Value $_.Substring($proc.Length); | |
# Get the path to the running Java executable | |
$obj | Add-Member -MemberType NoteProperty -Name Path -Value (Get-Process -id $proc).Path; | |
# Any additional features to check for | |
$obj | Add-Member -MemberType NoteProperty -Name "Feature$($feat + 1)" -Value (CheckCustomProperty $jcmd $proc "VM.check_commercial_features"); | |
# Add object to list | |
$procs += $obj; | |
}; | |
$procs |Format-Table # echo everything to the screen |
I'm wondering if JAVA_HOME has anything to do with it. I'm getting the following errors (I apologize for the length AND the formatting... the bullets are supposed to be "+" chars):
PS C:\temp> .\er_Find_java_CFs_20190518-1030.ps1
& : The term 'C:\Program Files\Java\bin\java' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At C:\temp\er_Find_java_CFs_20190518-1030.ps1:88 char:14
-
$info = &$value$($SLASH)bin$($SLASH)java -version 2>&1;
-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- CategoryInfo : ObjectNotFound: (C:\Program Files\Java\bin\java:String) [], CommandNotFoundException
- FullyQualifiedErrorId : CommandNotFoundException
Cannot index into a null array.
At C:\temp\er_Find_java_CFs_20190518-1030.ps1:92 char:8
-
If($digits[0] -eq "1") {
-
~~~~~~~~~~~~~~~~~~
- CategoryInfo : InvalidOperation: (:) [], RuntimeException
- FullyQualifiedErrorId : NullArray
Cannot convert value "" to type "System.Version". Error: "Version string portion was too short or too long."
At C:\temp\er_Find_java_CFs_20190518-1030.ps1:96 char:5
-
[System.Version]($digits -Join ".");
-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- CategoryInfo : InvalidArgument: (:) [], RuntimeException
- FullyQualifiedErrorId : InvalidCastParseTargetInvocation
& : The term 'C:\Program Files (x86)\Common Files\Oracle\Java\bin\java' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the
spelling of the name, or if a path was included, verify that the path is correct and try again.
At C:\temp\er_Find_java_CFs_20190518-1030.ps1:88 char:14
-
$info = &$value$($SLASH)bin$($SLASH)java -version 2>&1;
-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- CategoryInfo : ObjectNotFound: (C:\Program File...e\Java\bin\java:String) [], CommandNotFoundException
- FullyQualifiedErrorId : CommandNotFoundException
Cannot index into a null array.
At C:\temp\er_Find_java_CFs_20190518-1030.ps1:92 char:8
-
If($digits[0] -eq "1") {
-
~~~~~~~~~~~~~~~~~~
- CategoryInfo : InvalidOperation: (:) [], RuntimeException
- FullyQualifiedErrorId : NullArray
Cannot convert value "" to type "System.Version". Error: "Version string portion was too short or too long."
At C:\temp\er_Find_java_CFs_20190518-1030.ps1:96 char:5
-
[System.Version]($digits -Join ".");
-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- CategoryInfo : InvalidArgument: (:) [], RuntimeException
- FullyQualifiedErrorId : InvalidCastParseTargetInvocation
=========================================
Java found in the following locations
Path Version
C:\Program Files\Java\jdk1.8.0_201 8.0.201
C:\Program Files\Java\jre1.8.0_201 8.0.201
C:\Program Files (x86)\Common Files\Oracle\Java
C:\Program Files\Java
Using "C:\Program Files\Java\jdk1.8.0_201"
============================================================
Matching "*.exe" in "C:\Program Files\Java\jdk1.8.0_201"
appletviewer extcheck idlj jabswitch jar jarsigner java-rmi java javac javadoc javafxpackager javah javap javapackager javaw javaws jcmd jconsole jdb jdeps jhat jinfo jjs jmap jmc jps jrunscript jsadebugd jstack jstat jstatd jvisualvm keytool kinit klist ktab native2ascii orbd pack200 policytool rmic rmid rmiregistry schemagen serialver servertool tnameserv unpack200 wsgen wsimport xjc
============================================================
Matching "*.exe" in "C:\Program Files\Java\jre1.8.0_201"
jabswitch java-rmi java javacpl javaw javaws jjs jp2launcher keytool kinit klist ktab orbd pack200 policytool rmid rmiregistry servertool ssvagent tnameserv unpack200
=========================================================================
Matching "*.exe" in "C:\Program Files (x86)\Common Files\Oracle\Java"
===============================================
Matching "*.exe" in "C:\Program Files\Java"
================================================================================
Running "C:\Program Files\Java\jdk1.8.0_201\bin\jcmd" to get processes found
Error parsing arguments: No command specified
WARNING: "VM.check_commercial_features" was not found calling "C:\Program Files\Java\jdk1.8.0_201\bin\jcmd 12340"
PID Process Path Feature1
12340 C:\Program Files\Java\jdk1.8.0_201\bin\jmc.exe
PS C:\temp>
My thinking is GetJavaVersion function is trying to run even though I'm running it in Windows. "C:\Program Files\Java\bin\java" has something to do with it... because my PATH is listed as "C:\Program Files\Java\jdk1.8.0_201\bin..." and I don't know where "C:\Program Files\Java\bin\java" is coming from.
@elresleff I've tested the script on various JDKs and I cannot reproduce the error. It's probably a bug, but I'm not sure where. It should return the same value as if you call it on command line.