Sitelet https://web.archive.org/web/20210201005941/https://github.com/PowerShell/PowerShell/issues/13755
Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Microsoft.PowerShell.SDK doesn't work in a AspNetCore Blazor WebAssembly project #13755

Open
fMichaleczek opened this issue Oct 7, 2020 · 17 comments

Comments

@fMichaleczek
Copy link

@fMichaleczek fMichaleczek commented Oct 7, 2020 •

transferred from dotnet/aspnetcore#25844

When using the preview of AspNetCore Blazor WebAssembly and attempting to utilize the Microsoft.PowerShell.SDK package, Blazor WebAssembly is unable to load the System.Management.Automation.dll

Steps to reproduce

  • Create new Blazor WASM project. (net5-preview.7)
  • Update project file according to this guide.
  • Add Microsoft.PowerShell.SDK package reference
  • Do something with a PowerShell host Example repo

Expected behavior

it works

Actual behavior

Exceptions:

Could not load file or assembly 'System.Management.Automation, Version=7.0.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of its dependencies.

Environment data

  • PowerShell 7.0.3
  • AspNetCore Blazor WebAssembly 5.0 Preview 7
@iSazonov
Copy link
Collaborator

@iSazonov iSazonov commented Oct 7, 2020

Could you test with Microsoft.PowerShell.SDK 7.1 RC1?

@fMichaleczek
Copy link
Author

@fMichaleczek fMichaleczek commented Oct 7, 2020

@iSazonov In april, I put a demo on twitter of a working prototype on Mono.Wasm/PowerShell 7 hosted on Github Pages.

I will submit a working PR soon for net5.0 RC1, but there is a lot of subjects like fileless (on startup, help system, module), trimming, preprocessor, async, sdk etc... Wasm is an embedded stack so most of the time, it's not a question, it's a behaviour : the minimal path/size/features.

For example, the PowerShell master repo is not well compatible with this kind of restriction : <ThreadPoolMaxThreads>1</ThreadPoolMaxThreads>, so the HttpClient is unusable from the API which is async only.

You can try on my previous github page demo :

if (-not $task) {
  $task = [Net.Http.HttpClient]::new().GetStringAsync("https://gist.githubusercontent.com/IISResetMe/bcbee5f504c25b166003/raw/4ad303f09088ef38aa363863a93c33969080f6ae/Get-AST.ps1")
  $task.Wait()
}
$task.Result
# 1st Execution : Exception calling "Wait" with "0" argument(s): "Cannot wait on monitors on this runtime."
# 2nd execution : OK

I will use this issue as a home to publish technical informations and read community's suggestions.

@fMichaleczek
Copy link
Author

@fMichaleczek fMichaleczek commented Oct 7, 2020 •

Why there is no TFM for WebAssembly

  • WebAssembly is more like an instruction set (such as x86 or x64) than like an operating system. And we generally don’t offer divergent APIs between different architectures.
  • WebAssembly’s execution model in the browser sandbox is a key differentiator, but we decided that it makes more sense to only model this as a runtime check. Similar to how you check for Windows and Linux, you can use the OperatingSystem type. Since this isn’t about instruction set, the method is called IsBrowser() rather than IsWebAssembly().
  • There are runtime identifiers (RID) for WebAssembly, called browser and browser-wasm. They allow package authors to deploy different binaries when targeting WebAssembly in a browser. This is especially useful for native code which needs to be compiled to web assembly beforehand.

New patterns

guard the call :

private static string GetLoggingDirectory()
{
    //...

    if (!OperatingSystem.IsBrowser())
    {
        string exePath = Process.GetCurrentProcess().MainModule.FileName;
        string folder = Path.GetDirectoryName(exePath);
        return Path.Combine(folder, "Logging");
    }
    else
    {
        return string.Empty;
    }
}

mark the member as being unsupported :

[UnsupportedOSPlatform("browser")]
private static string GetLoggingDirectory()
{
    //...

    string exePath = Process.GetCurrentProcess().MainModule.FileName;
    string folder = Path.GetDirectoryName(exePath);
    return Path.Combine(folder, "Logging");
}

You have to explicitly indicate that you intend to support your project in Blazor Web Assembly by adding the item to your project file:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <SupportedPlatform Include="browser" />
  </ItemGroup>
</Project>

Microsoft.NetCore.App APIs unsupported by Blazor WebAssembly

Entire assemblies :

  • System.Diagnostics.FileVersionInfo
  • System.Diagnostics.Process
  • System.IO.Compression.Brotli
  • System.IO.FileSystem.Watcher
  • System.IO.IsolatedStorage
  • System.IO.Pipes
  • System.Net.Mail
  • System.Net.NameResolution
  • System.Net.NetworkInformation
  • System.Net.Ping
  • System.Net.Requests
  • System.Net.Security
  • System.Net.Sockets
  • System.Net.WebClient
  • System.Security.Cryptography.Csp
  • System.Security.Cryptography.Encoding
  • System.Security.Cryptography.Primitives
  • System.Security.Cryptography.X509Certificates

Partial / Limited / Restrictions :

  • System.Console
  • System.ComponentModel
  • System.Net
  • System.Net.Http
  • System.Net.WebSockets
  • System.Security.Authentication.ExtendedProtection
  • System.Security.Cryptography
  • System.Threading

Source :

@fMichaleczek
Copy link
Author

@fMichaleczek fMichaleczek commented Oct 7, 2020 •

General Trimming Options

MSBuild Property Name Default Value Type Description
PublishTrimmed false bool Enable/Disable Trimming
TrimMode CopyUsed enum:CopyUsed/Link CopyUsed: Assembly trimming, Link: Member trimming
SuppressTrimAnalysisWarnings true bool Suppress trim analysis warnings
ILLinkWarningLevel 999 int ILLink Warning Level
ILLinkTreatWarningsAsErrors false bool ILLink treat warnings as errors

Framework Feature Switches

MSBuild Property Name Description Trimmed when Blazor Specific
DebuggerSupport Any dependency that enables better debugging experience False No
EnableUnsafeUTF7Encoding Insecure UTF-7 encoding False No
EnableUnsafeBinaryFormatterSerialization BinaryFormatter serialization False No
EventSourceSupport Any EventSource related code or logic False No
InvariantGlobalization All globalization specific code and data True No
UseSystemResourceKeys Any localized resources for system assemblies, such as error messages True No
HttpActivityPropagationSupport Any dependency related to diagnostics support for System.Net.Http False No
TrimmerRemoveSymbols Remove Symbols False No
BlazorEnableTimeZoneSupport Remove TimeZone Support False Yes
MSBuild Property Name AppContext Setting
DebuggerSupport System.Diagnostics.Debugger.IsSupported
EnableUnsafeUTF7Encoding System.Text.Encoding.EnableUnsafeUTF7Encoding
EnableUnsafeBinaryFormatterSerialization System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization
EventSourceSupport System.Diagnostics.Tracing.EventSource.IsSupported
InvariantGlobalization System.Globalization.Invariant
UseSystemResourceKeys System.Resources.UseSystemResourceKeys
HttpActivityPropagationSupport System.Net.Http.EnableActivityPropagation
BlazorEnableTimeZoneSupport ?

source :

@fMichaleczek
Copy link
Author

@fMichaleczek fMichaleczek commented Oct 7, 2020

@SteveL-MSFT I will need some advices/directions about LogProvider. It's maybe a good candidat for your plugin system ?
With the new features in msbuild, we could add an external logger library, mark the consumer with UnsupportedOSPlatform and trim the library out to the other platforms when publish.( I will ask again for Android on PWSH7.2/net6.0 timeline, logprovider is a wall in the boot startup)

internal static class PSEtwLog
{
#if UNIX
private static PSSysLogProvider provider;
#else
private static PSEtwLogProvider provider;
#endif
/// <summary>
/// Class constructor.
/// </summary>
static PSEtwLog()
{
#if UNIX
provider = new PSSysLogProvider();
#else
provider = new PSEtwLogProvider();
#endif
}

Temporary Workaround

internal class PSSysLogProvider : LogProvider
 {
        private static bool isBrowser;

        private static SysLogProvider s_provider;

        static PSSysLogProvider()
        {
            if (OperatingSystem.IsBrowser())
            {
                isBrowser = true;
            }
            else 
            {
                s_provider = new SysLogProvider(PowerShellConfig.Instance.GetSysLogIdentity(),
                                                PowerShellConfig.Instance.GetLogLevel(),
                                                PowerShellConfig.Instance.GetLogKeywords(),
                                                PowerShellConfig.Instance.GetLogChannels());
            }
        }

        internal void WriteEvent(PSEventId id, PSChannel channel, PSOpcode opcode, PSTask task, LogContext logContext, string payLoad)
        {
            if (isBrowser)
            {
                Console.WriteLine($"Id:       {id}\n" + 
                                  $"Channel:  {channel}\n" + 
                                  $"Task:     {task}\n" + 
                                  $"OpCode:   {opcode}\n" +
                                  $"Severity: {GetPSLevelFromSeverity(logContext.Severity)}\n" +
                                  $"Context: \n{LogContextToString(logContext)}" +
                                  $"PayLoad:  {payLoad}"  +
                                  $"UserData: {GetPSLogUserData(logContext.ExecutionContext)}\n");
            }
            else
            {
                s_provider.Log(id, channel, task, opcode, GetPSLevelFromSeverity(logContext.Severity), DefaultKeywords,
                           LogContextToString(logContext),
                           GetPSLogUserData(logContext.ExecutionContext),
                           payLoad);
            }
        }
}
@iSazonov
Copy link
Collaborator

@iSazonov iSazonov commented Oct 7, 2020

I will need some advices/directions about LogProvider. It's maybe a good candidat for your plugin system ?

We could think about ILogger

@iSazonov iSazonov added Issue-Meta and removed Issue-Question labels Oct 7, 2020
@fMichaleczek
Copy link
Author

@fMichaleczek fMichaleczek commented Oct 7, 2020

We could think about ILogger

With a dependency injection system or by adding an internal static property Provider to PSEtwLog ?
The problem is more about this area than ILogger. Logger is the second thing in a DI after Configuration, so it's maybe an interesting point for a plugin system in SMA.

@iSazonov About trimming, I know it's an area than interest you, I need a script to benchmark size/performance/GC of each MSBuild Property (there is too much properties, I need raw data to choose). Do you know if something already exists ? I will have a look of DotNetBenchmark.

@iSazonov
Copy link
Collaborator

@iSazonov iSazonov commented Oct 8, 2020

With a dependency injection system or by adding an internal static property Provider to PSEtwLog ?

We have no need to use DI. I guess it will be simple custom implementation of the ILogger.
Existing PowerShell logging/tracing looks very expensive. With migrating to ILogger PowerShell would get better performance. It also fits well with the idea of PowerShell ​​_subsystems_ that MSFT team is developing.

Do you know if something already exists ? I will have a look of DotNetBenchmark.

  • DotNetBenchmark
  • PerfView
  • WPR/WPA
  • dotnet trace
  • VS Profiler
  • experimental PowerShell script profiler (draft PR)
@fMichaleczek
Copy link
Author

@fMichaleczek fMichaleczek commented Oct 8, 2020

After reading how works this plugin system DotNetCorePlugins, I'm afraid that the compatibility with Wasm will be an extra cost to implement.

FI, this is a sample for lazy load assemblies in wasm (through the router and url browsing).

@inject Microsoft.AspNetCore.Components.WebAssembly.Services.LazyAssemblyLoader assemblyLoader

<Router AppAssembly="@typeof(Program).Assembly" AdditionalAssemblies="@lazyLoadedAssemblies" OnNavigateAsync="@OnNavigateAsync">...</Router>

@code {
    private List<Assembly> lazyLoadedAssemblies = new List<Assembly>();

    private async Task OnNavigateAsync(NavigationContext args)
    {
            if (args.Path.EndsWith("/robot"))
            {
                LazyLoadedAssemblies.AddRange(await assemblyLoader.LoadAssembliesAsync(new List<string>() { "robot.dll" }));
            }
    }
}

source : Lazy load assemblies in ASP.NET Core Blazor WebAssembly

About ILogger, I will continue for Blazor to use Console.WriteLine inside SMA Logger code until the new logger will be available. There is only one destination to ILogger and Console : the browser console, the difference is only about formatting, so it doesn't matter now.

@SteveL-MSFT
Copy link
Member

@SteveL-MSFT SteveL-MSFT commented Oct 13, 2020

For browser scenario, it seems that logging should just be disabled as it's not obvious to me where the logs would go to be something you can inspect later. Sending it to the console (which presumably would show up in output) might be ok for development purposes, but not general usage.

@vexx32
Copy link
Collaborator

@vexx32 vexx32 commented Oct 13, 2020

That just tells me that we need to have a pluggable logging destination, then. It not being obvious is a reason to leave it to the person implementing it in nonstandard scenarios, not disabling it entirely.

@JustinGrote
Copy link

@JustinGrote JustinGrote commented Oct 13, 2020 •

On logging, here's the "best practice" apparently: https://docs.microsoft.com/en-us/aspnet/core/blazor/fundamentals/logging?view=aspnetcore-3.1#blazor-webassembly

Learn about logging in Blazor apps, including log level configuration and how to write log messages from Razor components.
@fMichaleczek
Copy link
Author

@fMichaleczek fMichaleczek commented Oct 13, 2020 •

Microsoft.Extensions.* are the librairies from aspnetcore team, but starting net5.0, they are splitted into runtime and aspnetcore
dimportant update on dotnet/extensions

We need a layer with ILogger, and a way to specify an already ILogger to SMA in App Mode (through InitialSessionState ?).

The only one logger extension for Blazor is BlazorExtensions.Logging. It's an interesting Logger because it implements the JS API Console.Table.
https://github.com/BlazorExtensions/Logging

@SteveL-MSFT AspNetCore use the logger to write error in the console (developper tools) like Javascript. To debug the C# code, we use the Browser debugger, it's very weird, you trace C# powershell source inside the browser tools. More informations here : Blazor Client Side Debugging

GitHub
Microsoft Extension Logging implementation for Blazor - BlazorExtensions/Logging
@fMichaleczek
Copy link
Author

@fMichaleczek fMichaleczek commented Oct 13, 2020

Working :

$PSVersionTable | Out-String
Get-ChildItem -Path / -Recurse | Out-String
Get-ChildItem env: | Out-String
Get-Variable | Out-String
Get-Module -ListAvailable | Out-String
Get-Command | Out-String
$PSVersionTable | ConvertTo-Json | Out-String
ConvertFrom-Markdown -InputObject "test" | Out-String
Get-Module | ConvertTo-Html -Fragment | Out-String
Get-Help -Name Get-Command | Out-String

Not Working : Update Help + Web Cmdlets + More

# click two times on the Run as a workaround. Need 2 invocations to handle the async task.
if (-not $task) {
    $task = [Net.Http.HttpClient]::new().GetStringAsync("https://gist.githubusercontent.com/IISResetMe/bcbee5f504c25b166003/raw/4ad303f09088ef38aa363863a93c33969080f6ae/Get-AST.ps1")
}
try {
   $task.Wait() # Cannot wait on monitors on this runtime 
}
catch {
   $_ | Out-String
}
$task.Result | Out-String
@TylerLeonhardt
Copy link
Member

@TylerLeonhardt TylerLeonhardt commented Dec 6, 2020 •

There is some work going on in OmniSharp/csharp-language-server-protocol#456 to allow the library that we use in PowerShellEditorServices to work in Blazor WASM. With that work + work to PowerShell to get it working in Blazor WASM, we can create a PowerShell editing experience that's entirely in the browser like what the Bicep team did here:

https://bicepdemo.z22.web.core.windows.net/experiment/lsp/index.html

@fMichaleczek
Copy link
Author

@fMichaleczek fMichaleczek commented Dec 7, 2020

@TylerLeonhardt Nice ! I started a new job, and we are confined in Paris. I will try to make an update the next week.

@JustinGrote
Copy link

@JustinGrote JustinGrote commented Dec 7, 2020

@TylerLeonhardt that Bicep POC is really inspiring! Thanks for sharing.

@SteveL-MSFT SteveL-MSFT added this to the Future milestone Feb 1, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

6 participants