Page 1 of 1

ShellExecuteEx behaviour under Micromacro v1.04.167

Posted: Thu Feb 12, 2015 9:09 am
by dx876234
Hey, I'm doing a Lua launcher for the Rom client to be able to connect to the client
without searching for the process.

As most probably know the client only accepts the NoCheckVersion argument in a
shell link, not on command line.

Now, the behavior is due to the client checking for WinMains's 4th argument (nCmdShow)
being equal to 1. This is typically done with ex. the SHELLEXECUTEINFO structure used in
the ShellExecuteEx call by setting SHELLEXECUTEINFO.nShow = SW_SHOWNORMAL which
is what the symlink does by default.

So, I've created a small program to do just this and return the PID for process identification:

Code: Select all

int runLink(char *cmd, char *args, char *dir)
	SHELLEXECUTEINFO ShExecInfo = {0};
	ShExecInfo.cbSize = sizeof(ShExecInfo);
	ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
	ShExecInfo.hwnd = NULL;
	ShExecInfo.lpVerb = NULL;
	ShExecInfo.lpFile = cmd;        
	ShExecInfo.lpParameters = args;   
	ShExecInfo.lpDirectory = dir;
	ShExecInfo.nShow = SW_SHOWNORMAL;
	ShExecInfo.hInstApp = NULL; 
	
	if(ShellExecuteExU(&ShExecInfo)){
		pid = GetProcessId(ShExecInfo.hProcess);
		CloseHandle(ShExecInfo.hProcess);
	} else {
		DWORD errorCode = GetLastError();
		DisplayError(TEXT("Unable to execute."), errorCode);
	}
		
	return (int)pid;
}

// Example usage
pid = runLink("C:\\Program Files (x86)\\Runes of Magic\\Client.exe", "NoCheckVersion", "C:\\Program Files (x86)\\Runes of Magic");
pid = runLink("C:\\Program Files (x86)\\Runes of Magic\\ClientRun.lnk", NULL, NULL);
Running this as a standalone .exe file in a command line window starts the client directly without any
updates running.

Using it as a plugin in Lua 5.2 also works as a charm, I can start by shell link or client directly and
it accepts the argument.

But, loading the plugin as a Lua plugin in a Micromacro script fails, the client starts but does not
accept the command line argument so I expect the nCmdShow does not reflect the SW_SHOWNORMAL
set in the call.

Code: Select all

mmext = require("mmext")

print("PID = ", mmext.Spawn("C:\\Program Files (x86)\\Runes of Magic\\ClientRun.lnk", nil, nil))
I'm banging my head against a wall here, there seem to be something in Micromacro that makes the
ShellExecuteEx call behave differently. My guess is something inherited, like environment but I
havent been able to identify it.

Any1 have ANY ideas on what that could be?

Best regards
DX

Re: ShellExecuteEx behaviour under Micromacro v1.04.167

Posted: Thu Feb 12, 2015 10:06 am
by dx876234
Lol, didn't have to bang for long - it struck me that the console is the difference.

Using 'ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE;' did the trick, it now
starts client and return clients PID.

Now to implement in userfunction_login.

-dx

Re: ShellExecuteEx behaviour under Micromacro v1.04.167

Posted: Thu Feb 12, 2015 12:17 pm
by Administrator
Great, glad you were able to find the problem, and appreciate that you shared your whole thought process and code. Hopefully this can go towards helping someone else in the future.

If you'd like, you're more than welcome to share your plugin (when finished), but you should probably do so in the RoM section instead of here.

Re: ShellExecuteEx behaviour under Micromacro v1.04.167

Posted: Thu Feb 12, 2015 3:01 pm
by dx876234
I agree, further discussions belong in Rom section, I put this question here as I
suspected a Micromacro specific issue.

But, it would be an idea to include a better way of starting new processes than
os.execute() in Micromacro. The os.execute() is basically a Windows "system()" call
and does not supply any handles to what it just did, and its really a synchronous thing, ie.
it waits for command to complete although this can be handled by doing the
os.execute("START <cmd>") call. But, it does not return any handles or pid's for programmer to
use to hook onto the process.

To be able to start any application from MM we should be able to hook onto PID
and HWND to uniquely identify what we started. After fiddling with this stuff
it seems to me the ShellExecuteEx would be nice to expose in MM, it would give a
lot of flexibility.

Best regards
DX

Re: ShellExecuteEx behaviour under Micromacro v1.04.167

Posted: Thu Feb 12, 2015 3:56 pm
by Administrator
Perhaps, yes. I can see that being useful in places, so I'll have to tinker around with adding that in.

There's also the system(cmd) [MicroMacro 1.04] or system.exec(cmd) [MicroMacro 1.09+] function which executes the program and returns its output as a string.
Example:

Code: Select all

print(system('echo %PATH%'))
Outputs:
C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\ATI Technologies\ATI.ACE\Core-Static;C:\PHP;C:\ProgramData\ComposerSetup\bin;C:\Program Files (x86)\Lua\5.1;C:\Program Files (x86)\Lua\5.1\clibs;C:\Program Files (x86)\AMD\ATI.ACE\Core-Static
*obviously it changes based on your own system.

Re: ShellExecuteEx behaviour under Micromacro v1.04.167

Posted: Thu Feb 12, 2015 4:52 pm
by dx876234
Ye, the system(cmd) command is basically the same as os.execute(cmd), it executes something and waits for it to finish, this is noticable if u do a system("/windows/system32/notepad") or a system("START /windows/system32/notepad").

Code: Select all

static int os_execute (lua_State *L) {
  const char *cmd = luaL_optstring(L, 1, NULL);
  int stat = system(cmd);
  if (cmd != NULL)
    return luaL_execresult(L, stat);
  else {
    lua_pushboolean(L, stat);  /* true if there is a shell */
    return 1;
  }
}
The system based commands are nice for their usage but for controlling the execution of an application they are really not very useful as no PID or HWND id's are returned.

-dx

Re: ShellExecuteEx behaviour under Micromacro v1.04.167

Posted: Sat Feb 14, 2015 12:07 am
by Administrator
After giving it some thought, I think the best way to go about adding this functionality would be through a key/value pair table that represents the SHELLEXECUTEINFO struct.

For example:

Code: Select all

local execInfo = {
  lpVerb = 'open',
  lpFile = 'whatever.bin',
  lpParameters = '-opt1 -opt2',
};

system.shellExec(execInfo);
What do you think?

Re: ShellExecuteEx behaviour under Micromacro v1.04.167

Posted: Sat Feb 14, 2015 1:03 am
by rock5
It might be useful if you need to start multiple processes. You could set up the process tables at the beginning then when you need to start them just use the table, eg.

Code: Select all

system.shellExec(process1)
system.shellExec(process2)
Then how do you stop the process? You are talking about using handles right? So then you would use the handle to stop the process?

What I think would be cool is if you set up a process table, probably using a class, then use it to start, stop, query, etc. the process and maybe also hold information about the process.

Re: ShellExecuteEx behaviour under Micromacro v1.04.167

Posted: Sat Feb 14, 2015 2:24 am
by Administrator
If you start the process and set the parent's handle to the calling MicroMacro process, you should be able to also use TerminateProcess().

Re: ShellExecuteEx behaviour under Micromacro v1.04.167

Posted: Sat Feb 14, 2015 5:05 am
by dx876234
I think the key/value is a good idea, this way we can customize the parameters to the specific process
we want to start. I would just like to be able to specify the full structure as other processes might
have other peculiarities in starting.

As you can see in my source it's basically three parts:
  • ShellExecuteEx, starting of the process
  • WaitForInputIdle, this waits until process is 'idle', ie when HWND is available
  • GetProcessId, traversal of the processes to get the HWND for the process we just started
The two first should probably be included as one, it makes sense to wait for process to "start" but we might have
cases where we do not want that waiting? There is a flag in SHELLEXECUTEINFO that might replace the need for a
separate WaitForInputIdle call.

The last is basically an extended version of the existing 'findWindow' function, it just includes the PID
in the search. This is necessary as there is not a 1:1 relationship between process and windows, its a 1:n.
I.e. a search for PID would return a list of windows, within this subset we want the window with the specified
name.

And finally, yes, we should make TerminateProcess() available, to use that we need the hProcess returned from
ShellExecuteEx as 'process handle'. So to get the PID if we need to have the GetProcessId() exposed. Or operate
on PID's for Lua interface and keep a table of our processes' hProcess internally.

-dx

Re: ShellExecuteEx behaviour under Micromacro v1.04.167

Posted: Sun Feb 15, 2015 1:54 pm
by Administrator
I've got it mostly out of the way. One thing that tripped me up was that console applications will actually throw an error even when no error has occurred (it does not need to process any initial input, therefor returns WAIT_FAILED).

I think that system.shellExec() will just return the process ID, and you can then obtain the window or window list from there. That seems more logical than iterating over all windows unnecessarily.

Re: ShellExecuteEx behaviour under Micromacro v1.04.167

Posted: Tue Feb 17, 2015 2:56 pm
by Administrator
Here's what I've got:
  • system.shellExec(table execInfo) takes a table of key/value pairs for any option you want (see ShellExecuteEx documentation for what those are), and returns the process ID on success or nil on failure.
  • process.terminate(handle procHandle[, number exitCode = 0]) takes a process handle and terminates the associated process. exitCode is optional. Returns true/false based on success.
  • process.getWindows(procId) accepts a process ID, and returns windows belonging to that process. The returned value is a table of tables containing 'hwnd', 'name', and 'class', that describe the window. *see example below for clarification.

process.getWindows() example:

Code: Select all

	procId = process.findByExe("micromacro.exe")
	print("Proc:", procId);
	assert(procId);

	windows = process.getWindows(procId);
	print("Windows:", #windows);

	for i,v in pairs(windows) do
		print(i, v.hwnd, v.name, v.class);
	end
Outputs:

Code: Select all

Proc:   1284
Windows:        1
1       1182136     MicroMacro v1.91.37     ConsoleWindowClass

I'm pretty happy with how that function returns its results, and think I'll change window.findList() to do the same.

Re: ShellExecuteEx behaviour under Micromacro v1.04.167

Posted: Thu Mar 05, 2015 9:06 am
by dx876234
This looks perfect for my usage at least :)

Best regards
DX