Asset - Extension Execute Shell (for Windows, macOS, and Ubuntu)

Samuel Venable

Time Killer






This extension is for Windows, macOS and Ubuntu.

Execute third-party applications from your GameMaker Studio games.

Open or run any file type, whether EXE or not. Supports "wait" param.

The DLL's full source code included.

Very big thanks to Josh Ventura, for writing the widen() and shorten() scripts, enabling UTF-8 support on Windows.

Download Free for GameMaker Studio 1.4 and 2
 
Last edited:

Samuel Venable

Time Killer
This extension has had over 600 downloads, so I assume a good number of users here have it. Is anyone ok with showing me your project and how it uses my extension? I'm just a little curious as to what it is being used for.
 

RizbIT

Member
I had a similar one that kept crashing at game start. Is this one stable and work every time game starts?
 

Samuel Venable

Time Killer
Hi @RizbIT

If you set the wait parameter to 1 on Windows and try to click on or mess with the main game window while waiting for the process to close, it will crash. This is a bug with GM:S itself and how it handles non-async dll calls on Windows. I reported it on the helpdesk and they said they're gonna fix it.

So if you want the program to run asynchronously or you want to target Ubuntu you should be fine. This is a Windows-only issue. Set the second argument to 0 on Windows and you should be all set. :)

For example:
Code:
ExecuteShell("notepad.exe",0);
 
Last edited:

RizbIT

Member
no i mean right at the start when you define the external call in the dll... on the other guys dll sometimes on game start it crashed at that point
 

Samuel Venable

Time Killer
As long as the wait parameter is 0, there is no crash. It's been thoroughly tested. No crash at game start. No crash after game start.
 

Samuel Venable

Time Killer
Great news everyone!

Version 9.1.0 Released!

  • Finally fixed that nasty bug on Windows with setting the wait argument to true.

  • Added an optional third argument to allow opening files hidden on Windows.
 

Samuel Venable

Time Killer
Thanks to @appleWolf, this extension is now 64-bit Mac compatible! Well, hopefully. I still need someone to test it. The code is portable for Unix-based operating systems. I already know it works on Linux, which is Unix-based.

Mac is also based on Unix, and it built without any errors, so I can't imagine it not working, unless someone tries it on an unsupported architecture. If you would like to be a tester, shoot me a PM! If it works as intended, I'll announce Mac is officially supported at the top of the asset's description. :D

(testers will be credited)
 
Hello!

I am new here, but definitely not new to Gamemaker. I hope this is an okay place to ask this question. It is directly related to your extension (which is a pretty cool piece of work, by the way).

So I programmed an updater/launcher in Gamemaker for Windows that downloads the executable file for my game and saves it to the launcher's dedicated app data folder. I then execute the program with your extension. The problem comes when there are multiple users on the system. The process all works flawlessly on the primary user account, but then when the secondary user uses the launcher, it downloads just fine, but will not execute the program.

Here is the code that defines the folder's location:
Code:
globalvar place;
place = environment_get_variable("LOCALAPPDATA") + "\Updater";
"Updater" being the name of the launcher I have created. I've tried using "game_save_id", "working_directory", and "program_directory". The latter two do not work and the first yields the same result.

Here is the code that downloads the file. This code works perfectly no matter the user account. I'm just including it to give you an idea of what's going on here:
Code:
size = ds_map_find_value(async_load,"contentLength");
progress = ds_map_find_value(async_load,"sizeDownloaded");
if (perc < 100)
perc = round((progress/size)*100)
if (ds_map_find_value(async_load, "id") == async_ini)
{
      var status = ds_map_find_value(async_load,"status");
      if (status == 0 && statussss < 2)
      {
            statussss = 3;
            ini_open("localversion.ini");
            ini_write_real('version','number',ver);
            ini_close();
            statussss = 4;
            instance_create(x,y,o_Launch);
      }
}
Finally, here is the code that launches the game (the game executable being called "GunCave.exe"):
Code:
timer++;
if (timer >= 30*60)
{
    if (file_exists(place+"\GunCave.exe"))
    {
        o_Updater.neednew = 6;
        if (timer >= 33*60)
        {
            ExecuteShell(place+"\GunCave.exe", true);
            game_end();
        }
    }
    else
    {
        o_Updater.neednew = 7;
        if (timer >= 33*60)
        {
            game_end();
        }
    }
}
This is where the problem lies. This code works on the primary user account, but not on the secondary account. On the primary account, it successfully launches my game and then closes the launcher once the game is closed. On the secondary account, the game fails to launch and the launcher closes.

I know I've read places that have discouraged making a launcher in Gamemaker, but I was able to create and get it to work, up until this issue. I hope someone can help me.

Thanks in advance,
~Gravityhamster
 
Last edited:
Thank you so much for replying!

The executable does not require administrative privileges to run. Even so, though, I've tried making the secondary user both regular and administrator and the result is the same: The executable doesn't launch and the launcher program closes.
 

Samuel Venable

Time Killer
Try removing the file_exists call. I'm interested in hearing if it runs the file after commenting that out. I might have a solution if that ends up being the problem.

I'd also check if the path and file exists manually, outside of code, i.e. in explorer, and also what value the environment variable returns on both users by contrast.
 
Last edited:
Try removing the file_exists call. I'm interested in hearing if it runs the file after commenting that out. I might have a solution if that ends up being the problem.
It still does not work if I remove the "file_exists" call. I sent you in my original post the code for when it launches the game offline. If the the launcher detects that it cannot connect to my server to check for a new update, it will run the code I sent you. Otherwise, it checks for an update and runs this code instead, which has no "file_exists" present in it:
Code:
i++;
if (i == 5)
{
    ExecuteShell(place+"\GunCave.exe", true);
    game_end();
}
This code still does not launch the game correctly under the secondary account. (Obviously, it does run correctly under the primary account.)

I'd also check if the path and file exists manually, outside of code, i.e. in explorer, and also what value the environment variable returns on both users by contrast.
The executable file is present in the desired appdata folder and I can even manually execute the file from there and it runs fine. Also, when I run my launcher I draw the appdata directory (stored in variable "place") onto the screen. The directory is correct on both accounts, changing the username to match the current user account and matches the directory manually found in Windows Explorer.

Hopefully that all makes sense. If you need clarification, please ask.
 

Samuel Venable

Time Killer
I'm not sure even if I had your source code to debug with how much help that would be, it's really odd that it works on one account, but not the other. I hate to say it, but I'm stumped. Unless I can reproduce this issue myself, I don't see how I can figure out what's going wrong. :(
 
Oh well. Thanks for trying!... Maybe I'll just have to go at it with a different approach. Maybe even make a launcher in a separate language. I dunno.

I will say that you can create the same basic scenario by following these steps: Make a new user on your computer. You can easily do this in the settings and don't even have to make a new Microsoft account to do it (and it can easily be deleted later). Create a new project in Gamemaker. Have the project write a text file (I prefer a .ini file) so that it creates the appdata directory for your project, then manually place any standalone executable in the appdata directory for your project (These steps must be done under BOTH user accounts). Make an "ExecuteShell" command to run that program you put in the project's directory. Now export the project to an executable and run it. It should run the executable that you placed in the appdata folder for your program under your primary user account, but it should not successfully run under the secondary account.

If it's too much of a pain to try to figure out, don't worry about it, but there's some basic steps if you wish to try to reproduce the issue.
 

Samuel Venable

Time Killer
Oh well. Thanks for trying!... Maybe I'll just have to go at it with a different approach. Maybe even make a launcher in a separate language. I dunno.

I will say that you can create the same basic scenario by following these steps: Make a new user on your computer. You can easily do this in the settings and don't even have to make a new Microsoft account to do it (and it can easily be deleted later). Create a new project in Gamemaker. Have the project write a text file (I prefer a .ini file) so that it creates the appdata directory for your project, then manually place any standalone executable in the appdata directory for your project (These steps must be done under BOTH user accounts). Make an "ExecuteShell" command to run that program you put in the project's directory. Now export the project to an executable and run it. It should run the executable that you placed in the appdata folder for your program under your primary user account, but it should not successfully run under the secondary account.

If it's too much of a pain to try to figure out, don't worry about it, but there's some basic steps if you wish to try to reproduce the issue.
I'll give that a shot tomorrow. It's getting late where I live. I'll get back to you with the results of my testing.

Edit:

Trying it now as we speak...

Edit2:

It works for me on both accounts. Am I doing something wrong?

I run this on account 1:

Code:
if (os_type == os_windows)
{
    ini_open("test.ini");
    ini_write_real("hello", "world", 1);
    ini_close();
   
    if (file_exists(game_save_id + 'notepad.exe'))
        ExecuteShell('"' + game_save_id + 'notepad.exe"', true);
}

game_end();
It created the ini file but the exe needs to be copied there in order to run so I do that.

Then I hit run again.

Export project.

Do all the same as above but on account 2.

It all worked for me. notepad.exe ran on both accounts after I copied it to the correct folder on both accounts.
 
Last edited:
Yes! Thanks to you, it's fixed!!

What I found out was that my syntax was incorrect. Here was the old code for the file path:
Code:
globalvar place;
place = environment_get_variable("LOCALAPPDATA") + "\Updater";
Execute:
Code:
i++;
if (i == 5)
{
    ExecuteShell(place+"\GunCave.exe", true);
    game_end();
}
And here is the new, working code for the file path execution:
Code:
i++
if (i == 5)
{
    ExecuteShell('"' + string(game_save_id) + 'GunCave.exe"', true);
    game_end();
}
I don't know if using "LOCALAPPDATA" rather than "game_save_id" would work, but it doesn't matter. I'd rather use "game_save_id" anyway. Here's the things I could note that you did differently compared to me:
  • First of all, you excluded the "\" before the executable.
  • Second, when I tried "game_save_id" I neglected to convert to a string.
  • Third, was the use of double and single quotes. Notice the difference between this:
    Code:
    place+"\GunCave.exe"
    and this:
    Code:
    '"' + string(game_save_id) + 'GunCave.exe"'
  • I also just removed the variable "place" for when I execute as a whole just to make it easier to implement the correct quotes and reduce confusion.
Thank you so much for your help! I'm so glad that it was a problem with my code and not with the extension!

You've been great to work with!
~Gravityhamster
 

Samuel Venable

Time Killer
I'm glad you've figured it out, and that I could help! Using the quotes within quotes is generally a good practice for command line stuff, since there are sometimes paths with spaces. Some path functions and variables do not include the final slash, but on Windows at least, I know working_directory and game_save_id have it. It may be different for getting the environment variables, as well as program_directory. I can't remember whether temp_directory has the last slash. In any case, I agree the inconsistency is a bit confusing and I have reported this to the bug tracker for GMS 1.4 and 2. However, they didn't fix it before the end of 1.4's lifetime, so your practice of using the extra slash is probably better, also because it is backwards compatible with pre-Studio GM versions.

Edit:

Also, as far as I know, you don't need to wrap game_save_id in a string() function. I did that by accident originally, but updated my post to remove that.
 
Last edited:

Samuel Venable

Time Killer
I have unfortunate news concerning the future of this project. It relies on Microsoft Script Control on Windows, i.e. "msscript.ocx" in your SysWOW64 folder, which is a legacy component built for 32-bit only by Microsoft. Normally I would just use CreateProcess or ShellExecute Windows API functions to achieve this, but for the wait parameter, a call to waiting for completion is necessary, and that crashes GameMaker due to a long-standing bug with GM's Runner.

So anyway, I am relying on something that essentially does the same thing but with interpreted VBScript. This can be done with one of two mechanisms - Microsoft Script Control, which is 32-bit only. Or I could use the IActiveScript interface, which would also support the upcoming 64-bit Windows GM Runner, but ironically enough that crashes the Runner, just like CreateProcess and ShellExecute does, but only when the wait argument is true.

As a final attempt to bypass all of this, i think I have good news as well. I just discovered while writing this post just now, that I can create an invisible modal dialog both the developer and the end user won't see, and allow that to create the waiting parameter to have the desired effect. Haven't tested it yet. Wish me luck! Will add my results in an edit.
 
That does sound unfortunate. I hope you can find a solution! However, if I do not receive a crash in my project as it stands now, do I need to worry about the bug?
 

Samuel Venable

Time Killer
Hey fam, so i fixed the Linux version which a user reported to me that it got stale and stopped working. Without changing the code, I simply rebuilt it with the latest G++ compiler. They said it works again now. Please let authors/publishers know if you ever run into similar issues down the line using extensions, as all of them on the marketplace that are not pure GML will need to be rebuilt over time to maintain compatibility with the latest systems.

This update only fixes the GMS 2.x version. If you need the update for GMS 1.4 please contact and I'll send you the new files. I can't update the 1.4 version on the MP anymore because YoYo's uploader for that version is still broken.
 
I downloaded this earlier, and was testing it out. It ran fine with the example where you bring up notepad and the calculator, but I wanted to load up an exe of a gamemaker project. I could see it being started, but it would shut down after appearing.

The aim is to see if I can get two versions of the project running in sync, and use it for multi-threading somehow. This is what I did (bare in mind I'm not that savvy...)

ExecuteShell('"C:\Windows\System32\calc.exe"', true); // your original code

ExecuteShell('C:\Users\jb209\Downloads\gamemaker stuff\market place stuff\test_project.exe', true); // my alteration - which I assumed was all that this was doing...giving the directory location?

Can you please let me know what I'm doing wrong? I just want to know why it doesn't stay open, and yet the calculator does when called in the same way.
 

Samuel Venable

Time Killer
I downloaded this earlier, and was testing it out. It ran fine with the example where you bring up notepad and the calculator, but I wanted to load up an exe of a gamemaker project. I could see it being started, but it would shut down after appearing.

The aim is to see if I can get two versions of the project running in sync, and use it for multi-threading somehow. This is what I did (bare in mind I'm not that savvy...)

ExecuteShell('"C:\Windows\System32\calc.exe"', true); // your original code

ExecuteShell('C:\Users\jb209\Downloads\gamemaker stuff\market place stuff\test_project.exe', true); // my alteration - which I assumed was all that this was doing...giving the directory location?

Can you please let me know what I'm doing wrong? I just want to know why it doesn't stay open, and yet the calculator does when called in the same way.
Because there are spaces in that path, you will need to do:

ExecuteShell('"C:\Users\jb209\Downloads\gamemaker stuff\market place stuff\test_project.exe"', true);
 
Thanks for the help :) That fixed it.

Does any part of this process give any control / communication between the two applications?

EDIT:
I've looked into networking, and have set up a project, so I imagine that covers it?
 
Last edited:

Samuel Venable

Time Killer
Thanks for the help :) That fixed it.

Does any part of this process give any control / communication between the two applications?

EDIT:
I've looked into networking, and have set up a project, so I imagine that covers it?
If you made the executable yourself that you are trying to run, you can communicate between the two by reading and writing to external files, is that what you meant?
 
If you made the executable yourself that you are trying to run, you can communicate between the two by reading and writing to external files, is that what you meant?
I was initially thinking of reading and writing to files, though decided it's probably not the best way. The YOYO demo of the LAN platformer showed how to use networking on the same machine, and that seems simpler.

Just wondered if there was anything inherent to execute shell that made doing the networking redundant (I still want to use it to open up the second exe though) , but I have the solution if it doesn't.
 

Samuel Venable

Time Killer
I was initially thinking of reading and writing to files, though decided it's probably not the best way. The YOYO demo of the LAN platformer showed how to use networking on the same machine, and that seems simpler.

Just wondered if there was anything inherent to execute shell that made doing the networking redundant (I still want to use it to open up the second exe though) , but I have the solution if it doesn't.
Not that I know of, but I suppose it may be possible with shell scripts or something. I honestly don't know...
 

Lonewolff

Member
You could communicate between applications via window handles, processes, etc.. but you'd have to have applications that are aware of each other. Not too hard to achieve, but is out of scope of this type of extension. Not really the sort of thing that a shell execute is designed to do.
 
You could communicate between applications via window handles, processes, etc.. but you'd have to have applications that are aware of each other. Not too hard to achieve, but is out of scope of this type of extension. Not really the sort of thing that a shell execute is designed to do.
Thanks for the info. That sounds a bit beyond my capabilities (for now), but I have an alternative which should suffice.
 

Samuel Venable

Time Killer
You could communicate between applications via window handles, processes, etc.. but you'd have to have applications that are aware of each other. Not too hard to achieve, but is out of scope of this type of extension. Not really the sort of thing that a shell execute is designed to do.
@matharoo informed me he is in the process of making a tutorial that does exactly what @the_dude_abides is looking for, and he said he is using my Execute Shell extension to do it. I love it when I make something that has more practical uses than I originally anticipated. I'm sure you being a much more experienced developer than I am have run into this kind of thing a lot more than I have. Regardless, one day i will be as good as you, just wait and see you stinker. :p
 
Thanks Samuel for tagging me into that - I will be interested in that tutorial when matharoo gets around to it. I guess for me it's just a question of which form of "communication" is the least costly and / or effective to do - local networking and sending limited data in a form that fits a buffer, or whatever matharoo' process entails (which will hopefully be more direct in communicating / manipulating data).

But that's getting a bit off topic from your original thread, so I'll leave it there. Cheers!
 

matharoo

Udemy Instructor
Thanks Samuel for tagging me into that - I will be interested in that tutorial when matharoo gets around to it. I guess for me it's just a question of which form of "communication" is the least costly and / or effective to do - local networking and sending limited data in a form that fits a buffer, or whatever matharoo' process entails (which will hopefully be more direct in communicating / manipulating data).

But that's getting a bit off topic from your original thread, so I'll leave it there. Cheers!
Here's a script that opens two windows, if that's what you're looking for:
Code:
if (parameter_count() == 3) {
    ExecuteShell(parameter_string(0) + " " +
        parameter_string(1) + " " +
        parameter_string(2) + " " +
        parameter_string(3) + " -secondary" + " -tertiary", false, false)
    // <primary instance>
    window_set_caption("P1")
 
    window_set_position(200, 260);
}

if (parameter_count() > 3) {
    // <secondary instance>
    window_set_caption("P2")
 
    window_set_position(900, 260);
}
The tutorial Samuel's talking about is a networking tutorial, and that's why I needed two windows.
 
The tutorial Samuel's talking about is a networking tutorial, and that's why I needed two windows.
@matharoo Thanks for the info. I will check out that tutorial, as I am looking into networking. I don't want to hijack the original thread, but while you have responded - my original question was if there was a direct way to control an exe opened through Execute Shell, rather than using local networking to communicate limited information.

I'm not sure the parameter functions really cover exactly what I want to do, or that I have the capability to handle things directly. As an example: I have two exe of my game running, one is a slave, one is a master. If I make an mp grid in one, is it possible to manipulate it in the other? The networking attempt would simply be sending the data to be entered into the grid, whereas direct manipulation (if even possible) entails...dunno, beyond my pay grade :) One version being pointed to the memory that is storing the variables and info from the other version, and....*my brain explodes as it tries to consider what that involves*

Will your tutorial cover anything like that?
 

YellowAfterlife

ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ
Forum Staff
Moderator
If I make an mp grid in one, is it possible to manipulate it in the other?
Not without tricks that are unjustifiably complex - with some luck (see this), you could expose functions to a C++ DLL, and then write another DLL to allow the first process to run path_get_point_x/y functions on mp_grid paths in the second process, and communicate path indexes between them, but at this point a sizable chunk of your code would move to C++, and all for what
 
Not without tricks that are unjustifiably complex - with some luck (see this), you could expose functions to a C++ DLL, and then write another DLL to allow the first process to run path_get_point_x/y functions on mp_grid paths in the second process, and communicate path indexes between them, but at this point a sizable chunk of your code would move to C++, and all for what
Ouch! That is quite beyond my capabilities.....Soooo, uh, I'll stick to a more manageable solution. Thanks for replying :)

EDIT:
That link is interesting, so thanks for that too. I will see if I can understand it. This is still linked to using Execute Shell, but it feels like me asking you further questions would be hijacking this topic. If I wanted to ask you about the meaning of 'function_get_address" would a PM be okay, or is there a thread about it?
 
Last edited:

YellowAfterlife

ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ
Forum Staff
Moderator
Ouch! That is quite beyond my capabilities.....Soooo, uh, I'll stick to a more manageable solution. Thanks for replying :)

EDIT:
That link is interesting, so thanks for that too. I will see if I can understand it. This is still linked to using Execute Shell, but it feels like me asking you further questions would be hijacking this topic. If I wanted to ask you about the meaning of 'function_get_address" would a PM be okay, or is there a thread about it?
I suppose a separate topic would work best - that way it'll also be useful for anyone else with same questions
 

ocnuybear

Member
Cannot start Python - please assist?
Code:
ExecuteShell("C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python37_64\python.exe",true);
Nothings happens, but I can start Python using that from command prompt
 

Samuel Venable

Time Killer
Cannot start Python - please assist?
Code:
ExecuteShell("C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python37_64\python.exe",true);
Nothings happens, but I can start Python using that from command prompt
You have spaces in that path. You need to use quotes within quotes around the path. For passing arguments, if those contain spaces as well you'll need quotes around that too within the initial quotes. This also applies to when using the command line, if you dont put quotes around the path with spaces, it won't work.

GMS 1.4 example:
Code:
ExecuteShell('"C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python37_64\python.exe"',true);
GMS 2.x example:
Code:
ExecuteShell(@'"C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python37_64\python.exe"',true);
And if you need arguments with potential whitespace hazards...

GMS 1.4 example:
Code:
ExecuteShell('"C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python37_64\python.exe" "C:\Program Files (x86)\Python Scripts\example.py"',true);
GMS 2.x example:
Code:
ExecuteShell(@'"C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python37_64\python.exe" "C:\Program Files (x86)\Python Scripts\example.py"',true);
Always use quotes within quotes as a standard practice because there's no telling in some cases whether there will be spaces in the directory specified necessarily, i.e. if you use the working_directory constant, for example.
 
Last edited:

Samuel Venable

Time Killer
Version 9.5.0 Released! (GMS2-only)
  • Fixed an issue where a zombie process would be left behind on macOS and Linux when the wait argument was set to false.
  • No more script resources are being used. The function included is now a part of the extension package itself.
  • Removed the Windows-specific optional argument for running the program silently; this is now done automatically with all console windows.
  • Renamed the function from ExecuteShell() to execute_shell().
Additionally, the Windows DLL now has code that may be compiled as-is into a 64-bit DLL in preparation for the upcoming 64-bit Windows Runner. The previous code I used relied on a 32-bit only system file which is no longer being used which means 64-bit compiling of the DLL is now fully supported.
 
Last edited:
Top