lundi 26 mai 2008

PowerShell and ASP.NET Part 3

This is the third and last part of this little Tutorial on PowerShell and ASP.NET.

We will discuss about managing script from a webform and how to deploy this website on a IIS server.

Manage script

We saw previously how to execute PowerShell code from a Textbox. We will now learn how to manage on-the-fly changes on a prebuild PowerShell script.

This is a good method when you want, for example, create à provisioning or audit web portal. User won't write directly PowerShell code, but will fill a form that will then call a script with arguments.

To illustrate this, here is another example with a WMI request engine that takes a machine name as parameter. I slightly modified our former example : I added a Textbox to put a machine Name :

powerASP4

Let's see what changed in our code. Ny the way, not much things : We will now read a embbeded Script and modify is content before execution.

Here's the script added to the project :

$strComputer = "*ComputerName*"
get-wmiobject -class "Win32_LogicalDisk" -namespace "root\cimv2" -computername $strComputer where{$_.DriveType –eq 3}out-string

A classic PowerShell WMI request. As you can see, I put the Machine name as an argument on the first line. I gives an arbitrary name "*ComputerName*" that will let us replace it with the content of the Textbox.

Below is the modified code :



        // On the fly script modification before execution


        StreamReader objReader = new StreamReader(Server.MapPath("~/scripts/WmiAsset.ps1"));
        string strContent = objReader.ReadToEnd();
        strContent = strContent.Replace("*ComputerName*", TxtComputerName.Text);
        this.executePowerShellCode(strContent);


First thing, we will read the PowerShell script file. I put it in a subfolder called "Scripts". We use the SYSTEM.IO library to do this.


StreamReader objReader = new StreamReader(Server.MapPath("~/scripts/WmiAsset.ps1"));

Then we put this script in a string variable and close the StreamReader object:

string strContent = objReader.ReadToEnd();

Now, we call the replace method on this string. Our goal is to replace "*computername*" with the content of the Textbox.


strContent = strContent.Replace("*ComputerName*", TxtComputerName.Text);

The important thing here is to have unique name in the script when you want to do this change (here *computername* is used only once). This is easy to achieve, because you're the Writer of the POwerShell Code ;)

That's the deal, now we execute the PowerShell code as before :


this.executePowerShellCode(strContent);

Nothing really tricky here. If you need to modifiy your request engine, you only need to edit the .PS1 file, no C#/ASP.NET is required (this can be delegate to every administrator that got skills on PowerShell)

You can of course put several parameters in your web form. The limit is your imagination (and business needs, by the way). There's many little things to implement in order to have a "Profesionnal" powershell website (Error handler, better output support...), but you now have the bases to achieve this. Hope you got a better view of PowerShell/ASP.NET interaction.

To finish this tutorial, let's see how to publish the website on IIS.

WebSite deployment

Our main goal is to use a Service Account to execute tasks (here, our scripts). This simplify the rights management process and delegation, cause it's not necessary to gives administrator rights to every user of the website.

We only need to know the identity of the end user and filter website access.

The deployment strategy on IIS is :

First, Filter the website access. Just put appropriate rights on the website folder (put a user group on the folder with read access only).

On your IIS server, desactivate the anonymous access, and select "Integrated Windows Authentication"

powerASP5

Then create an application pool to delegate the server side code to a service Account. To do this, create a new application pool open it's properties and define "Identity" as shown below :

powerASP6

Finally, open your website properties and select the good application pool for it:

PowerASP7

Modify your web.config to use impersonation. Go to the "Authentication mode" line of this file and add the following code :


        <authentication mode="Windows"/>
    <identity impersonate="true"/>

Here you go : your website now use impersonation and access is filtered by NTFS rights.

I hope this little tutorial gives you ideas and inspiration. I well come back soon with another concept with ASP.NET / PowerShell / AJAX and Silverlight !

Here is the source code : PowerShellASP2

PowerShell and ASP.NET Part 2

Reminder : Source code of this sample is available here.

You need the following tools to use it :

WebDevelopper Express Edition

Ajax controls toolkit and library

We will now study this web site topology and codes.

WEB.CONFIG

We start with the easiest thing : our web.config

there's very few modifications : our first objective is to add assemblies references in our project, in order to create runspace and pipeline in the code-behind.

<assemblies> 
    <add assembly="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/> 
    <add assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> 
    <add assembly="System.Data.DataSetExtensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/> 
    <add assembly="System.Xml.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/> 
    <add assembly="System.Management, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/> 
    <add assembly="System.Management.Automation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> 
</assemblies>

That's a cool start, now we will study the user interface !

DEFAULT.ASPX

You can do your design with simple drag and drop of controls (Textbox, Combox, etc...). This is pretty simple. There is a good ASP.NET tutorial here if you want to know more about it.
Let see what are the particularities of this example

UpdatePanel

In the first part of this tutorial, I told you that I used AJAX to :
· Manage a Timer
· Manage refresh of the Output Textbox.
In order to use AJAX extensions in our website project, you need to put a ScriptManager in the .aspx document (this will let you use other AJAX controls)

I put it at the beginning of the form :

<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>

Next, here is the core interface (I bypass the top informations, not really needed):

<asp:TextBox ID="TxtPowerShellScript" runat="server" TextMode="MultiLine" Width="597px"
            Height="194px" BackColor="#012456" ForeColor="#EEEDF0" Wrap="False"></asp:TextBox>
        <br />
        <asp:UpdatePanel ID="UpdatePanel1" runat="server">
            <ContentTemplate>
                <asp:Button ID="BtnExecuteScript" runat="server" Text="Launch Script" OnClick="BtnExecuteScript_Click" />
                <br />
                Output :
                <br />
                <asp:TextBox ID="TxtResult" runat="server" Height="199px" TextMode="MultiLine" Width="600px"
                    Wrap="False" BackColor="#012456" ForeColor="#EEEDF0"></asp:TextBox>
                <br />
                <asp:Timer ID="Timer1" runat="server" Enabled="False" Interval="100" OnTick="Timer1_Tick" />
            </ContentTemplate>
        </asp:UpdatePanel>
First element is our Input Textbox to write scripts :

<asp:TextBox ID="TxtPowerShellScript" runat="server" TextMode="MultiLine" Width="597px"
          Height="194px" BackColor="#012456" ForeColor="#EEEDF0" Wrap="False"></asp:TextBox>
Nothing special here, I just put some parameters like multiline suspport and colors to match PowerShell console.
Then there is the Execute button and the output Textbox. They are embbeded in a UpdatePanel Control. This control let us refresh only this elements without refreshing the whole page when we'll update content. You can see our Timer too :

<
asp:Timer ID="Timer1" runat="server" Enabled="False" Interval="100" OnTick="Timer1_Tick" />



The Interval parameter (Interval="100") define the refresh rate (milliseconds), the OnTick (OnTick="Timer1_Tick") specify the function called in the code-behind when the Timer refresh. The Timer is desactivated by default (Enabled="False") in order to avoid useless charge on the serve. It's activated only when the Button is clicked.
That's all for the user interface. Except the UpdatePanel (which is only another control from the designer point of view), we are really on a classic webForm design. We will no study the Code-Behind part of the website.

DEFAULT.ASPX.CS

This is the C# Code that will manage our PowerShell execution and the update of the output Textbox.

Let's go from the beginning : At the very top of the code, I just add 3 more instances (all the others are generated by default when you create an asp.NET project website) :


using System.Management.Automation;

using System.Management.Automation.Runspaces;

using System.IO;


System.Management.Automation and .Runspaces are required to create the PowerShell runspace and the Pipeline. System.IO will be use to convert the Input Textbox content as you'll see later.

We'll then first create Runspace and Pipeline object at the beginning of the main class to expose them in all functions.


Runspace runspace = RunspaceFactory.CreateRunspace();
Pipeline pipe;

We will now study the code, beginning from the user interaction : the Click on the Button. The function (line 85) is :


protected void BtnExecuteScript_Click(object sender, EventArgs e)
{
    string strCurrentId = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
    // Enable timer and disable button, clear TxtResult textbox
    this.Timer1.Enabled = true;
    this.BtnExecuteScript.Enabled = false;
    this.TxtResult.Text = "";

    // put the username at the beginning of the output (optional)
    Session["PowerTrace"] = "Initiateur de la demande : " + strCurrentID + "\r\n";

     // Gather script from the TxtPowerShellScript and convert it from html to clean text
    // then call executePowerShellCode function with the result
      string strContent = TxtPowerShellScript.Text;
    StringWriter writer = new StringWriter();
     Server.HtmlDecode(strContent, writer);
    this.executePowerShellCode(writer.ToString());
}
Let's see what happen. First, I create the strCurrentID string that will hold the current user name. This is not mandatory for our example, but this information is interesting to monitor our website activities.
When we'll deploy our site, it's a Service Account that will handle all the code execution. as we want (if it's not the case, you should!) monitor the Site Activities, it's important to know the name of the user who fired an action, in order to know who to blame if something goes wrong :)

System.Security.Principal.WindowsIdentity.GetCurrent().Name gives us this info.

Then we activate the Timer:


this.Timer1.Enabled = true;

We desactivate the button (to avoid unwanted launch of script while another is running):

this.BtnExecuteScript.Enabled = false;


And we reset the Output Textbox :

this.TxtResult.Text = "";


Then we will create our Session Variable that I called "PowerTrace". We feed it first with the name of the user :


Session["PowerTrace"] = "Initiateur de la demande : " + strCurrentID + "\r\n";


Let's go deeper on this point. All our problem here is to pass data from our code behind to the aspx controls (output textbox) when we refresh the page. As these 2 elements are isolated (each has its context : WebForm on the client side, codebehind on the server side), we need a way to put the output of our script in the Output Textbox.

There is several solutions, more or less smart (Create temporary textfile, add output as an URL element...). The Session VAriable is in our context the best and simple way to do this. This let you present datas to the user-side of the website that lives until the user session ends. Without this, when we refresh the page, every informations that are on the server side (variables from the codebehind) will be reset.
As the script output won't takes a lot of Memory and our website is targeted to be an intranet solution for administrators, this is a good choice, easy to setup.

The content of this variable will be exposed until the end of the user session. Now we'll gather the content of the Input Textbox :

string strContent = TxtPowerShellScript.Text;

Problem here : We are in an HTML Context, the content of the Textbox is in HTML format. This won't let us execute the code as is, cause unwanted informations are added ("\r\n" for carraige return...). We can't use this code with PowerShell like this.

We are lucky : there is a method that let us do just that : convert HTML string to standard text string. It's name is HTMLDECODE

This is this method that I use here : I create a StringWriter object (to build string), and use the HTMLDECODE method :


StringWriter writer = new StringWriter()
Server.HtmlDecode(strContent, writer);


Then I call the ExecutePowerShellCode function to execute this code.

this.executePowerShellCode(writer.ToString());

Let's see what's inside this ExecutePowerShellCode function:


private void executePowerShellCode(string code)
{
    runspace.Open();
    pipe = runspace.CreatePipeline(code);
    pipe.Input.Close();

    // Call output_DataReady when data arrived in the pipe
    pipe.Output.DataReady += new EventHandler(Output_DataReady);
    // Call pipe_StateChanged 
    pipe.StateChanged += new EventHandler<PipelineStateEventArgs>(pipe_StateChanged);
    pipe.InvokeAsync();
}

Nothing special, I use a really classic way to execute Powershell code async. You'll find many sample on internet that do just that.

We open a new runspace

runspace.Open();


Then we create a piepline in this runspace, with the code from the Input Textbox

pipe = runspace.CreatePipeline(code);


We close the Pipeline Input

pipe.Input.Close();


We then create a Event manager. It calls the "Output_Dataready" function when something appears in the pipeline output.

pipe.Output.DataReady += new EventHandler(Output_DataReady);

We create another Event manager. It calls the "pipe_StateChanged" function when the state of the pipeline Changed. Here, we test if the pipeline state is "Terminated", wich told us that the script ends. Then we will do some things, like activate the button again (we'll speak of that later)
Finally, we call the "InvokeAsync" method that will execute the pipeline asynchronously.

pipe.InvokeAsync();

Ok, now our code is waiting for activities from the pipeline. We'll now look at the functions "Output_Dataready" when data arrived at the pipeline output .


void Output_DataReady(object sender, EventArgs e)
{
    PipelineReader<PSObject> reader = (PipelineReader<PSObject>)sender;
    String strPowershellTrace = reader.Read().ToString();
    Session["PowerTrace"] += strPowershellTrace + "\r\n";
}

We first read the objects in the pipeline output and put it in a reader object :


PipelineReader<PSObject> reader = (PipelineReader<PSObject>)sender;


Then, we create a string variable wich get the output from the pipeline (script output)

String strPowershellTrace = reader.Read().ToString();

Finally, we add this variable in our Session Variable


Session["PowerTrace"] += strPowershellTrace + "\r\n";

As you can see, I add "\r\n" at the end to handle carriage return in HTML (this output will be in a HTML Textbox). To conclude this part on PowerShell code exectution, let's have a look on the last function when the pipeline state changes.

Here, we put a condition on the pipeline state : if the status is completed, we do some actions, else we do nothing (we only need for the example to test if our script execution is completed)


if (pipe.PipelineStateInfo.State == PipelineState.Completed)


So, what we do here ? First we close the runspace

Runspace.close();

Thens, while our Session Variable isn't null AND there's something in it (Number of caracters greater than zero), we don't do anything. We do this to let time for our Timer to write the Session Variable data in the Output Textbox. We'll see after how and why

while ((Session["PowerTrace"] != null) && (Session["PowerTrace"].ToString().Length > 0))
{
}
runspace.Close();

Our last task is to remove our Session variable and wait for another script :

Session.Remove("PowerTrace");

Let's study the Timer Function :


protected void Timer1_Tick(object sender, EventArgs e)
    {
        if (Session["PowerTrace"] == null)
        {
            this.BtnExecuteScript.Enabled = true;
            Timer1.Enabled = false;
            this.TxtResult.Text += "Fin du script";
        }
        else
        {
            String strPoshTrace = Session["PowerTrace"].ToString();
              this.TxtResult.Text += strPoshTrace;
            Session["PowerTrace"] = "";
        }
    }
This function is called every 100 ms in our case, regardless of the pipeline Events. Here is what we do :
If our Session Variable is null (this happens only after the "remove" in the "Pipe_StateChanged" function), we :
· Activate the button
· Deactivate the Timer
· Write "End of script" in the Output Textbox
this.BtnExecuteScript.Enabled = true;
Timer1.Enabled = false;
this.TxtResult.Text += "Fin du script";
Else we gather the Session variable content in a string

String strPoshTrace = Session["PowerTrace"].ToString();


We add it to the Output Textbox

this.TxtResult.Text += strPoshTrace;


And we empty the Session Variable

Session["PowerTrace"] = "";


Note that we don't remove the Session variable, this will valid the condition in our Pipe_StateChange :


while ((Session["PowerTrace"] != null) && (Session["PowerTrace"].ToString().Length > 0))

This is it, we've finished reviewing the sample code. We will talk in the third and last part of this tutorial about :

· How to manage PowerShell script from a Web Form

· How to deploy this site on a IIS server with appropriate security

mardi 20 mai 2008

PowerShell and ASP.NET Part 1

Original French version available here

As you know, PowerShell let us create graphical interfaces with the .NET winforms. With it, we can build front end to manage our scripts. It's a really nice feature, and tools like AdminScriptEditor make it pretty easy.

I've been oftenly asked this question :

How could we integrate easely PowerShell scripting to a web site ?

This is really a good point, cause using a web portal instead of winforms brings many benefits :

  • No needs to install PowerShell and Snapins on every admin workstations in order to use scripts (I think about Active Directory provisioning, Support dashboard, virtual machines manager and so on...)

  • ... in order to centralize and delegate administration easely

  • Filter acces and use of powershell in the company (everything is launched from the server and not from the workstation)

  • Tracability : from a single point, we know who did what (with appropriate logging), and could do a life-saving CTRL+Z on a innapropriate action

  • Can be embbeded in any entreprise web portal (Sharepoint/WSS for example)

This is a great story, however there's some drawbacks at first look for people who didn't put an hand on ASP.NET :

  • Developping with ASP.NET and C# is radically different from scripting

  • Samples of invocation method of PowerShell from C# seems difficult for Infrastructure people

  • We can do C# developpement to achieve result but ...

  • ... we need to be lucky enough to have a "real" .NET developper at hand.
That's why I suggest you this first example of PowerShell/ASP.NET interaction that takes things from a different point of view, more "Infra-People compliant". To make it easier to understand, we will separate things :
  • ASP.NET and C# to manage only the user interface

  • Call PowerShell scripts directly (no C# block)
  • Establish a communication between PowerShell output and the web site to follow the progress

This let us use PowerShell as our process engine as usual (regular scripting)

As we'll see in this tutorial, the code is rather simple and the web site really light, but we will do pretty complex tasks by calling PowerShell. I won't make a ASP.NET tutorial, cause I'm not the best guy around to do this, and we'll only use basics to achieve our result.

However there's some unavoidable things to understand before going deeper in the code

Some bases on ASP.NET (for Infrastructure people)

To summarize intensely, you can look at a ASP.NET site as a HTML page that can call .NET code from a server, and not just the Browser/workstation.

Here's a example that will be our first project from this tutorial. Note that we can (and will do!) use the free version of Visual Studio for Web development: Visual Studio Web developper Express Edition, please download it. This version is really good for our needs and will let us do everything required for the entire development, so we won't hesitate.


The smallest possible ASP.NET site is composed of the following elements :

An ASP.NET page (Here : default.aspx) : Here we will find all the user interface definition of our project (Buttons, Dropdown list, Textboxes...). This is merely HTML / ASP.NET code. You can find javascript in other projects, but we won't use it for now.

A .CS file bind to the ASPX document (here : default.aspx.cs) : This file contains what is called the "Code-Behind". This is the server side code invoked by the asp.net site. This code is in a .NET language (C# or VB.NET). I'll use C# here cause it's easier to read and pretty close to PowerShell syntax.

web.config : This is the website configuration file. To makes things simple, we find in it the website config : Authentication Method, DLL invokation (like the ones used to call PowerShell), and so on...

Of course, Web developper express ease the process, everything is pre-build when you create a project. Before going deeper in the creation of the project, let me brief you with the strategy used to call scripts from our webform :

PowerShell interaction strategy

The following diagram presents the strategy used in this first example of PowerShell calling from ASP.NET. This is what we'll discuss in the second part of this tutorial :

Our first website is composed of 2 Textbox (one for script input, one for script output) and an "Execute" Button. That's all, as you can see, my first user interface is pretty basic.

The mecanism isn't too diffifcult to understand. We will go in the details in the Part 2 of this tutorial, but here's the resume :

When we fire "Launch Script", let's talk about what happen :

We activate the Timer (1), we'll come back on this after, but let say for the moment that this control execute some code at a certain rate (for example every second).

In the meantime, the code-Behind launch a PowerShell runspace and initialize a pipeline asyncrhonously. It read the first textbox, and execute it in the pipeline (2).

Every seconds, our Timer check the pipeline output (3), and write what it found in a special variable called "Session variable" (4) (we'll see why in the second part of the tutorial)

We refresh the output Textbox with the content of the Session Variable (5). The timer is activated until the end of the script in the pipeline (6).

Using a Timer let us have a near-realtime view of the script output (every seconds). Please note that a script used in this context isn't forced to have output : As every PowerShell script, many things can happen without any console output, so please be carefull with what you code here ;)

I keep all the code really simple to let you identify every functions more easely. We will improve it later.

We use some AJAX functionnality in this example. The Timer is a ASP.NET ajax control, we use an UpdatePanel too that let us refresh the output textbox without refreshing the entire document. Using this kind of Ajax control in ASP.NET with web developper Express is really easy (as simple as using every classic control like Textbox). We don't need to do any Javascript and XML here, you'll see that in the 2nd part of this tutorial.

For the moment, you can test this site. Open the attached project in Web developper Express. Launch it and you'll have this page :

Enter your script in the upper Textbox, launch it with the button, you'll see the result in the bottom Textbox.

Note : The script is run from the server side (if you test locally, your workstation will do the job). Note that we retrieve the "object" output of PowerShell : that's why I use in this example the "out-string" cmdlet to grab an output similar to what you see in the standard PowerShell console. Do some test, you'll see that you get objects in the output Textbox.

You can of course launche every kind of PowerShell commands and scripts, so be carefull ! :). This site is really lite to let you understand the code : I didn't put any error control or script management, nor advanced user interface.

We'll see in the second part the details of every element and code of this website, we'll see how to manage a script from Form controls, and advice regarding security.

Here is the source : PowerShellASP.zip , feel free to ask questions in the comments below !