I build a 32-bit .NET DLL that executes PowerShell scripts.
I need it to be able to run scripts alternatively as 64-bit and 32-bit.
I already know how to do it with the command line:
C:\Windows\Sysnative\cmd /c powershell -ExecutionPolicy ByPass "& 'script.ps1' arguments"
C:\Windows\SysWOW64\cmd /c powershell -ExecutionPolicy ByPass "& 'script.ps1' arguments"
But I need to be able to use the interface to C#, with either the System.Management.Automation.PowerShell class or the System.Management.Automation.Runspaces.Pipeline class, in order to asynchronously collect outputs from the script.
The comment from #PetSerAl is the solution. With an out of process runspace, I can change the bitness.
I copy his code here:
using System;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
public static class TestApplication {
public static void Main() {
Console.WriteLine(Environment.Is64BitProcess);
using(PowerShellProcessInstance pspi = new PowerShellProcessInstance()) {
string psfn = pspi.Process.StartInfo.FileName;
psfn=psfn.ToLowerInvariant().Replace("\\syswow64\\", "\\sysnative\\");
pspi.Process.StartInfo.FileName=psfn;
using(Runspace r = RunspaceFactory.CreateOutOfProcessRunspace(null, pspi)) {
r.Open();
using(PowerShell ps = PowerShell.Create()) {
ps.Runspace=r;
ps.AddScript("[Environment]::Is64BitProcess");
foreach(PSObject pso in ps.Invoke()) {
Console.WriteLine(pso);
}
}
}
}
}
}
Related
If I run function prompt {"PS: $(get-date)>"} in the terminal it changes the prompt.
how can I run this command from a c# cmdlet, for example, what I'm trying is this:
protected override void ProcessRecord()
{
Host.UI.Write(ConsoleColor.Green, Host.UI.RawUI.BackgroundColor, "function prompt {\"PS: $(get-date)> \"}");
}
But it just prints that script in a new line.
(Using PS7.0)
It appears that the correct question is "how to run script from a c# cmdlet"
These two lines will run a script that modify the prompt to display the current time.
ScriptBlock block = SessionState.InvokeCommand.NewScriptBlock("function prompt {\"PS: $(get-date)> \"}");
SessionState.InvokeCommand.InvokeScript(SessionState, block);
First create a .ps1 file named prompt_change.ps1:
function prompt {"PS: $(Get-Date)>"}
Then you can run this from powershell console to change your powershell prompt from C#:
$code = #'
using System;
namespace TestTest {
public class Program {
public static void Main( string[] args ) {
var ps = PowerShell.Create();
ps.AddScript(#"Full path of prompt_change.ps1 without double backslashes").Invoke();
}
}
}
'#
Add-Type -TypeDefinition $code -Language CSharp
Invoke-Expression "[TestTest.Program]::Main()"
I have been looking around here but unable to get specifics on implementing this PowerShell cmdlet in C#. I attempted the following but failed to get it to compile and run.
The cmdlet I would like to run in PowerShell from C#:
Restart-Computer -Computername (Get-Content C:\machineslist.txt) -Credential Administrator -Force
Here is my humble attempt:
PowerShell ps = PowerShell.Create();
ps.AddCommand("Restart-Computer");
ps.AddParameter("-ComputerName");
ScriptBlock filter2 = ScriptBlock.Create("(Get-Content C:\\machineslist.txt)");
ps.AddParameter("FilterScript2", filter2);
ps.AddParameter("-Credential");
ps.AddArgument("Administrator");
//not sure how to add password
ps.AddParameter("-Force");
foreach (PSObject result in ps.Invoke())
{
Console.WriteLine(
"{0,-24}{1}",
result.Members["Length"].Value,
result.Members["Name"].Value);
} // End foreach
To make this code snippet to compile and run, you will first need to reference the System.Management.Automation assembly (located under C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\3.0).
You do not need a ScriptBlock as it increases complexity, it's easier to just add the script with AddScript.
You do not need to prefix parameters with -
To pass credentials you can use PSCredential. Normally you would provide a secure string, you can translate a string into a secure string using NetworkCredential as an helper.
You will need to handle errors as well, but this is out of scope for this question!
Enjoy :-)
using System;
using System.Management.Automation;
using System.Net;
using System.Security;
namespace Sample
{
class Program
{
static void Main(string[] args)
{
PowerShell ps = PowerShell.Create();
ps.AddScript("Get-Content C:\\machineslist.txt");
ps.AddCommand("Restart-Computer");
SecureString secureString = new NetworkCredential("", "Password").SecurePassword;
PSCredential psc = new PSCredential("Administrator", secureString);
ps.AddParameter("Credential", psc);
ps.AddParameter("Force");
// Simulation only
ps.AddParameter("WhatIf");
var results = ps.Invoke();
foreach (var error in ps.Streams.Error)
{
Console.WriteLine(error);
}
foreach (PSObject result in results)
{
Console.WriteLine(result);
//Console.WriteLine("{0,-24}{1}", result.Members["Length"].Value, result.Members["Name"].Value);
}
}
}
}
I am currently trying to use c# to call the Get-SMBShare which can be used in Powershell... however, it's throwing this error:
Exception:Caught: "The term 'Get-SMBShare' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again." (System.Management.Automation.CommandNotFoundException)
A System.Management.Automation.CommandNotFoundException was caught: "The term 'Get-SMBShare' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again."
Time: 25/10/2015 19:17:59
Thread:Pipeline Execution Thread[6028]
My first language is PowerShell, so I'm trying to translate a GUI tool from PowerShell to C#, and the tool uses hundreds of PS commands - is there something I should be calling? I'm testing things out in console here.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Threading.Tasks;
namespace ConsoleApplication2
{
class Program
{
private static void GetShareNames()
{
// Call the PowerShell.Create() method to create an
// empty pipeline.
PowerShell ps = PowerShell.Create();
ps.AddCommand("Get-SmbShare");
Console.WriteLine("Name Path");
Console.WriteLine("----------------------------");
// Call the PowerShell.Invoke() method to run the
// commands of the pipeline.
foreach (PSObject result in ps.Invoke())
{
Console.WriteLine(
"{0,-24}{1}",
result.Members["Name"].Value,
result.Members["Path"].Value);
} // End foreach.
Console.ReadLine();
} // End Main.
static void Main(string[] args)
{
GetShareNames();
}
}
}
You need to import the module first. Stick in this line before you try to execute the Get-SmbShare command:
ps.AddCommand("Import-Module").AddArgument("SmbShare");
ps.Invoke();
ps.Commands.Clear();
ps.AddCommand("Get-SmbShare");
Another way is to initialize the runspace with the SmbShare module pre-loaded e.g.:
InitialSessionState initial = InitialSessionState.CreateDefault();
initial.ImportPSModule(new[] {"SmbShare"} );
Runspace runspace = RunspaceFactory.CreateRunspace(initial);
runspace.Open();
PowerShell ps = PowerShell.Create();
ps.Runspace = runspace;
I have a simple console application, written in C# which invokes a PowerShell script. Everything works until I add an additional assembly to the console application which is also loaded within the PowerShell script.
Here is the invoke of the PowerShell script:
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
RunspaceInvoke runSpaceInvoker = new RunspaceInvoke(runspace);
Pipeline pipeline = runspace.CreatePipeline();
Command command = new Command(#"d:\invokeme.ps1");
pipeline.Commands.Add(command);
pipeline.Invoke();
The PowerShell script loads two assemblies:
AssemblyA:
namespace AssemblyA
{
public class ClassA
{
public readonly string Name;
public ClassA(string aName)
{
Name = aName;
}
}
}
AssemblyB:
namespace AssemblyB
{
public class ClassB
{
private readonly ClassA classA;
public ClassB(ClassA aClassA)
{
classA = aClassA;
}
public string GetText()
{
return classA.Name;
}
}
}
And here is the PowerShell script:
$assemblyA = "D:\Projects\PsInvoke\AssemblyA\bin\Debug\AssemblyA.dll"
$assemblyB = "D:\Projects\PsInvoke\AssemblyB\bin\Debug\AssemblyB.dll"
Add-Type -Path $assemblyA
Add-Type -Path $assemblyB
$objA = New-Object -TypeName AssemblyA.ClassA 'Hello'
$objB = New-Object -TypeName AssemblyB.ClassB $objA
The console application works until I add and use a reference of AssemblyA to the console application, then I receive the following error:
An unhandled exception of type
'System.Management.Automation.CmdletInvocationException' occurred in
System.Management.Automation.dll
Additional information: Cannot convert argument "0", with value:
"AssemblyA.ClassA", for "ClassB" to type "AssemblyA.ClassA": "Cannot
convert the "AssemblyA.ClassA" value of type "AssemblyA.ClassA" to
type "AssemblyA.ClassA"."
If I also add and use a refernce to AssemblyB to the console application, everything works fine!
Is there a different workaround than loading each assembly used in the PowerShell script in both - the PowerShell script and the console application? Can someone explain that behaviour?
I am running into issues loading an assembly into a PowerShell Runspace within a .net console application.
When running the application, I get the following error: "Cannot find type [Test.Libary.TestClass]: make sure the assembly containing this type is loaded."
I tried installing the assembly into the GAC, but it didn't seem to make any difference. I couldn't seem to find very much documentation on the AssemblyConfigurationEntry class, so any help is appreciated.
Console Application:
namespace PowerShellHost
{
class Program
{
static void Main(string[] args)
{
string assemblyPath = Path.GetFullPath("Test.Library.dll");
RunspaceConfiguration config = RunspaceConfiguration.Create();
var libraryAssembly = new AssemblyConfigurationEntry("Test.Library, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d7ac3058027aaf63", assemblyPath);
config.Assemblies.Append(libraryAssembly);
Runspace runspace = RunspaceFactory.CreateRunspace(config);
runspace.Open();
PowerShell shell = PowerShell.Create();
shell.Runspace = runspace;
shell.AddCommand("New-Object");
shell.AddParameter("TypeName", "Test.Libary.TestClass");
ICollection<PSObject> output = shell.Invoke();
}
}
}
Test.Library.dll:
namespace Test.Library
{
public class TestClass
{
public string TestProperty { get; set; }
}
}
You can call Add-Type from script to accomplish this.
PowerShell shell = PowerShell.Create();
shell.AddScript(#"
Add-Type -AssemblyName Test.Library
$myObj = New-Object Test.Library.TestClass
$myObj.TestProperty = 'foo'
$myObj.TestPropery
");
ICollection<PSObject> output = shell.Invoke();
This should work if your DLL is in the GAC. Otherwise, when calling Add-Type instead of -AssemblyName Test.Library, you would instead need to use -Path c:\path\to\Test.Library.dll