How to get the installation directory? - windows

The MSI stores the installation directory for the future uninstall tasks.
Using the INSTALLPROPERTY_INSTALLLOCATION property (that is "InstallLocation") works only the installer has set the ARPINSTALLLOCATION property during the installation. But this property is optional and almost nobody uses it.
How could I retrieve the installation directory?

Use a registry key to keep track of your install directory, that way you can reference it when upgrading and removing the product.
Using WIX I would create a Component that creates the key, right after the Directy tag of the install directory, declaration

I'd use MsiGetComponentPath() - you need the ProductId and a ComponentId, but you get the full path to the installed file - just pick one that goes to the location of your installation directory. If you want to get the value of a directory for any random MSI, I do not believe there is an API that lets you do that.

I would try to use Installer.OpenProduct(productcode). This opens a session, on which you can then ask for Property("TARGETDIR").

Try this:
var sPath = this.Context.Parameters["assemblypath"].ToString();

As stated elsewhere in the thread, I normally write a registry key in HKLM to be able to easily retrieve the installation directory for subsequent installs.
In cases when I am dealing with a setup that hasn't done this, I use the built-in Windows Installer feature AppSearch: http://msdn.microsoft.com/en-us/library/aa367578(v=vs.85).aspx to locate the directory of the previous install by specifying a file signature to look for.
A file signature can consist of the file name, file size and file version and other file properties. Each signature can be specified with a certain degree of flexibility so you can find different versions of the the same file for instance by specifying a version range to look for. Please check the SDK documentation: http://msdn.microsoft.com/en-us/library/aa371853(v=vs.85).aspx
In most cases I use the main application EXE and set a tight signature by looking for a narrow version range of the file with the correct version and date.

Recently I needed to automate Natural Docs install through Ketarin. I could assume it was installed into default path (%ProgramFiles(x86)%\Natural Docs), but I decided to take a safe approach. Sadly, even if the installer created a key on HKLM\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall, none of it's value lead me to find the install dir.
The Stein answer suggests AppSearch MSI function, and it looks interesting, but sadly Natural Docs MSI installer doesn't provide a Signature table to his approach works.
So I decided to search through registry to find any reference to Natural Docs install dir, and I find one into HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components key.
I developed a Reg Class in C# for Ketarin that allows recursion. So I look all values through HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components and if the Main application executable (NaturalDocs.exe) is found into one of subkeys values, it's extracted (C:\Program Files (x86)\Natural Docs\NaturalDocs.exe becomes C:\Program Files (x86)\Natural Docs) and it's added to the system environment variable %PATH% (So I can call "NaturalDocs.exe" directly instead of using full path).
The Registry "class" (functions, actually) can be found on GitHub (RegClassCS).
System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo("NaturalDocs.exe", "-h");
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
var process = System.Diagnostics.Process.Start (startInfo);
process.WaitForExit();
if (process.ExitCode != 0)
{
string Components = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components";
bool breakFlag = false;
string hKeyName = "HKEY_LOCAL_MACHINE";
if (Environment.Is64BitOperatingSystem)
{
hKeyName = "HKEY_LOCAL_MACHINE64";
}
string[] subKeyNames = RegGetSubKeyNames(hKeyName, Components);
// Array.Reverse(subKeyNames);
for(int i = 0; i <= subKeyNames.Length - 1; i++)
{
string[] valueNames = RegGetValueNames(hKeyName, subKeyNames[i]);
foreach(string valueName in valueNames)
{
string valueKind = RegGetValueKind(hKeyName, subKeyNames[i], valueName);
switch(valueKind)
{
case "REG_SZ":
// case "REG_EXPAND_SZ":
// case "REG_BINARY":
string valueSZ = (RegGetValue(hKeyName, subKeyNames[i], valueName) as String);
if (valueSZ.IndexOf("NaturalDocs.exe") != -1)
{
startInfo = new System.Diagnostics.ProcessStartInfo("setx", "path \"%path%;" + System.IO.Path.GetDirectoryName(valueSZ) + "\" /M");
startInfo.Verb = "runas";
process = System.Diagnostics.Process.Start (startInfo);
process.WaitForExit();
if (process.ExitCode != 0)
{
Abort("SETX failed.");
}
breakFlag = true;
}
break;
/*
case "REG_MULTI_SZ":
string[] valueMultiSZ = (string[])RegGetValue("HKEY_CURRENT_USER", subKeyNames[i], valueKind);
for(int k = 0; k <= valueMultiSZ.Length - 1; k++)
{
Ketarin.Forms.LogDialog.Log("valueMultiSZ[" + k + "] = " + valueMultiSZ[k]);
}
break;
*/
default:
break;
}
if (breakFlag)
{
break;
}
}
if (breakFlag)
{
break;
}
}
}
Even if you don't use Ketarin, you can easily paste the function and build it through Visual Studio or CSC.
A more general approach can be taken using RegClassVBS that allow registry key recursion and doesn't depend on .NET Framework platform or build processes.
Please note that the process of enumerating the Components Key can be CPU intense. The example above has a Length parameter, that you can use to show some progress to the user (maybe something like "i from (subKeysName.Length - 1) keys remaining" - be creative). A similar approach can be taken in RegClassVBS.
Both classes (RegClassCS and RegClassVBS) have documentation and examples that can guide you, and you can use it in any software and contribute to the development of them making a commit on the git repo, and (of course) opening a issue on it's github pages if you find any problem that you couldn't resolve yourself so we can try to reproduce the issue to figure out what we can do about it. =)

Related

Problem Generating Html Report Using DbUp during Octopus Deployment

Using Octopus Deploy to deploy a simple API.
The first step of our deployment process is to generate an HTML report with the delta of the scripts run vs the scripts required to run. I used this tutorial to create the step.
The relevant code in my console application is:
var reportLocationSection = appConfiguration.GetSection(previewReportCmdLineFlag);
if (reportLocationSection.Value is not null)
{
// Generate a preview file so Octopus Deploy can generate an artifact for approvals
try
{
var report = reportLocationSection.Value;
var fullReportPath = Path.Combine(report, deltaReportName);
Console.WriteLine($"Generating upgrade report at {fullReportPath}");
upgrader.GenerateUpgradeHtmlReport(fullReportPath);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return operationError;
}
}
The Powershell which I am using in the script step is:
# Get the extracted path for the package
$packagePath = $OctopusParameters["Octopus.Action.Package[DatabaseUpdater].ExtractedPath"]
$connectionString = $OctopusParameters["Project.Database.ConnectionString"]
$reportPath = $OctopusParameters["Project.HtmlReport.Location"]
Write-Host "Report Path: $($reportPath)"
$exeToRun = "$($packagePath)\DatabaseUpdater.exe"
$generatedReport = "$($reportPath)\UpgradeReport.html"
Write-Host "Generated Report: $($generatedReport)"
if ((test-path $reportPath) -eq $false){
New-Item "Creating new directory..."
} else {
New-Item "Directory already exists."
}
# Run this .NET app, passing in the Connection String and a flag
# which tells the app to create a report, but not update the database
& $exeToRun --connectionString="$($connectionString)" --previewReportPath="$($reportPath)"
New-OctopusArtifact -Path "$($generatedReport)"
The error reported by Octopus is:
'Could not find file 'C:\DeltaReports\Some API\2.9.15-DbUp-Test-9\UpgradeReport.html'.'
I'm guessing that is being thrown when this powershell line is hit: New-OctopusArtifact ...
And that seems to indicate that the report was never created.
I've used a bit of logging to log out certain variables and the values look sound:
Report Path: C:\DeltaReports\Some API\2.9.15-DbUp-Test-9
Generated Report: C:\DeltaReports\Some API\2.9.15-DbUp-Test-9\UpgradeReport.html
Generating upgrade report at C:\DeltaReports\Some API\2.9.15-DbUp-Test-9\UpgradeReport.html
As you can see in the C#, the relevant code is wrapped in a try/catch block, but I'm not sure whether the error is being written out there or at a later point by Octopus (I'd need to do a pull request to add a marker in the code).
Can anyone see a way forward win resolving this? Has anyone else encountered this?
Cheers
I recently redid some of the work from that article for this video up on YouTube. I did run into some issues with the .SQL files not being included in the assembly. I think it was after I upgraded to .NET 6. But that might be a coincidence.
Anyway, because the files weren't being included in the assembly, when I ran the command line app via Octopus, it wouldn't properly generate the file for me. I ended up configuring the project to copy the .SQL files to a folder in the output directory instead of embedding them in the assembly. You can view a sample package here.
One thing that helped me is running the app in a debugger with the same parameters just to make sure it was actually generating the file. I'm sure you already thought of that, but I'd be remiss if I forgot to include it in my answer. :)
FWIW, this is my updated scripts.
First, the Octopus Script:
$packagePath = $OctopusParameters["Octopus.Action.Package[Trident.Database].ExtractedPath"]
$connectionString = $OctopusParameters["Project.Connection.String"]
$environmentName = $OctopusParameters["Octopus.Environment.Name"]
$reportPath = $OctopusParameters["Project.Database.Report.Path"]
cd $packagePath
$appToRun = ".\Octopus.Trident.Database.DbUp"
$generatedReport = "$reportPath\UpgradeReport.html"
& $appToRun --ConnectionString="$connectionString" --PreviewReportPath="$reportPath"
New-OctopusArtifact -Path "$generatedReport" -Name "$environmentName.UpgradeReport.html"
My C# code can be found here but for ease of use, you can see it all here (I'm not proud of how I parse the parameters).
static void Main(string[] args)
{
var connectionString = args.FirstOrDefault(x => x.StartsWith("--ConnectionString", StringComparison.OrdinalIgnoreCase));
connectionString = connectionString.Substring(connectionString.IndexOf("=") + 1).Replace(#"""", string.Empty);
var executingPath = Assembly.GetExecutingAssembly().Location.Replace("Octopus.Trident.Database.DbUp", "").Replace(".dll", "").Replace(".exe", "");
Console.WriteLine($"The execution location is {executingPath}");
var deploymentScriptPath = Path.Combine(executingPath, "DeploymentScripts");
Console.WriteLine($"The deployment script path is located at {deploymentScriptPath}");
var postDeploymentScriptsPath = Path.Combine(executingPath, "PostDeploymentScripts");
Console.WriteLine($"The deployment script path is located at {postDeploymentScriptsPath}");
var upgradeEngineBuilder = DeployChanges.To
.SqlDatabase(connectionString, null)
.WithScriptsFromFileSystem(deploymentScriptPath, new SqlScriptOptions { ScriptType = ScriptType.RunOnce, RunGroupOrder = 1 })
.WithScriptsFromFileSystem(postDeploymentScriptsPath, new SqlScriptOptions { ScriptType = ScriptType.RunAlways, RunGroupOrder = 2 })
.WithTransactionPerScript()
.LogToConsole();
var upgrader = upgradeEngineBuilder.Build();
Console.WriteLine("Is upgrade required: " + upgrader.IsUpgradeRequired());
if (args.Any(a => a.StartsWith("--PreviewReportPath", StringComparison.InvariantCultureIgnoreCase)))
{
// Generate a preview file so Octopus Deploy can generate an artifact for approvals
var report = args.FirstOrDefault(x => x.StartsWith("--PreviewReportPath", StringComparison.OrdinalIgnoreCase));
report = report.Substring(report.IndexOf("=") + 1).Replace(#"""", string.Empty);
if (Directory.Exists(report) == false)
{
Directory.CreateDirectory(report);
}
var fullReportPath = Path.Combine(report, "UpgradeReport.html");
if (File.Exists(fullReportPath) == true)
{
File.Delete(fullReportPath);
}
Console.WriteLine($"Generating the report at {fullReportPath}");
upgrader.GenerateUpgradeHtmlReport(fullReportPath);
}
else
{
var result = upgrader.PerformUpgrade();
// Display the result
if (result.Successful)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Success!");
}
else
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(result.Error);
Console.WriteLine("Failed!");
}
}
}
I hope that helps!
After long and detailed investigation, we discovered the answer was quite obvious.
We assumed the existing deploy process configuration was sound. Because we never had a problem with it (until now). As it transpires, there was a problem which led to the Development deployments being deployed twice.
Hence, the errors like the one above and others which talked about file handles being held by another process.
It was actually obvious in hindsight, but we were blind to it as we thought the existing process was sound 😣

IShellLink - how to get the original target path

I created a shortcut in a Windows PC with a target path of:
C:\Users\b\Desktop\New Text Document.txt
Then I copied the shortcut to another PC with a different user name, and I want to retrieve the original target path.
If you open the shortcut file with a text editor, you can see the original path is preserved, so the goal is definitely possible.
The following code does not work, despite the presence of SLGP_RAWPATH. It outputs:
C:\Users\a\Desktop\New Text Document.txt
It is changing the user folder name to the one associated with the running program.
I understand that the problem is not about environment variables, because no environment variable name can be seen in the file. But I can't find any documentation about this auto-relocation behavior.
IShellLinkW*lnk;
if (CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW, (LPVOID*)&lnk) == 0){
IPersistFile* file;
if (lnk->QueryInterface(IID_IPersistFile, (void**)&file) == 0){
if (file->Load(L"shortcut", 0) == 0){
wchar_t path[MAX_PATH];
if (lnk->GetPath(path, _countof(path), 0, SLGP_RAWPATH) == 0){
_putws(path);
}
IShellLinkDataList* sdl;
if (lnk->QueryInterface(IID_IShellLinkDataList, (void**)&sdl) == 0){
EXP_SZ_LINK* lnkData;
if (sdl->CopyDataBlock(EXP_SZ_LINK_SIG, (void**)&lnkData) == 0){
_putws(lnkData->swzTarget);
LocalFree(lnkData);
}
sdl->Release();
}
}
file->Release();
}
lnk->Release();
}
The Windows Shell Link class implements a property store, so you can get access to this with code like this (with ATL smart pointers):
int main()
{
// note: error checking omitted!
CoInitialize(NULL);
{
CComPtr<IShellLink> link;
link.CoCreateInstance(CLSID_ShellLink);
CComPtr<IPersistFile> file;
link->QueryInterface(&file);
file->Load(L"shortcut", STGM_READ);
// get the property store
CComPtr<IPropertyStore> ps;
link->QueryInterface(&ps);
// dump all properties
DWORD count = 0;
ps->GetCount(&count);
for (DWORD i = 0; i < count; i++)
{
PROPERTYKEY pk;
ps->GetAt(i, &pk);
// get property's canonical name from pk
CComHeapPtr<wchar_t> name;
PSGetNameFromPropertyKey(pk, &name);
PROPVARIANT pv;
PropVariantInit(&pv);
ps->GetValue(pk, &pv);
// convert PropVariants to a string to be able to display
CComHeapPtr<wchar_t> valueAsString;
PropVariantToStringAlloc(pv, &valueAsString); // propvarutil.h
wprintf(L"%s: %s\n", name, valueAsString);
PropVariantClear(&pv);
}
}
CoUninitialize();
return 0;
}
It will output this:
System.ItemNameDisplay: New Text Document.txt
System.DateCreated: 2021/06/03:14:45:30.000
System.Size: 0
System.ItemTypeText: Text Document
System.DateModified: 2021/06/03:14:45:29.777
System.ParsingPath: C:\Users\b\Desktop\New Text Document.txt
System.VolumeId: {E506CEB2-0000-0000-0000-300300000000}
System.ItemFolderPathDisplay: C:\Users\b\Desktop
So, you're looking for System.ParsingPath, which you can get directly like this:
...
ps->GetValue(PKEY_ParsingPath, &pv); // propkey.h
...
Your shortcut is a .lnk file, just without the .lnk file extension present. According to Microsoft's latest "Shell Link (.LNK) Binary File Format" documentation, your shortcut appears to be configured as a relative file target. The relative name is just New Text Document.txt. I didn't dig into the file too much, but I'm guessing that it is relative to the system's Desktop folder, so it will take on whatever the actual Desktop folder of the current PC is. Which would explain why querying the target changes the relative root from C:\Users\b\Desktop to C:\Users\a\Desktop when you change PCs.
As for being able to query the original target C:\Users\b\Desktop\New Text Document.txt, that I don't know. It is also present in the file, so in theory there should be a way to query it, but I don't know which field it is in, without taking the time to fully decode this file. You should try writing your own decoder, using the above documentation.

How can I programmatically determine if path is protected? [duplicate]

I need to know if a specified directory (local or shared path with login credentials) has write permissions or not.
I am using GetFileAttributes but it always returns FILE_ATTRIBUTE_DIRECTORY and nothing else.
My code is something like below
if(storageLocation != "")
{
//! check if local storage - user name password would be empty
if(storageUsername == "" && storagePassword == "")
{
//! local storage
//! lets check whether the local path is a valid path or not
boost::filesystem::path fpath(storageUsername.c_str());
if(boost::filesystem::exists(fpath))
{
DWORD attrib = ::GetFileAttributes(storageLocation.c_str());
if((attrib != INVALID_FILE_ATTRIBUTES) &&
((attrib & FILE_ATTRIBUTE_READONLY) != FILE_ATTRIBUTE_READONLY))
{
string strWritePermission = "TRUE";
}
}
}
else
{
uncLocation_t uncLocation;
uncLocation.m_location = storageLocation;
uncLocation.m_username = storageUsername;
uncLocation.m_password = storagePassword;
if(0 == connectToUNCLocation(uncLocation)) // My function to connect to UNC location
{
//! successful connection
DWORD attrib = ::GetFileAttributes(storageLocation.c_str());
if((attrib != INVALID_FILE_ATTRIBUTES) &&
((attrib & FILE_ATTRIBUTE_READONLY) != FILE_ATTRIBUTE_READONLY))
{
string strWritePermission = "TRUE";
}
}
}
}
I don't understand why but GetFileAttributes always return 0x16.
I have tested it by creating a shared folder and creating 2 folders in it. One with read only permissions and other with default permissions. But in all 3 cases (shared folder, read only folder and default permission folder) I am getting same return value.
There is on way to find write permission, to create a temporary file (usinf CreateFile in GENERIC_WRITE mode) and if successfully created, delete it. But I don't want to use this method as I don't want my application to create a temporary file each time user specifies a location.
Please suggest what should be done.
You would need to replicate the security checking that Windows performs. The AccessCheck function will help that. You are currently well wide of the mark in looking at the file attributes. Windows security is so much more complicated than that.
Although you said you did not want to do it, the right solution is not to try to check. Simply do whatever it is you are attempting to do. If the system decides that the user does not have sufficient rights, then CreateFile will fail, and the last error will be set to ERROR_ACCESS_DENIED. There's no need for temporary files. You just try to do whatever it is you are doing, and let it fail. You have to handle failure anyway since there are many ways for a file operation to fail, not just security.
As the saying goes, it is better to ask forgiveness than permission.
I think you are looking for AccessCheck. FYI, this is not a C++ question, but a Windows API question.

uninstalling applications using SCCM SDK

I have been trying to uninstall applications on devices or users using SCCM. I have been successful in creating an application assignment that would install applications, but I haven't been able to get it to uninstall. The code I have been using is:
IResultObject assignment = this.manager.CreateInstance("SMS_ApplicationAssignment");
IResultObject application =
this.manager.GetInstance("SMS_Application.CI_ID=16777339");
assignment["ApplicationName"].StringValue = application["LocalizedDisplayName"].StringValue;
assignment["AssignedCI_UniqueID"].StringValue = application["CI_UniqueID"].StringValue;
assignment["AssignedCIs"].IntegerArrayValue = new[] { application["CI_ID"].IntegerValue};
assignment["AssignmentName"].StringValue = "Deepak's deployment";
assignment["CollectionName"].StringValue = "Deepak's Collection of Devices";
assignment["DisableMomAlerts"].BooleanValue = true;
assignment["NotifyUser"].BooleanValue = false;
assignment["OfferFlags"].IntegerValue = 0;
assignment["DesiredConfigType"].IntegerValue = 1;
assignment["OverrideServiceWindows"].BooleanValue = false;
assignment["RebootOutsideOfServiceWindows"].BooleanValue = false;
assignment["SuppressReboot"].IntegerValue = 0;
assignment["TargetCollectionID"].StringValue = "UKN0000F";
assignment["EnforcementDeadline"].DateTimeValue = DateTime.Now.AddDays(1);
assignment["StartTime"].DateTimeValue = DateTime.Now;
assignment["UseGMTTimes"].BooleanValue = false;
assignment["UserUIExperience"].BooleanValue = false;
assignment["WoLEnabled"].BooleanValue = false;
assignment["RequireApproval"].BooleanValue = true;
assignment["OfferTypeId"].IntegerValue = 2;
assignment.Put();
This code will put up the application as an install deployment in SCCM. How do I get it as an uninstall deployment?
There is an AppAction enumeration, which I suspect is used by the client and not on the server.
typedef enum AppAction
{
appDiscovery = 0,
appInstall = 1,
appUninstall = 2
} AppAction;
Any help would be appreciated!
The setting that needs to be changed is DesiredConfigType.
For your code add the following before put():
assignment["DesiredConfigType"].IntegerValue = 2;
A value of 1 represents install (required) and 2 will uninstall (not allowed).
https://msdn.microsoft.com/en-us/library/hh949014.aspx
The way I do it is first use uninstall.exe to determine the guid of the program, and then create a program for the package I wish to uninstall, and just call uninstall.exe /whatever as the command. This works for most apps that show up in Add/Remove, and if it doesn't show up there then it'll have to be a hack (or script) anyway to uninstall. I believe the reason you're falling short is because if there is no command to uninstall the deployment in sccm, then it has nothing to run.
After you create an uninstall program, you could just call that deployment from your code, and voila.
As long as the target program that you are trying to use was installed via an MSI (Microsoft Installer) then you can loop through the registry to find your product (Registry Location: "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall") And just look at each DisplayName value.
In our environment, I accomplish this task by using Powershell, and we setup a program that specifically uninstalls whatever we are after.
Hope this helps...
Raged.

Find absolute java.exe path programmatically from java code

If I have a java jar or class file which is launched by the user (assuming java path is set in environment variables), so how can i from within the code, figure out absolute path of java.exe/javaw.exe from which this file is being launched.
Like on ubuntu we can run: % which java and it shows the path.
However on windows, if i check System.getenv() it may happen that there are multiple path's found e.g for old or new version. If through cmd line, I run java -version it does not show the path.
Can you tell me either through pure java or command line on windows how is it possible to find out the location of javaw.exe?
String javaHome = System.getProperty("java.home");
Can you tell me either through pure Java ... on windows how is it possible to find out the location of javaw.exe?
E.G.
import java.io.File;
class JavawLocation {
public static void main(String[] args) {
String javaHome = System.getProperty("java.home");
File f = new File(javaHome);
f = new File(f, "bin");
f = new File(f, "javaw.exe");
System.out.println(f + " exists: " + f.exists());
}
}
Output
C:\Program Files (x86)\Java\jdk1.6.0_29\jre\bin\javaw.exe exists: true
Press any key to continue . . .
And yes, I am confident that will work in a JRE.
On Windows, the java.library.path System Property begins with the path to the bin directory containing whichever java.exe was used to run your jar file.
This makes sense, because on Windows the first place any executable looks for DLL files is the directory containing the executable itself. So naturally, when the JVM runs, the first place it looks for DLLs is the directory containing java.exe.
You can acquire the path to java.exe as follows:
final String javaLibraryPath = System.getProperty("java.library.path");
final File javaExeFile = new File(
javaLibraryPath.substring(0, javaLibraryPath.indexOf(';')) + "\\java.exe"
);
final String javaExePath =
javaExeFile.exists() ? javaExeFile.getAbsolutePath() : "java";
This code is Windows-specific - I hard-coded the path separator (;) and the file separator (). I also put in a fallback to just "java" in case the library path trick somehow doesn't work.
I have tested this with Java 6 and 7 on Windows 7. I tried a 32-bit and 64-bit version of Java.
Here's a slightly more generalised solution that I came up with. Maybe useful:
private static String javaExe()
{
final String JAVA_HOME = System.getProperty("java.home");
final File BIN = new File(JAVA_HOME, "bin");
File exe = new File(BIN, "java");
if (!exe.exists())
{
// We might be on Windows, which needs an exe extension
exe = new File(BIN, "java.exe");
}
if (exe.exists())
{
return exe.getAbsolutePath();
}
try
{
// Just try invoking java from the system path; this of course
// assumes "java[.exe]" is /actually/ Java
final String NAKED_JAVA = "java";
new ProcessBuilder(NAKED_JAVA).start();
return NAKED_JAVA;
}
catch (IOException e)
{
return null;
}
}
an issue with using "System.getProperty("java.home");", is that it is not always the java exe that the jar is running on, if you want to get that, you can use "System.getProperty("sun.boot.library.path");", from there you can find "java", "java.exe", "javaw", or "javaw.exe"... However there is still an issue with this, java will run just fine if the executable has been renamed, and the actual java executable's structure changes from different JRE's/JDKS's, so there is not much way to find the java exe if it has been renamed. unless someone else has a method ofc, in which case, can you share? :)
(Also, I have seen some people suggest using the first index of System.getProperty("java.library.path");, note, this might not work if the user/launcher has manually set the library path, something which is not too uncommon)
Compilation of All above methods
static String getJavaPath(){
String tmp1 = System.getProperty("java.home") + "\\bin\\java.exe";
String tmp2 = System.getProperty("sun.boot.library.path") + "\\java.exe";
String tmp3 = System.getProperty("java.library.path")+ "\\java.exe";
if(new File(tmp1).exists()) {
return tmp1;
}else if(new File(tmp2).exists()){
return tmp2;
}else if(new File(tmp3).exists()) {
return tmp3;
}else{
String[] paths = System.getenv("PATH").split(";");
for(String path:paths){
if(new File(path + "\\java.exe").exists()){
return path + "\\java.exe";
}
}
}
return "";
}

Resources