Skip to content

Instantly share code, notes, and snippets.

@tresf
Forked from elresleff/Find_java_CFs.ps1
Last active June 24, 2019 14:47
Show Gist options
  • Save tresf/9bf55330421860e923fcbad8f606f53a to your computer and use it in GitHub Desktop.
Save tresf/9bf55330421860e923fcbad8f606f53a to your computer and use it in GitHub Desktop.
Search Java for Commercial Features
# 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
@tresf
Copy link
Author

tresf commented May 17, 2019

@elresleff I think that explanation helps. I think @tellison's asking more broadly in regards to the usefulness of those lines. If the script is useful, I encourage others to fork and edit it. It may be a good candidate for a small CmdLet where we can do...

DetectJava.ps1 # lists JREs and JDKs and their locations
DetectJava.ps1 -CF # same as above also listing commercial features

This detection logic can get fun too... Some JDKs are provided by 3rd parties (WebSphere, SAP) and we can add more search locations easily. If it gets to that point, we can move it to GitHub so that we have a centralized location for the code, and I can even throw some unit tests in the mix if it gets to that point.

@elresleff
Copy link

elresleff commented May 17, 2019

@tresf I'm glad it helped. And with the idea of expanding it to include other third parties... I can see this helping others immensely. @tellison may be more right than not... the usefulness of those lines might be considered extra fluff and not necessary. However, in my case they may hold more weight. I'd love to help collaborate further on this, especially after I've gotten more testing done.

I'd like to first take a stab at introducing Get-content machinelist.txt and export-csv before asking you to do it.

@tresf
Copy link
Author

tresf commented May 17, 2019

Updated. Details below:

what is the purpose of showing the locations of files with the word java in them?

@tellison addressing this and per @elresleff's request, I've switched this to *.exe (this won't match anything on non-Windows OSs) and condensed the format to make it fit on a terminal screen. Sample updated.

more useful to show the install locations with the versions detected?

@tellison Done.

PS U:\> .\FindJava.ps1


=========================================
  Java found in the following locations
=========================================

Path                                               Version
----                                               -------
C:\Program Files\AdoptOpenJDK\jdk-11.0.3.7-hotspot 11.0.3
c:\Program Files\Java\jdk-11.0.2\                  11.0.2
C:\Program Files\Java\jre1.8.0_211                 8.0.211
C:\Program Files\Java\jdk1.8.0_202                 8.0.202

has five lines of Java version, but only four Java locations found?

11.0.3_7 is listed twice for some reason.

@tellison Fixed.

I'd like to first take a stab at introducing Get-content machinelist.txt and export-csv before asking you to do it.

Sure, I haven't added this yet. Knock yourself out. ;)

@elresleff
Copy link

elresleff commented May 21, 2019

Hi Tres,

I'm wondering if the jcmd cmd was altered in the script...(?) I just ran the script against my machine, and got basically nothing at the point the jcmd was executed. This, although I'd like to have a statement like "No Commercial Features running" or what I had seen before "Java commercial features are locked" is understandable since I didn't have a CF running. I then launched JMC and reran the script and now it returns: (Please pardon the irregular formatting...)

================================================================================
  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

However, scratching my head, I just ran jcmd separate from the script and got this:

PS > jcmd 12340 VM.check_commercial_features
12340:
Commercial Features are unlocked.
Status of individual features:
  Java Flight Recorder has been used.
  Resource Management is disabled.
  Current Memory Restriction: None (0)
PS >

So, I actually would expect if any CF's are running to have the script return at least: "Commercial Features are unlocked." but would like to have the whole "Status" message.

So did something change?

Ed

@tresf
Copy link
Author

tresf commented May 21, 2019

Probably a bug in my VM.check_commercial_features sanitization logic.

@tresf
Copy link
Author

tresf commented May 21, 2019

@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.

@elresleff
Copy link

elresleff commented May 22, 2019

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment