Displaying return value of a cmdlet that uses C# fails

Hey, I’m maybe trying to put together something unconventional and if there is better ways to do this i’m very keen to know.

So, I already got a working cmdlet that has some C# code to access a websocketsharp dll, with it i’m sending a command to a remote websocket server and it is returning the value to the output stream.

I want to incorporate this functionality intoa UD Dashboard that I just started tinkering with but I’m facing issues with Add-Type not working properly in the context of the dashboard.

function Send-Cmd
{
	[CmdletBinding()]
	Param (
		[Parameter(Mandatory = $True)]
        [string]$WSEndpointUrl,
        [Parameter(Mandatory = $True)]
		[string]$Command
	)
	
	try
	{
		Add-Type -Path ".\websocket-sharp.dll"
		Add-Type -Path ".\Newtonsoft.Json.dll"
	}
	catch [System.Reflection.ReflectionTypeLoadException]
	{
   Write-Host "Message: $($_.Exception.Message)"
   Write-Host "StackTrace: $($_.Exception.StackTrace)"
   Write-Host "LoaderExceptions: $($_.Exception.LoaderExceptions)"
	}

	$CurrentlyLoadedAssemblies = [System.AppDomain]::CurrentDomain.GetAssemblies()
    $AssembiesFullInfo = $CurrentlyLoadedAssemblies | Where-Object {
        $_.GetName().Name -eq "Microsoft.CSharp" -or
        $_.GetName().Name -eq "mscorlib" -or
        $_.GetName().Name -eq "System" -or
        $_.GetName().Name -eq "System.Collections" -or
        $_.GetName().Name -eq "System.Core" -or
        $_.GetName().Name -eq "System.IO" -or
        $_.GetName().Name -eq "System.Linq" -or
        $_.GetName().Name -eq "System.Runtime" -or
        $_.GetName().Name -eq "System.Runtime.Extensions" -or
        $_.GetName().Name -eq "System.Runtime.InteropServices" -or
        $_.GetName().Name -eq "System.Threading" -or
        $_.GetName().Name -eq "websocket-sharp" -or
		$_.GetName().Name -eq "Newtonsoft.Json"
    }
    $AssembiesFullInfo = $AssembiesFullInfo | Where-Object {$_.IsDynamic -eq $False}
    $ReferencedAssemblies = $AssembiesFullInfo.FullName | Sort-Object | Get-Unique

	
	$usingStatementsAsString = @"
    using Microsoft.CSharp;
    using System.Collections.Generic;
    using System.Collections;
    using System.IO;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Runtime;
    using System.Threading;
    using System;
    using WebSocketSharp;
	using System.Net.WebSockets;
	using Newtonsoft.Json;
"@

	$TypeDefinition = @"
    $usingStatementsAsString

namespace MyCore.Utils
{
    public class WebSocketClient
    {
        public static string StartWSSession(string url, string cmd)
        {
            string reply = null;
            using (var ws = new WebSocketSharp.WebSocket(url))
            {
                int received = 0;

                // Set the WebSocket events.
                ws.OnOpen += (sender, e) =>
                {
                    Response jsonmsg = new Response();
                    jsonmsg.Identifier = 2000;
                    jsonmsg.Message = cmd;
                    jsonmsg.Name = "PowershellWS";
                    string output = JsonConvert.SerializeObject(jsonmsg);
                    ws.Send(output);
                };

                ws.OnMessage += (sender, e) => {
                    Response response = JsonConvert.DeserializeObject<Response>(e.Data);
                    if (response.Identifier == 2000) {
                        Console.WriteLine(response.Message);
                        reply = response.Message;
                        received ++;
                        ws.Close();
                    }
                };
                ws.OnError += (sender, e) =>
                    Console.WriteLine(e.Message);

                ws.OnClose += (sender, e) =>
                    Console.WriteLine(e.Reason);

                // Connect to the server.
                ws.Connect();

                while (received < 1){Thread.Sleep(1);}
            }
            return reply;
        }
        
    }

    internal class Response
    {
        [JsonProperty(PropertyName = "Identifier")]
        public int Identifier { get; set; }

        [JsonProperty(PropertyName = "Message")]
        public string Message { get; set; }

        [JsonProperty(PropertyName = "Name")]
        public string Name { get; set; }
    }
}
"@
	Add-Type -ReferencedAssemblies $ReferencedAssemblies -TypeDefinition $TypeDefinition
	return [MyCore.Utils.WebsocketClient]::StartWSSession($WSEndpointUrl,$Command)

$WS.Assemblies.Add(".\Newtonsoft.Json.dll")
$WS.Assemblies.Add(".\websocket-sharp.dll")

$WS = New-UDEndpointInitialization -Function @("Send-Cmd")   

$Dashboard = New-UDDashboard -Title "Testing" -Content {
    New-UDCard -Title "Title" -Endpoint {Send-Cmd -WSEndpointUrl ws://websocket-server.com -Command hello}
} -EndpointInitialization $WS
Enable-UDLogging 
Start-UDDashboard -Dashboard $Dashboard -Port 10001

The error message is:

ExecutionService Error executing endpoint 54401398-459e-475f-8f72-94ea871b0be6. c:\Users\<username>\AppData\Local\Temp\babnz2ck.0.cs(69) : The type Newtonsoft.Json.JsonPropertyAttribute exists in both c:\Users\<username>\.vscode\extensions\ms-vscode.powershell-2019.5.0\modules\PowerShellEditorServices\bin\Desktop\Newtonsoft.Json.dll and c:\Users\<username>\.vscode\extensions\ms-vscode.powershell-2019.5.0\modules\PSScriptAnalyzer\1.18.0\Newtonsoft.Json.dll

Hello @hugil it’s amazing to see what uses people have for dashboards. I am replying to this as no-one else has :upside_down_face: I’m not the best person to answer this question either, but I see you are dot sourcing to the .dll files so I am assuming these files are in the same location as the dashboard? Have you tried specifying a full path to .dll files?

@hugil,

It look like when you run the function in VSCode, Newtonsoft.Json.dll is already loaded. This presumably won’t be a problem in your production environment. I would try adding a check.

If ( -not [type]::GetType( 'Newtonsoft.Json.JsonPropertyAttribute' ) )
    { Add-Type -Path ".\Newtonsoft.Json.dll" }

Thanks,
Tim Curwick