PowerShellASP with SharePoint: Scripts in Content Database

In my last post, I mentioned that PowerShellASP doesn’t work for files stored in a SharePoint content database. It wasn’t hard to track down why this is the case, so my next step was to attempt a fix.

First, a recap of the relevant code. In PSHandler.ProcessRequest(HttpContext), the entry point for the handler, we have:

    string filename = A_0.Request.MapPath(A_0.Request.FilePath);
    ...
    this.a(filename);

MapPath doesn’t know to do anything special with content files, so it returns a useless path which is then passed to PSHandler.a(String A_0) and used here:

    using (StreamReader reader = new StreamReader(A_0)) {
      ...

Rather than modify the existing handler (much), I propose creating a SharePoint-specific handler that inherits from PSHandler. Before we can do that, we need to refactor the original slightly to facilitate the extension.

The biggest change is extracting the contents of the using(StreamReader) block into a new protected method that accepts a StreamReader:

    using (StreamReader reader = new StreamReader(A_0)) {
      this.a(reader);
    }

We should also change the private HttpContext field to protected, which I will call _a for our example. Now we have everything we need to extend PSHandler:

  public class SPPSHandler : PSHandler, IHttpHandler {
    public new void ProcessRequest(HttpContext context) {
      base._a = context;

      HttpRequest request = context.Request;
      string filePath = request.FilePath;
      string mappedPath = request.MapPath(filePath);
      if (File.Exists(mappedPath))
        using (StreamReader sr = new StreamReader(mappedPath)) {
          base.a(sr);
          return;
        }

      SPContext currentContext = SPContext.Current;
      if (currentContext != null) {
        SPWeb web = currentContext.Web;
        SPFile file = null;

        if (web != null && (file = web.GetFile(filePath)) != null && file.Exists)
          using (StreamReader sr = new StreamReader(file.OpenBinaryStream())) {
            base.a(sr);
            return;
          }
      }

      // Quoth the server...
      context.Response.StatusCode = 404;
    }
  }

A theoretical subclass is great and all, but does it work? Well we can’t change the original assembly, and I don’t feel like messing with reflection, so let’s build a mock PSHandler instead:

  public class PSHandler : IHttpHandler {
    protected HttpContext _a;

    public bool IsReusable {
      get { return true; }
    }

    public void ProcessRequest(HttpContext A_0) {
      this._a = A_0;
      A_0.Response.Write("PSHandler"); // Shouldn't see this
    }

    protected void a(StreamReader reader) {
      TextWriter o = _a.Response.Output;
      o.Write("<html><body><pre>");
      o.Write(SPEncode.HtmlEncodePreserveSpaces(reader.ReadToEnd().ToUpper()));
      o.Write("</pre></body></html>");
    }
  }

After adjusting the feature receiver to reference our new handler, we see that it does indeed behave as expected:
PowerShellASP in SharePoint - Extended Handler
I haven’t tested extensively, but it’s a start. I doubt the PoShASP team would bother with these changes just for me, so if you’re at all interested leave a comment or at least contact them.