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
Related
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);
}
}
}
}
}
}
I am working on a c# program using System.Speech.Recognition to recognize speech and run PowerShell commands according to what is said.
I have the following powershell script that represents a macro for creating a speech command:
Add-Type -Path ".\GAVPI.Lib.dll"
Add-Type -Path ".\GAVPI.Lib.Logging.dll"
[Action[GAVPI.Lib.Logging.Parameter]]$speechRecognized = {
param($i)
[System.Windows.MessageBox]::Show("test")
}
$parameter = New-Object -TypeName GAVPI.Lib.Logging.Parameter -ArgumentList
("parameter", "value")
$phrase = New-Object -TypeName GAVPI.Lib.Core.Triggers.Phrase -ArgumentList
("default","test", $speechRecognized, $parameter)
return $phrase
This phrase object is used to tell what commands can be said and recognized. It successfully is passed to c# like this:
var list = new List<Phrase>();
var ps = PowerShell.Create();
var run = RunspaceFactory.CreateRunspace();
ps.Runspace = run;
run.Open();
var script = ps.AddScript(".\\PowershellTemplate.ps1", true);
var result = ps.Invoke();
foreach (var psObject in result)
{
if (psObject.BaseObject is Phrase)
{
list.Add((Phrase)psObject.BaseObject);
}
}
return list;
When a command is recognized,the Phrase class invokes the Action:
public override void Run(Parameter selectedparameter)
{
if (parAction != null)
{
parAction.Invoke(selectedparameter);
}
}
private Action<Parameter> parAction;
When the program is run, if you say "test parameter" it invokes the action<parameter> in the phrase class, which invokes the script block in powershell.
I get the following exception at the parAction.Invoke(selectedparameter); line:
There is no Runspace available to run scripts in this thread. You can
provide one in the DefaultRunspace property of the
System.Management.Automation.Runspaces.Runspace type. The script block
you attempted to invoke was:
param($i)...w("test")
How do I pass the runspace to the Sysem.Action<Parameter> so that it can run the PowerShell script block?
I am trying to get a list of all Virtual Processes started by Microsoft AppV using C#.
I tried using Powershell in C# but I get this error:
System.Management.Automation.CommandNotFoundException: 'The 'Get-AppvVirtualProcess' command was found in the module 'AppvClient', but the module could not be loaded. For more information, run 'Import-Module AppvClient'.'
The weird thing is that if I use the Powershell command line, it works just fine and lists the virtual processes.
So in C# I did a:
ps.Commands.AddCommand("Get-Command");
and it shows Get-AppvVirtualProcess listed as a command:
The result:
Function Get-AppvVirtualProcess 1.0.0.0 A
I tried loading the module in C# manually using:
InitialSessionState initial = InitialSessionState.CreateDefault();
initial.ImportPSModule(new string[] {#"C:\Program Files\Microsoft Application Virtualization\Client\AppvClient\AppvClient.psd1" });
and
ps.Commands.AddCommand("Import-Module").AddArgument("AppvClient");
But it still gives me the same error mentioned above.
The code in C# looks like this:
public static void powershellCommand()
{
Collection<PSObject> result;
using (Runspace myRunSpace = RunspaceFactory.CreateRunspace())
{
InitialSessionState initial = InitialSessionState.CreateDefault();
initial.ImportPSModule(new string[] {#"C:\Program Files\Microsoft Application Virtualization\Client\AppvClient\AppvClient.psd1" });
Runspace runspace = RunspaceFactory.CreateRunspace(initial);
runspace.Open();
PowerShell ps = PowerShell.Create();
ps.Runspace = runspace;
ps.Commands.AddCommand("Import-Module").AddArgument("AppvClient");
ps.Commands.AddCommand("Get-AppvVirtualProcess");
result = ps.Invoke();
var builder = new StringBuilder();
foreach (PSObject psObject in result)
{
builder.Append(psObject.ToString() + "\n");
builder.ToString();
}
Console.WriteLine("Virtual Process: {0}", builder.ToString());
}
}
Instead of Runspace, I tried this as well but I get the same error:
public static void p()
{
using (var powershell = PowerShell.Create())
{
powershell.AddCommand("Get-AppvVirtualProcess");
powershell.Invoke();
}
}
You could try to iterate through all the running process, and find those that loaded either AppVEntSubsystems32.dll or AppVEntSubsystems64.dll.
You can read more about this here: https://blogs.msdn.microsoft.com/gladiator/2014/09/04/app-v-5-on-application-launch/
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 want to create a powershell function and use it from inside the C#
using System;
using System.Management.Automation;
using System.Text;
namespace PowerShell_eg
{
public class Program
{
public static void Main(String[] args)
{
var psFunction = #" function Get-Hostname { hostname } ";
RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();
Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration);
runspace.Open();
RunspaceInvoke scriptInvoker = new RunspaceInvoke(runspace);
Pipeline pipeline = runspace.CreatePipeline();
??// HOW TO Add Get-Hostname & INVOKE IT ??
pipeline.Commands.AddScript(psFunction);
pipeline.Commands.Add("Get-Hostname");
var results = pipeline.Invoke();
foreach (var obj in results.Where(o => o != null))
{
Console.WriteLine("\t" + obj);
}
}
}
}
Currently I get CommandNotFound exception # Invoke.
The term 'Get-Hostname' is not recognized as the name of a cmdlet, function, script file, or operable program.
Please advice how to correctly do this!
Also it will be ideal if I can add multiple functions and cont. to use them over the life of the powershell session without having to add them again and again.
This C# code seems to work fine for me. Just add a reference to the System.Management.Automation .NET assembly.
using System;
using System.Management.Automation;
namespace PowerShellTest02
{
class Program
{
static void Main(string[] args)
{
string func = #"function Test { Write-Host 'hello' };";
PowerShell ps = PowerShell.Create();
ps.AddScript(func);
ps.Invoke();
ps.AddCommand("Test");
ps.Invoke();
Console.WriteLine("Successfully executed function");
Console.ReadLine();
}
}
}