Execute Lua WINAPI code without displaying the shell - shell

I created a simple Lua application using Alien for Lua. It works perfectly, except when you execute it, the Lua Shell shows as well. Is there a way to "hide" this shell, run it in background, turn it off, etc so that I simply see the message box?
Code:
require "luarocks.require"
require "alien"
local MessageBox = alien.User32.MessageBoxA
MessageBox:types{ret = "long", abi = "stdcall", "long", "string", "string", "long" }
MessageBox(0, "Hello World!", "My Window Title", 0x00000040)
Current Output:
Desired Output:

tl;dr
Rename your script to hello.wlua so that wlua.exe is used.
Details
While it is likely possible, if verbose, to locate and close the offending console window that Windows provided your process, it would be better if that console never appeared in the first place. If it does appear, then it is likely to flash on screen, and cause some users to be confused.
Subsystems
Windows has, since its earliest days, had the concept of a "subsystem" which each individual executable identifies with. Normal GUI applications are linked with /SUBSYSTEM:WINDOWS and get the full GUI treatment including the responsibility to create and display their own window(s) if and when needed.
Applications that expect to be run from a command line (or batch file) are linked with /SUBSYSTEM:CONSOLE, and as a result have standard file handles that are guaranteed to be open and are likely to be connected to some console window (or a pipe, or redirected to a file, but they do exist). That guarantee is strong enough that when a console program is started outside of a console (as when double-clicked from Exporer, or named in the Start|Run box) then the system automatically creates a console window for it, and binds the standard file handles to the new console.
There are other subsystems, but those two are the only important ones for normal users and developers.
lua.exe and wlua.exe
So why does this matter?
The stock lua.exe will be linked for the console, because that makes it possible to use interactively from a command prompt. However, it means that it will always be supplied with a console window even when you don't want one.
The Lua for Windows distribution (which from the pathname showing in your console's title bar it looks like you are using) includes a second copy named wlua.exe which only differs by being linked for the Windows subsystem. As a result, it only displays a window if the script explicitly creates one to display. Of course, it also means that it cannot be used interactively at the command prompt.
File types and associations
For convenience, you can associate the file type .wlua with wlua.exe, and name your GUI script with that file type. That will enable launching programs in the usual way without getting the extra consoles. Of course, when debugging them, you can always run them with lua.exe from a command prompt and take advantage of the existence of stdout and the utility of the print function.
On my PC (64-bit Win 7 Pro) I have the following associations, which look like they were created by the installation of Lua for Windows:
C:...>assoc .lua
.lua=Lua.Script
C:...>ftype lua.script
lua.script="C:\Program Files (x86)\Lua\5.1\lua.exe" "%1" %*
C:...>assoc .wlua
.wlua=wLua.Script
C:...>ftype Wlua.script
Wlua.script="C:\Program Files (x86)\Lua\5.1\wlua.exe" "%1" %*
Extra credit: PATHEXT
You could also add .lua to the PATHEXT environment variable to save typing the file type at the command prompt. I'm not configured that way presently, but have in the past done that. I found that the standard practice of naming both modules and scripts with the same file type made that less useful.
The PATHEXT environment variable lists the file types that will be searched for in the PATH when you name a program to run without specifying its file type. Documentation for this is rather hard to locate, as there does not appear to be a single MSDN page listing all the "official" environment variables and their usage. This chapter of a book about Windows NT has a nice description of the interaction of PATH and PATHEXT, and despite being subtly out of date in some respects, it is the clearest detailed explanation of how the command prompt operates that I've come across.
It clarifies that each folder in PATH is searched for each extension named in PATHEXT:
If the command name includes a file extension, the shell searches each directory for the exact file name specified by the command name. If the command name does not include a file extension, the shell adds the extensions listed in the PATHEXT environment variable, one by one, and searches the directory for that file name. Note that the shell tries all possible file extensions in a specific directory before moving on to search the next directory (if there is one).
It also documents how file types and associations interact with the command prompt. Despite its age, it is well worth the read.

Windows executables explicitly list the subsystem they run on. As the windows "lua.exe" is linked for the console subsystem, windows automagically creates a console window for it. Just relink "lua.exe" for gui subsystem, and you won't get to see the output any more unless you run it from a console window. BTW: Gui programs can programmatically create the console.
An alternative is closing the created console on start.
For that, you must first use SetStdHandle to redirect STDIN, STDOUT and STDERR (use a file open to device nul if you don't want it at all), and then call FreeConsole to finally dismiss your unloved console window. No sweat, you have "alien" set up already...

Programmatic solution (run the same script under wlua.exe if possible)
do
local i, j = 0, 0
repeat j = j + 1 until not arg[j]
repeat i = i - 1 until not arg[i-1]
local exe = arg[i]:lower()
-- check if the script is running under lua.exe
if exe:find('lua%.exe$') and not exe:find('wlua%.exe$') then
arg[i] = exe:gsub('lua%.exe$','w%0')
-- check if wlua.exe exists
if io.open(arg[i]) then
-- run the same script under wlua.exe
os.execute('"start "" "'..table.concat(arg,'" "',i,j-1)..'""')
-- exit right now to close console window
os.exit()
end
end
end
-- Your main program is here:
require "luarocks.require"
require "alien"
local MessageBox = alien.User32.MessageBoxA
MessageBox:types{ret = "long", abi = "stdcall", "long", "string", "string", "long" }
MessageBox(0, "Hello World!", "My Window Title", 0x00000040)

If you can use winapi module or have similar calls in Alien, you can find the handler of the console window and hide the window itself. The code would be similar to this:
require winapi
local pid = winapi.get_current_pid()
local wins = winapi.find_all_windows(function(w)
return w:get_process():get_pid() == pid
and w:get_class_name() == 'ConsoleWindowClass'
end)
for _,win in pairs(wins) do win:show_async(winapi.SW_HIDE) end
You'll need to check if this leave the MessageBox visible or not.

Related

How to launch an external executable on windows in go

I am trying to launch an external executable (msi driver installer) on windows with go. It is referred to with the relative path bin\launchme.msi
As of now, I am using the commander-cli/cmd library and it is working well with other external executable (though only command line ones).
My command is:
c := cmd.NewCommand(`bin\launchme.msi`)
c.Execute()
The executable is launched but displays the following:
An error occurred while writing installation information to disk.
Check to make sure enough disk space is available."
See here:
What is especially confusing to me is that, as you can see on the screenshot, the msi installer created a %SystemDrive% folder in the working directory.
This folder in turn contains a Program Files folder and some more subfolders.
Otherwise, if I simply launch the msi installer by double-clicking it, everything is fine, no error occurs.
Any help with this would be greatly appreciated.
It could be, that your .MSI file is using cmd.exe in the background.
If you type echo %my_env_var% in cmd.exe, there are two cases what can happen:
If the environment variable my_env_var is defined, the built-in echo command of cmd.exe prints it’s value.
If the environment variable my_env_var is not defined, the built-in echo command of cmd.exe prints %my_env_var% literally.
Every process inherits the environment variables of it’s parent process. If a process is started from the (Windows) File Explorer (explorer.exe) or from the (graphical) shell of Windows (also explorer.exe), then it inherits the system settings for environment variables.
It seems like your library is dropping some environment variables like SystemDrive when starting a new process from the executable bin\launchme.msi.
Try to use import("os/exec") from the Go standard library https://pkg.go.dev/os/exec .
For more information about environment variable in Microsoft Windows see for example: https://learn.microsoft.com/en-us/windows/win32/procthread/environment-variables
Tip:
You can use Process Explorer (procexp.exe) to see the environment variables of running processes, by doing this:
Open Process Explorer, to get a list of running processes
Right-click on the process you are intrested in, to open the context menu
In the context menu click on Preferences
In the new window click on the Tab Environment.
one workaround, for testing, would be to launch that Go program in C:\ as working directory:
// caputred is the captured output from all executed source code
// fnResult contains the result of the executed function
captured, fnResult := cmd.CaptureStandardOut(func() interface{} {
c := NewCommand("C:\path\to\bin\launchme.msi", cmd.WithWorkingDir("C:\\")
err := c.Execute()
return err
})
// prints "hello"
fmt.Println(captured)

Difference between calling "start myapp" and "myapp"

In a Windows batch file, or in the Command Prompt, what's the difference between calling start mspaint, for example, and mspaint? They appear to do exactly the same thing.
Another example, where all 4 cases appear to do the same thing. Can you please help me understand what are the subtle differences, if any?
taskmgr
C:\Windows\System32\Taskmgr.exe
start taskmgr
start C:\Windows\System32\Taskmgr.exe
Follow-up: it looks like start opens a separate background command prompt to run the program you write after it (source: https://technet.microsoft.com/en-us/library/cc770297(v=ws.11).aspx). Is this the same as Linux's myApp & format--where you have the & suffix?
Starting a Program
See start /? and call /? for help on all three ways.
Specify a program name
c:\windows\notepad.exe
In a batch file the batch will wait for the program to exit. When
typed the command prompt does not wait for graphical
programs to exit.
If the program is a batch file control is transferred and the rest of the calling batch file is not executed.
Use Start command
start "" c:\windows\notepad.exe
Start starts a program and does not wait. Console programs start in a new window. Using the /b switch forces console programs into the same window, which negates the main purpose of Start.
Start uses the Windows graphical shell - same as typing in WinKey + R (Run dialog). Try
start shell:cache
Also note the first set of quotes, if any, MUST be the window title.
Use Call command
Call is used to start batch files and wait for them to exit and continue the current batch file.
With Reference to Start and just typing a program name.
Help Windows Find Programs and Documents
Programs and documents can be added to the registry so typing their name without their path in the Start - Run dialog box or shortcut enables Windows to find them.
REGEDIT4
;The bolded name below is the name of the document or program, <filename>.<file extension>
[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\App Paths\IE.txt]
;The # means the path to the file is assigned to the default value for the key.
;The whole path in enclosed in a quotation mark ".
#="\"C:\\Program Files\\Internet Explorer\\IE.txt\""
;Optional Parameters. The semicolon means don't process the line. Remove it if you want to put it in the registry
;Informs the shell that the program accepts URLs.
;"useURL"="1"
;Sets the path that a program will use as its' default directory. This is commented out.
;"Path"="C:\\Program Files\\Microsoft Office\\Office\\"
For a technical discussion.
CMD preprocesses commands and finds the file then calls CreateProcess. Start - Run dialog or the Start command uses ShellExecuteEx which eventually calls CreateProcess.
This is CreateProcess rules - note CMD provides full paths to CreateProcess. https://msdn.microsoft.com/en-us/library/ms682425
1.The directory from which the application loaded.
2.The current directory for the parent process.
3.The 32-bit Windows system directory. Use the GetSystemDirectory function to get the path of this directory.
The 16-bit Windows system directory. There is no function that obtains the path of this directory, but it is searched. The name of this directory is System.
5.The Windows directory. Use the GetWindowsDirectory function to get the path of this directory.
6.The directories that are listed in the PATH environment variable. Note that this function does not search the per-application path specified by the App Paths registry key. To include this per-application path in the search sequence, use the ShellExecute function.
ShellExecuteEx is here https://msdn.microsoft.com/en-us/library/bb759784(v=vs.85).aspx
CMD preprocessing is available on my Skydrive - originally from MS web site but no more. See Windows NT Command Shell Ch 2 https://1drv.ms/f/s!AvqkaKIXzvDieQFjUcKneSZhDjw
you need to call start application when you want the application to launch and return immediately to the command shell for further commands.
(on Linux, closest equivalent of start is the & suffix)
Some commands have the ability to do that without start prefix. That's what you're experiencing.
On the other hand, if you want an application returning immediately to "block" the shell, prefix by cmd /c
Try cmd /c taskmgr for instance, you'll see it blocks the command window until you quit it.
About the question about taskmgr, the 3 possibilities amount to the same result:
taskmgr is in system path: with or without full path the system finds it
taskmgr returns to the command window immediately. In that case, start is redundant.

Coding a Delphi GUI utility for use in a CMD window

I'm writing myself a GUI utility for use in a CMD window to navigate between folders,
rather in the style of the old Norton Change Directory utility for DOS.
When run, the app pops up a folder tree to allow the user to select a folder to which
to navigate and then closes and returns to the CMD prompt. At the moment, the way it
works is that it is run as the first command in a "main" batch file. It writes a secondary batch
file, in my app's folder under AppData, containing the commands to change drive and
directory to the folder the user selected, and the main batch file then invokes this
second batch file using CALL.
It works fine, but this way of actually changing the CMD window's current directory
strikes me as inelegant both from the point of view of needing to be run from a batch file
(so that the user's selection can be acted upon after my app has closed) and of
needing the secondary batch file to do the actual navigation.
So, my question is, how can my app send the instructions to the instance of CMD
that owns the window in which the app is run to the folder the user selected? I've tried doing a ShellExecute
of "CMD /K ..." but although that does indeed navigate to the
selected folder, it does so in a new CMD window, not the one my app is run in. The
conceptual gap I have is how to get the current CMD to act on my app's instructions
after my app has terminated.
Fwiw, I thought of trying to write the user's folder selection into an environment variable in the CMD window's environment for the CMD processor to
act upon from there, but this seems to require that the CMD window be opened via "Run as Administrator", which I definitely don't want.
Your program cannot influence the environment variables of the command interpreter because they're separate processes. Your program cannot change the directory of the command interpreter directly, either, because, again, they're separate processes.
You need to use a batch file because the command interpreter executes batch files internally. Since it's all the same process, the batch file has the power to change the current directory, and for that change to remain in effect after the batch file finishes running.
Therefore, you need some way for your interactive program to communicate the directory selection back to the batch file so that it can act on it.
Instead of writing the instructions to another batch file, you could write the result to standard output. Have the batch file capture that output into a variable, and then execute cd on that variable. The batch code would look something like this:
for /f "tokens=*" %%a in ('[select_dir.exe]') do (
set DIRSELECTION=%%a
)
cd /d %DIRSELECTION%
Your Delphi code would look like this:
writeln(selected_dir);
To allow that command to work, you'll need to make sure your program is marked as a console program, as with {$APPTYPE CONSOLE}. If it's not, then the batch file won't receive any output, and probably won't even wait for your program to finish running before proceeding. It's OK for a console program to display a TForm, just like a GUI program.

Why does "start firefox" command works in Windows command prompt?

I'm curious why commands like "start iexplore" and "start firefox" work in Windows cmd.
They're not standalone commands. If you try to type in just "firefox", you'll get:
"'firefox' is not recognized as an internal or external command,
operable program or batch file."
This leads to the conclusion that this is a special behaviour of the "start" command.
My first guess was that it works in a similar way to how the %path% variable is used, having known directories to search in.
I ruled it out easily by trying to run "start [executable]" for another executable located in the same directory as firefox.
My conclusion is that somewhere on my computer there's a list of designated file paths, that can be started by simply typing their file name after the "start" command, instead of the entire path.
Imagine the potential of being able to add things to this list..
Anyone knows where I can find it?
It is in the registry
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\
Under this key there are defined applications that can be invoked without need to change the path environment variable.
Usual invocation (typing the name in the command line) will not search inside this list, but call to start command, windows Run dialog or call from anything that uses the ShellExecute or ShellExecuteEx API calls, will check for applications defined in this list.

How do I make my Perl scripts act like normal programs on Windows?

I want my Perl scripts to behave just like any other executable (*.exe file).
When I double-click on myscript.pl I want it to execute instead of opening in a text editor.
I want to run myscript.pl instead of perl myscript.pl.
I really want to run myscript instead of myscript.pl.
I want to run program | myscript instead of program | perl myscript.pl.
I want to be able to run my script via drag & drop.
There are a number of changes you have to make on Windows to make all of
these things work. Users typically stumble upon things that don't work one at
a time; leaving them confused whether they've made an error, there's a bug in
Perl, there's a bug in Windows, or the behavior they want just isn't possible.
This question is intended to provide a single point of reference for making
everything work up front; ideally before these problems even occur.
Related questions:
How do I make Perl scripts recognize parameters in the Win32 cmd console?
Running a perl script on windows without extension
Perl execution from command line question
How can I read piped input in Perl on Windows?
Perl on Windows, file associations and I/O redirection
How do I create drag-and-drop Strawberry Perl programs?
Note: The actions below require administrative privileges. For
steps utilizing the command prompt it must be launched via "Run as
administrator" on Windows Vista / Windows 7.
Associate *.pl files with perl
Run the following commands at a shell prompt:
assoc .pl=PerlScript
ftype PerlScript=C:\bin\perl.exe "%1" %*
Replace C:\Perl\bin\perl.exe with the path to your Perl installation. This
enables you to run myscript.pl instead of perl myscript.pl.
Default install locations are:
ActivePerl: C:\Perl
Strawberry Perl: C:\Strawberry
Add .PL to your PATHEXT environment variable.
This makes Windows consider *.pl files to be executable when searching your
PATH. It enables you to run myscript instead of myscript.pl.
You can set it for the current cmd session
set PATHEXT=%PATHEXT%;.PL
To set it permanently (under Windows Vista or Windows 7)
setx PATHEXT %PATHEXT%;.PL
Under Windows XP you have to use the GUI:
Right-click My Computer, and then click Properties.
Click the Advanced tab.
Click Environment variables.
Select PATHEXT, then click Edit.
Append ;.PL to the current value.
Make I/O redirection work
I/O redirection (e.g. program | myscript) doesn't work for programs started
via a file association. There is a registry patch to correct the problem.
Start Registry Editor.
Locate and then click the following key in the registry:
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer
On the Edit menu, click Add Value, and then add the following registry value:
Value name: InheritConsoleHandles
Data type: REG_DWORD
Radix: Decimal
Value data: 1
Quit Registry Editor.
Warning: In principle, this should only be necessary on Windows XP. In my experience it's also necessary in Windows 7. In Windows 10 this is actively harmful—programs execute but produce nothing on stdout/stderr. The registry key needs to be set to 0 instead of 1.
See also:
STDIN/STDOUT Redirection May Not Work If Started from a File Association
Perl Scripts on Windows 10 run from Explorer but not Command Prompt
If patching the registry isn't an option running program | perl -S myscript.pl
is a less annoying work-around for scripts in your PATH.
Add a drop handler
Adding a drop handler for Perl allows you to run a Perl script via drag & drop;
e.g. dragging a file over the file icon in Windows Explorer and dropping it
there. Run the following script to add the necessary entries to the registry:
use Win32::TieRegistry;
$Registry->Delimiter("/");
$perlKey = $Registry-> {"HKEY_CLASSES_ROOT/Perl/"};
$perlKey-> {"shellex/"} = {
"DropHandler/" => {
"/" => "{86C86720-42A0-1069-A2E8-08002B30309D}"
}};
Convert your perl scripts into batch files using pl2bat once they are ready to be run by users.
The trick works through the perl -x switch which, according to perldoc perlrun, makes Perl search for the first line looking like #!.*perl.
After following the instructions in the accepted answer, a double click still led to .pl files opening with Notepad in Windows 10 — even when perl.exe was set as the default file handler.
After finding Jack Wu's comment at ActivePerl. .pl files no longer execute but open in Notepad instead I was able to run perl scripts on double-click as such:
Select and right-click a .pl file
Use the "Open With" submenu to "Choose another app"
Select "Always use this app to open .pl files" (do this now – you won't get the chance after you have selected a program)
Scroll to the bottom of the "Other options" to find "More apps", and select "Look for another app on this PC"
Navigate to C:/path/to/perl/bin/ and select Perl5.16.3.exe (or the equivalent, depending on which version of Perl you have installed: but not Perl.exe)
Then the Perl icon appears next to .pl files and a double-click leads to them opening in Perl every time, as desired.
I tried the assoc and ftype methods and they didn't work for me.
What worked was editing this registry key:
Computer\HKEY_CURRENT_USER\Software\Classes\Applications\perl.exe\shell\open\command
It was set to:
"C:\Perl64\bin\perl.exe" "%1"
When it should be:
"C:\Perl64\bin\perl.exe" "%1" %*
It is the same content as the ftype, but for arcane windows reasons, I had to set it there too.
Like some others, I had set 'assoc' and 'ftype', but also had set Notepad text editor via the GUI, and when I tried to execute a script via the command line, Windows invoked Notepad to edit the script instead of running my script.
Using the GUI to instead point the .pl file association to the script-running executable was not much of an improvement, since it would invoke the executable on my script, but would pass no command-line arguments (even when I invoked my script from the command line).
I finally found salvation here which advised me to delete some registry keys.
Key quote:
"The problem is that if you have already associated the program with the extension via the Open With dialog then you will have created an application association, instead of a file extension association, between the two. And application associations take precedence."
In my case, following the instructions to use RegEdit to delete
HKEY_CLASSES_ROOT \ Applications \ perl.exe
where perl.exe is the name of my Perl executable, and then also deleting:
HKEY_CLASSES_ROOT \ .pl
seemed to solve my problem, and then (after re-executing 'assoc' and 'ftype' commands as shown in other answers) I could then execute scripts from cmd.exe and have them run with access to their command-line parameters.
Some other related information here.

Resources