2

I'm very new to c# and I expect what I'm trying to do is quite simple-ish but I'm not able to find or follow other examples where output from a powershell array populates a gridview for further manipulation / execution of another script. The process on page load is to run a powershell script, which creates an array of session details which populate a gridview. A second script can then be initiated to interact wit that session (e.g. force a logoff) by way of selection of the gridview row.

Using other examples I have managed to initiate the first powershell execution, which throws the data to a form via:

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="PowerShellExecution.Default" %>

<asp:Content ID="BodyContent" ContentPlaceHolderID="MainContent" runat="server">
     <div>
           <h1>PowerShell Harness<asp:Label ID="Label1" runat="server" Text="Label" Visible="False"></asp:Label>
           </h1>
           <asp:TextBox ID="ResultBox" TextMode="MultiLine" Width="1000px" Height="400px" runat="server"></asp:TextBox>
    </div>
</asp:Content>

CodeBehind

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)
        {
          // Gets the name if authenticated.
                if (User.Identity.IsAuthenticated)
                    Label1.Text = User.Identity.Name;
                else
                    Label1.Text = "No user identity available.";

            // 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);
            // shell.Commands.AddScript("D:\\Local_Scripts\\sessioncall.ps1");
            shell.Commands.AddCommand("c:\\Local_Scripts\\sessioncall.ps1");

            // Add Params
            // shell.Commands.AddParameter(null,User.Identity.Name);
            // shell.Commands.AddParameter("Username", Label1.Text);
            shell.Commands.AddArgument(User.Identity.Name);

            // 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());
            }

        }
    }
}

Sessioncall.ps1

$SessionUser = "$($args[0])"
set-brokersite -AdminAddress UKSite 
$a = @(Get-BrokerSession -BrokeringUserName $SessionUser | Select-Object UserFullName, BrokeringTime, ClientName,DesktopGroupName, sessionstate, uid, machinename,@{Name='ENV';Expression={'UK'}})
#Pull US Sessions into array
Set-brokersite -AdminAddress USSite
$a += @(Get-BrokerSession -BrokeringUserName $SessionUser | Select-Object UserFullName, BrokeringTime, ClientName,DesktopGroupName, sessionstate, uid, machinename,@{Name='ENV';Expression={'US'}})

If ($a -ne $null){
    Write-Output $a | out-string
}
Else {
    Write-Output "No User session! Username was $SessionUser"
}

Currently the output is thrown to the textbox as an out-string. I'm struggling in even how to start to data-bind that array output as rows within a gridview. Just need a little hand-holding to get this started!

Thanks in advance! Paul.

mklement0
  • 382,024
  • 64
  • 607
  • 775
darkgen
  • 33
  • 4

2 Answers2

3

It's been a while since I've dabbled with WebForms, but I found a way to do what you're after...

First off, let's change your PowerShell script slightly. Rather than return a string (which is what | out-string is doing), we can simply return the objects. The shell.Invoke() method in the C# code knows how to extract fully-fledged objects from the output of the script so we don't need to serialize to a string inside the PowerShell script and then try to deserialize that again back to objects inside our C# code.

Ignoring your line-of-business logic for a minute, my script simply returns an array of PSCustomObjects and looks like this:

MyScript.ps1

write-output @(
    (new-object PSCustomObject -Property ([ordered] @{
         "MyProperty1" = "MyValue1.1"
         "MyProperty2" = "MyValue2.1"
         "MyProperty3" = "MyValue3.1"
    })),
    (new-object PSCustomObject -Property ([ordered] @{
          "MyProperty1" = "MyValue1.2"
          "MyProperty2" = "MyValue2.2"
          "MyProperty3" = "MyValue3.2"
    }))
);

Now, my C# Page_Load method does this:

Default.aspx.cs

protected void Page_Load(object sender, EventArgs e)
{

    // Initialize PowerShell engine
    var powershell = PowerShell.Create();

    // Add the script to the PowerShell object
    var script = "c:\\temp\\MyScript.ps1";
    powershell.Commands.AddCommand(script);

    // Execute the script
    var results = powershell.Invoke();

    ...

and results contains a System.Collections.ObjectModel.Collection<PSObject>. We can't databind that directly to a GridView because the properties are tucked away inside key-value pairs in the Properties member of each PSObject, but if we create a new class it's pretty easy to extract the values into something we can databind:

MyClass.cs

public class MyClass
{
    public string MyProperty1 { get; set; }
    public string MyProperty2 { get; set; }
    public string MyProperty3 { get; set; }
}

and our Page_Load can convert the PSObjects into instances of this class:

Default.aspx.cs

    ...

    var objects = results.Select(
        r => new MyClass
        {
            MyProperty1 = (string)r.Properties["MyProperty1"].Value,
            MyProperty2 = (string)r.Properties["MyProperty2"].Value,
            MyProperty3 = (string)r.Properties["MyProperty3"].Value,
        }
    );

    this.ResultGrid.DataSource = objects;
    this.ResultGrid.DataBind();

}

Then, to display the data you just need a GridView added to your Default.aspx with whatever columns and formatting you want defined:

Default.aspx

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication1._Default" %>

<asp:Content ID="BodyContent" ContentPlaceHolderID="MainContent" runat="server">
     <div>
           <h1>PowerShell Harness<asp:Label ID="Label1" runat="server" Text="Label" Visible="False"></asp:Label></h1>
            <asp:GridView ID="ResultGrid" runat="server" AutoGenerateColumns="false">
                <Columns>
                    <asp:BoundField DataField="MyProperty1" HeaderText="My Property 1" />
                    <asp:BoundField DataField="MyProperty2" HeaderText="My Property 2"  />
                    <asp:BoundField DataField="MyProperty3" HeaderText="My Property 3"  />
                </Columns>
            </asp:GridView>
    </div>
</asp:Content>

Run that and you should see something like this on the page:

ASP.Net page with GridView bound from a PowerShell script

Note

You might find your Get-BrokerSession cmdlet returns a collection of a specific type of object already rather than PSCustomObject, in which case you could possibly skip the conversion step and databind directly to the results object, so you might have to play with that to see. Hopefully the above will give you some pointers if there are any differences.

Hope this helps.

mclayton
  • 8,025
  • 2
  • 21
  • 26
0

Many thanks for the guidance. Gridview now populates.

    protected void Page_Load(object sender, EventArgs e)
    {
      // Gets the name if authenticated.
            if (User.Identity.IsAuthenticated)
                Label1.Text = User.Identity.Name;
            else
                Label1.Text = "No user identity available.";

        // Clean the Result TextBox

        // Initialize PowerShell engine
        var shell = PowerShell.Create();

        // Add the script to the PowerShell object
        // shell.Commands.AddScript(Input.Text);
        // shell.Commands.AddScript("D:\\Local_Scripts\\sessioncall.ps1");
        shell.Commands.AddCommand("c:\\Local_Scripts\\sessioncall.ps1");

        // Add Params
        // shell.Commands.AddParameter(null,User.Identity.Name);
        // shell.Commands.AddParameter("Username", Label1.Text);
        shell.Commands.AddArgument(User.Identity.Name);

        // 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 results2 = shell.Invoke();
            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
                var UserFullName = (psObject.Members["UserFullName"]);
                var BrokeringTime = (psObject.Members["BrokeringTime"]);
                var ClientName = (psObject.Members["ClientName"]);
                var DesktopGroupName = (psObject.Members["DesktopGroupName"]);
                var SessionState = (psObject.Members["SessionState"]);
                var Uid = (psObject.Members["Uid"]);
                var MachineName = (psObject.Members["MachineName"]);
                var ENV = (psObject.Members["ENV"]);
                // builder.Append(psObject.BaseObject.ToString() + "\r\n");
            }

            this.ResultGrid.DataSource = results2;
            this.ResultGrid.DataBind();
        }

        }

Returns [![enter image description here][1]][1]

However this method throws an exception error when you then define a datakeyname.

<asp:GridView ID="ResultGrid" runat="server" DataKeyNames="uid" AutoGenerateColumns="False" OnSelectedIndexChanged="ResultGrid_SelectedIndexChanged">
                <Columns>
                     <asp:buttonfield buttontype="Button" 
                 commandname="Select"
                 headertext="View" 
                 text="View"/>
                    <asp:BoundField DataField="UserFullName" HeaderText="UserFullName" />
                    <asp:BoundField DataField="BrokeringTime" HeaderText="BrokeringTime"  />
                    <asp:BoundField DataField="ClientName" HeaderText="ClientName"  />
                    <asp:BoundField DataField="DesktopGroupName" HeaderText="DesktopGroupName" />
                    <asp:BoundField DataField="SessionState" HeaderText="SessionState"  />
                    <asp:BoundField DataField="Uid" HeaderText="Uid"  />
                    <asp:BoundField DataField="MachineName" HeaderText="MachineName"  />
                    <asp:BoundField DataField="ENV" HeaderText="ENV"  />
                </Columns>
            </asp:GridView>

code behind

 protected void ResultGrid_SelectedIndexChanged(object sender, EventArgs e)
        {
            // Determine the RowIndex of the Row whose Button was clicked.
            //int rowIndex = ((sender as Button).NamingContainer as GridViewRow).RowIndex;
            String key = ResultGrid.SelectedDataKey.Value.ToString();
            //Get the value of column from the DataKeys using the RowIndex.
            //int id = Convert.ToInt32(ResultGrid.DataKeys[rowIndex].Values[01]);
            //  Response.Write("IM_RAA_657x_Date.aspx?Day=" + ResultGrid.SelectedDataKey.Value(0) + "&BusinessCategory=" + ResultGrid.SelectedDataKey.Values(1).ToString())
        }

This throws an exception error at

this.ResultGrid.DataBind();

"System.Web.HttpException: 'DataBinding: 'System.Management.Automation.PSObject' does not contain a property with the name 'uid'.'"

I'm not clear if the method is now the issue or something outside of that. I'm confused as it must see inside PSObjects correctly in order for the variables to be defined and populate the gridview?! Hmm.

Wow; OK; I just realised this entire section is ignored! Case in point; it can be commented out! So clearly adjust the output from the powershell script!

 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
                    //var UserFullName = (psObject.Members["UserFullName"]);
                    //var BrokeringTime = (psObject.Members["BrokeringTime"]);
                    //var ClientName = (psObject.Members["ClientName"]);
                    //var DesktopGroupName = (psObject.Members["DesktopGroupName"]);
                    //var SessionState = (psObject.Members["SessionState"]);
                    //var Uid = (psObject.Members["Uid"]);
                    //var MachineName = (psObject.Members["MachineName"]);
                    //var ENV = (psObject.Members["ENV"]);
                    // builder.Append(psObject.BaseObject.ToString() + "\r\n");
                }

Forgive me but I'm almost there!

r => new MyClass
                        {
                            UserFullName = (string)r.Properties["UserFullName"].Value,
                            BrokeringTime = (DateTime)r.Properties["BrokeringTime"].Value,
                            ClientName = (string)r.Properties["ClientName"].Value,
                            DesktopGroupName = (string)r.Properties["DesktopGroupName"].Value,
                            //SessionState = (string)r.Properties["SessionState"].Value,
                            Uid = (Int64)r.Properties["Uid"].Value,
                            //MachineName = (string)r.Properties["MachineName"].Value,
                            //ENV = (string)r.Properties["ENV"].Value,
                        }
                    );
                this.ResultGrid.DataSource = objects;
                this.ResultGrid.DataBind();
            }

            }

        protected void ResultGrid_SelectedIndexChanged(object sender, EventArgs e)
        {

            Response.Write(ResultGrid.SelectedValue.ToString());

        }
    }

    internal class MyClass
    {
        public string UserFullName { get; set; }
        public DateTime BrokeringTime { get; set; }
        public string ClientName { get; set; }
        public string DesktopGroupName { get; set; }
        public String SessionState { get; set; }
        public Int64 Uid { get; set; }
        public string MachineName { get; set; }
        public string ENV { get; set; }
    }

So I'm now correctly populating the gridview; Some columns are still being problematic and are not being treated as strings BUT I'm almost there!

Looks like Get-member type:

BrokeringTime    NoteProperty datetime BrokeringTime=28/02/2020 06:56:39 
ClientName       NoteProperty string ClientName=clientname           
DesktopGroupName NoteProperty string DesktopGroupName=desktopgroupname
ENV              NoteProperty System.String ENV=UK                       
MachineName      NoteProperty string MachineName=machinename  
SessionState     NoteProperty SessionState SessionState=Active           
Uid              NoteProperty long Uid=12345678                           
UserFullName     NoteProperty string UserFullName=username  

C# Does seem to like system.string.

darkgen
  • 33
  • 4