Running EXE and BATCH Files As CGI Scripts In Apache Under Windows

If you need to run an executable file as part of the request made to your server, get its output, and pass that output back to the client, there are 3 things that you have to be aware of:

  1. EXE files are typically Windows command-line (or Desktop) binary files that where not built/compiled to be executed through CGI.
  2. Everything that is ran through CGI, has to conform to the CGI 1.1 Specification.
  3. The CGI specification only requires that the first two output lines are: a Header line (e.g., “Content-type: text/html”) followed by an empty line.

To execute regular EXE files as CGI scripts, read on…

Mistake #1. “AddHandler cgi-script .exe”

The most common mistake is to treat all EXE files as CGI compliant script files by using configuration:

AddHandler cgi-script .exe

This is not going to work because the EXE file:

  • Is not a CGI compliant binary.
  • Nor is it a script file with a readable “shebang” line (which is the first line of file that specifies what interpreter is used to run this “script”).

Mistake #2. “#!C:\windows\system32\cmd.exe /c”

Another common mistake is to wrap the EXE file into a BATCH (BAT) file, and then using the “shebang” line to tell Apache to execute the script via cmd.exe.

This will not work, and will either produce an “500 / Internal Server Error” or a blank page, as when that BATCH script is ran it will output an error as its first line is read (i.e., the regular “#!…” shebang line is not a valid BATCH ignore/comment line).

The Right Way To Execute Regular EXE and BAT Files as CGI Scripts

1. Wrap the EXE in a BATCH (.bat) file, with the proper cgi-compliant header and header-body separator.

@echo off
echo Content-Type: text/plain
echo.
dir

“dir” is a standard command-line binary that outputs the current directory’s file + folder names. Substitute your binary in.

2. Do not use a “shebang” line, by directly telling Apache to execute it via cmd.exe

Update Apache configuration for the directory either in the main configuration, the website’s VirtualHost file, or the .htaccess file:

ScriptInterpreterSource Registry-Strict
AddHandler cgi-script .bat
Options +ExecCGI +FollowSymlinks
<IfModule mod_ssl.c>
    SSLOptions +StdEnvVars
</IfModule>

WampDeveloper Pro already has this configuration for /cgi-bin.

You can also set a cgi error log with line: ScriptLog /path/to/logs/cgilog.txt

Create and set Registry Key/Value to shell execute .bat files via cmd.exe:

Key: HKEY_CLASSES_ROOT\.bat\Shell\ExecCGI\Command
Value name: (default)
Value type: REG_SZ
Value data: "C:\windows\system32\cmd.exe /c %s %s"

Run “regedit” to open the Registry editor. You will need to create key “Shell\ExecCGI\Command” as it’s unlikely to already exist.

3. And if elevated privileges are needed to run this EXE file, switch Apache to use a different account in its Service Properties / Log On field.

BATCH HTML

To output HTML, you’ll have to set the proper Content-type header, and escape special BATCH characters…

@echo off
echo Content-Type: text/html
echo.
echo ^<HTML^>
echo.
echo ^<HEAD^>
echo ^<TITLE^>Hello, world!^</TITLE^>
echo ^</HEAD^>
echo.
echo ^<BODY^>
echo ^<H1^>Hello, world!^</H1^>
echo ^<p^>Hello, world!^</p^>
echo ^</BODY^>
echo.
echo ^</HTML^>

BATCH Environment

The environment of the CGI has variables containing passed in parameters (QUERY_STRING) and other info

Script:

@echo off
echo Content-Type: text/plain
echo.
set

Output:

PROMPT=$P$G
SCRIPT_URL=/cgi-bin/test.bat
SCRIPT_URI=http://example.com/cgi-bin/test.bat
MYSQL_HOME=C:\WampDeveloper\Components\Mysql
PHPRC=C:\WampDeveloper\Components\Php
TMP=C:\WampDeveloper\Temp
MAGICK_HOME=C:/WampDeveloper/Tools/ImageMagick
OPENSSL_CONF=C:/WampDeveloper/Components/Apache/bin/openssl.cnf
HTTP_HOST=example.com
HTTP_CONNECTION=keep-alive
HTTP_CACHE_CONTROL=max-age=0
HTTP_ACCEPT=text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
HTTP_USER_AGENT=Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36
HTTP_ACCEPT_ENCODING=gzip, deflate, sdch
HTTP_ACCEPT_LANGUAGE=en-US,en;q=0.8
PATH=...removed for brevity...
SystemRoot=C:\Windows
COMSPEC=C:\Windows\system32\cmd.exe
PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
WINDIR=C:\Windows
SERVER_SIGNATURE=<address>Apache/2.4.10 (Win32) OpenSSL/1.0.1j PHP/5.6.4 Server at example.com Port 80</address>
SERVER_SOFTWARE=Apache/2.4.10 (Win32) OpenSSL/1.0.1j PHP/5.6.4
SERVER_NAME=example.com
SERVER_ADDR=127.0.0.1
SERVER_PORT=80
REMOTE_ADDR=127.0.0.1
DOCUMENT_ROOT=C:/WampDeveloper/Websites/www.example.com/webroot
REQUEST_SCHEME=http
CONTEXT_PREFIX=/cgi-bin/
CONTEXT_DOCUMENT_ROOT=C:/WampDeveloper/Websites/www.example.com/cgi-bin/
SERVER_ADMIN=admin@httpd.host
SCRIPT_FILENAME=C:/WampDeveloper/Websites/www.example.com/cgi-bin/test.bat
REMOTE_PORT=56464
GATEWAY_INTERFACE=CGI/1.1
SERVER_PROTOCOL=HTTP/1.1
REQUEST_METHOD=GET
QUERY_STRING=
REQUEST_URI=/cgi-bin/test.bat
SCRIPT_NAME=/cgi-bin/test.bat

5 thoughts on “Running EXE and BATCH Files As CGI Scripts In Apache Under Windows”

  1. Since Apache 2.2.18, you can also CGI execute VBS scripts (and JS via WSF scripts).

    As above, except…

    Configuration:

    AddHandler cgi-script .vbs

    Script:

    '!c:/windows/system32/cscript //nologo
    WScript.Echo("Content-Type: text/plain");
    WScript.Echo("");
    WScript.Echo("Hello World!");
    Wscript.Quit("0")
  2. Checking the source code of Apache / httpd, we can see that it does not need to have an explicitly set interpreter for: .exe, .com, .bat, and .cmd

    https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x/modules/arch/win32/mod_win32.c

    
    /* If the file has an extension and it is not .com and not .exe and
     * we've been instructed to search the registry, then do so.
     * Let apr_proc_create do all of the .bat/.cmd dirty work.
     */
    if (ext && (!strcasecmp(ext,".exe") || !strcasecmp(ext,".com")
                || !strcasecmp(ext,".bat") || !strcasecmp(ext,".cmd"))) {
        interpreter = "";
    

    And here is the part that detects the “shebang line”, and also tests to see if the file is a DOS binary…

    
    /* Script or executable, that is the question...
     * we check here also for '! so that .vbs scripts can work as CGI.
     */
    if ((bytes >= 2) && ((buffer[0] == '#') || (buffer[0] == '\''))
                     && (buffer[1] == '!')) {
        /* Assuming file is a script since it starts with a shebang */
        for (i = 2; i < bytes; i++) {
            if ((buffer[i] == '\r') || (buffer[i] == '\n')) {
                buffer[i] = '\0';
                break;
            }
        }
        if (i < bytes) {
            interpreter = buffer + 2;
            while (apr_isspace(*interpreter)) {
                ++interpreter;
            }
            if (e_info->cmd_type != APR_SHELLCMD) {
                e_info->cmd_type = APR_PROGRAM_PATH;
            }
        }
    }
    else if (bytes >= sizeof(IMAGE_DOS_HEADER)) {
        /* Not a script, is it an executable? */
        IMAGE_DOS_HEADER *hdr = (IMAGE_DOS_HEADER*)buffer;
        if (hdr->e_magic == IMAGE_DOS_SIGNATURE) {
            if (hdr->e_lfarlc < 0x40) {
                /* Ought to invoke this 16 bit exe by a stub, (cmd /c?) */
                interpreter = "";
            }
            else {
                interpreter = "";
            }
        }
    }
    
  3. I’ve attempted this, and in the first example where the batch file calls “dir” or “set” everything works fine. When I look at it in the browser it runs and I get the directory listing, or environment.

    When I go to substitute my own executable in…..nothing. Even a “hello world” doesn’t seem to get executed. Tried full path specification to my exe’s, still nothing.

    Any ideas?

    1. 1. Run the batch file from the command-line to test it, and not via Apache and PHP… This way you can see the output / and can make sure the problem is not there.

      2. Make sure to change the working-directory (e.g., command-line prompt) to the binary’s path…

      C:
      cd \path\to\binary

      3. Make sure this binary does not require elevated or Admin privileges. If it requires anything other than what the Apache Service or process provides (it runs under the LocalSystem account), you’ll need to change the “Log On” account for the Apache Service (run ‘services.msc’ to manage it). If you create a new account for it, make sure to set a password for it (password-less accounts have issues).

  4. Running Apache 2.2.25. Added directive
    AddHandler cgi-script .cgi
    and
    Options +ExecCGI
    main.cpp:
    cout<<"Content-type: text/html\r\n\r\n";
    cout<<"I'm uhh… Outputting.";
    Compiled executable with cgi extension. And as it seems I don't have to wrap it in a batch or do anything at all. Apache simply executes program, passes on its output.

Leave a Reply to admin Cancel reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>