Reminder : Source code of this sample is available here.
You need the following tools to use it :
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>
DEFAULT.ASPX
In the first part of this tutorial, I told you that I used AJAX to :
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>
<asp:TextBox ID="TxtPowerShellScript" runat="server" TextMode="MultiLine" Width="597px"
Height="194px" BackColor="#012456" ForeColor="#EEEDF0" Wrap="False"></asp:TextBox>
<asp:Timer ID="Timer1" runat="server" Enabled="False" Interval="100" OnTick="Timer1_Tick" />
DEFAULT.ASPX.CS
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;
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());
}
System.Security.Principal.WindowsIdentity.GetCurrent().Name gives us this info.
Then we activate the Timer:
this.Timer1.Enabled = true;
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";
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();
}
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);
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();
while ((Session["PowerTrace"] != null) && (Session["PowerTrace"].ToString().Length > 0))
{
}
runspace.Close();
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"] = "";
}
}
Timer1.Enabled = false;
this.TxtResult.Text += "Fin du script";
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
1 commentaire:
I know it has been some time since you have written these articles... but, do you still have the source files available for Part 2 and 3? They don`t seem to be available through the link you have provided.
Thank you!
Enregistrer un commentaire