How to update PATH variable permanently from Windows command line? - java

If I execute set PATH=%PATH%;C:\\Something\\bin from the command line (cmd.exe) and then execute echo %PATH% I see this string added to the PATH. If I close and open the command line, that new string is not in PATH.
How can I update PATH permanently from the command line for all processes in the future, not just for the current process?
I don't want to do this by going to System Properties → Advanced → Environment variables and update PATH there.
This command must be executed from a Java application (please see my other question).

You can use:
setx PATH "%PATH%;C:\\Something\\bin"
However, setx will truncate the stored string to 1024 bytes, potentially corrupting the PATH.
/M will change the PATH in HKEY_LOCAL_MACHINE instead of HKEY_CURRENT_USER. In other words, a system variable, instead of the user's. For example:
SETX /M PATH "%PATH%;C:\your path with spaces"
You have to keep in mind, the new PATH is not visible in your current cmd.exe.
But if you look in the registry or on a new cmd.exe with "set p" you can see the new value.

The documentation on how to do this can be found on MSDN. The key extract is this:
To programmatically add or modify system environment variables, add them to the HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment registry key, then broadcast a WM_SETTINGCHANGE message with lParam set to the string "Environment". This allows applications, such as the shell, to pick up your updates.
Note that your application will need elevated admin rights in order to be able to modify this key.
You indicate in the comments that you would be happy to modify just the per-user environment. Do this by editing the values in HKEY_CURRENT_USER\Environment. As before, make sure that you broadcast a WM_SETTINGCHANGE message.
You should be able to do this from your Java application easily enough using the JNI registry classes.

I caution against using the command
setx PATH "%PATH%;C:\Something\bin"
to modify the PATH variable because of a "feature" of its implementation. On many (most?) installations these days the variable will be lengthy - setx will truncate the stored string to 1024 bytes, potentially corrupting the PATH (see the discussion here).
(I signed up specifically to flag this issue, and so lack the site reputation to directly comment on the answer posted on May 2 '12. My thanks to beresfordt for adding such a comment)

This Python-script[*] does exactly that:
"""
Show/Modify/Append registry env-vars (ie `PATH`) and notify Windows-applications to pickup changes.
First attempts to show/modify HKEY_LOCAL_MACHINE (all users), and
if not accessible due to admin-rights missing, fails-back
to HKEY_CURRENT_USER.
Write and Delete operations do not proceed to user-tree if all-users succeed.
Syntax:
{prog} : Print all env-vars.
{prog} VARNAME : Print value for VARNAME.
{prog} VARNAME VALUE : Set VALUE for VARNAME.
{prog} +VARNAME VALUE : Append VALUE in VARNAME delimeted with ';' (i.e. used for `PATH`).
{prog} -VARNAME : Delete env-var value.
Note that the current command-window will not be affected,
changes would apply only for new command-windows.
"""
import winreg
import os, sys, win32gui, win32con
def reg_key(tree, path, varname):
return '%s\%s:%s' % (tree, path, varname)
def reg_entry(tree, path, varname, value):
return '%s=%s' % (reg_key(tree, path, varname), value)
def query_value(key, varname):
value, type_id = winreg.QueryValueEx(key, varname)
return value
def yield_all_entries(tree, path, key):
i = 0
while True:
try:
n,v,t = winreg.EnumValue(key, i)
yield reg_entry(tree, path, n, v)
i += 1
except OSError:
break ## Expected, this is how iteration ends.
def notify_windows(action, tree, path, varname, value):
win32gui.SendMessage(win32con.HWND_BROADCAST, win32con.WM_SETTINGCHANGE, 0, 'Environment')
print("---%s %s" % (action, reg_entry(tree, path, varname, value)), file=sys.stderr)
def manage_registry_env_vars(varname=None, value=None):
reg_keys = [
('HKEY_LOCAL_MACHINE', r'SYSTEM\CurrentControlSet\Control\Session Manager\Environment'),
('HKEY_CURRENT_USER', r'Environment'),
]
for (tree_name, path) in reg_keys:
tree = eval('winreg.%s'%tree_name)
try:
with winreg.ConnectRegistry(None, tree) as reg:
with winreg.OpenKey(reg, path, 0, winreg.KEY_ALL_ACCESS) as key:
if not varname:
for regent in yield_all_entries(tree_name, path, key):
print(regent)
else:
if not value:
if varname.startswith('-'):
varname = varname[1:]
value = query_value(key, varname)
winreg.DeleteValue(key, varname)
notify_windows("Deleted", tree_name, path, varname, value)
break ## Don't propagate into user-tree.
else:
value = query_value(key, varname)
print(reg_entry(tree_name, path, varname, value))
else:
if varname.startswith('+'):
varname = varname[1:]
value = query_value(key, varname) + ';' + value
winreg.SetValueEx(key, varname, 0, winreg.REG_EXPAND_SZ, value)
notify_windows("Updated", tree_name, path, varname, value)
break ## Don't propagate into user-tree.
except PermissionError as ex:
print("!!!Cannot access %s due to: %s" %
(reg_key(tree_name, path, varname), ex), file=sys.stderr)
except FileNotFoundError as ex:
print("!!!Cannot find %s due to: %s" %
(reg_key(tree_name, path, varname), ex), file=sys.stderr)
if __name__=='__main__':
args = sys.argv
argc = len(args)
if argc > 3:
print(__doc__.format(prog=args[0]), file=sys.stderr)
sys.exit()
manage_registry_env_vars(*args[1:])
Below are some usage examples, assuming it has been saved in a file called setenv.py somewhere in your current path.
Note that in these examples i didn't have admin-rights, so the changes affected only my local user's registry tree:
> REM ## Print all env-vars
> setenv.py
!!!Cannot access HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment:PATH due to: [WinError 5] Access is denied
HKEY_CURRENT_USER\Environment:PATH=...
...
> REM ## Query env-var:
> setenv.py PATH C:\foo
!!!Cannot access HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment:PATH due to: [WinError 5] Access is denied
!!!Cannot find HKEY_CURRENT_USER\Environment:PATH due to: [WinError 2] The system cannot find the file specified
> REM ## Set env-var:
> setenv.py PATH C:\foo
!!!Cannot access HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment:PATH due to: [WinError 5] Access is denied
---Set HKEY_CURRENT_USER\Environment:PATH=C:\foo
> REM ## Append env-var:
> setenv.py +PATH D:\Bar
!!!Cannot access HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment:PATH due to: [WinError 5] Access is denied
---Set HKEY_CURRENT_USER\Environment:PATH=C:\foo;D:\Bar
> REM ## Delete env-var:
> setenv.py -PATH
!!!Cannot access HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment:PATH due to: [WinError 5] Access is denied
---Deleted HKEY_CURRENT_USER\Environment:PATH
[*] Adapted from: http://code.activestate.com/recipes/416087-persistent-environment-variables-on-windows/

For reference purpose, for anyone searching how to change the path via code, I am quoting a useful post by a Delphi programmer from this web page: http://www.tek-tips.com/viewthread.cfm?qid=686382
TonHu (Programmer) 22 Oct 03 17:57 I found where I read the original
posting, it's here:
http://news.jrsoftware.org/news/innosetup.isx/msg02129....
The excerpt of what you would need is this:
You must specify the string "Environment" in LParam. In Delphi you'd
do it this way:
SendMessage(HWND_BROADCAST, WM_SETTINGCHANGE, 0, Integer(PChar('Environment')));
It was suggested by Jordan Russell, http://www.jrsoftware.org, the
author of (a.o.) InnoSetup, ("Inno Setup is a free installer for
Windows programs. First introduced in 1997, Inno Setup today rivals
and even surpasses many commercial installers in feature set and
stability.") (I just would like more people to use InnoSetup )
HTH

In a corporate network, where the user has only limited access and uses portable apps, there are these command line tricks:
Query the user env variables: reg query "HKEY_CURRENT_USER\Environment". Use "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" for LOCAL_MACHINE.
Add new user env variable: reg add "HKEY_CURRENT_USER\Environment" /v shared_dir /d "c:\shared" /t REG_SZ. Use REG_EXPAND_SZ for paths containing other %% variables.
Delete existing env variable: reg delete "HKEY_CURRENT_USER\Environment" /v shared_dir.

This script
http://www.autohotkey.com/board/topic/63210-modify-system-path-gui/
includes all the necessary Windows API calls which can be refactored for your needs. It is actually an AutoHotkey GUI to change the System PATH easily. Needs to be run as an Administrator.

Related

How can I set the compiler:sys.fullName via command line?

I tried the commmand line option:
-D sys.fullName=CustomName
but to no effect (empty full name).
The help mentions that only variables defined on the "General Settings->Compiler Variables" step can be overridden like this.
Does that mean I have to create an additional 'intermediate' compiler variable and place it in the Full Name field with ${compiler:intermediate_full_name_variable}?
(v8.0.11)
I tried the approach with the intermediate compiler variable, but now I have the problem that the localization does not work:
-D intermediate_full_name_variable=Custom Name ${i18n:msgkey}
Results in "Welcome to Custom Name messages not found Setup Assistant". I double checked that the language files and keys exist.
Does that mean I have to create an additional 'intermediate' compiler variable and
place it in the Full Name field with ${compiler:intermediate_full_name_variable}?
That is correct.
now I have the problem that the localization does not work:
You could set the "Full name" field to ${i18:myFullName} and define a message key "myFullName" that includes a compiler variable, like
myFullName=${compiler:productName} Setup Assistant
You can then pass -D productName=Custom name on the command line.

How to automatically commit only src folder (and its content) of each projects with SVN [duplicate]

If I had 20 directories under trunk/ with lots of files in each and only needed 3 of those directories, would it be possible to do a Subversion checkout with only those 3 directories under trunk?
Indeed, thanks to the comments to my post here, it looks like sparse directories are the way to go. I believe the following should do it:
svn checkout --depth empty http://svnserver/trunk/proj
svn update --set-depth infinity proj/foo
svn update --set-depth infinity proj/bar
svn update --set-depth infinity proj/baz
Alternatively, --depth immediates instead of empty checks out files and directories in trunk/proj without their contents. That way you can see which directories exist in the repository.
As mentioned in #zigdon's answer, you can also do a non-recursive checkout. This is an older and less flexible way to achieve a similar effect:
svn checkout --non-recursive http://svnserver/trunk/proj
svn update trunk/foo
svn update trunk/bar
svn update trunk/baz
Subversion 1.5 introduces sparse checkouts which may be something you might find useful. From the documentation:
... sparse directories (or shallow checkouts) ... allows you to easily check out a working copy—or a portion of a working copy—more shallowly than full recursion, with the freedom to bring in previously ignored files and subdirectories at a later time.
I wrote a script to automate complex sparse checkouts.
#!/usr/bin/env python
'''
This script makes a sparse checkout of an SVN tree in the current working directory.
Given a list of paths in an SVN repository, it will:
1. Checkout the common root directory
2. Update with depth=empty for intermediate directories
3. Update with depth=infinity for the leaf directories
'''
import os
import getpass
import pysvn
__author__ = "Karl Ostmo"
__date__ = "July 13, 2011"
# =============================================================================
# XXX The os.path.commonprefix() function does not behave as expected!
# See here: http://mail.python.org/pipermail/python-dev/2002-December/030947.html
# and here: http://nedbatchelder.com/blog/201003/whats_the_point_of_ospathcommonprefix.html
# and here (what ever happened?): http://bugs.python.org/issue400788
from itertools import takewhile
def allnamesequal(name):
return all(n==name[0] for n in name[1:])
def commonprefix(paths, sep='/'):
bydirectorylevels = zip(*[p.split(sep) for p in paths])
return sep.join(x[0] for x in takewhile(allnamesequal, bydirectorylevels))
# =============================================================================
def getSvnClient(options):
password = options.svn_password
if not password:
password = getpass.getpass('Enter SVN password for user "%s": ' % options.svn_username)
client = pysvn.Client()
client.callback_get_login = lambda realm, username, may_save: (True, options.svn_username, password, True)
return client
# =============================================================================
def sparse_update_with_feedback(client, new_update_path):
revision_list = client.update(new_update_path, depth=pysvn.depth.empty)
# =============================================================================
def sparse_checkout(options, client, repo_url, sparse_path, local_checkout_root):
path_segments = sparse_path.split(os.sep)
path_segments.reverse()
# Update the middle path segments
new_update_path = local_checkout_root
while len(path_segments) > 1:
path_segment = path_segments.pop()
new_update_path = os.path.join(new_update_path, path_segment)
sparse_update_with_feedback(client, new_update_path)
if options.verbose:
print "Added internal node:", path_segment
# Update the leaf path segment, fully-recursive
leaf_segment = path_segments.pop()
new_update_path = os.path.join(new_update_path, leaf_segment)
if options.verbose:
print "Will now update with 'recursive':", new_update_path
update_revision_list = client.update(new_update_path)
if options.verbose:
for revision in update_revision_list:
print "- Finished updating %s to revision: %d" % (new_update_path, revision.number)
# =============================================================================
def group_sparse_checkout(options, client, repo_url, sparse_path_list, local_checkout_root):
if not sparse_path_list:
print "Nothing to do!"
return
checkout_path = None
if len(sparse_path_list) > 1:
checkout_path = commonprefix(sparse_path_list)
else:
checkout_path = sparse_path_list[0].split(os.sep)[0]
root_checkout_url = os.path.join(repo_url, checkout_path).replace("\\", "/")
revision = client.checkout(root_checkout_url, local_checkout_root, depth=pysvn.depth.empty)
checkout_path_segments = checkout_path.split(os.sep)
for sparse_path in sparse_path_list:
# Remove the leading path segments
path_segments = sparse_path.split(os.sep)
start_segment_index = 0
for i, segment in enumerate(checkout_path_segments):
if segment == path_segments[i]:
start_segment_index += 1
else:
break
pruned_path = os.sep.join(path_segments[start_segment_index:])
sparse_checkout(options, client, repo_url, pruned_path, local_checkout_root)
# =============================================================================
if __name__ == "__main__":
from optparse import OptionParser
usage = """%prog [path2] [more paths...]"""
default_repo_url = "http://svn.example.com/MyRepository"
default_checkout_path = "sparse_trunk"
parser = OptionParser(usage)
parser.add_option("-r", "--repo_url", type="str", default=default_repo_url, dest="repo_url", help='Repository URL (default: "%s")' % default_repo_url)
parser.add_option("-l", "--local_path", type="str", default=default_checkout_path, dest="local_path", help='Local checkout path (default: "%s")' % default_checkout_path)
default_username = getpass.getuser()
parser.add_option("-u", "--username", type="str", default=default_username, dest="svn_username", help='SVN login username (default: "%s")' % default_username)
parser.add_option("-p", "--password", type="str", dest="svn_password", help="SVN login password")
parser.add_option("-v", "--verbose", action="store_true", default=False, dest="verbose", help="Verbose output")
(options, args) = parser.parse_args()
client = getSvnClient(options)
group_sparse_checkout(
options,
client,
options.repo_url,
map(os.path.relpath, args),
options.local_path)
Or do a non-recursive checkout of /trunk, then just do a manual update on the 3 directories you need.
If you already have the full local copy, you can remove unwanted sub folders by using --set-depth command.
svn update --set-depth=exclude www
See: http://blogs.collab.net/subversion/sparse-directories-now-with-exclusion
The set-depth command support multipile paths.
Updating the root local copy will not change the depth of the modified folder.
To restore the folder to being recusively checkingout, you could use --set-depth again with infinity param.
svn update --set-depth=infinity www
I'm adding this information for those using the TortoiseSvn tool: to obtain the OP same functionality, you can use the Choose items... button in the Checkout Depth section of the Checkout function, as shown in the following screenshot:
Sort of. As Bobby says:
svn co file:///.../trunk/foo file:///.../trunk/bar file:///.../trunk/hum
will get the folders, but you will get separate folders from a subversion perspective. You will have to go separate commits and updates on each subfolder.
I don't believe you can checkout a partial tree and then work with the partial tree as a single entity.
Not in any especially useful way, no. You can check out subtrees (as in Bobby Jack's suggestion), but then you lose the ability to update/commit them atomically; to do that, they need to be placed under their common parent, and as soon as you check out the common parent, you'll download everything under that parent. Non-recursive isn't a good option, because you want updates and commits to be recursive.

How to pass parameters into JMX MBean function from command line

I am trying to remotely invoke an MBean via a commandline. Right now, I am able to list attributes and operations. For example, I can list all the attributes and operations for HotspotDiagnostic using this command:
java -jar cmdline-jmxclient-0.10.3.jar admin:P#sSw0rd 10.11.12.13:1111 com.sun.management:type=HotSpotDiagnostic
Which gives me this list of Attributes and Operations
Attributes:
DiagnosticOptions: DiagnosticOptions (type=[Ljavax.management.openmbean.CompositeData;)
ObjectName: ObjectName (type=javax.management.ObjectName)
Operations:
dumpHeap: dumpHeap
Parameters 2, return type=void
name=p0 type=java.lang.String p0
name=p1 type=boolean p1
getVMOption: getVMOption
Parameters 1, return type=javax.management.openmbean.CompositeData
name=p0 type=java.lang.String p0
setVMOption: setVMOption
Parameters 2, return type=void
name=p0 type=java.lang.String p0
name=p1 type=java.lang.String p1
But now lets say I want to invoke the dumpHeap operation which takes two parameters p0 and p1 of type string and boolean, respectively. How would I pass those arguments in?
I've tried these:
java -jar cmdline-jmxclient-0.10.3.jar admin:P#sSw0rd10.11.12.13:1111 com.sun.management:type=HotSpotDiagnostic dumpHeap p0=aaa p1=true
java -jar cmdline-jmxclient-0.10.3.jar admin:P#sSw0rd10.11.12.13:1111 com.sun.management:type=HotSpotDiagnostic dumpHeap aaa true
But I'm not sure what the syntax is, or even what I'm supposed to pass for the string parameter. This isn't for anything specific btw. Merely want to learn and understand more about how to leverage these operations from the command line. Any docs and assistance much appreciated.
EDIT: I'm naive. Oracle docs indicate the string param is an output file per this link. But still uncertain about how to pass the parameters into my command.
According to the cmdline-jmxclient documentation:
http://crawler.archive.org/cmdline-jmxclient/ you have to use comma-delimited parameters to pass to your operation.
So in your case if would be:
java -jar cmdline-jmxclient-0.10.3.jar admin:P#sSw0rd10.11.12.13:1111 com.sun.management:type=HotSpotDiagnostic dumpHeap test,true
Take note that there is an present bug in the cmdline jar file that doesn't take into account Java primitives(int, booelean, byte, etc.) and will throw a ClassNotFoundException because it can't find by the primitive name.
If you find yourself coming across this issue you can either apply the patch to the jar code that is documented here: https://webarchive.jira.com/browse/HER-1630. Or simply change the type field in the jmx endpoint code from it's primitive type to it's Wrapper object type (int -> Integer)

Running java from VBA macro in Windows 7 bypassing System32 / SysWOW64 javaw.exe

Is there a way to bypass the 32-bit java version (maybe a different way to start a proccess in VBA to invoke the 64-bit version cmd, turn off the UAC or some other sort of tweek) that is being "forced" by the following VBA code (this is just an assumption, I am explaining the debugging process below):
handleDbl = Shell("javaw -cp theJar.jar com.java.SampleClass", vbNormalFocus)
The main point here is that I want to share my macro and avoid putting extra instructions on the recipient so I am trying to do everything on the code (I am using late binding on VBA code to avoid to setting References manually and that kind of stuff).
Debugging Process
An error was thrown so I used the following line instead:
handleDbl = Shell("cmd /k java -cp theJar.jar com.java.SampleClass", vbNormalFocus)
And got the error Exception in thread "main" java.lang.UnsupportedClassVersionError: Unsupported major.minor version so I checked the java -version and tried to find out which java was running with:
C:\>where java
C:\Windows\System32\java.exe
C:\Program Files\Java\_anyJava.x.x.x_\bin\java.exe
I went to System32 folder and there was no java there but I knew that redirection happens from there to C:\Windows\SysWOW64 so I compared the previously extracted java version against C:\Windows\SysWOW64\java.exe -version and they matched.
After that I checked my Outlook version and turned out to be a 32-bit installation. That was a hint but it was mostly that and that big *32 next to the cmd.exe in the Task Manager. I don't know if a 64-bit Outlook would make a difference or it would be the same because of the VBA implementation but that's how I concluded the Shell function from VBA is causing 32-bit java call.
Normally there is a JAVA_HOME environment variable set. If so, then you can do something like this:
Dim JavaExe As String
JavaExe = """" & Environ("JAVA_HOME") & "\bin\java.exe"""
handleDbl = Shell("cmd /k " & JavaExe & " -cp theJar.jar com.java.SampleClass", vbNormalFocus)
If it isn't set, you'll have to find it by some searching, before compiling the command.
Sam's answer is great but I just felt uneasy about user going through more settings so I wrote some functions to check java's versions and notify the user if it is not there (would have to install java in that case anyway) so here is my code. It might contain some helpful stuff.
Private Function IsJavaAvailable(ByVal displayMessage As Boolean, Optional ByVal isJavaMandatory As Boolean) As Boolean
Dim availability As Boolean
Dim minJavaVersion As Integer
minJavaVersion = 8
'isJavaSetup is a global variable
If (Not isJavaSetup) Then
javawPathQuoted = GetMinimumJavaVersion(minJavaVersion)
If StrComp(javawPathQuoted, "") <> 0 Then
isJavaSetup = True
End If
SetGlobalVars
End If
If javawPathQuoted = Empty Then
availability = False
Else
availability = True
End If
If (displayMessage) Then
If (isJavaMandatory) Then
If Not availability Then
MsgBox "This functionality is NOT available without Java " & minJavaVersion & "." & _
vbNewLine & vbNewLine & _
"Please install Java " & minJavaVersion & " or higher.", vbCritical, _
"Mimimum Version Required: Java " & minJavaVersion
End If
Else
If Not availability Then
MsgBox "Some features of this functionality were disabled." & _
vbNewLine & vbNewLine & _
"Please install Java " & minJavaVersion & " or higher.", vbExclamation, _
"Mimimum Version Required: Java " & minJavaVersion
End If
End If
End If
IsJavaAvailable = availability
End Function
Private Function GetMinimumJavaVersion(ByVal javaMinimumMajorVersionInt As Integer) As String
'Run a shell command, returning the output as a string
Dim commandStr As String
Dim javawPathVar As Variant
Dim javaPathStr As Variant
Dim javaVersionStr As String
Dim javaMajorVersionInt As Integer
Dim detectedJavaPaths As Collection
Dim javaVersionElements As Collection
Dim javaVersionOutput As Collection
Dim detectedJavaVersions As Collection
Dim suitableJavawPath As String
'Check available javaw executables in the SO
commandStr = "where javaw"
Set detectedJavaPaths = GetCommandOutput(commandStr)
Set detectedJavaVersions = New Collection
For Each javawPathVar In detectedJavaPaths
'Look for java.exe instead of javaw.exe by substituting it in path
' javaw.exe does NOT return version output like java.exe
javaPathStr = StrReverse(Replace(StrReverse(javawPathVar), StrReverse("javaw.exe"), StrReverse("java.exe"), , 1))
commandStr = """" & javaPathStr & """" & " -version"
Set javaVersionOutput = GetCommandOutput(commandStr)
javaVersionStr = javaVersionOutput.item(1)
Debug.Print "Getting java version: ", commandStr
Debug.Print "Version detected: "; javaVersionStr
Set javaVersionElements = SplitOnDelimiter(javaVersionStr, " ")
'Check that output is not an error or something else
'java version "1.8.0_75"
If javaVersionElements.Count > 2 Then
If StrComp(javaVersionElements.item(1), "java", vbTextCompare) = 0 Then
If StrComp(javaVersionElements.item(2), "version", vbTextCompare) = 0 Then
detectedJavaVersions.Add javaVersionStr
'Remove quotes from "1.8.0_75", split on '.', get 2nd item (java major version) and cast it to Integer
javaMajorVersionInt = CInt(SplitOnDelimiter(SplitOnDelimiter(javaVersionElements.item(3), """").item(1), ".").item(2))
'JAR will only run in Java 8 or later
If (javaMajorVersionInt >= javaMinimumMajorVersionInt) Then
'Validate that "javaw.exe" exists since the validation was made with "java.exe"
Debug.Print "Verifying if javaw.exe exists: ", javawPathVar
If Len(Dir(javawPathVar)) > 0 Then
suitableJavawPath = javawPathVar
Debug.Print "A suitable javaw.exe version found: ", suitableJavawPath
Exit For
End If
End If
End If
End If
End If
Next javawPathVar
GetMinimumJavaVersion = suitableJavawPath
End Function
Private Function GetCommandOutput(ByRef commandStr As String) As Collection
'Run a shell command, returning the output as a string
Dim shellObj As Object
Set shellObj = CreateObject("WScript.Shell")
'run command
Dim wshScriptExecObj As Object
Dim stdOutObj As Object
Dim stdErrObj As Object
Set wshScriptExecObj = shellObj.Exec(commandStr)
Set stdOutObj = wshScriptExecObj.StdOut
Set stdErrObj = wshScriptExecObj.StdErr
'handle the results as they are written to and read from the StdOut object
Dim fullOutputCollection As Collection
Set fullOutputCollection = New Collection
Dim lineStr As String
While Not stdOutObj.AtEndOfStream
lineStr = stdOutObj.ReadLine
If lineStr <> "" Then
fullOutputCollection.Add lineStr
End If
Wend
If fullOutputCollection.Count = 0 Then
While Not stdErrObj.AtEndOfStream
lineStr = stdErrObj.ReadLine
If lineStr <> "" Then
fullOutputCollection.Add lineStr
End If
Wend
End If
Set GetCommandOutput = fullOutputCollection
End Function

Code Signing JavaFX Executable with VBScript, the SignTool errors with Access Denied

Alright so I've finally figured out how to go about code-signing the executable created by the JavaFX Bundler BEFORE (I hope) it is placed by the INNO script into a setup file : There's just one problem :
I've broken myself into pieces trying to resolve this stupid problem but the Access Denied error keeps popping up. This is the VBScript I'm using to try and make this work.
Initially I thought it was a permission error but as I was able to successfully call the script elevated, and still got this error, I can only assume I am mistaken. So...
Can someone please tell me what it is I am doing wrong here?
<?xml version = "1.0" ?>
<package>
<job id="CodeSign">
<script language = "VBScript">
<![CDATA[
WScript.Echo "Setting bElevate to False. . ."
bElevate = false
If WScript.Arguments.Count > 0 Then
WScript.Echo "Arguments.Count > 0"
If WScript.Arguments(WScript.Arguments.Count-1) <> "|" Then
WScript.Echo "Arg N - 1 != ""|"" . . ."
bElevate = true
End If
End If
If bElevate Or WScript.Arguments.Count = 0 Then
WScript.Echo "Elevating . . ."
ElevateUAC
End If
Dim Shell
Set Shell = WScript.CreateObject("WScript.Shell")
Shell.Run "C:\Sign.bat ""Registration Test\Registration Test.exe""", 1, True
Set Shell = Nothing
Sub ElevateUAC
sParams = "|"
If WScript.Arguments.Count > 0 Then
For I = WScript.Arguments.Count - 1 to 0 Step -1
SParams = " " & WScript.Arugments(I) & sParams
Next
End If
Dim objShell
Set objShell = CreateObject("Shell.Application")
objShell.ShellExecute "wscript.exe", """" & WScript.ScriptFullName & """ """ & sParams & """", "", "runas", 1
WScript.Echo "Elevated . . ."
WScript.Quit
End Sub
]]>
</script>
</job>
</package>
Okay so I found it. This is pretty painful because there's (if I'm using this term in the correct context) "race conditions" here in that if the code signing doesn't finish before you start running the process again, it fails because it can't do anything with the .exe because it's being used by another process.
Turns out as long as your IDE is running in Admin (for Windows anyway) there's no need to elevate your script either.
Anyay, my problem was that the file was being set as Read-Only. VERY annoying but fortunately VBScript allows for you to change a files attributes so it was just a matter of doing that before trying to code-sign it :
<?xml version = "1.0" ?>
<package>
<job id="CodeSign">
<script language = "VBScript">
<![CDATA[
'Set File as Normal. . .
Dim objFSO, objFile
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.GetFile("<Relative Path>\<File.exe>")
objFile.Attributes = 0
Dim Shell
Set Shell = WScript.CreateObject("WScript.Shell")
Shell.Run "<Code Sign> ""<Relative Path>\<File.exe>""", 1, True
Set Shell = Nothing
Set objFSO = Nothing
Set objFile = Nothing
]]>
</script>
</job>
</package>
So now, finally, at LONG LAST we have a complete and viable answer to this, evidently very esoteric question that no one but myself has dealt with (or is a very closely guarded secret, I'm not sure which) : How do you code-sign the executable created by a JavaFX bundle before it gets stored?
Create a WSF file with either JavaScript or VBScript (your
preference)
Add the directory in which the file is being stored to the Class Path (for NetBeans you go to Tools -> Options -> Java Tab, select Add Directory next to Class Path, browse to the directory, and add it).
In your WSF file, in the Script section, get the file object and set its attributes to normal (0).
Then Shell.Run your preferred method of code signing applications. 1 is for showing the Code Sign window, True is to make the VBScript wait until it's finished to hopefully avoid a race condition.
The .exe will ALWAYS be stored one directory up from the WSF script file so the relative path is always going to be <FILENAME>\<FILENAME.exe>.
I really, REALLY hope this saves someone a LOT of grief some day...

Categories

Resources