- 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.
To test the script;
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
Please find attached my first .NET plugin for TCC using the experimental support in Version 36.
Ref: .NET Plugins in TCC v36 - Looking for .NET Developers
I have been using the cscs.exe from the CS-Script scripting system to execute .csx and .cs scripts.
Ref: GitHub - oleg-shilo/cs-script.net-framework: A mirror of the oleg-shilo/cs-script repository of CS-Script for .NET Framework. A copy of the repo before the product migration on .NET 5
The CsScript plugin eliminates the need to use cscs.exe now.
The included template.csx works with both...
Ref: .NET Plugins in TCC v36 - Looking for .NET Developers
I have been using the cscs.exe from the CS-Script scripting system to execute .csx and .cs scripts.
Ref: GitHub - oleg-shilo/cs-script.net-framework: A mirror of the oleg-shilo/cs-script repository of CS-Script for .NET Framework. A copy of the repo before the product migration on .NET 5
The CsScript plugin eliminates the need to use cscs.exe now.
The included template.csx works with both...
- Joe Caverly
- Replies: 2
- Forum: Plugins
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