Create a Powershell Web Application

Create a Powershell Web Application

How to create a Powershell Web Application? This article will give you some guidelines on how to deploy a very simple web application that leverages Powershell and if you follow it completely, you’ll be able to have a fully functional web application.

This is a very important instrument, especially when our goal is to automate as much as possible the environment and repetitive tasks or offload a 2nd or 3rd level task to a 1st level representative. You could build a web application to check permissions for a specific shared folder (I’ve done that successfully) or you could deploy an app that would check the current top 10 RAM processes being used on a remote server (super handy to hand off to a 1st level support team who many have no access to the server) and so on.

Please see the final notes at the end of this article.

Prerequisites

You will need Visual Studio to follow this.

Creating a new Project

  • Click on File > New > Project
    • visual-studio_create-new-project
  • Select Installed > Templates > Visual C# > Web and click ASP .NET Web Application (.NET Framework). Give it a name (ITDropletsPowershell in the example).
    • visual-studio_create-new-asp.net-web-application
  • Since we want to start with a clean solution, let’s select “Empty” and click OK.
    • visual-studio_create-new-asp.net-web-application-EMPTY

Adding the needed packages to the Solution

  • Click on Tools > NuGet Package Manager > Package Manager Console
    • visual-studio-Package-Manager-Console
  • Let’s install these two packages, by running the below two lines of code:
    • Install-Package System.Management.Automation
      Install-Package Microsoft.PowerShell.5.ReferenceAssemblies

      This is how it’s going to look:

      visual-studio-install-packages-output

Creating the Default.aspx and the Code Behind

  • Add a New Item
    • visual-studio-add-new-item
  • Select Web Form and name it Default.aspx
    • visual-studio-new-web-form
  • Here it is:
    • visual-studio-default.aspx-brand-new

Now we have the basic to start building a web interface that will need to get our input and tweak the code behind file to let it do what we need it to do.

Web Application Example

Let’s try to build a real life example. Let’s say we want a simple form where we input a Directory (i.e. C:\Users\Username\Desktop) and the script must return us the child items.

So what we want the script to do is simply get the user to input a variable (the directory) and then call the following:

Get-ChildItem -Path "directory chosen here"

Let’s first edit the Default.aspx to look like this:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="ITDropletsPowershell.Default" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Get-ChildItem</title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <div><h1 align="center">Get-ChildItem (itdroplets.com)</h1></div>
            <p>Please type the directory for which you want to get the child items:
                <asp:TextBox ID="Input" runat="server" Width="100%" Height="20px" ></asp:TextBox>
            </p>
            <asp:Button ID="ExecuteInput" runat="server" Text="Execute" Width="200" onclick="ExecuteInputClick" />

            <p>Result
            <asp:TextBox ID="Result" TextMode="MultiLine" Width="100%" Height="450px" runat="server"></asp:TextBox>
            </p>

        </div>
    </form>
</body>
</html>

So what we changed from the original Default.aspx is:

  • Added a title “Get-ChildItem”.
  • Added an h1 Title on the page and centered “Get-ChildItem (itdroplets.com)”.
  • Added a text “Please type the directory for which you want to get the child items:” followed with a TextBox with ID Input.
  • A button with text “Run” and ID RunInput.
  • Added a text “Result” with a MultiLine TextBox with ID Result.

Time to edit the code behind. Expand Default.aspx and select Default.aspx.cs. Here’s how it’s going to look:

visual-studio-edit-code-behind

This is how my code behind looks like now, I tried adding comments so that there’s no need to repeat it below.

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.Management.Automation.Runspaces;
using System.Text;

namespace ITDropletsPowershell
{
    public partial class Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {

        }

        //Note: Part of this code was taken a long time ago from http://jeffmurr.com/blog/?p=142.
        protected void ExecuteInputClick(object sender, EventArgs e)
        {
            // First of all, let's clean the TextBox from any previous output
            Result.Text = string.Empty;

            // Create the InitialSessionState Object
            InitialSessionState iss = InitialSessionState.CreateDefault2();

            //In our specific case we don't need to import any module, but I'm adding these two lines below
            //to show where we would import a Module from Path.
            //iss.ImportPSModulesFromPath("C:\\inetpub\\LocalScriptsAndModules\\MyModuleFolder1");
            //iss.ImportPSModulesFromPath("C:\\inetpub\\LocalScriptsAndModules\\MyOtherModule2");


            // Initialize PowerShell Engine
            var shell = PowerShell.Create(iss);

            // Add the command to the Powershell Object, then add the parameter from the text box with ID Input
            //The first one is the command we want to run with the input, so Get-ChildItem is all we need
            shell.Commands.AddCommand("Get-ChildItem");
            //Now we're adding the variable (so the directory) chosen by the user of the web application
            //Note that "Path" below comes from Get-ChildItem -Directory and Input.Text it's what the user typed
            shell.Commands.AddParameter("Path", Input.Text);

            // Execute the script 
            try
            {
                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 < >
                    Result.Text = Server.HtmlEncode(builder.ToString());
                }
            }
            catch (ActionPreferenceStopException Error) { Result.Text = Error.Message; }
            catch (RuntimeException Error) { Result.Text = Error.Message; };
        }
    }
}

Let’s test this!

  • Right click on Default.aspx and select View in Browser
    • visual-studio-view-in-browser

 

Here’s our web application.

visual-studio-web-application-get-child-item

And here’s an example of the result for the folder C:\.

visual-studio-web-application-get-child-item_Test

 

Running an external Function that is stored in a Module

If you look back at the code behind above, I’ve added these lines (I removed the comments from the below):

//In our specific case we don't need to import any module, but I'm adding these two lines below
//to show where we would import a Module from Path.
iss.ImportPSModulesFromPath("C:\\inetpub\\LocalScriptsAndModules\\MyModuleFolder1");
iss.ImportPSModulesFromPath("C:\\inetpub\\LocalScriptsAndModules\\MyOtherModule2");

Now those two lines will import two modules from a path under MyModuleFolder1 and MyOtherModule2. If you had a function into one of them called Sample-Function-ITDroplets, and said function would accept a parameter called “Username”, you could just called it as if you were calling Get-ChildItem!

Something like this:

shell.Commands.AddCommand("Sample-Function-ITDroplets");
shell.Commands.AddParameter("Username", Input.Text);

You can understand that you could then go and add many other input fields in the Default.aspx page and every “variable” will be available here.

One last example

Imagine that now you want to add another parameter to the Get-ChildItem command, for instance you want to also add -Recurse. To do that, just add the following line, right under “shell.Commands.AddParameter(“Path”, Input.Text);” in the Default.aspx.cs file.

shell.Commands.AddParameter("Recurse");

Once you run the web application again, you’ll run it in Recurse. If you’re now testing this, pay attention! If you specify a folder with a ton of files, you’ll end up waiting for quite some time!

Publishing the entire solution to an existing IIS Web Server

This is so simple and so powerful and so handy! So we’re done and we want to deploy our web application to a server we own.

  • Right click on ITDropletsPowershell and click Publish
    • visual-studio-publish
  • Select IIS, FTP, etc and then click Publish
    • visual-studio-publish-IIS-FTP-etc
  • Now add all of the info and credentials for the web-server you’re deploying this to and you’re done! Just follow the wizard through.
    • visual-studio-publish-custom-profile

Final Notes

It’s important to understand security. I will have a separated article ready soon to talk about how to secure this application we just deployed to make sure only a certain group or user can access it.

Another important factor is the ability for the account running the web application (IIS) to have access to whatever the script is doing. So the best is to run the web application with a service account and provide access to said service account. For instance, in our case with the Get-ChildItem web application, we could target a share \\itdroplets.com\myshare, but if the service account running IIS has no access to it, you’ll just get an access denied error.

I also suggest to work as much as possible on error handling, this is simple when calling external scripts as everything will be in a powershell format.

IT Droplets

IT Droplets