Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using PowerShell as a CGI binary in IIS Express

I'm trying to use PowerShell as a CGI binary from IIS Express. I got it to work, but with a silly work-around that I don't particularly like. I'd like to understand why that work-around is needed.

Here's a simple cgi.ps1 file that I'm using to try this out:

"HTTP/1.1 200 OK`r`nContent-Type: text/plain`r`n`r`n"

Get-ChildItem env:

Along with this web.config file:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <handlers>
            <add name="PowerShell CGI" path="*.cgi" verb="GET,HEAD,POST" modules="CgiModule" scriptProcessor="&quot;%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe&quot; -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -File C:\Users\Nikhil\Documents\GitHub\PowerShellCGI\App\cgi.ps1"/>
        </handlers>
    </system.webServer>
</configuration>

Then, after starting IIS Express in this directory, and navigating to http://localhost:8080/hello.cgi, I get an error:

HTTP Error 502.2 - Bad Gateway
The specified CGI application misbehaved by not returning a complete set of HTTP headers. The headers it did return are "".

I had a look with Process Monitor, and powershell.exe does, in fact, launch with the correct arguments. The IIS Express trace log files reveal an exit code of 0, but nothing in the output stream. The weird thing is, when launched from IIS Express, powershell.exe never even reads my ps1 file; I can see the file being read if I manually use the same command line while capturing events with Process Monitor.

Next, I tried to see if a plain cmd.exe batch script could be used as a CGI script. That works just fine; here's the simple cgi.cmd that I used:

@echo off
echo HTTP/1.1 200 OK
echo Content-Type: text/plain
echo.
echo Hello, World!

Hmm... What if I start powershell.exe from within the cgi.cmd script? That didn't work either -- all of the output from the cmd script gets returned to IIS Express; the PowerShell output is still lost somewhere.

However, what's interesting is that when I did this (call PowerShell from the cmd script), there is a PowerShell window that flashes on every page refresh. So it looks like PowerShell does execute my script, just in a new window, so the stdin/stdout is not connected to the CGI in IIS.

What if I use start /b powershell.exe ... in the cmd script? That didn't make a difference.

What if I set system.webServer/cgi/createCGIWithNewConsole = true in IIS Express configuration? That didn't work either, but it did have the effect of having a PowerShell console pop up with every request.

The Workaround

I figured the issue is that powershell.exe wants a console to itself, and when it starts a new console, the input and output redirection is no longer hooked up to the new console.

This is how I finally got it to work: I wrote a thin wrapper that launches powershell.exe, and redirects stdio, stdout, and stderr. Here's the complete Program.cs:

using System;
using System.Diagnostics;

namespace PowerShell_CGI
{
    class Program
    {
        static void Main(string[] args)
        {
            var process = new Process();
            process.StartInfo = new ProcessStartInfo
            {
                FileName = "powershell.exe",
                Arguments = String.Join(" ", args),
                UseShellExecute = false,
                RedirectStandardInput = true,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                CreateNoWindow = true
            };

            process.OutputDataReceived += (sender, e) =>
            {
                Console.WriteLine(e.Data);
            };

            process.ErrorDataReceived += (sender, e) =>
            {
                Console.Error.WriteLine(e.Data);
            };

            process.Start();
            process.BeginOutputReadLine();
            process.BeginErrorReadLine();

            process.StandardInput.Write(Console.In.ReadToEnd());

            process.WaitForExit();
        }
    }
}

Now, with this line in my web.config, everything works just fine:

<add name="PowerShell-CGI" path="*" verb="GET,HEAD,POST" modules="CgiModule" scriptProcessor="C:\Users\Nikhil\Documents\GitHub\PowerShellCGI\App\PowerShell-CGI.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -File C:\Users\Nikhil\Documents\GitHub\PowerShellCGI\App\cgi.ps1" />

My question is: why is this silly workaround needed? My program is simply executing powershell.exe with the same arguments, the same stdio, the same stdout, and the same stderr that it gets. I suspect that it might have to do with the CreateNoWindow flag; but isn't start /b doing the same thing? How come my little wrapper program can redirect input and output from powershell.exe just fine, but the CGI module in IIS Express cannot?

Corollary question: how can I use powershell.exe as a CGI binary, without any hacks to make it work? I'd like to see it work as simply as cmd.exe does.

like image 659
Nikhil Dabas Avatar asked Sep 30 '22 18:09

Nikhil Dabas


1 Answers

If somebody is intrested, I had the same problem and was able to solve it with the following config on IIS 8.5.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <handlers>
            <add name="PowerShell CGI" path="*.ps1" verb="GET,POST,HEAD" modules="CgiModule" scriptProcessor="C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -NoLogo -NoProfile -NonInteractive -File &quot;%s&quot; %s" resourceType="File" requireAccess="Script" />
        </handlers>
    </system.webServer>
</configuration>
like image 183
stoop497 Avatar answered Oct 16 '22 17:10

stoop497