Friday, 10 October 2014

Execute PowerShell from a ASP.NET Web Application

Execute PowerShell from a ASP.NET Web Application

I had an interesting request come in to automate existing server-side PowerShell processes that have been initiated by server admins thus far locally on the server, using a web interface instead.  Turns out, the admins want to offload the work back to the business users so it can be more on demand. Essentially, they want the non-techies to reuse what the techies have been using server side without having to involve 1) the techies, 2) the overhead of logging in to the server, or worse 3) the business users perform PowerShell commands server side (..and therefore running the risk of executing something incorrectly and/or making a security admin’s head explode).
No problem.  Here’s a quick demo on how it can be done.

Our plan of attack will be to build a web form project using Visual Studio 2012 and .NET Framework 4.5.  It will execute server side and scripts of PowerShell commands using the
framework’s PowerShell class from the System.Management.Automation name space or dll you can find( C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35).  The response I’ll handle as a string, and paint it to a Text box

Here we go…
First, create the Empty Web Application called PowerShellExecution.

PowerShell-BlankWebApplication

Next, add a New Item… of Default web form named “Default.aspx” to the solution to align with the demo content below. Now, copy and paste over the default Default.aspx code that’s generated with the below for the quick UI test:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="PowerShellExecution.Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title></title>
</head>
<body>
<form id="form1" runat="server">
    <div>
        <table>
            <tr><td>&nbsp;</td><td><h1 align="left">PowerShell Command Harness</h1></td></tr>
            <tr><td>&nbsp;</td><td>&nbsp;</td></tr>
            <tr><td>&nbsp;</td><td>PowerShell Command</td></tr>
            <tr><td>
                <br />
                </td><td>
                <asp:TextBox ID="Input" runat="server" TextMode="MultiLine" Width="433px" Height="73px" ></asp:TextBox>
            </td></tr>
            <tr><td>
                &nbsp;</td><td>
                <asp:Button ID="ExecuteCode" runat="server" Text="Execute" Width="200" onclick="ExecuteCode_Click" />
            </td></tr>
                <tr><td>&nbsp;</td><td><h3>Result</h3></td></tr>
                <tr><td>
                    &nbsp;</td><td>
                    <asp:TextBox ID="ResultBox" TextMode="MultiLine" Width="700" Height="200" runat="server"></asp:TextBox>
                </td></tr>
        </table>
    </div>
</form>
</body>
</html>
Next, to get the System.Management.Automation name space included in our solution we need to install the System.Mangement.Automation package.  To do that, under the Tools menu select Library Package Manager >> Package Manager Console

PowerShell-VisualStudioLibraryPackage

…within the
Package Manager Console execute “Install-Package System.Management.Automation”

PowerShell-NuGet

After a successful execution of that package install, you’ll want to add the reference to your Default.aspx.cs file:

using System.Management.Automation;
I’m going to use a button’s onclick action to invoke the method that will perform the magic of submitting and reading the response from my PowerShell.  The remaining item to address, that may vary on your circumstance, is how to handle the response from the PowerShell.

Remember, everything in PowerShell is returned as an object.  We need to display our object back on the page as a string for this demo.  You’ll need to be mindful of that when executing, as well as how or what you’re wanting your results displayed on the page.

To ensure that happens for me, I am going manage the output of my PowerShell execution by building a string out of the objects returned.  I’ll paint those string casted results in a TextBox control called “ResultBox” using the StringBuilder class within in the System.Text name space.  Therefore, I need to reference that name space as well in my code behind.

using System.Text;
Below is my complete Default.aspx.cs code for the purposes of this demo.  It includes the above references  I discussed, the default page load, and especially the onclick method to support the execution details submitted to the Input control using the PowerShell class.
I sprinkled in comments for context:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Management.Automation;
using System.Text;
namespace PowerShellExecution
{
    public partial class Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        { 
       } 
        protected void ExecuteCode_Click(object sender, EventArgs e)
        {
            // Clean the Result TextBox
            ResultBox.Text = string.Empty;
            // Initialize PowerShell engine
            var shell = PowerShell.Create();
            // Add the script to the PowerShell object
            shell.Commands.AddScript(Input.Text);
            // Execute the script
            var results = shell.Invoke();
            // display results, with BaseObject converted to string
            // Note : use |out-string for console-like output
            if (results.Count > 0)
            {
                // We use a string builder ton create our result text
                var builder = new StringBuilder();
                foreach (var psObject in results)
                {
                    // Convert the Base Object to a string and append it to the string builder.
                    // Add \r\n for line breaks
                    builder.Append(psObject.BaseObject.ToString() + "\r\n");
                }
                // Encode the string in HTML (prevent security issue with 'dangerous' caracters like < >
                ResultBox.Text = Server.HtmlEncode(builder.ToString());
            }
        }
    }
}
Copy and paste that in and you’re ready to test…
Once rendered in your browser, type into the “PowerShell Command” window Get-Service | Out-String (or your favorite cmdlet for a test).

UITest

Remember, port everything to the Out-String cmdlet so it can be handled properly by the StringBuilder class.  Otherwise, you’ll get stringbuilder output of the data type details of the objects returned by your cmdlet – one long get-member call, most likely not what you’re after.
If you’re wondering if you can run scripts that are on the local file system of the server, the answer is yes.  Simply pass the script location an parameters in string form to the AddScript() method within Default.aspx.cs using double slashes format.

//shell.Commands.AddScript(Input.Text);
shell.Commands.AddScript("C:\\Scripts\\PowerShell\\PowerShellScript.ps1");
Contents of this test PowerShellScript.ps1 (same as what was used in the Input control):

Get-Service | Out-String
(I removed the Input box control and test again the solution below)


UITest

Note: To run this on a server, the ApplicationPool hosting your application will have to have administrator rights.
There you have it, a quick server side PowerShell web method.

7 comments:

  1. Hi Mohan,

    Very nice post, thanks for sharing it. Could you please tell me how I can contact you if i have a question. I dont want to post it in comment section.

    Thanks

    Saty

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
  3. Hi Mohan,

    it is very usefull tutorial. However, what I would like to do is when I execute a command such as get-service , I can bring these objects into objects of service class for example , to be able to reuse them. is it possible?

    ReplyDelete
  4. Thanks, it was really interesting and it works as baseline to include some other elements.
    BTW, I test it and the results pane as you mentioned shows everything as object and thus we need to add an " | Out-String". So I use the following to get that output without specified every time in the text box.

    shell.Commands.AddScript(Input.Text + " | out-string");

    Yes I know this is a baseline and I'm sure you have tested other ways or components.

    Thanks you!

    ReplyDelete
  5. I changed the script to execute to shell.Commands.AddScript("D:\\myfolder\\myscript.ps1 | out-string");
    It does work as expected but I do not get any output in the results?
    I do have these lines in my script
    Write-Host "Current State" $appPoolName (Get-WebAppPoolState $appPoolName).Value -ForegroundColor Green
    and
    Write-Host "----------Deployment Completed View Deployment log $Deploymentlog" -ForegroundColor Yellow

    Is it possible becasue I cam creating logs for an email that gets sent out when finished that it is overriding output to the results box?

    ReplyDelete
  6. Nice article, worked very well. Do you have more such articles which can help understand using powershell in advanced with asp.net at frontend

    ReplyDelete