Skip to content

Commit

Permalink
Implementing runspaced async PowerShell execution #5
Browse files Browse the repository at this point in the history
  • Loading branch information
megastary committed Aug 24, 2021
1 parent 38194b4 commit e61f78a
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

<ItemGroup>
<PackageReference Include="Cake.Powershell" Version="1.0.1" />
<PackageReference Include="Microsoft.PowerShell.SDK" Version="7.1.4" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.902.49" />
<PackageReference Include="System.Management.Automation" Version="7.1.4" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
using Microsoft.Web.WebView2.Core;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Threading;

namespace HoubyStudio.LazyAdmin.DesktopApp
{
Expand All @@ -16,25 +19,175 @@ namespace HoubyStudio.LazyAdmin.DesktopApp
public class LazyAdminPowerShell
{

public TextBox MockPowerShell;
//private Dispatcher dispatcher = Dispatcher.CurrentDispatcher;

private PowerShell _powerShell;
private RunspacePool runespacePool = RunspaceFactory.CreateRunspacePool();
public void ReceiveMessage(object sender, CoreWebView2WebMessageReceivedEventArgs args)
{
MockPowerShell.Text = "Staaaaaaarting";
LazyAdminWebView.ShowMessage("Execution started");
////try
////{
//// Open new runspace
//runspacePool.Open();

//public static TextBox PowerShell
//{
// set => _powerShell = value;
//}
//// Create PowerShell object
//PowerShell powerShell = PowerShell.Create();

public void ReceiveMessage(object sender, CoreWebView2WebMessageReceivedEventArgs args)
//// Assign PowerShell object to runspace
//powerShell.RunspacePool = runspacePool;

//// Command received from WebView
//string data = args.WebMessageAsJson;

//// Debug
//MockPowerShell.Text = data;

//// Add command to PowerShell object
//powerShell.AddCommand("Get-Command");

//// Invoke the command asynchronously.
//IAsyncResult gpcAsyncResult = powerShell.BeginInvoke();
//// Get the results of running the command.
//PSDataCollection<PSObject> gpcOutput = powerShell.EndInvoke(gpcAsyncResult);
////}
////catch
////{
//// MockPowerShell.Text = "";
////}
//runspacePool.Close();


// TODO: If running command within JEA session
//string computerName = "SERVER01";
//string configName = "JEAMaintenance";
// See https://docs.microsoft.com/dotnet/api/system.management.automation.pscredential

// If running on local machine
// Needs to run elevated to be able to remote on localhost
//WSManConnectionInfo connectionInfo = new WSManConnectionInfo();

//// If loading alternate credentials from credential store
//System.Security.SecureString password = new();
//PSCredential creds = new("test", password);// create a PSCredential object here

//// Run with alternate credentials on remote host
//WSManConnectionInfo connectionInfoRemoteCred = new WSManConnectionInfo(
// false, // Use SSL
// computerName, // Computer name
// 5985, // WSMan Port
// "/wsman", // WSMan Path
// // Connection URI with config name
// string.Format(CultureInfo.InvariantCulture, "http://schemas.microsoft.com/powershell/{0}", configName),
// creds); // Credentials

//// Run with default credentials on remote host
//WSManConnectionInfo connectionInfoRemoteDefaultCred = new WSManConnectionInfo(new Uri($"http://{computerName}:5985/WSMAN"));
//connectionInfoRemoteDefaultCred.ShellUri = string.Format(CultureInfo.InvariantCulture, "http://schemas.microsoft.com/powershell/{0}", configName);

// Now, use the connection info to create a runspace where you can run the commands
using (Runspace runspace = RunspaceFactory.CreateRunspace(/*connectionInfo*/))
{
// Open the runspace
runspace.Open();

using (PowerShell ps = PowerShell.Create())
{
// Set the PowerShell object to use the JEA runspace
ps.Runspace = runspace;

// Now you can add and invoke commands
ps.AddCommand("Get-Process");

// Simple handle PowerShell async
//IAsyncResult gpcAsyncResult = ps.BeginInvoke();
//// Get the results of running the command.
//PSDataCollection<PSObject> gpcOutput = ps.EndInvoke(gpcAsyncResult);

// Simple handle PowerShell sync
//foreach (var result in ps.Invoke())
//{
// Console.WriteLine(result);
// MockPowerShell.Text += result;
//}

// Handle properly asyncwith events and all

// Add the event handlers. If we did not care about hooking the DataAdded
// event, we would let BeginInvoke create the output stream for us.
PSDataCollection<PSObject> output = new();
output.DataAdded += new EventHandler<DataAddedEventArgs>(Output_DataAdded);
ps.InvocationStateChanged += new EventHandler<PSInvocationStateChangedEventArgs>(Powershell_InvocationStateChanged);

// Invoke the pipeline asynchronously.
IAsyncResult asyncResult = ps.BeginInvoke<PSObject, PSObject>(null, output);

PSDataCollection<PSObject> gpcOutput = ps.EndInvoke(asyncResult);

// This is how different thread may access function from main thread
App.Current.Dispatcher.BeginInvoke(new Action(() =>
{
MainWindow.ShowMessageFromThread();
}));
// This throws error
//LazyAdminWebView.ShowMessage("Endded invoke");


// Wait for things to happen. If the user hits a key before the
// script has completed, then call the PowerShell Stop() method
// to halt processing.
//Console.ReadKey();
//if (ps.InvocationStateInfo.State != PSInvocationState.Completed)
//{
// // Stop the invocation of the pipeline.
// Console.WriteLine("\nStopping the pipeline!\n");
// ps.Stop();

// // Wait for the Windows PowerShell state change messages to be displayed.
// System.Threading.Thread.Sleep(500);
// Console.WriteLine("\nPress a key to exit");
// Console.ReadKey();
//}
}

// Close the runspace
runspace.Close();
}
}

/// <summary>
/// The output data added event handler. This event is called when
/// data is added to the output pipe. It reads the data that is
/// available and displays it on the console.
/// </summary>
/// <param name="sender">The output pipe this event is associated with.</param>
/// <param name="e">Parameter is not used.</param>
private void Output_DataAdded(object sender, DataAddedEventArgs e)
{
try
PSDataCollection<PSObject> myp = (PSDataCollection<PSObject>)sender;

Collection<PSObject> results = myp.ReadAll();
foreach (PSObject result in results)
{
// TODO: Replace with args.WebMessageAsJson and handle data accordingly
//_powerShell = args.TryGetWebMessageAsString();
//LazyAdminWebView.ShowMessage("Result mate");
//dispatcher.Invoke((Action)(() => MockPowerShell.Text = result + "`r`n"));
}
catch
}

/// <summary>
/// This event handler is called when the pipeline state is changed.
/// If the state change is to Completed, the handler issues a message
/// asking the user to exit the program.
/// </summary>
/// <param name="sender">This parameter is not used.</param>
/// <param name="e">The PowerShell state information.</param>
private void Powershell_InvocationStateChanged(object sender, PSInvocationStateChangedEventArgs e)
{
//dispatcher.Invoke((Action)(() => MockPowerShell.Text = ("PowerShell object state changed: state: {0}\n", e.InvocationStateInfo.State) + "`r`n"));
if (e.InvocationStateInfo.State == PSInvocationState.Completed)
{
//_powerShell = "";
//dispatcher.Invoke((Action)(() => MockPowerShell.Text = "Processing completed, press a key to exit!"));
//MockPowerShell.Text = "Processing completed, press a key to exit!";
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,33 @@ public static WebView2 WebView
/// </summary>
/// <returns> Usually the returned file path will conform to this format:
/// <c>C:\Users\{username}\AppData\Local\LazyAdmin\EBWebView\WebResources\index.html</c></returns>
private static readonly Uri _indexFilePath = new Uri(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "LazyAdmin/EBWebView/WebResources/index.html"));
private static readonly Uri _indexFilePath = new(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "LazyAdmin/EBWebView/WebResources/index.html"));

/// <summary>
/// Displays alert message within the WebView2 control.
/// </summary>
/// <param name="Message">String displayed in the alert popup.</param>
public static void ShowMessage(string Message)
{
_webView.CoreWebView2.ExecuteScriptAsync($"alert('{Message}')");
}

/// <summary>
/// Posts WebMessage in the JSON form to the WebView2 control.
/// </summary>
/// <param name="Message">JSON sent to the WebView2 control.</param>
public static void PostWebMessageAsJSON(string Message)
{
PowerShellData jsonMessage = new PowerShellData(Message);
PowerShellData jsonMessage = new(Message);

string jsonString = JsonSerializer.Serialize(jsonMessage);
_webView.CoreWebView2.PostWebMessageAsJson(jsonString);
}

/// <summary>
/// Initializes new WebView2 instance with custom environment settings.<br />
/// Then ensures latest Lazy Admin UI files are present and loads them within the WebView2 control.
/// </summary>
public static async void InitializeWebView()
{
// Initialize WebView with custom environment settings
Expand All @@ -71,7 +83,7 @@ public static async void InitializeWebView()
//await _webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync("window.chrome.webview.addEventListener(\'message\', event => alert(event.data));");

// Register event listener for PowerShell handler
//_webView.CoreWebView2.WebMessageReceived += LazyAdminPowerShell.ReceiveMessage;
_webView.CoreWebView2.WebMessageReceived += MainWindow.LazyAdminPwsh.ReceiveMessage;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ namespace HoubyStudio.LazyAdmin.DesktopApp
public partial class MainWindow : Window
{

private static LazyAdminPowerShell _lazyAdminPwsh = new();

public static LazyAdminPowerShell LazyAdminPwsh
{
get => _lazyAdminPwsh;
set => _lazyAdminPwsh = value;
}

public MainWindow()
{
// Required. Loads the compiled page of a component from XAML.
Expand All @@ -19,8 +27,7 @@ public MainWindow()
// Map webView generated from XAML to LazyAdminWebView, which initializes component.
LazyAdminWebView.WebView = webView;

// TEST: Map mock PowerShell (TextBox) from XAML to
//LazyAdminPowerShell.PowerShell = PowerShell;
LazyAdminPwsh.MockPowerShell = PowerShell;
}

protected override void OnContentRendered(EventArgs e)
Expand Down Expand Up @@ -50,5 +57,10 @@ private void Execute_Click(object sender, RoutedEventArgs e)
{
LazyAdminWebView.PostWebMessageAsJSON(PowerShell.Text);
}

public static void ShowMessageFromThread()
{
LazyAdminWebView.ShowMessage("Ended");
}
}
}

This file was deleted.

0 comments on commit e61f78a

Please sign in to comment.