I have an NSIS installer which has 'RequestExecutionLevel user'. That installer contains an embedded NSIS installer which has 'RequestExecutionLevel admin'. This child installer is only called in certain scenarios. This was done so that certain admin operations could be contained in the child installer and the user would only be prompted for UAC approval when nessacary.
My question is, how can I invoke the child installer so that:
a) the user will be prompted with the UAC dialog (if UAC is enabled), and
b) the child installer will be terminated if the user does not click Yes or No within a given time period.
I have been using ShellExecWait (http://nsis.sourceforge.net/ShellExecWait), which works well, but does not provide the option to specify a timeout value (i.e. it will wait forever).
Note that I have already tried using the following NSIS functions:
* ShellExecWait - Does not allow timeout
* ExecWait - The child installer fails as it inherits the parent installer's execution level.
* ExecShell - Does not allow timeout
* nsExec::Exec - The child installer fails as it inherits the parent installer's execution level.
I'm getting pretty desperate here - any help would be appreciated.
UAC is not designed to support this and any attempt to do this should be considered a hack!
Part of the problem is that ShellExecuteEx in your process is part of the elevation operation so it cannot be killed without killing the thread or process. To work around this I allow the setup to act as a middle man that we can kill. One flaw with this design is that the UAC dialog stays around after the timeout has killed the process (The UAC GUI is in consent.exe running as SYSTEM) This means that the user could elevate the child installer after your main installer is gone! I only tested this on Win7 and there UAC does not start a new process if the parent is gone but this could be a bug or at least the behavior is not documented AFAIK and probably not something that should be used in production code!
Outfile "test.exe"
RequestExecutionlevel User
!include LogicLib.nsh
!include WinMessages.nsh
!include FileFunc.nsh
Page Instfiles
!macro ElevateWithTimeout_OnInit
${GetParameters} $0
${GetOptions} $0 '--ExecTimer' $1
${If} $1 != ""
StrCpy $1 $0 "" 12
ExecShell 'runas' $1 ;RunAs is not really required as long as the .exe has a manifest that requests elevation...
Quit
${EndIf}
!macroend
Function ElevateWithTimeout
InitPluginsDir
System::Call '*(&i60,tsr1)i.r0'
StrCpy $1 "--ExecTimer $1"
System::Call '*$0(i 60,i 0x40,i $HwndParent,i0,t"$ExePath",tr1,to,i1)i.r0'
System::Call 'shell32::ShellExecuteEx(ir0)i.r1'
System::Call '*$0(i,i,i,i,i,i,i,i,i,i,i,i,i,i,i.r2)'
System::Free $0
Pop $0 ; Timeout (MS)
${If} $1 <> 0
System::Call 'kernel32::WaitForSingleObject(ir2,ir0)i.r0'
${If} 0x102 = $0 ;WAIT_TIMEOUT
System::Call 'kernel32::TerminateProcess(ir2,i666)'
${EndIf}
System::Call 'kernel32::CloseHandle(ir2)'
${EndIf}
FunctionEnd
Function .onInit
!insertmacro ElevateWithTimeout_OnInit
FunctionEnd
Section
Push 30000
Push "regedit.exe"
call ElevateWithTimeout
SectionEnd
To create a safer more robust solution the child installer would have to be in on the game and know when to abort itself if the parent is gone but doing that in pure NSIS code is way too much work.
I would probably recommend that you drop the timeout requirement and use RequestExecutionlevel highest in the outer installer and only run the child if you are admin (UserInfo::GetAccountType).
Related
I'm working with an electron application that is built via electron-builder. When I generate the installer and begin installing with it, I see that the cancel button is disabled.
I've looked around at some electron builder documentation and done a few google searches but I seem to be coming up blank here.
edit: Discovered that I can use build/installer.nsh to actually modify UI elements, now I'm just wondering, how do I gain access to the button to enable / disable it, the examples I've seen use an .ini file to store options or something similar, but I'm getting recommendations to use nsDialogs.
Is nsDialogs something that's already readily accessible to me or do I need to import something into my installer.nsh file to use nsDialogs?
And by that token, how would I access the cancel button in nsDialogs?
Is there a configurable value that enables me to... enable that cancel button so users can choose to cancel during installation?
Thanks!
NSIS installers do not support canceling the installation when you enter the InstFiles page (or any page after it). This is by design because of how the scripting language works.
If you don't mind hacks you can call the uninstaller when the user clicks cancel during the install phase:
Var Cancel
!include LogicLib.nsh
!include WinMessages.nsh
Function .onUserAbort
StrCmp $Cancel "" +3
IntOp $Cancel $Cancel | 1
Abort
FunctionEnd
!macro FakeWork c
!if "${c}" < 10
Sleep 333
DetailPrint .
!define /redef /math c "${c}" + 1
!insertmacro ${__MACRO__} "${c}"
!endif
!macroend
Section Uninstall
!insertmacro FakeWork 0
Delete "$InstDir\Uninst.exe"
RMDir "$InstDir"
SectionEnd
Function CheckCancel
${If} $Cancel = 1
IntOp $Cancel $Cancel + 1
GetDlgItem $0 $hwndParent 2
EnableWindow $0 0
DetailPrint "Canceling..."
SetDetailsPrint none
ExecWait '"$InstDir\Uninst.exe" /S _?=$InstDir'
Delete "$InstDir\Uninst.exe"
RMDir "$InstDir"
SetDetailsPrint both
Quit
${EndIf}
FunctionEnd
Section
SetOutPath $InstDir
WriteUninstaller "$InstDir\Uninst.exe"
StrCpy $Cancel 0 ; Allow special cancel mode
GetDlgItem $0 $hwndParent 2
EnableWindow $0 1 ; Enable cancel button
!insertmacro FakeWork 0 ; Replace these with File or other instructions
Call CheckCancel
!insertmacro FakeWork 0
Call CheckCancel
!insertmacro FakeWork 0
Call CheckCancel
StrCpy $Cancel "" ; We are done, ignore special cancel mode
SectionEnd
(I have no idea how to integrate this with electron-builder, sorry)
If you want a proper roll-back installer, try Inno Setup or WiX (MSI).
Is there a choice equivalent? I do not want to create a batch file but a script for PowerShell ISE. What can I do?
Choice.exe is not a native PowerShell command but an interactive console application.
Try e.g.:
Start-Process -Wait "choice.exe"
Or use something like Read-Host instead:
$Choice = Read-Host -Prompt 'Input your choice'
Also see: http://powershell.com/cs/blogs/tips/archive/2012/08/17/disabling-console-apps-in-powershell-ise.aspx
Generally, you don't strictly need a choice.exe equivalent in PowerShell, given that it is a standard executable that ships with Windows (C:\WINDOWS\system32\choice.exe): you can simply call it from PowerShell - except from the ISE, where interactive console applications are fundamentally unsupported.
Note: This fundamental limitation is one of several reasons to avoid the Windows PowerShell ISE, but these days the primary reason is that it is no longer actively developed and there are other reasons not to use it (bottom section), notably not being able to run PowerShell (Core) 7+. The actively developed, cross-platform editor that offers the best PowerShell development experience is Visual Studio Code with its PowerShell extension.
For example (in a regular console window or Windows Terminal):
# Prompt the user:
choice /c IRC /m 'An error occurred: [I]gnore, [R]etry, or [C]ancel'
# Handle the response, which is implied by choice.exe's exit code.
# Note:
# * An external program's exit code is reflected in the
# automatic $LASTEXITCODE variable in PowerShell.
# * choice.exe sets its exit code to 1 if the first choice was selected,
# 2 for the second, ...
switch ($LASTEXITCODE) {
1 { 'Ignoring...'; <# ... #>; break }
2 { 'Retrying...'; <# ... #>; break }
default { Write-Warning 'Canceling...'; exit 2 }
}
PowerShell does have a similar feature, $host.ui.PromptForChoice(), but:
its syntax is a bit awkward
typing a selection letter must be submitted with Enter, whereas with choice.exe typing just typing the letter is enough.
while it works in the ISE too, the prompt is presented as a modal GUI dialog.
For example (the analog of the above):
# Prompt the user.
# Note:
# * The "&" in each choice string designates the selection letter.
# * The 2 argument is the 0-based index of the *default* choice,
# which applies if you just press ENTER.
# * Similarly, the return value is the *0*-based index of the selected choice
# (0 for the first, 1 for the second, ...)
$response =
$host.ui.PromptForChoice(
"Error", # title
"An error occurred:", # prompt text
('&Ignore', '&Retry', '&Cancel'), # choice strings,
2 # default choice
)
# Handle the response.
switch ($response) {
0 { 'Ignoring...'; <# ... #>; break }
1 { 'Retrying...'; <# ... #>; break }
default { Write-Warning 'Canceling...'; exit 2 }
}
To make the above easier in the future, GitHub issue #6571 discusses providing a dedicated Read-Choice cmdlet or enhancing Read-Host (which currently only supports free-form input).
However, note that only a future PowerShell (Core) version would receive such an enhancement, which means that you won't be able to use it in the ISE.
So i got a new keyboard wit hG-keys. (hotkeys) And i'm not familiar with lua...
So could anybody give me a very simple command that sets my pc to sleep? please?
if gkey == 7 and mkey == 1 then
if event == "G_PRESSED" then
end
end
gkeys
so gkey is the key that is pressed, and mkey is the set it uses. i can have up to 54 differint scripts/macro's.
I want to know what i have to put after the last 'then' so my pc goes to sleep.
thx ahead
edit 1:
got this:
if gkey == 1 and mkey == 3 then
if event == "G_PRESSED" then
execute("rundll32.exe powrprof.dll,SetSuspendState 0,1,0");
end
end
error is: [string "LuaVM"]:40: attempt to call global 'execute'(a nil value)
and with os.execute i get this error:
[string "LuaVM"]:40: attempt to index global 'os'(a nil value)
final answer: not possible with gseries keyboard. use a shortcut
Given the reference to G-keys and the G_PRESSED in your code snippet, I am assuming you have one of the Logitech G-Series keyboards. These keyboards can be programmed so that certain keys run Lua scripts using a custom Lua 5.1 interpreter. The documentation is included with Logitech's Gaming Software program.
According to the documentation, only some of the standard library functions are supported: the string, math and table are available. However, the io, os and debug libraries are not available.
So I doubt you'll be able to make your PC go to sleep.
EDIT in response to OP edit: the Lua library you have access to has the os library removed, so you're probably not going to be able to make your computer sleep directly.
There might be an indirect way to do this by writing something that listens for debugging messages, which you can generate with OutputDebugMessage. There's a Team Speak plugin that does this. But it's probably beyond your programming ability right now and far beyond the scope of a Stackoverflow post to explain.
You can use os.execute to run an executable from Lua.
If you google "Windows sleep command line" you'll get another Stackoverflow post which shows two ways of doing it. One requires that you turn hibernation off, the other requires that you download an additional utility (PsShutdown).
Assuming you've downloaded PsShutdown and put it somewhere in your PATH, then you can use the following to sleep the computer:
os.execute('psshutdown -d -t 0')
I'm trying to update the path of my JAVA_HOME Following the instruction of this page
But just after having set new value, the value seems not to have changed.
This is my code :
StrCpy $TemplateJavaPath "$INSTDIR\jdk1.7.0_03"
System::Call 'Kernel32::SetEnvironmentVariableA(t, t) i("JAVA_HOME", "$TempJavaPath").r0'
ReadEnvStr $R0 "JAVA_HOME"
MessageBox MB_OK $R0 ; The value is still C:\program Files\Java6...
ExecWait '"C:\test.bat" ;containing the single line echo %JAVA_HOME%, set to Java6...
I made the same test with PATH with the same result.
Any idea ?
The wiki page where you got that code is/was wrong, you should never mix the A/W function suffix with the t type.
Var /Global TempJavaPath
StrCpy $TempJavaPath "C:\foo\bar"
System::Call 'kernel32::SetEnvironmentVariable(t "JAVA_HOME", t "$TempJavaPath")i'
Exec '"cmd" /k echo %JAVA_HOME%'
This will only work for processes started by your setup, you need to write to the registry if this variable should apply to all processes...
I wrote the following two functions, and call the second ("callAndWait") from JavaScript running inside Windows Script Host. My overall intent is to call one command line program from another. That is, I'm running the initial scripting using cscript, and then trying to run something else (Ant) from that script.
function readAllFromAny(oExec)
{
if (!oExec.StdOut.AtEndOfStream)
return oExec.StdOut.ReadLine();
if (!oExec.StdErr.AtEndOfStream)
return "STDERR: " + oExec.StdErr.ReadLine();
return -1;
}
// Execute a command line function....
function callAndWait(execStr) {
var oExec = WshShell.Exec(execStr);
while (oExec.Status == 0)
{
WScript.Sleep(100);
var output;
while ( (output = readAllFromAny(oExec)) != -1) {
WScript.StdOut.WriteLine(output);
}
}
}
Unfortunately, when I run my program, I don't get immediate feedback about what the called program is doing. Instead, the output seems to come in fits and starts, sometimes waiting until the original program has finished, and sometimes it appears to have deadlocked. What I really want to do is have the spawned process actually share the same StdOut as the calling process, but I don't see a way to do that. Just setting oExec.StdOut = WScript.StdOut doesn't work.
Is there an alternate way to spawn processes that will share the StdOut & StdErr of the launching process? I tried using "WshShell.Run(), but that gives me a "permission denied" error. That's problematic, because I don't want to have to tell my clients to change how their Windows environment is configured just to run my program.
What can I do?
You cannot read from StdErr and StdOut in the script engine in this way, as there is no non-blocking IO as Code Master Bob says. If the called process fills up the buffer (about 4KB) on StdErr while you are attempting to read from StdOut, or vice-versa, then you will deadlock/hang. You will starve while waiting for StdOut and it will block waiting for you to read from StdErr.
The practical solution is to redirect StdErr to StdOut like this:
sCommandLine = """c:\Path\To\prog.exe"" Argument1 argument2"
Dim oExec
Set oExec = WshShell.Exec("CMD /S /C "" " & sCommandLine & " 2>&1 """)
In other words, what gets passed to CreateProcess is this:
CMD /S /C " "c:\Path\To\prog.exe" Argument1 argument2 2>&1 "
This invokes CMD.EXE, which interprets the command line. /S /C invokes a special parsing rule so that the first and last quote are stripped off, and the remainder used as-is and executed by CMD.EXE. So CMD.EXE executes this:
"c:\Path\To\prog.exe" Argument1 argument2 2>&1
The incantation 2>&1 redirects prog.exe's StdErr to StdOut. CMD.EXE will propagate the exit code.
You can now succeed by reading from StdOut and ignoring StdErr.
The downside is that the StdErr and StdOut output get mixed together. As long as they are recognisable you can probably work with this.
Another technique which might help in this situation is to redirect the standard error stream of the command to accompany the standard output.
Do this by adding "%comspec% /c" to the front and "2>&1" to the end of the execStr string.
That is, change the command you run from:
zzz
to:
%comspec% /c zzz 2>&1
The "2>&1" is a redirect instruction which causes the StdErr output (file descriptor 2) to be written to the StdOut stream (file descriptor 1).
You need to include the "%comspec% /c" part because it is the command interpreter which understands about the command line redirect. See http://technet.microsoft.com/en-us/library/ee156605.aspx
Using "%comspec%" instead of "cmd" gives portability to a wider range of Windows versions.
If your command contains quoted string arguments, it may be tricky to get them right:
the specification for how cmd handles quotes after "/c" seems to be incomplete.
With this, your script needs only to read the StdOut stream, and will receive both standard output and standard error.
I used this with "net stop wuauserv", which writes to StdOut on success (if the service is running)
and StdErr on failure (if the service is already stopped).
First, your loop is broken in that it always tries to read from oExec.StdOut first. If there is no actual output then it will hang until there is. You wont see any StdErr output until StdOut.atEndOfStream becomes true (probably when the child terminates). Unfortunately, there is no concept of non-blocking I/O in the script engine. That means calling read and having it return immediately if there is no data in the buffer. Thus there is probably no way to get this loop to work as you want. Second, WShell.Run does not provide any properties or methods to access the standard I/O of the child process. It creates the child in a separate window, totally isolated from the parent except for the return code. However, if all you want is to be able to SEE the output from the child then this might be acceptable. You will also be able to interact with the child (input) but only through the new window (see SendKeys).
As for using ReadAll(), this would be even worse since it collects all the input from the stream before returning so you wouldn't see anything at all until the stream was closed. I have no idea why the example places the ReadAll in a loop which builds a string, a single if (!WScript.StdIn.AtEndOfStream) should be sufficient to avoid exceptions.
Another alternative might be to use the process creation methods in WMI. How standard I/O is handled is not clear and there doesn't appear to be any way to allocate specific streams as StdIn/Out/Err. The only hope would be that the child would inherit these from the parent but that's what you want, isn't it? (This comment based upon an idea and a little bit of research but no actual testing.)
Basically, the scripting system is not designed for complicated interprocess communication/synchronisation.
Note: Tests confirming the above were performed on Windows XP Sp2 using Script version 5.6. Reference to current (5.8) manuals suggests no change.
Yes, the Exec function seems to be broken when it comes to terminal output.
I have been using a similar function function ConsumeStd(e) {WScript.StdOut.Write(e.StdOut.ReadAll());WScript.StdErr.Write(e.StdErr.ReadAll());} that I call in a loop similar to yours. Not sure if checking for EOF and reading line by line is better or worse.
You might have hit the deadlock issue described on this Microsoft Support site.
One suggestion is to always read both from stdout and stderr.
You could change readAllFromAny to:
function readAllFromAny(oExec)
{
var output = "";
if (!oExec.StdOut.AtEndOfStream)
output = output + oExec.StdOut.ReadLine();
if (!oExec.StdErr.AtEndOfStream)
output = output + "STDERR: " + oExec.StdErr.ReadLine();
return output ? output : -1;
}