I stumbled upon this epic .net project by PhonicUK on Github for “an event-driven windowing system using a line-art interface for use by command-line applications” and thought I would see if it worked in PowerShell. The results, I think are pretty cool!

The code to create this is really very similar to WinForms style of coding and can be generated very quickly. I will give a short walkthrough on how to create a GUI using this sweet library.

How to make the magic happen

Build the project (to get the DLL)

  1. Grab the source code from the above mentioned Github repository.
  2. Open the project in VS Community 15
  3. Build the solution

This should generate the DLL we will need to reference in a bin/debug folder of the project.

Load the DLL into PowerShell

 
    Import-Module "Whereveryoustoredtheproject/bin/debug/CLRCLI.dll"
 

Build out your GUI in something similar to WinForms style

Create a root base

 
    $Root = [CLRCLI.Widgets.RootWindow]::new()
 

Add your first dialog. The dialog is the base that you can attach buttons, listboxes etc. to.

 
    $Dialog = [CLRCLI.Widgets.Dialog]::new($Root)

    $Dialog.Text = "List Running Processes"
    $Dialog.Width = 60
    $Dialog.Height = 32
    $Dialog.Top = 4
    $Dialog.Left = 4
    $Dialog.Border = [CLRCLI.BorderStyle]::Thick
 

Cool. Now lets add a nice label, a couple of buttons and a listbox. Note that in the creation they are all attached to my first $Dialog.

 
    $Label = [CLRCLI.Widgets.Label]::new($Dialog)
    $Label.Text = "Running Processes"
    $Label.Top = 2
    $Label.Left = 2

    $Button = [CLRCLI.Widgets.Button]::new($Dialog)
    $Button.Text = "Get Processes"
    $Button.Top = 4
    $Button.Left = 6
    $Button.Width = 25

    $Button2 = [CLRCLI.Widgets.Button]::new($Dialog)
    $Button2.Text = "Show Alternate Window"
    $Button2.Top = 4
    $Button2.Left = 34
    $Button2.Width = 25

    $list = [CLRCLI.Widgets.ListBox]::new($Dialog)
    $list.top = 10
    $list.Left = 4
    $list.Width = 32
    $list.height = 6
    $list.Border = [CLRCLI.BorderStyle]::Thin
 

Cool. Seems pretty straightforward. Now let’s build another dialog that is hidden as basically another page.

 
    $Dialog2 = [CLRCLI.Widgets.Dialog]::new($Root)
    $Dialog2.Text = "ooooh"
    $Dialog2.Width = 32
    $Dialog2.Height = 5
    $Dialog2.Top = 6
    $Dialog2.Left = 6
    $Dialog2.Border = [CLRCLI.BorderStyle]::Thick
    $Dialog2.Visible = $false
 

Now I’m going to add a button to the second dialog window. Since the dialog is hidden the button also will be hidden.

 
    $Button3 = [CLRCLI.Widgets.Button]::new($Dialog2)
    $Button3.Text = "Bye!"
    $Button3.Width = 8
    $Button3.Height =3
    $Button3.Top = 1
    $Button3.Left = 1
 

Sweet! Now let’s make the buttons do something.

 
    $Button3.Add_Clicked({$Dialog2.Hide(); $Dialog.Show()})
    $Button2.Add_Clicked({$Dialog.Hide(); $Dialog2.Show()})
    $Button.Add_Clicked({ Get-Process | select -ExpandProperty ProcessName | foreach { $list.items.Add($_) }  })
 

You can guess what is happening from the code for Buttons 2 and 3. Those buttons are going to be used to switch between the dialogs. The code for button one might be a bit more complicated but basically we are adding the process names from get-process into the listbox we created earlier.

Now, we run the GUI with by running the $Root.

 
    $Root.Run()
 

And we get the magic! Pretty awesome way to make an oldschool GUI right? This project is pretty sweet but I would also like to check out CursesSharp as that project looks to be a bit more developed.

The Series

Okay, so about 5 months ago I wrote a blog post on a potential method of executing PowerShell from a user interface built in HTML and javascript. It turns out that the series was one of the more popular ones I have posted and it consistently receives traffic.

The Problem

I did not really love my other method. It was just the only method I had found. The problems were:

  • Complex to learn (build a server, build a client, learn HTTP methods)
  • Possible security concerns (even though we were only listening on localhost a user could potentially gain access to another user session and execute code as a different user)

A MUCH Better method

So, this still is not cross-platform. I would still like to investigate ElectronJS and AppJS to see if integration with PowerShell would be easy to achieve and thus provide a method of making cross-platform apps using PowerShell, HTML and JavaScript but this will work for now on Windows devices and is pretty slick in my opinion.

Step 1 - Create a WPF web browser

 
[xml]$xaml = @"
   <Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        Title="$Title" Height="500" Width="700">
        <Grid>
            <DockPanel>
                <WebBrowser Name="WebBrowser" DockPanel.Dock="Top" Margin="30">
                </WebBrowser>
            </DockPanel>
        </Grid>
    </Window>
"@

#Read XAML
$reader=(New-Object System.Xml.XmlNodeReader $xaml) 
$Form=[Windows.Markup.XamlReader]::Load( $reader )

$WebBrowser = $Form.FindName("WebBrowser")
 

Step 2 - Create a C# class that is COMVisible for executing PowerShell

Okay, so this part was a little scary to figure out but it shouldn’t be too hard to figure out if you have been in the PowerShell space for awhile or tinkering with PowerShell v5 Classes. Basically we want something that will execute PowerShell code and that is ComVisible.

 

Add-Type -TypeDefinition @"
    using System.Text;
    using System.Runtime.InteropServices;

    //Add For PowerShell Invocation
    using System.Collections.ObjectModel;
    using System.Management.Automation;
    using System.Management.Automation.Runspaces;

    [ComVisible(true)]
    public class PowerShellHelper
    {

        Runspace runspace;

        public PowerShellHelper()
        {
            
            runspace = RunspaceFactory.CreateRunspace();
            runspace.Open();

        }

        public string InvokePowerShell(string cmd)
        {
            //Init stuff
            RunspaceInvoke scriptInvoker = new RunspaceInvoke(runspace);
            Pipeline pipeline;

            pipeline = runspace.CreatePipeline();

            //Add commands
            pipeline.Commands.AddScript(cmd);

            Collection<PSObject> results = pipeline.Invoke();

            //Convert records to strings
            StringBuilder stringBuilder = new StringBuilder();
            foreach (PSObject obj in results)
            {
                stringBuilder.Append(obj);
            }


            return stringBuilder.ToString();

	    }

    }
"@ -ReferencedAssemblies @("System.Management.Automation","Microsoft.CSharp")

 

Step 3 - (The magic part) Add a new PowerShellHelper Object as the ObjectForScripting on the WPF browser

 
    $WebBrowser.ObjectForScripting = [PowerShellHelper]::new()
 

Step 4 - Run PowerShell code from HTML!

 
$HTML = @'
<div id="results">Loading processes..</div>

<script>
    function updateProcesses()
    {
        
        var HTMLProcessesFromPowerShell = window.external.InvokePowerShell("Get-Process | select name, id | convertTo-HTML");
        
        document.getElementById("results").innerHTML = HTMLProcessesFromPowerShell;
     
    }
    updateProcesses();
</script>
'@

$WebBrowser.NavigateToString($HTML)

#===========================================================================
# Shows the form
#===========================================================================
write-host "To show the form, run the following" -ForegroundColor Cyan
$Form.ShowDialog() | out-null
 

The result:

Sweet! Now we have a simple method of executing PowerShell from HTML that does not require a server or require knowledge of HTTP. This code executes synchronously just like a good old HTA would have with your vbscript so the GUI will lock up while the code is running. If you want to play around with one that executes asynchronously and executes a javascript function with the results when the PowerShell script is finished running you can check out my gist here. The c# code is a bit more complicated there though. I may blog through this at a later date and possibly package it up as a module.

It might be nice to be able to wrap this up as something like Invoke-PSHTA -FilePath C:\MyHTMLApp.html right?

Let me know in the comments what you think of this method. I appreciate the read!

More Reading

This is just going to be a quick tip but I have not posted in awhile and thought I would share this little quick hit. PowerShell has a SUPER useful command for rendering variables in PowerShell into HTML called ConvertTo-HTML and of course HTML in general is really easy to spice up a bit as far as the looks go.

How can we show that information to an end user in a pop-up?

Since PowerShell has full .NET access we can pass an HTML string into an embedded WPF WebBrowser?

Usage of the function is:

 

    $HTML = Get-Process | select name,id | ConvertTo-HTML | Out-String
    Show-HTML -HTML $HTML

 

And this is what we would see:

Cool! Now we can start to build some interesting result pages for our scripts or interesting popups to show. Check out a couple of my more simple samples using this same technique:

 


function Show-HTML ([string]$HTML)
{

    [void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
    [xml]$XAML = @'
    <Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="PowerShell HTML GUI" WindowStartupLocation="CenterScreen">
            <WebBrowser Name="WebBrowser"></WebBrowser>
    </Window>
'@

    #Read XAML
    $reader=(New-Object System.Xml.XmlNodeReader $xaml) 
    $Form=[Windows.Markup.XamlReader]::Load( $reader )
    #===========================================================================
    # Store Form Objects In PowerShell
    #===========================================================================
    $WebBrowser = $Form.FindName("WebBrowser")

    $WebBrowser.NavigateToString($HTML)

    $Form.ShowDialog()

}

 

The Series

If you have been following along with the previous two parts to this blog, I know what you have been saying. When will we get to the GUI part! Everything up to this point has been all web servers and web technology. Which could come in handy but we just want to make a simple GUI in HTML that runs PowerShell. The browser thing is cool but you don’t want to pop open a browser every time you want to run some scripts. Integrating that into the pipeline would be difficult too right?

Today, we finally get to the good stuff!

Step 1 - Build our own web browser (and just not tell people it is a browser)

So, what if we take the code just below which launches a super simple XAML GUI with just a webbrowser object in it, in another runspace immediately before we run our code from the last post.

 
    Start-Sleep -Seconds 15
    [void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
    [xml]$XAML = @'
    <Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="PowerShell HTML GUI" WindowStartupLocation="CenterScreen">

            <WebBrowser Name="WebBrowser"></WebBrowser>

    </Window>
'@

    #Read XAML
    $reader=(New-Object System.Xml.XmlNodeReader $xaml) 
    $Form=[Windows.Markup.XamlReader]::Load( $reader )
    #===========================================================================
    # Store Form Objects In PowerShell
    #===========================================================================
    $WebBrowser = $Form.FindName("WebBrowser")

    $WebBrowser.Navigate("http://localhost:8000/")

    $Form.ShowDialog()
 

Voila! We have our own PowerShell web browser displaying our simple form but doesn’t look anything like a web browser! It looks like a regular old windows desktop application.

Now with this we can do some pretty amazing things but we still have a few problems to solve.

How do we know when to shut down the web server?

If you were just going to get a single request from the browser you can close it as soon as you receive and process the request from the browser. If not you can simply build in a URL that will stop the server for example:

 
while($SimpleServer.IsListening)
{

    $Context = $SimpleServer.GetContext()

    # Creating a friendly way to shutdown the server
    if($Context.Request.Url.LocalPath -eq "/kill")
    {

                $Context.Response.Close()
                $SimpleServer.Stop()
                break

    }
    
    ...

}
 

How do I eliminate the Start-Sleep and launch my GUI as soon as the server is ready?

You may come up with your own better method for doing this but I came up with this little function. Which will continually attempt to access the URL until it gets a response and then it will continue to load the xaml:

 
function Wait-ServerLaunch
{

    try {
        $url="http://localhost:8000/"
        $Test = New-Object System.Net.WebClient
        $Test.DownloadString($url);
    }
    catch
    { start-sleep -Seconds 1; Wait-ServerLaunch }

}
 

How do I hide the PowerShell window so end users won’t know it is there?

You would obviously want your PowerShell console showing when you are debugging but if you are using your script client-side you may not want them to know that it is there. DexterPosh had a great post on this awhile back. Check it out here.

How do I include modern web technologies in this GUI? It seem to work in IE8 by default.

To make your GUI look pretty using magic like materializecss or Office UI Fabric you need the following meta tag in the header of your HTML:

<meta http-equiv="X-UA-Compatible" content="IE=edge" />

This will force it to use IE Edge. For more information on that magic line see here.

How do I build a complete UI that collects input and returns / displays results using the UI?

Here is my code for this solution. A usage example would be like this:

 
Start-PoshWebGUI -ScriptBlock {
    
    $Parameters = $Context.Request.QueryString

    switch ($Context.Request.Url.LocalPath)
    {

        "/showProcesses" { "<a href='/'>Main Menu</a><form action='/filterProcesses'>Filter:<input Name='Name'></input></form>$(Get-Process | select cpu,name | ConvertTo-Html -Fragment | Out-String)" }
        "/filterProcesses" { "<a href='/'>Main Menu</a><form action='/filterProcesses'>Filter:<input Name='Name'></input></form>$(Get-Process $Parameters["Name"] | select cpu, name | ConvertTo-Html -Fragment | Out-String)" }
        "/showServices" { "<a href='/'>Main Menu</a><form action='/filterServices'>Filter:<input Name='Name'></input></form>$(Get-Service | select Status,Name,DisplayName | ConvertTo-Html -Fragment | Out-String)" }
        "/filterServices" { "<a href='/'>Main Menu</a><form action='/filterServices'>Filter:<input Name='Name'></input></form>$(Get-Service $Parameters["Name"] | select Status,Name,DisplayName | ConvertTo-Html -Fragment | Out-String)" }
        
        default { @"
<h1>My Simple Task Manager</h1>
<a href="/showProcesses"><h2>Show Running Processes</h2></a>
<a href="/showServices"><h2>Show Running Services</h2></a>      
"@
         }

    }

}
 

A not too bad chunk of code and we get this:

Awesome Right!

Full code for Start-PoshWebGUI

*NOTE: I do plan on putting this into a module fairly soon hopefully with help and examples. If you want to help out hit me up or fork the repo on GitHub.

 
Function Start-PoshWebGUI ($ScriptBlock)
{
    # We create a scriptblock that waits for the server to launch and then opens a web browser control
    $UserWindow = {
            
            # Wait-ServerLaunch will continually repeatedly attempt to get a response from the URL before continuing
            function Wait-ServerLaunch
            {

                try {
                    $url="http://localhost:8000/"
                    $Test = New-Object System.Net.WebClient
                    $Test.DownloadString($url);
                }
                catch
                { start-sleep -Seconds 1; Wait-ServerLaunch }
 
            }

            Wait-ServerLaunch
            [void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
            [xml]$XAML = @'
            <Window
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                Title="PowerShell HTML GUI" WindowStartupLocation="CenterScreen">

                    <WebBrowser Name="WebBrowser"></WebBrowser>

            </Window>
'@

            #Read XAML
            $reader=(New-Object System.Xml.XmlNodeReader $xaml) 
            $Form=[Windows.Markup.XamlReader]::Load( $reader )
            #===========================================================================
            # Store Form Objects In PowerShell
            #===========================================================================
            $WebBrowser = $Form.FindName("WebBrowser")

            $WebBrowser.Navigate("http://localhost:8000/")

            $Form.ShowDialog()
            Start-Sleep -Seconds 1

            # Once the end user closes out of the browser we send the kill url to tell the server to shut down.
            $url="http://localhost:8000/kill"
            (New-Object System.Net.WebClient).DownloadString($url);
    }
 
    $RunspacePool = [RunspaceFactory]::CreateRunspacePool()
    $RunspacePool.ApartmentState = "STA"
    $RunspacePool.Open()
    $Jobs = @()
 

       $Job = [powershell]::Create().AddScript($UserWindow).AddArgument($_)
       $Job.RunspacePool = $RunspacePool
       $Jobs += New-Object PSObject -Property @{
          RunNum = $_
          Pipe = $Job
          Result = $Job.BeginInvoke()
       }


    # Create HttpListener Object
    $SimpleServer = New-Object Net.HttpListener

    # Tell the HttpListener what port to listen on
    #    As long as we use localhost we don't need admin rights. To listen on externally accessible IP addresses we will need admin rights
    $SimpleServer.Prefixes.Add("http://localhost:8000/")

    # Start up the server
    $SimpleServer.Start()

    while($SimpleServer.IsListening)
    {
        Write-Host "Listening for request"
        # Tell the server to wait for a request to come in on that port.
        $Context = $SimpleServer.GetContext()

        #Once a request has been captured the details of the request and the template for the response are created in our $context variable
        Write-Verbose "Context has been captured"

        # $Context.Request contains details about the request
        # $Context.Response is basically a template of what can be sent back to the browser
        # $Context.User contains information about the user who sent the request. This is useful in situations where authentication is necessary


        # Sometimes the browser will request the favicon.ico which we don't care about. We just drop that request and go to the next one.
        if($Context.Request.Url.LocalPath -eq "/favicon.ico")
        {
            do
            {

                    $Context.Response.Close()
                    $Context = $SimpleServer.GetContext()

            }while($Context.Request.Url.LocalPath -eq "/favicon.ico")
        }

        # Creating a friendly way to shutdown the server
        if($Context.Request.Url.LocalPath -eq "/kill")
        {

                    $Context.Response.Close()
                    $SimpleServer.Stop()
                    break

        }
    
        $Context.Request
        # Handling different URLs

        $result = try {.$ScriptBlock} catch {$_.Exception.Message}

        if($result -ne $null) {
            if($result -is [string]){
                
                Write-Verbose "A [string] object was returned. Writing it directly to the response stream."

            } else {

                Write-Verbose "Converting PS Objects into JSON objects"
                $result = $result | ConvertTo-Json
                
            }
        }

        Write-Host "Sending response of $Result"

        # We convert the result to bytes from ASCII encoded text
        $buffer = [System.Text.Encoding]::ASCII.GetBytes($Result)

        # We need to let the browser know how many bytes we are going to be sending
        $context.Response.ContentLength64 = $buffer.Length

        # We send the response back to the browser
        $context.Response.OutputStream.Write($buffer, 0, $buffer.Length)

        # We close the response to let the browser know we are done sending the response
        $Context.Response.Close()

        $Context.Response
    }

}
 

The Series

More boring stuff today. We are going to cover how to handle URLs in this PowerShell web server thingy and covering handling query strings. Stick with me on this though! I promise we will get to the good stuff soon.

Handling URLs

In part 1 of this series we covered making a basic server that just sends a single response one time back to your web browser.

We saw it responded fine when I went to http://localhost:8000 but what would I do if I wanted to send one response for http://localhost:8000 and a different response for http://localhost:8000/getProcesses ?

We are going to use a simple if-then statement and the $Context.Request variable to set the value of $Result based on the value of $Context.Request.Url.LocalPath.

 
if($Context.Request.Url.LocalPath -eq "/getProcesses")
{

    $result = Get-Process | select name,cpu | ConvertTo-Html | Out-String

}
else 
{

    $result = "<html><body>Hello World!</body></html>"

}
 

The localpath property of url will remove any query strings (We’ll cover those in a bit) and the beginning of the url so that you just get the path that user has requested.

You can see how the ConvertTo-HTML function is going to make this very easy to work with. So what is this going to get us?

We are going to run the code from part 1 with the small change of the if-then statement above and launch Google Chrome and point it at http://localhost:8000. We get the following as a response:

My capture on the PowerShell side should have come to an end, but if I run my code again and this time point my browser at http://localhost:8000/getProcesses, I now get the following:

Awesome! We are now able to serve up completely different user interfaces based on what the user requests with a simple if-then statement. Now onto something that also sounds scary but is cake in PowerShell.

Handling Query Strings

Okay, so in the PowerShell world we have cmdlets or functions that run code and we have parameters that we feed to those cmdlets or functions. In the web world they have URLs instead of cmdlets and what they feed into those URLs is what is called a query string. The query string is basically identical to parameters, it is just passed in a different way.

Basics of a query string (in PowerShell speak)

What a query string looks like is this ?ProcessName=Chrome and breaks down like this:

  • ? - Signifies the beginning of the query string
  • ProcessName(Part 1 of 3 in 1st parameter) - This is the parameter name. This would look like -ProcessName in PowerShell.
  • =(Part 2 of 3 in 1st parameter) - Tells the server that what follows is the value that should be passed to the ProcessName parameter.
  • Chrome(Part 3 of 3 in 1st parameter) - This is the value that is to be used for the ProcessName parameter.

So, if I wanted to write Get-Process -Name Chrome in a URL form it could look like this: /getProcess?Name=Chrome.

If I wanted to send a second parameter I would just add a & to the URL and do another parameter.

So, if I wanted to write Get-Process -Name Chrome -ComputerName MyLabComputer2. It would look like this: /getProcess?Name=Chrome&ComputerName=MyLabComputer2

Not as readable as PowerShell but not terrible once you understand it right?

Handling query strings in PowerShell

So, you are thinking, how do I parse that query string out in PowerShell? If you’re anything like me you’re probably thinking, “Hey, I could split that with a regex or just a couple of splits and I could save it as hashtable! Sweet!” Well my friend. What is even sweeter is the net.httplistener object does that for you already.

So, if my browser sent a request to my PowerShell server with the above URL /getProcess?Name=Chrome&ComputerName=MyLabComputer2. All I would need to do to get at those parameters is this:

 
$Name = $Context.Request.QueryString["Name"]
$ComputerName = $Context.Request.QueryString["ComputerName"]
 

Nice! Now, if only there were a simple way to build a form or something in HTML that would send query strings in the URL back to PowerShell… Hmmm…. What about an HTML form?!?!

Check out how easy this is.

<form action="/getProcesses">
    <label for="Name">Process Name</label>
    <input name="Name"></input>
    <label for="ComputerName">Computer Name</label>
    <input name="ComputerName" value="."></input>
    <button type="Submit">Submit</button>
</form>

There is a parameter called action for the HTML form element that tells the form what URL the parameters should be sent to. The Name of each input sets the parameter name. The value that the user types into that input is the value that gets sent for that parameter. The button just tells the form to submit those parameters to the url path given in the action parameter.

So, if I re-write the code above I used to handle the URL like this:

 
if($Context.Request.Url.LocalPath -eq "/getProcesses")
{

    $Name = $Context.Request.QueryString["Name"]
    $ComputerName = $Context.Request.QueryString["ComputerName"]
    $result = Get-Process -Name $Name -ComputerName $ComputerName | select name,cpu | ConvertTo-Html | Out-String

}
else 
{

    $result = @"
<h1> List Running Processes </h1>
<form action="/getProcesses">
    <label for="Name">Process Name</label>
    <input name="Name"></input>
    <label for="ComputerName">Computer Name</label>
    <input name="ComputerName" value="."></input>
    <button type="Submit">Submit</button>
</form>
"@

}
 

The first time I run my server and point my browser at http://localhost:8000, I will get a nice little HTML form that looks like the following:

Cool, and if I fill it out with Chrome as the process name, leave localhost as the default computer name start my mini PowerShell webserver listening for a request again and click submit. It now responds back with the following:

Awesome! So, I built a PowerShell web server that can prompt for input and render a response in HTML to the end user. All in less than 100 lines of code. Now, stay tuned for the next blog posting where we are going to take this out of the Chrome browser and launch a PowerShell web browser that looks like a standard UI application, because when you are designing a UI for end users anyway you don’t want to have them launching a browser to access what they would consider a desktop application. It would seem tacky I think.

Full Code

 
# Create HttpListener Object
$SimpleServer = New-Object Net.HttpListener

# Tell the HttpListener what port to listen on
#    As long as we use localhost we don't need admin rights. To listen on externally accessible IP addresses we will need admin rights
$SimpleServer.Prefixes.Add("http://localhost:8000/")

# Start up the server
$SimpleServer.Start()

# Tell the server to wait for a request to come in on that port.
$Context = $SimpleServer.GetContext()

#Once a request has been captured the details of the request and the template for the response are created in our $context variable
Write-Verbose "Context has been captured"

# $Context.Request contains details about the request
# $Context.Response is basically a template of what can be sent back to the browser
# $Context.User contains information about the user who sent the request. This is useful in situations where authentication is necessary


# Sometimes the browser will request the favicon.ico which we don't care about. We just drop that request and go to the next one.
do
{

    $Context.Response.Close()
    $Context = $SimpleServer.GetContext()

}while($Context.Request.Url.LocalPath -eq "/favicon.ico")


# Handling different URLs

if($Context.Request.Url.LocalPath -eq "/getProcesses")
{

    $Name = $Context.Request.QueryString["Name"]
    $ComputerName = $Context.Request.QueryString["ComputerName"]
    $result = Get-Process -Name $Name -ComputerName $ComputerName | select name,cpu | ConvertTo-Html | Out-String

}
else 
{

    $result = @"
<h1> List Running Processes </h1>
<form action="/getProcesses">
    <label for="Name">Process Name</label>
    <input name="Name"></input>
    <label for="ComputerName">Computer Name</label>
    <input name="ComputerName" value="."></input>
    <button type="Submit">Submit</button>
</form>
"@

}







# In order to send it to the browser we need to convert it from ASCII encoded text into bytes.
$buffer = [System.Text.Encoding]::ASCII.GetBytes($result)

# We need to let the browser know how many bytes we are going to be sending
$context.Response.ContentLength64 = $buffer.Length

# We send the response back to the browser
$context.Response.OutputStream.Write($buffer, 0, $buffer.Length)

# We close the response to let the browser know we are done sending the response
$Context.Response.Close()

# We stop our server
$SimpleServer.Stop()