-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Description
I am trying to expose the Microsoft.PowerShell.SDK APIs to native code (C/Rust) with a .NET native host that dynamically loads everything (libhostfxr.so, .NET assemblies, etc) from an existing PowerShell 7 installation (.NET 5). I managed to load and run "pwsh" from native code instead of launching it as a subprocess. While this works (!), it can only accept the same arguments as the real "pwsh" command-line executable.
The real deal would be to load and wrap functions from System.Management.Automation.dll. Imagine being able to call PowerShell.Create(), .AddScript(), .AddCommand(), .Invoke(), etc, all from a C/Rust. API, instead of calling "pwsh" with -EncodedCommand and very little control over the PowerShell engine.
My work in progress code and notes are here. I got started using the HostWithHostFxr sample and wrote my own simplified equivalent in C for Linux. During my tests I figured out how to link and call the nethost library, but I decided against using it for now and simply give it the path to the hostfxr library. In the case of PowerShell, it is relatively easy to find the library (excluding the special PowerShell builds that don't contain it).
I have collected a list of relevant links and references on .NET native hosting:
- https://github.com/PowerShell/PowerShell/tree/master/docs/host-powershell
- https://docs.microsoft.com/en-us/dotnet/core/tutorials/netcore-hosting
- https://github.com/dotnet/runtime/blob/master/docs/design/features/native-hosting.md
- https://github.com/dotnet/runtime/blob/master/docs/design/features/host-components.md
- https://github.com/dotnet/samples/tree/master/core/hosting
- https://github.com/dotnet/runtime/tree/master/src/coreclr/hosts
- https://github.com/dotnet/runtime/tree/master/src/installer/corehost
- https://github.com/dotnet/runtime/blob/master/docs/design/features/host-probing.md
- Trying to load a self-contained dll, with an own runtime-host #35329
- Support custom hosting a hybrid between app and component #35465
- Proposal for allowing "get delegate" functionality on application host context #36990
- What is an "Assembly qualified delegate type name"? docs#16646
- https://github.com/sanosdole/nodeclrhost
A lot of them are issues answered by @vitek-karas so I hope this gets noticed!
Following some of these tips given in the issues above, I loaded pwsh.dll from its install location using "hostfxr_initialize_for_dotnet_command_line" instead of "hostfxr_initialize_for_runtime_config" to workaround the issues with PowerShell being a self-contained executable. I then got it working with a call to hostfxr_run_app, which does some magic under the hood to find the Main entry point and call it. I double checked, I managed to run "pwsh" in-process, no "pwsh" subprocess was created. This alone is sufficient to call "pwsh" without leaking command-line parameters to the entire system, so it is a big win already. Someone could use this technique to launch commands with sensitive data.
Now the problem is that it gets all blurry when trying to go beyond that, especially when trying to load existing . NET APIs and calling them from C. My understanding is that CoreCLR delegates are required to facilitate the bridge between native code and managed code. The sample uses a simple "default" delegate type, and then shows how to make a few custom delegate types, but these appear to require additional definitions in the C# code.
The correct values to be passed to "load_assembly_and_get_function_pointer" aren't so clear to me when trying to load functions like Microsoft.PowerShell.ManagedPSEntry.Main(System.String[]) in pwsh.dll. I tried a lot of combinations only to trigger different errors. I know one is not supposed to call Main directly, but even then I should be able to load and attempt calling it.
If we forget about the pwsh.dll entry point, a valuable function would be [System.Management.Automation.PowerShell]::Create(), a static function inside System.Management.Automation.dll that creates an instance of the PowerShell object. I just don't know how to get it loaded and called without creating a custom delegate. Maybe I need to resort to using lower-level CoreCLR APIs? My understanding is that the hostfxr APIs do a lot of heavy-lifting under the hood, so maybe they are not the most appropriate for this.
Please guide me to the next steps, I'm stuck figuring out how to load random . NET functions and calling them from C# without having to write additional C# boilerplate. Surely there must be a way to do it, just like JNI in Java? I would rather have a lot more boilerplate code in C than boilerplate in C#, because once I get to port this to Rust, I'll be able to write a lot of automatic helpers. There is also the possibility of generating a lot of code based on the .NET type definitions (nothing that a good PowerShell script can't handle).
Metadata
Metadata
Assignees
Labels
Type
Projects
Status