customizing installation directory in install4j - java

I am building a setup in install4j which will be run for each client of a marketing agency. There is one installer, but the user can run it more than once, specifying a different clientId value at the installation time. In the end, I would like to end up with a directory structure like this:
on Mac:
/Applications/MYPRODUCTNAME-clientID1/
/Applications/MYPRODUCTNAME-clientID2/
/Applications/MYPRODUCTNAME-clientID3/
on Windows:
/Program Files/MYPRODUCTNAME-clientID1/
/Program Files/MYPRODUCTNAME-clientID2/
/Program Files/MYPRODUCTNAME-clientID3/
Where the IDs are entered at installation time, in independent installer runs. The IDs are not known in advance - I can't build as many installers as there are IDs.
Ideally, on Mac, I would also prefer to change the name of the launcher file, so that it can be easily discerned from the others in Spotlight search.
I've been playing with Directory Resolver - no luck, especially on Mac which seams to produce a broken launcher on every attempt to change its directory structure.
Any help will be greatly appreciated!

You can change the installation directory by calling
context.setInstallationDirectory(...);
in a "Run script" action or any code snippet in install4j.
Changing launcher names at runtime is not directly supported by install4j.

I ended up doing something like this:
At activation of the Location window:
systemInstallPath = context.getVariable( "sys.programFilesDir" ); // if Windows
if( systemInstallPath == null || systemInstallPath.isEmpty() ) // assume Mac
systemInstallPath = "/Applications";
context.setInstallationDirectory( new File( systemInstallPath ) );
Then at activation of Installation window:
final Boolean confirmedUpdate = context.getBooleanVariable("sys.confirmedUpdateInstallation");
if( confirmedUpdate == null || !confirmedUpdate ) {
final File originalInstallDir = context.getInstallationDirectory();
final String clientId = ( String )context.getVariable( "clientId" );
final File clientInstallDir = new File( originalInstallDir, "MYPRODUCTNAME-" + clientId );
context.setInstallationDirectory( clientInstallDir );
}
That did the trick.

Related

Installing Java with silent install into a directory with spaces

I am trying to install Java using the silent mode and also specify an installation directory that contains spaces. When I do this it pops up the "Windows Installer" dialog box indicating one of the parameters is incorrect. If I use the short path name it works correctly, but I really would prefer not to use the short directory name because that is the value that gets stored in the Registry.
The command I want to use...
jre-6u39-windows-i586.exe /s INSTALLDIR="C:\Program Files (x86)\Java"
This pops up the Windows Installer dialog box.
When I use...
jre-6u39-windows-i586.exe /s INSTALLDIR=C:\Progra~2\Java
This works.
NOTE: "Program Files (x86)" is just an example. This is installed at client sites and they choose the install directory, therefore we have to be able to support any directory they may specify.
Any idea how I can do a silent install but still use the long path name?
UPDATE:
I thought I would share the final solution. One cool thing I found that I wanted to share is that you can suppress the auto-reboot of install and it returns an exit code of 3010. Therefore you can defer the reboot to another time. Here is the code (rewritten a bit to eliminate a bunch of our own abstraction)
public bool InstallJava(string installPath, string logFile)
{
bool rebootRequired = false;
string fullLogFileName = Path.Combine(logFile, "JavaInstall.log");
string arguments = string.Format("/s /v\"/qn REBOOT=Suppress INSTALLDIR=\\\"{0}\\\" STATIC=1 /L \\\"{1}\\\"\"", installPath, fullLogFileName);
ProcessStartInfo startInfo = new ProcessStartInfo { RedirectStandardError = true, RedirectStandardOutput = true, RedirectStandardInput = true, UseShellExecute = false, CreateNoWindow = true,
FileName = "jre-7u25-windows-x64.exe", Arguments = arguments };
var process = Process.Start(startInfo);
process.WaitForExit();
if (process.ExitCode == 3010)
rebootRequired = true;
else if (process.ExitCode != 0)
{
// This just looks through the list of error codes and returns the appropriate message
string expandedMessage = ExpandExitCode(StringResources.JAVA_INSTALL_ERROR, process.ExitCode, fullLogFileName);
throw new Exception(expandedMessage);
}
return rebootRequired;
}
i recall encountering this issue before....
You need to use quotes when passing paths to the installer if the
paths have spaces. Because the path arg is already in quotes, you
need to escape each quote with a '\' so it gets passed through. So
the command would be
j2re.exe /s /v"/qn INSTALLDIR=\"C:\Program Files\JRE\""
reference :
http://docs.oracle.com/javase/1.5.0/docs/guide/deployment/deployment-guide/silent.html
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4966488

Alloy API resulting in java.lang.UnsatisfiedLinkError

I'm currently using the Alloy Analyzer API to build a program, and getting some peculiar behavior. Specifically, if I open a file and parse it (using CompUtil.parseEverything), then make a new Command and call TranslateAlloyToKodkod.execute_command on the parsed file and newly created command using MiniSat with UNSAT core, it runs fine. However, later in execution, my program parses a second input file (also using CompUtil.parseEverything), gets another world, makes a new command, and then I try to call TranslateAlloyToKodkod.execute_command again, it throws the following error:
ERROR: class edu.mit.csail.sdg.alloy4.ErrorFatal: The required JNI library cannot be found:
java.lang.UnsatisfiedLinkError: no minisatproverx5 in java.library.path
edu.mit.csail.sdg.alloy4compiler.translator.TranslateAlloyToKodkod.execute_command(TranslateAlloyToKodkod.java:390)
Does anyone have any idea why this is thrown the second time, but not the first?
To summarize, I have something similar to the following:
Module someWorld = CompUtil.parseEverything_fromFile(rep, null, "someFile.als");
//For the following, "sig" is a sig in someWorld.getAllReachableSigs();
Command command = sig.not();
A4Options options = new A4Options();
options.solver = A4Options.SatSolver.MiniSatProverJNI;
A4Solution ans =
TranslateAlloyToKodkod.execute_command(rep, someWorld, command, options);
//No thrown error
Module someOtherWorld = CompUtil.parseEverything_fromFile(rep, null, "someOtherFile.als");
//For the following, "sig" is a sig in someOtherWorld.getAllReachableSigs();
Command commandTwo = sig.not();
A4Solution ansTwo =
TranslateAlloyToKodkod.execute_command(rep, someOtherWorld, commandTwo, options);
//Thrown error above. Why?
I tried to reproduce this behavior, but I couldn't. If I don't add MiniSat binaries to the LD_LIBRARY_PATH environment variable, I get the exception you mentioned the very first time I invoke execute_command. After configuring LD_LIBRARY_PATH, the exception doesn't happen.
To configure LD_LIBRARY_PATH:
(1) if using Eclipse, you can right-click on one of your source folders, choose Build Path -> Configure Build Path, then on the "Source" tab make sure that "Native library location" points to a folder in which MiniSat binaries reside.
(2) if running from the shell, just add the path to a folder with MiniSat binaries to LD_LIBRARY_PATH, e.g., something like export LD_LIBRARY_PATH=alloy/extra/x86-linux:$LD_LIBRARY_PATH.
Here is the exact code that I was running, and everything worked
public static void main(String[] args) throws Exception {
A4Reporter rep = new A4Reporter();
A4Options options = new A4Options();
options.solver = A4Options.SatSolver.MiniSatProverJNI;
Module someWorld = CompUtil.parseEverything_fromFile(rep, null, "someFile.als");
Command command = someWorld.getAllCommands().get(0);
A4Solution ans = TranslateAlloyToKodkod.execute_command(rep, someWorld.getAllReachableSigs(), command, options);
System.out.println(ans);
Module someOtherWorld = CompUtil.parseEverything_fromFile(rep, null, "someOtherFile.als");
Command commandTwo = someOtherWorld.getAllCommands().get(0);
A4Solution ansTwo = TranslateAlloyToKodkod.execute_command(rep, someOtherWorld.getAllReachableSigs(), commandTwo, options);
System.out.println(ansTwo);
}
with "someFile.als" being
sig A {}
run { some A } for 4
and "someOtherFile.als"
sig A {}
run { no A } for 4
I use alloy4.2.jar as a library in my eclipse plugin project.
A4Reporter rep = new A4Reporter();
Module world = CompUtil.parseEverything_fromFile(rep, null, "civi.als");
A4Options options = new A4Options();
options.solver = A4Options.SatSolver.SAT4J;
options.skolemDepth = 1;
When I use SAT4J, the default solver, the problem mentioned here will not show up. But another exception comes out. The reason is that my civi.als file need Integer model, which located in alloy4.2.jar under the folder /models/util/. But when I run the application, it tries to find the file util/Integer.als directly. That causes the exception. Is it possible to fix that problem?
Besides, I also tried to put the alloy4.2.jar in eclipse plugin project and run my application as an eclipse application (running my application as a plugin). With the default solver, the application has no problem at all. But when I switch to MiniSatProverJNI, the problem mentioned here comes out (I have set the alloy4.2.jar as classpath).

How can I make OS X recognize drive letters?

I know. Heresy. But I'm in a bind. I have a lot of config files that use absolute path names, which creates an incompatibility between OS X and Windows. If I can get OS X (which I'm betting is the more flexible of the two) to recognize Q:/foo/bar/bim.properties as a valid absolute file name, it'll save me days of work spelunking through stack traces and config files.
In the end, I need this bit of Java test code to print "SUCCESS!" when it runs:
import java.io.*;
class DriveLetterTest {
static public void main(String... args) {
File f = new File("S:");
if (f.isDirectory()) {
System.out.println("SUCCESS!");
} else {
System.out.println("FAIL!");
}
}
}
Anyone know how this can be done?
UPDATE: Thanks for all the feedback, everyone. It's now obvious to me I really should have been clearer in my question.
Both the config files and the code that uses them belong to a third-party package I cannot change. (Well, I can change them, but that means incurring an ongoing maintenance load, which I want to avoid if at all possible.)
I'm in complete agreement with all of you who are appalled by this state of affairs. But the fact remains: I can't change the third-party code, and I really want to avoid forking the config files.
Short answer: No.
Long answer: For Java you should use System.getProperties(XXX).
Then you can load a Properties file or Configuration based on what you find in os.name.
Alternate Solution just strip off the S: when you read the existing configuration files on non-Windows machines and replace them with the appropriate things.
Opinion: Personally I would bite the bullet and deal with the technical debt now, fix all the configuration files at build time when the deployment for OSX is built and be done with it.
public class WhichOS
{
public static void main(final String[] args)
{
System.out.format("System.getProperty(\"os.name\") = %s\n", System.getProperty("os.name"));
System.out.format("System.getProperty(\"os.arch\") = %s\n", System.getProperty("os.arch"));
System.out.format("System.getProperty(\"os.version\") = %s\n", System.getProperty("os.version"));
}
}
the output on my iMac is:
System.getProperty("os.name") = Mac OS X
System.getProperty("os.arch") = x86_64
System.getProperty("os.version") = 10.6.4
Honestly, don't hard-code absolute paths in a program, even for a single-platform app. Do the correct thing.
The following is my wrong solution, saved to remind myself not to repeat giving a misdirected advice ... shame on me.
Just create a symbolic link named Q: just at the root directory / to / itself.
$ cd /
$ ln -s / Q:
$ ln -s / S:
You might need to use sudo. Then, at the start of your program, just chdir to /.
If you don't want Q: and S: to show up in the Finder, perform
$ /Developer/Tools/SetFile -P -a V Q:
$ /Developer/Tools/SetFile -P -a V S:
which set the invisible-to-the-Finder bit of the files.
The only way you can replace java.io.File is to replace that class in rt.jar.
I don't recommend that, but the best way to do this is to grab a bsd-port of the OpenJDK code, make necessary changes, build it and redistribute the binary with your project. Write a shell script to use your own java binary and not the built-in one.
PS. Just change your config files! Practice your regex skills and save yourself a lot of time.
If you are not willing to change your config file per OS, what are they for in first place?
Every installation should have its own set of config files and use it accordingly.
But if you insist.. you just have to detect the OS version and if is not Windows, ignore the letter:
Something along the lines:
boolean isWindows = System.getProperty("os.name").toLowerCase()
.contains("windows");
String folder = "S:";
if (isWindows && folder.matches("\\w:")) {
folder = "/";
} else if (isWindows && folder.matches("\\w:.+")) {
folder = folder.substring(2);// ignoring the first two letters S:
}
You get the idea
Most likely you'd have to provide a different java.io.File implementation that can parse out the file paths correctly, maybe there's one someone already made.
The real solution is to put this kind of stuff (hard-coded file paths) in configuration files and not in the source code.
Just tested something out, and discovered something interesting: In Windows, if the current directory is on the same logical volume (i.e. root is the same drive letter), you can leave off the drive letter when using a path. So you could just trim off all those drive letters and colons and you should be fine as long as you aren't using paths to items on different disks.
Here's what I finally ended up doing:
I downloaded the source code for the java.io package, and tweaked the code for java.io.File to look for path names that start with a letter and a colon. If it finds one, it prepends "/Volumes/" to the path name, coughs a warning into System.err, then continues as normal.
I've added symlinks under /Volumes to the "drives" I need mapped, so I have:
/Volumes/S:
/Volumes/Q:
I put it into its own jar, and put that jar at the front of the classpath for this project only. This way, the hack affects only me, and only this project.
Net result: java.io.File sees a path like "S:/bling.properties", and then checks the OS. If the OS is OS X, it prepends "/Volumes/", and looks for a file in /Volumes/S:/bling.properties, which is fine, because it can just follow the symlink.
Yeah, it's ugly as hell. But it gets the job done for today.

Set Windows System Variables with Java

is there a way to add a specific directory to the Windows systemvariable %PATH%?
This doesn't seem to work:
String[] cmd = { "cmd", "/c", "set", "PATH=\"%PATH%;c:\\test\"" };
Runtime.getRuntime().exec( cmd );
c:\test\ doesn't appear in System.getenv("PATH"); or in the output of
String[] cmd = { "cmd", "/c", "echo", "%PATH%" };
Runtime.getRuntime().exec( cmd );
What I need is to modify the %PATH%-variable for the current Java-Process under Windows. The reason is, that I need to load some native dll-files which cross-reference each other. So I'd like to add the application-path to the Windows environment.
The next thing I tried was a small JNI-Wrapper for the C-Function "putenv" which looks like this:
JNIEXPORT void JNICALL Java_com_splitscreen_AppletTest_PutEnv_putEnv
(JNIEnv *env, jobject jobj, jstring val) {
jboolean iscopy;
const char *mvalue = (*env)->GetStringUTFChars(
env, val, &iscopy);
putenv(mvalue);
}
This is how I call it:
final String curPath = System.getenv( "PATH" );
final PutEnv pe = new PutEnv();
pe.putEnv( "PATH=" + curPath + ";c:\test" );
final String newPath = System.getenv( "PATH" );
System.out.println( newPath );
But the pathes are equal. I'm not sure whether the Map of the Java-System-Environment isn't updated or whether putenv didn't work. Is there a way to check this?
The reason this doesn't work is that the two exec() invocations start two different shells; the one you set the path in isn't the one you check it in.
It's difficult to change the permanent, systemwide path setting. But you can change the path for the duration of the invocation of one or more programs that you need it for.
Specifically, the thing to do is to write yourself a batch file (.CMD or .BAT, as you please), set the PATH near the beginning, follow that with whatever DOS/Windows commands you'd like executed with that path, and then exec() that script file.
Updating the PATH for the current Java process seems pretty pointless. Java, once running, doesn't care about the path. Or are you running some library code that does?
If you are running DOS/Windows commands from Java using exec(), the above trick will work.
Update: OK, you have library code that for reasons of its own wants the PATH set just so, and you want to give it what it wants.
What I would consider here is to fire up a new JVM. You can use exec(cmd, envp) to start up a new Java application ("yourself," in a pinch) with a custom set of environment variables in envp. Just copy the ones that are already there and manipulate the contents of PATH, if any.
The standard way to start up a new Java app is to create a new ClassLoader, and there are various descriptions on how to accomplish that. But I'm not sure you can use that procedure to come up with a new environment - so exec-ing the JVM may not only be simpler, but possibly the only way.
This is not possible with just running a batch file. See here for details.
Your solution doesn't work, because it only modifies the environmental variable in the process level and not in system level.
You can pass paths to where the native libraries are located via the -Djava.library.path option if you are using JNI extensions, this may also work for your exec case. The other option is to launch the java app from a batch file and edit the PATH settings in the command interpreter "before" you launch the java app, the java app will inherit this PATH settings.
NASA WorldWind uses native libraries and can be run as an Applet, here is a howto on setting this up with JNLPAppletLauncher. What this basically does is detect the OS, fetch appropriate native libraries, save them in a location in default jvm path and execute. Calling exec from a Java applet violates all sorts of sane security and sandboxing principles and I would really avoid it.

Launching java classes via windows drag-and-drop

I have a java class file with a main method. In Windows, I would like to be able to drag files onto a desktop icon/short/etc that would call supply the filenames to my main method. Basically, I want to allow users to drag-and-drop files at program execution instead of having type them on the command line.
Any thoughts?
To build on daub815's answer, in Windows, you can use a batch file to pass
arguments to another command. In this case, we'll use the java launcher to
launch your class with the main method.
I did a quick Google search on how to do write a batch file to take multiple arguments,
and found a page with a batch file to pass arguments to another command. Adapting from
the example, here is what you can do:
#ECHO OFF
:Loop
IF "%1" == "" GOTO Done
java YourClass %1
SHIFT
GOTO Loop
:Done
Save the above file as a batch file (with a ".bat" extension), and then you can drag-and-drop
files onto it, and it will be passed as arguments.
Also, you can call the batch file from the command line and pass arguments as well.
Edit: It appears that the batch file will not work with quoted arguments which contain spaces. Using a workaround presented in the site I've linked to will split the spaces contained in the quoted full path of the file into separate arguments, so that won't work either. If anyone has a good idea how to fix this, please either edit this entry, or post another answer. I will make this a community wiki.
PhiLho's answer works perfectly if you pack the classes in an executable JAR file (it's how you're meant to do it anyway) and make a .reg file that looks like the one below. Then just double-click that .reg file to merge it into the registry and you're good to go. This lets you both double-click a JAR file to run it, and starting it by Drag & Drop.
Do remember to change the path to where your Java executable is installed.
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\.jar]
#="jarfile"
[HKEY_CLASSES_ROOT\jarfile\DefaultIcon]
#="C:\\Java\\jdk1.7.0\\bin\\java.exe,1"
[HKEY_CLASSES_ROOT\jarfile\shell\open]
#="Run Java Program"
[HKEY_CLASSES_ROOT\jarfile\shell\open\command]
#="\"C:\\Java\\jdk1.7.0\\bin\\java.exe\" -jar \"%1\" %*"
[HKEY_CLASSES_ROOT\jarfile\shellex\DropHandler]
#="{86C86720-42A0-1069-A2E8-08002B30309D}"
OK, I made it work... The base knowledge is to use DropHandler UUID in the registry. I made a base setting, as follow:
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\.class]
#="JavaClass"
[HKEY_CLASSES_ROOT\JavaClass\DefaultIcon]
#="C:\\Java\\jdk1.6.0_05\\bin\\java.exe,1"
[HKEY_CLASSES_ROOT\JavaClass\shell\open]
#="Run Java class"
[HKEY_CLASSES_ROOT\JavaClass\shell\open\command]
#="\"C:\\Java\\jdk1.6.0_05\\bin\\java.exe\" \"%1\" %*"
[HKEY_CLASSES_ROOT\JavaClass\shellex\DropHandler]
#="{86C86720-42A0-1069-A2E8-08002B30309D}"
and... it didn't work!
I just forgot that java.exe wants a class name, not a file name! But I see no way to do that in the registry.
Fortunately, there is a workaround, which still need a script file if we want to be generic, to work on any/all class files (with static main function, of course!). Not batch, I avoid them when I can. I chose to use WSH, as it should be available on any modern Windows system. I also chose to make a JS script, it could have been a VB script as well.
So I made the following script (LaunchJavaClass.js):
if (WScript.Arguments.count() == 0)
{
WScript.StdOut.Write("No parameters");
WScript.Quit(1);
}
var className = WScript.Arguments.Item(0);
//~ WScript.StdOut.Write(className + "\n");
var m = className.match(/^(.*)\\(.+?)\.class$/);
if (m == null)
{
WScript.StdOut.Write("Not a class file");
WScript.Quit(1);
}
var classPath = m[1];
className = m[2];
//~ WScript.StdOut.Write(classPath + " >>> " + className + "\n");
var params = new Array();
for (i = 1; i < WScript.Arguments.count(); i++)
{
params[params.length] = WScript.Arguments.Item(i);
}
var cmd = "cmd /c cd /D " + classPath +
" & C:/Java/jdk1.6.0_05/bin/java.exe " +
className + " " + params.join(" ");
//~ WScript.StdOut.Write(cmd + "\n");
var shell = WScript.CreateObject("WScript.Shell");
//~ var exec = shell.Exec(cmd); // Can be used to get stdout
shell.Run(cmd, 0);
I left some output, not useful in this context, but usable for debugging (run with cscript).
Of course, the path to the JRE must be adjusted.
And I changed the command in the registry, as follow:
[HKEY_CLASSES_ROOT\JavaClass\shell\open\command]
#="\wscript -b "D:\\_PhiLhoSoft\\WSH\\LaunchJavaClass.js\" %1 %*"
Of course, adjust path, and keep the above other lines.
Now, if I drag'n'drop some files to a .class file, it gets the short file paths as arguments of the main() function.
import java.io.*;
class TestDnD
{
public static void main(String[] args)
{
Writer output = null;
try
{
output = new BufferedWriter(new FileWriter(new File("LogFile.txt")));
for (String arg : args)
{
output.write(arg + "\n");
}
}
catch (IOException ioe)
{
ioe.printStackTrace();
return;
}
finally
{
try { output.close(); } catch (IOException e) {}
}
}
}
I think the first version of the .reg file can be used for something else, eg. to drag'n'drop on .jar files (adapting it, of course).
This technique has limited use: we rarely make one-class programs in Java! But it looked like a good and interesting challenge, so I didn't resist to solve it. Note: you can add stuff like -Djava.ext.dirs="some path;another path" if you ever need to use external libraries (in jar files).
Adding onto Adiel A. If you create a batch file, which launches your a Java window using Swing. You would have the user drop the files onto that window. You could then be able to root through those dropped files.
So there's no way to have windows itself pass the args into main() via drag and drop?

Categories

Resources