PowerShellASP with SharePoint

PowerShellASP was announced earlier this week, and naturally my first thought was “Does it work with SharePoint?” It turns out that it does, but only for paths mapped to the file system (_layouts, _admin, etc). I’m hoping the authors will consider making a SharePoint extension of the handler to support files stored in the content database as well (more on that later). Just think…writing PowerShell in SharePoint Designer! What could be better?!

Solutionizing PoShASP

Of course you could just follow the provided installation instructions by hand, but what if we wanted to use a WSP?

Step 1: Install PowerShell 1.0

PowerShell is included on Windows Server 2008; for other flavors of Windows, download here.

Step 2: Web.Config Changes

PoShASP requires adding an HttpHandler to web.config. The preferred way to do this is through a WebApplication-scoped feature receiver. The code would look something like this:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;

namespace Solutionizing.PowerShellASP {
  class WebApplicationFeatureReceiver : SPFeatureReceiver {
    private const string MODS_OWNER = "PowerShellASP";
    private const string PS_VERB = "*";
    private const string PS_PATH = "*.ps1x";
    private const string PS_TYPE = "PowerShellToys.PowerShellASP.PSHandler, PowerShellToys.PowerShellASP";
    private const string MOD_PATH  = "configuration/system.web/httpHandlers";
    private const string MOD_NAME  = @"add[@path=""{0}""]";
    private const string MOD_VALUE = @"<add verb=""{0}"" path=""{1}"" type=""{2}""/>";

    private SPWebConfigModification AddHttpHandler(string verb, string path, string type) {
      SPWebConfigModification mod = new SPWebConfigModification(string.Format(MOD_NAME, path), MOD_PATH);
      mod.Value = String.Format(MOD_VALUE, verb, path, type);
      mod.Owner = MODS_OWNER;
      mod.Sequence = 0;
      mod.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
      return mod;
    }

    public override void FeatureActivated(SPFeatureReceiverProperties properties) {
      SPWebApplication webApp = properties.Feature.Parent as SPWebApplication;
      webApp.WebConfigModifications.Add(AddHttpHandler(PS_VERB, PS_PATH, PS_TYPE));
      webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
    }

    public override void FeatureDeactivating(SPFeatureReceiverProperties properties) {
      SPWebApplication webApp = properties.Feature.Parent as SPWebApplication;
      Collection<SPWebConfigModification> mods = webApp.WebConfigModifications;

      int startCount = mods.Count;
      for (int c = startCount - 1; c >= 0; c--) {
        SPWebConfigModification mod = mods[c];
        if (mod.Owner == MODS_OWNER)
          mods.Remove(mod);
      }

      if (startCount > mods.Count) {
        webApp.Update();
        webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
      }
    }

    public override void FeatureInstalled(SPFeatureReceiverProperties properties) { }
    public override void FeatureUninstalling(SPFeatureReceiverProperties properties) { }
  }
}

And we need a feature for the receiver. (Download image here.)
PowerShell Logo

<Feature Id="316F5804-C11B-4E4B-9CD3-E6BD6801091A"
         Title="PowerShellASP"
         Description="Installs PowerShellASP - http://www.powershelltoys.com/"
         Scope="WebApplication"
         Version="1.0.0.0"
         ImageUrl="PowerShellASP/PoSh_60x45.jpg"
         ImageUrlAltText="PowerShell"
         ReceiverAssembly="Solutionizing.PowerShellASP, ..."
         ReceiverClass="Solutionizing.PowerShellASP.WebApplicationFeatureReceiver"
         xmlns="http://schemas.microsoft.com/sharepoint/" />

Step 3: Test Script

Before we finish the package, let’s add a sample script in LAYOUTS – test.ps1x:

<html>
<body>
<% $s = new-object Microsoft.SharePoint.SPSite($Request.Url) %>
<% $raw = $Request.RawUrl %>
<% $w = $s.OpenWeb($raw.Substring(0, $raw.IndexOf($Request.Path))) %>
<h1>Hidden Lists in <a href="<%=$w.Url %>"><%=$w.Title %></a></h1>
<table width="100%">
<tr><th>List Title</th><th>Items<tr></th></tr>
<% $w.Lists | ?{$_.Hidden -eq $true} |
   sort @{expression="ItemCount";Ascending=$true},@{expression="Title";Descending=$true} | %{ %>
<tr>
  <td><a href="<%= $w.Url %><%= $_.DefaultViewUrl %>"><%= $_.Title %></a></td>
  <td><%= $_.ItemCount %></td>
</tr>
<tr><td colspan="2"><p style="overflow: auto; height: 8em">
<%= [Microsoft.SharePoint.Utilities.SPEncode]::HtmlEncode($_.PropertiesXml) %>
</p></td></tr>
<% } %>
</table>
<pre>
<% $w %>
<% $s %>
<% $Request %>
</pre>
</body>
</html>

This contrived example fetches the current SPSite and SPWeb and then outputs a table of the hidden lists in the current site with a few properties. It also dumps the default PowerShell view of the current SPWeb, SPSite and HttpRequest objects. It’s not exactly pretty, but it sufficiently demonstrates a few key concepts of PoShASP.

Step 4: Deploy DLL

The final piece is the assembly, which needs to go in the web application’s bin directory. Our solution brings everything together:

<Solution SolutionId="5C594B30-9AE5-4910-8DA2-1D7BA622FACC"
          ResetWebServer="True"
          xmlns="http://schemas.microsoft.com/sharepoint/">
  <FeatureManifests>
    <FeatureManifest Location="PowerShellASP\feature.xml" />
  </FeatureManifests>
  <TemplateFiles>
    <TemplateFile Location="IMAGES\PowerShellASP\PoSh_60x45.jpg" />
    <TemplateFile Location="LAYOUTS\PowerShellASP\test.ps1x" />
  </TemplateFiles>
  <Assemblies>
    <Assembly Location="Solutionizing.PowerShellASP.dll"
              DeploymentTarget="GlobalAssemblyCache" />
    <Assembly Location="PowerShellToys.PowerShellASP.dll"
              DeploymentTarget="WebApplication" />
  </Assemblies>
</Solution>

Step 5: Deploy

Now that our solution is complete, we can install and deploy the WSP. Note that the package contains a resource—the PoShASP DLL—scoped for a web application, so deploy accordingly. Once the solution is deployed, the feature needs to be activated through Central Admin or stsadm (or PowerShell ;). If all goes according to plan, the application’s bin directory will contain PowerShellToys.PowerShellASP.dll and web.config will have an HttpHandler for .ps1x files. Now open your browser to http://yourapp/_layouts/PowerShellASP/test.ps1x and see what we have. You can also try http://yourapp/SubSite/_layouts/PowerShellASP/test.ps1x to see the change in site context. (Screenshot was taken before I rolled test.ps1x into my test solution.)

From Content Database

Earlier I mentioned that the handler doesn’t work for scripts stored in the content database. The easiest way to test this is to upload a .ps1x file to a document library. Attempting to open the file yields a lovely exception. So where is this exceptional path coming from? Well the stack trace gives us a good place to start:

   System.IO.StreamReader..ctor(String path) +112
   PowerShellToys.PowerShellASP.PSHandler.a(String A_0) +75

Cue Reflector. In PSHandler.a(String A_0) we find the following:

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

And A_0 comes from the implementation of IHttpHandler.ProcessRequest(HttpContext):

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

Since Docs/Documents/Get-Process.ps1x doesn’t map to anything special like _layouts, IIS just maps it to the local root of the site. We know this won’t work, but how can we let the handler in on our little secret? In my next post I’ll go over some code that could get us to that point.

With or without the content database limitation, how would you use PowerShellASP with SharePoint?

2 Responses to “PowerShellASP with SharePoint”

  1. PowerShellASP with SharePoint: Scripts in Content Database « Solutionizing .NET Says:

    […] with SharePoint: Scripts in Content Database July 28, 2008 — Keith Dahlby In my last post, I mentioned that PowerShellASP doesn’t work for files stored in a SharePoint content […]

  2. PowerShellASP Applied: JSON Factory « Solutionizing .NET Says:

    […] Applied: JSON Factory August 6, 2008 — Keith Dahlby Now that we can use PowerShellASP with SharePoint, what can we do with it? Well without master page or web part support, we can’t easily […]


Comments are closed.