WPF WebBrowser / ObjectForScripting / Execute PowerShell... a possibly excellent app framework.

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

Read More

Getting the related work items for an Azure Pipeline run in a monorepo Reading time ~3 minutes

## The problem.. I absolutely love this little ['related work......

The quest for an ergonomic keyboard - Part 1 Reading time ~1 minute

If you didn't read my [previous blog post](./2024-05-30-blogging-from-android.md) I'm hunting......

Blogging from Android Reading time ~1 minute

I love to write blog posts and tinker with technology.......