Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Executing PowerShell code at the API level
#1
Background:
Compared to BAT and VBS, PowerShell code is simpler and more understandable, making it very beginner-friendly for programming. Additionally, all Microsoft products have PowerShell interfaces, and there are many powerful open-source libraries available.(https://www.powershellgallery.com/)
Many times, I've encountered various issues when trying to convert PowerShell code to C# using ChatGPT. This conversion often doesn't provide significant value.
 
I have been using the method described in the link below to execute PowerShell code, but it's too slow.
https://www.libreautomate.com/cookbook/P...ython.html
 
I found a method in the link below for executing PowerShell code at the API level.
https://github.com/p3nt4/PowerShdll
 
Is it possible to create a similar class in LA?
 
It would be great if seamless switching and using variables were possible.
For example, the variable name "var1" in C# and "$var1" in PowerShell represent the same variable, with smooth input and output.
I believe this is achievable, perhaps requiring more programming skills.

C# + PowerShell is simply unbeatable. Tongue

E.g:

string var1="""
hello
world
""";

string var2="test.txt";
    
var R = ps.run("""
($var1 -split '\r?\n') -join '|' > $HOME\Desktop\$var2
'hello world'
""");

print.it($"Res: {R}");
#2
https://www.nuget.org/packages/Microsoft.PowerShell.SDK

2 examples.

Code:
Copy      Help
// script "nuget PowerShell.cs"
/*/ nuget ps\Microsoft.PowerShell.SDK; /*/
using System.Management.Automation;

PS();

void PS() {
    var psCode = """
    & 'C:\Program Files\Windows NT\Accessories\wordpad.exe'
    """
;

    using var ps = PowerShell.Create();
    ps.AddScript(psCode);
    ps.Invoke();
}

Code:
Copy      Help
// script "PowerShell2.cs"
/*/ nuget ps\Microsoft.PowerShell.SDK; /*/
using System.Management.Automation;

print.clear();

string code = """
Get-Process | out-string
"""
;

var results = PS.Invoke(code);
foreach (var result in results) {
    print.it(result.ToString());
}


static class PS {
    public static System.Collections.ObjectModel.Collection<PSObject> Invoke(string command) {
        using (var ps = PowerShell.Create()) {
            ps.AddScript(command);
            
            var results = ps.Invoke();
            
            // report non-runspace-terminating errors, if any.
            foreach (var error in ps.Streams.Error) {
                print.it("ERROR: " + error.ToString());
            }

            
            return results;
        }
    }
}
#3
Thank you for sharing.

Using the SDK requires downloading a large number of files, and the execution speed hasn't improved.

I put the PowerShdll source code into the LA and only referenced the System.Management.Automation.dll from the PowerShell 7 installation folder. This allowed me to execute PowerShell code without any additional dependencies, and the execution speed improved significantly.
#4
Does not work here.
Quote:System.Management.Automation.Runspaces.PSSnapInException: Cannot load PowerShell snap-in Microsoft.PowerShell.Diagnostics because of the following error: The PowerShell snap-in module C:\Program Files\PowerShell\7\Microsoft.PowerShell.Commands.Diagnostics.dll does not have the required PowerShell snap-in strong name Microsoft.PowerShell.Commands.Diagnostics, Version=7.2.5.500, Culture=neutral, PublicKeyToken=31bf3856ad364e35, ProcessorArchitecture=MSIL.

 
#5
Set EXE outputPath %folders.ProgramFiles%\PowerShell\7

/*/ role exeProgram; outputPath %folders.ProgramFiles%\PowerShell\7; r %folders.ProgramFiles%\PowerShell\7\System.Management.Automation.dll; /*/
Furthermore, if the generated .exe is not located in the PowerShell 7 installation folder, how can we call the DLLs located in that folder?

Line 326 public class PS
https://github.com/p3nt4/PowerShdll/blob...on.cs#L326

The speed of executing PowerShell code at the API level is acceptable, and it's faster than executing Python.
So, I am looking forward to the following features. The data types in C# and PowerShell are compatible, which is much more convenient than using Python
-----------------------------------------
string var1="""
hello
world
""";

string var2="test.txt";
    
var R = ps.run("""
($var1 -split '\r?\n') -join '|' > $HOME\Desktop\$var2
'hello world'
""");

print.it($"Res: {R}");
#6
Parse variable names and variable values in the Powershell code
 
Code:
Copy      Help
// script "PS_variable values.cs"
/*/ r %folders.ProgramFiles%\PowerShell\7\System.Management.Automation.dll; /*/ //.
using System.Management.Automation.Language;
script.setup(trayIcon: true, sleepExit: true);
//..

string s = """
$Hash = @{
    Path       = "D:\\c.txt"
    Encoding   = 'UTF8'
}
$string = "hello"
$int = 888
"""
;

Token[] tokens;
ParseError[] errors;
var parsedCode = Parser.ParseInput(s, out tokens, out errors);

foreach (var ast in parsedCode.FindAll(astItem => astItem is AssignmentStatementAst, true))
{

    var assignmentAst = (AssignmentStatementAst)ast;
    if (assignmentAst.Left is VariableExpressionAst variableExpressionAst)
    {

        var variableName = variableExpressionAst.VariablePath.UserPath;
        var variableValueAst = assignmentAst.Right;
        var variableValue = variableValueAst.Extent.Text.Trim();
        Console.WriteLine($"variableName: {variableName}");
        Console.WriteLine($"variableValue: {variableValue}");
    }
}
#7
After compiling and generating the EXE, some DLL files(Local: C:\Program Files\PowerShell\7) are always missing in the directory where the EXE file is located. For example:

- Microsoft.PowerShell.Commands.Diagnostics.dll
- Microsoft.PowerShell.Commands.Management.dll
...

How can I ensure that these DLLs are always copied when generating the EXE? Is this possible?
#8
Code:
Copy      Help
/*/ r file1.dll; r file2.dll; ... /*/
#9
Another way. Not tested with PowerShell dlls.
 
Code:
Copy      Help
using System.Runtime.Loader;

AssemblyLoadContext.Default.Resolving += (o, e) => {
    //print.it(e.Name);
    var path = $@"C:\Program Files\PowerShell\7\{e.Name}.dll";
    if (filesystem.exists(path)) return AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
    return null;
};

It will not copy dlls to the exe folder, but will use dlls in the PowerShell folder.
#10
The above code is useful, thank you!
#11
After executing the following code, the following error will be prompted, but it can be successfully executed in the pwsh.exe command line
ERROR: Could not find a part of the path 'C:\Users\Administrator\Documents\LibreAutomate\Main\.compiled\ref'.
ERROR: Unable to find type [LockWorkStationClass].

 
Code:
Copy      Help
/*/ nuget ps\Microsoft.PowerShell.SDK; /*/
using System.Management.Automation;

print.clear();

string code = """
Add-Type @"
using System.Runtime.InteropServices;
public class LockWorkStationClass {
    [DllImport("user32.dll", SetLastError=true)]
    public static extern void LockWorkStation();
}
"@
[LockWorkStationClass]::LockWorkStation()
"""
;

var results = PS.Invoke(code);
foreach (var result in results) {
    print.it(result.ToString());
}


static class PS {
    public static System.Collections.ObjectModel.Collection<PSObject> Invoke(string command) {
        using (var ps = PowerShell.Create()) {
            ps.AddScript(command);
            
            var results = ps.Invoke();
            
            // report non-runspace-terminating errors, if any.
            foreach (var error in ps.Streams.Error) {
                print.it("ERROR: " + error.ToString());
            }

            
            return results;
        }
    }
}
#12
PowerShell requires the ref folder to compile the Add-Type code. But it looks for the folder in a wrong place.

https://github.com/PowerShell/PowerShell/issues/18028

Set role exeProgram. Tested, works. Not tested with Publish.
#13
Thanks for your help.
I'm using the above code in the class file of the HTTP Server. Is there a solution?
#14
If don't want to change role, here is another way.

Code:
Copy      Help
if (script.role == SRole.MiniProgram) {
    var refLink = pathname.getDirectory(System.Reflection.Assembly.GetEntryAssembly().Location) + @"\ref";
    if (!filesystem.exists(refLink)) filesystem.more.createSymbolicLink(refLink, folders.Workspace + @".nuget\ps\ref", CSLink.Junction);
}
#15
It's helpful, thank you.
#16
ERROR: The 'Get-CimInstance' command was found in the module 'CimCmdlets', but the module could not be loaded due to the following error: [Cannot find the built-in module 'CimCmdlets' that is compatible with the 'Core' edition. Please make sure the PowerShell built-in modules are available. They usually come with the PowerShell package under the $PSHOME module path, and are required for PowerShell to function properly.]
For more information, run 'Import-Module CimCmdlets'.
0


The following code will display the above error message when the program is executed after being published using the following menu items in LA:
Run -> Publish... -> +Add .NET Runtime

PS:  The exe file generated directly using the compile button on the toolbar works correctly during testing.
 
Code:
Copy      Help
// script "PowerShell_SDK_T4.cs"
/*/ role exeProgram; nuget ps\Microsoft.PowerShell.SDK; /*/
using System.Management.Automation;

print.clear();

string code = """
$memoryInfo = Get-CimInstance -ClassName Win32_PhysicalMemory
$totalMemoryGB = ($memoryInfo | Measure-Object -Property Capacity -Sum).Sum / 1GB
$totalMemoryGB | Out-String
"""
;

var results = PS.Invoke(code);
foreach (var result in results)
{

    print.it(result.ToString());
    dialog.show(result.ToString());
}


static class PS
{
    public static System.Collections.ObjectModel.Collection<PSObject> Invoke(string command)
    {

        using (var ps = PowerShell.Create())
        {

            ps.AddScript(command);
            
            var results = ps.Invoke();
            
            // report non-runspace-terminating errors, if any.
            foreach (var error in ps.Streams.Error)
            {

                print.it("ERROR: " + error.ToString());
            }

            
            return results;
        }
    }
}

My goal is to execute the above program on a computer that does not have PowerShell 7 and .NET 8 runtime installed.
#17
I noticed that when using the publish function, the DLLs from PowerShell.SDK and .NET 8 overlap, which causes some DLL version inconsistencies. Could this lead to the following situation occurring?
https://www.quickmacros.com/forum/showth...7#pid37997
#18
thanks for your help!

DLLs from PowerShell.SDK, .NET 8, and DLL files from C:\Program Files\LibreAutomate\Roslyn (LA CsScript), if included in a project simultaneously, can cause version inconsistencies during compilation.

Is there a parameter to prevent the generation of folders named 32 and 64? Can the 64-bit AuCpp.dll file be used directly?
#19
I tested your #16 script with postBuild postBuild add .NET.cs;
No errors.
I did not copy Roslyn dlls. Do you really need them? PowerShell includes the main 2 dlls; others probably are not useful in scripts.

Probably the newest PowerShell uses .NET 9. My PowerShell is older and uses .NET 8.

------
Quote:Is there a parameter to prevent the generation of folders named 32 and 64? Can the 64-bit AuCpp.dll file be used directly?
No, but your postBuild script can move the dll and delete the folders. Not tested, but should work.
#20
My previous description wasn't clear enough, so let me give an example:
.NET 8.02 (which might include Newtonsoft.Json.dll version 13.01) is installed on computer A. I use the installed version of LA to create a portable version of LA, and then I copy the portable version of LA to computer B, which has .NET 8.04 installed (which might include Newtonsoft.Json.dll version 13.03). When I compile, will it use the .NET 8.04 version of the DLL?

My idea is that if I press the compile or publish button in the portable version of LA, it should use the .NET runtime files from the portable version, and not use the .NET runtime files from the system.

I am currently stuck on an issue where the .NET 8.0.X runtime and PowerShell 7.0.X runtime files on several computers are always of different versions, causing the program to fail to compile.
As far as I know, the runtime version of PowerShell 7.SDK needs to be matched with a specific .NET 8 runtime version.
#21
Quote:.NET 8.02 (which might include Newtonsoft.Json.dll version 13.01)
Normally .NET does not include Newtonsoft.Json.dll. Maybe the other Newtonsoft.Json.dll comes from another nuget package which is installed in another folder. If you can find that package, try to install it in the ps folder. The NuGet tool probably will pick the one that makes both nuget packages happy.
#22
.NET SDK
https://i.ibb.co/sR869d9/C.jpg
#23
.NET runtime files are in dotnet\shared\...

SDK is used only by the NuGet tool, which runs dotnet.exe, which uses msbuild, which uses the SDK dlls as private dlls, it does not copy them.
#24
Thanks for your explanation and clarification.
 
I'm still exploring: Is there a way to make both the installed version of LA and the portable version of LA use the same version of the .NET 8 runtime when compiling a project or a script file (regardless of the .NET runtime currently installed on the system)? This would solve many potential issues.
#25
Let the portable setup tool update program files. Then they will use the same version.

The Publish tool probably always copies .NET dlls from Program Files, even in portable LA. Cannot change this. The tool uses dotnet.exe.
#26
Quote:The Publish tool probably always copies .NET dlls from Program Files,
Here's the issue:

I want to know if clicking the compile button on the LA toolbar will also copy DLL files from the above path?
When using the portable version of LA for compiling, it would be much better if DLLs could be copied from the dotnet directory in the LA portable folder.
#27
The Compile button copies dlls from the dotnet directory in the LA portable folder.


Forum Jump:


Users browsing this thread: 3 Guest(s)