Welcome!

By registering with us, you'll be able to discuss, share and private message with other members of our community.

SignUp Now!

Integrating PowerShell commands within C# Code

Aug
2,799
144
In reference to the CsScript command from the .NET CsScript plugin;

Here is a test.csx file,
that allows the calling of a PowerShell 5.1 command from a C# script.

This is just a proof-of-concept to see if it could be done.

Yes, many verbose code comments.

Code:
// test.csx - load a specific .NET Framework System.Management.Automation.dll passed as first argument
// csscript test.csx "C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35\System.Management.Automation.dll"
//
// Your machine contains many variants of System.Management.Automation.dll including PowerShell Core and WinSxS/policy shims.
// Loading the wrong variant causes load failures.
// The Desktop PowerShell .NET Framework build must be used and the script should accept an explicit path to that file.
//
// When you create and invoke a PowerShell instance programmatically with PowerShell.Create()
// you get a noninteractive runspace that does not load user/profile scripts by default,
// so the automatic variable $Profile is often not populated the way it is in an interactive shell.
//
// Get-Date works because it’s a built-in cmdlet available in any runspace;
// $Profile is an automatic session variable that points to profile script paths only
// when the engine has initialized session state and loaded profile files (or when a host sets it).
// Other factors that can make $Profile appear missing or different are execution host differences,
// execution policy, or using a PowerShell Core assembly vs the Desktop PowerShell assembly.
using System;
using System.IO;
using System.Reflection;
using System.Collections;

class Program
{
    static void Main(string[] args)
    {
        // Normalize and accept the full path to the Desktop PowerShell assembly as the first argument
        string explicitPath = (args != null && args.Length > 0) ? NormalizePath(args[0]) : null;
        if (string.IsNullOrEmpty(explicitPath))
        {
            Console.Error.WriteLine("ERROR: No assembly path provided. Invoke as:");
            Console.Error.WriteLine("  csscript test.csx \"C:\\Path\\To\\System.Management.Automation.dll\"");
            return;
        }

        if (!File.Exists(explicitPath))
        {
            Console.Error.WriteLine("ERROR: The specified assembly file does not exist:");
            Console.Error.WriteLine("  " + explicitPath);
            return;
        }

        Assembly psa = TryLoadPowerShellAssembly(explicitPath);
        if (psa == null)
        {
            Console.Error.WriteLine("ERROR: Could not load the specified System.Management.Automation assembly.");
            return;
        }

        Type psType = psa.GetType("System.Management.Automation.PowerShell");
        if (psType == null)
        {
            Console.Error.WriteLine("ERROR: PowerShell type not found in assembly.");
            return;
        }

        MethodInfo createMethod = FindMethod(psType, "Create", BindingFlags.Public | BindingFlags.Static, new Type[0]);
        if (createMethod == null)
        {
            Console.Error.WriteLine("ERROR: PowerShell.Create() method not found.");
            return;
        }

        object psInstance = null;
        try
        {
            psInstance = createMethod.Invoke(null, null);
            if (psInstance == null)
            {
                Console.Error.WriteLine("ERROR: Failed to create PowerShell instance.");
                return;
            }

            MethodInfo addScript = FindMethod(psType, "AddScript", BindingFlags.Public | BindingFlags.Instance, new Type[] { typeof(string) });
            if (addScript == null)
            {
                Console.Error.WriteLine("ERROR: AddScript(string) method not found.");
                return;
            }

//            addScript.Invoke(psInstance, new object[] { "Get-Process" });
//            addScript.Invoke(psInstance, new object[] { "2026-1960" });
            addScript.Invoke(psInstance, new object[] { "get-date" });

            MethodInfo invoke = FindMethod(psType, "Invoke", BindingFlags.Public | BindingFlags.Instance, new Type[0]);
            if (invoke == null)
            {
                Console.Error.WriteLine("ERROR: Invoke() method not found.");
                return;
            }

            object resultsObj = invoke.Invoke(psInstance, null);
            IEnumerable results = resultsObj as IEnumerable;
            if (results == null)
            {
                Console.Error.WriteLine("ERROR: Invoke did not return an enumerable result.");
                return;
            }

            foreach (object psObject in results)
            {
                if (psObject == null)
                {
                    Console.WriteLine("null");
                    continue;
                }

                PropertyInfo baseObjProp = psObject.GetType().GetProperty("BaseObject");
                object baseObj = baseObjProp != null ? baseObjProp.GetValue(psObject, null) : psObject;
                Console.WriteLine(baseObj != null ? baseObj.ToString() : "null");
            }
        }
        finally
        {
            if (psInstance != null)
            {
                MethodInfo dispose = FindMethod(psType, "Dispose", BindingFlags.Public | BindingFlags.Instance, new Type[0]);
                if (dispose != null)
                {
                    try { dispose.Invoke(psInstance, null); } catch { }
                }
            }
        }
    }

    // Trim surrounding quotes, trim whitespace, and expand environment variables
    static string NormalizePath(string path)
    {
        if (string.IsNullOrEmpty(path)) return path;
        path = path.Trim();
        if (path.Length >= 2)
        {
            char first = path[0];
            char last = path[path.Length - 1];
            if ((first == '"' && last == '"') || (first == '\'' && last == '\''))
            {
                path = path.Substring(1, path.Length - 2).Trim();
            }
        }
        try { path = Environment.ExpandEnvironmentVariables(path); } catch { }
        return path;
    }

    // Robust method finder to avoid AmbiguousMatchException
    static MethodInfo FindMethod(Type type, string name, BindingFlags flags, Type[] paramTypes)
    {
        MethodInfo[] methods = type.GetMethods(flags);
        MethodInfo best = null;
        foreach (MethodInfo m in methods)
        {
            if (m.Name != name) continue;
            if (m.IsGenericMethodDefinition) continue;

            ParameterInfo[] ps = m.GetParameters();
            if (ps.Length != paramTypes.Length) continue;

            bool ok = true;
            for (int i = 0; i < ps.Length; i++)
            {
                Type pType = ps[i].ParameterType;
                if (pType.IsByRef) pType = pType.GetElementType();

                Type desired = paramTypes[i];
                if (desired == null) continue;

                if (!pType.IsAssignableFrom(desired))
                {
                    ok = false;
                    break;
                }
            }

            if (!ok) continue;

            if (best == null) { best = m; continue; }

            bool bestExact = ParametersExactlyMatch(best.GetParameters(), paramTypes);
            bool curExact = ParametersExactlyMatch(ps, paramTypes);
            if (curExact && !bestExact) best = m;
        }
        return best;
    }

    static bool ParametersExactlyMatch(ParameterInfo[] parameters, Type[] desired)
    {
        if (parameters.Length != desired.Length) return false;
        for (int i = 0; i < parameters.Length; i++)
        {
            Type pType = parameters[i].ParameterType;
            if (pType.IsByRef) pType = pType.GetElementType();
            if (desired[i] == null) return false;
            if (pType != desired[i]) return false;
        }
        return true;
    }

    static Assembly TryLoadPowerShellAssembly(string explicitPath)
    {
        try
        {
            // Load the exact assembly file the user provided
            return Assembly.LoadFrom(explicitPath);
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine("ERROR: Assembly.LoadFrom failed: " + ex.Message);
            return null;
        }
    }
}

To test the script;
Code:
csscript test.csx "C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35\System.Management.Automation.dll"

Looks like it would be easier just to include the PowerShell stuff in the CsScript plugin.

Again,
just proof of concept,
to see if it could be done.

Ref: CsScript .NET plugin for TCC v36
Ref: C++ and PowerShell
Ref: Run a .PS1 file without PowerShell

Joe
 
The C# code samples that Rob van der Woude has published on his website over the years have been of great benefit to me.

They may also be of interest to others, so posting the link here;


Joe
 
Back
Top