I have this code in which I load a snapin (from MS Dynamics NAV in this case):
using (Runspace runspace = RunspaceFactory.CreateRunspace())
{
runspace.Open();
using (var ps = PowerShell.Create())
{
ps.Runspace = runspace;
ps.AddScript("Add-PSSnapin Microsoft.Dynamics.Nav.Management")
.AddScript("Get-NAVServerInstance");
//This does not work. Says unknown cmdlet Get-NAVServerInstance
//ps.AddCommand("Add-PSSnapin").AddArgument("Microsoft.Dynamics.Nav.Management")
// .AddCommand("Get-NAVServerInstance");
var output = ps.Invoke();
}
}
This code works when I use the AddScript method as shown in the code.
But why does AddCommand method not work (see commented code)? Looks like the snapin is not loaded, because the error says that the Get-NAVServerInstance cmdlet is unknown.
How is this supposed to work?
I know I can create a runspace with an InitialSessionState on which I have imported the snapin. Then the ps.AddCommand("Get-NAVServerInstance") is working.
But when I want to create a remote runspace session (using WSManConnectionInfo) I can't find a way to supply an initialSessionState.
UPDATE:
So it seems that AddCommand only can be used for cmdlets available when the runspace is opened (or created?). Using an InitialSessionState or RunspaceConfiguration instance with RunspaceFactory.CreateRunspace(...) will do. So this code works:
var config = RunspaceConfiguration.Create();
PSSnapInException warning;
config.AddPSSnapIn("Microsoft.Dynamics.Nav.Management", out warning);
using (Runspace runspace = RunspaceFactory.CreateRunspace(config))
{
runspace.Open();
using (var ps = PowerShell.Create())
{
ps.Runspace = runspace;
ps.AddCommand("Get-NAVServerInstance");
var output = ps.Invoke();
}
}
But my problem is in that case, that I can't specify a WSManConnectionInfo instance.
So how can I create a runspace with a remote connection with a snapin (installed on the remote machine) loaded? How to supply a configuration for a remote connection?
I finally found a hint how to configure a remote session (see https://superuser.com/a/518567).
You need to register a session configuration on the remote computer with
Register-PSSessionConfiguration -Name MyShell -StartupScript 'MyInitScript.ps1'
Then you can set the shellUri parameter of WSManConnectionInfo to http://schemas.microsoft.com/powershell/MyShell
The runspace you create this way will have the commands available which are imported by the MyInitScript.ps1 startup script.
So now this code will work:
string shell = "http://schemas.microsoft.com/powershell/MyShell";
var target = new Uri("https://myserver:port/wsman");
var secured = new SecureString();
foreach (char letter in "password")
{
secured.AppendChar(letter);
}
secured.MakeReadOnly();
var credential = new PSCredential("username", secured);
var connectionInfo = new WSManConnectionInfo(target, shell, credential);
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
runspace.Open();
using (var ps = PowerShell.Create())
{
ps.Runspace = runspace;
ps.AddCommand("Get-NAVServerInstance");
var output = ps.Invoke();
}
}
Try invoking AddScript like so:
.AddScript("...", false)
this will execute the command in the global scope instead of a new local scope.
I think the proper way to do this is to use the RunspaceConfiguration class. It has an AddPSSnapin method.
Related
I would like to run a powershell (.ps1) script on a remote machine, from a .NET program.
The remote machine is set up correctly, I can connect to it from a PowerShell console.
The run script code is the following (using System.Management.Automation, from PowerShell.SDK.7.2.0-Preview.4 nuget package)
public static void RunScript(string scriptFile, string remoteHost, string remoteUser, SecureString remotePassword)
{
PSCredential credential = new PSCredential(remoteUser, remotePassword);
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(false, remoteHost, 5985, "/wsman",
"http://schemas.microsoft.com/powershell/Microsoft.PowerShell", credential);
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Negotiate;
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
runspace.Open();
try
{
var shell = PowerShell.Create();
shell.Runspace = runspace;
shell.Commands.AddScript(scriptFile, false);
Collection<PSObject> results = shell.Invoke();
}
finally
{
runspace.Close();
}
}
}
The remote host, and the username/password credentials are correct (I can connect to the remote machine with the exact same credentials from the PowerShell console)
The CreateRunspace going fine. But the shell.Invoke() does nothing. No exceptions, no result (results contains 0 elements)
If I run the exact same code without the runspace assignment (so the PowerShell usign a default, local runspace), the Invoke() method runs fine, and the result collection is correct.
Has anybody has an idea what should I look for?
Thanks in advance!
When I run the code below whilst debugging in Visual Studio 2015 it works fine. When it gets deployed to IIS, I get the following error at the second ps.Invoke() line:
The WinRM service cannot process the request. A command already exists
with the command ID specified by the client.
public static PowerShellResponse AddToDistributionGroup(Credentials creds, string groupName, string memberEmail)
{
PSCredential cred = new PSCredential(creds.Username, creds.Password.ToSecureString());
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri(Settings.ExchangeServerAutomationUrl), Settings.ExchangeAutomationSchemaName, cred);
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Kerberos;
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
using (PowerShell ps = PowerShell.Create())
{
runspace.Open();
ps.Runspace = runspace;
//can't pipe OU to Add-DistrubtionGroupMember b/c it blows up w/ "null reference exception" when member already exists
var group =
ps
.AddCommand("Get-DistributionGroup")
.AddParameter("Identity", groupName)
.AddParameter("OrganizationalUnit", creds.GetUserDN())
.Invoke()
.SingleOrDefault();
if (group == null)
return new PowerShellResponse() { Errors = new List<string> { "Group not found." } };
ps.AddStatement();
ps.AddCommand("Add-DistributionGroupMember")
.AddParameter("Identity", ((dynamic)group).Identity)
.AddParameter("Member", memberEmail);
ps.Invoke(); //this is where the error shows up
return ps.GetResponse();
}
}
}
I'm connecting to Exchange (API Docs: https://technet.microsoft.com/en-us/library/dn641234(v=exchg.160).aspx) using C# and PowerShell 3.0 trying to add a member to a distribution group in Exchange.
PowerShellResponse is a custom class we have, and ps.GetResponse() is a custom function to create said PowerShellResponse.
Having not been able to find this error discussed anywhere online, the solution I came up with was not calling ps.Invoke() twice in one using statement. The following worked fine both locally and once deployed to IIS:
public static PowerShellResponse AddToDistributionGroup(Credentials creds, string groupName, string memberEmail)
{
PSCredential cred = new PSCredential(creds.Username, creds.Password.ToSecureString());
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri(Settings.ExchangeServerAutomationUrl), Settings.ExchangeAutomationSchemaName, cred);
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Kerberos;
PSObject group;
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
using (PowerShell ps = PowerShell.Create())
{
runspace.Open();
ps.Runspace = runspace;
group =
ps
.AddCommand("Get-DistributionGroup")
.AddParameter("Identity", groupName)
.AddParameter("OrganizationalUnit", creds.GetUserDN())
.Invoke()
.SingleOrDefault();
}
}
//can't pipe OU to Add-DistrubtionGroupMember b/c it blows up w/ "null reference exception" when member already exists
if (group == null)
return new PowerShellResponse() { Errors = new List<string> { "Group not found." } };
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
using (PowerShell ps = PowerShell.Create())
{
runspace.Open();
ps.Runspace = runspace;
ps.AddCommand("Add-DistributionGroupMember")
.AddParameter("Identity", ((dynamic)group).Identity)
.AddParameter("Member", memberEmail);
ps.Invoke();
return ps.GetResponse();
}
}
}
How to verify whether module (Import module ) command is imported module successfully in C# ? I have my own custom powershell, which is imported in Windows powershell.I have written C# code which will import the my custom powershell into windows powershell, When I try to execute custom powershell command from code it is not returning any result whereas when i execute same command from windows powershell (By importing module and writing a custom powershell command) it is working. I am using following code
InitialSessionState initial = InitialSessionState.CreateDefault();
initial.ImportPSModule(new string[] { "ABCD" });
Runspace runspace = RunspaceFactory.CreateRunspace(initial);
initial.ThrowOnRunspaceOpenError = true;
runspace.Open();
RunspaceInvoke runSpaceInvoker = new RunspaceInvoke(runspace);
runSpaceInvoker.Invoke("Set-ExecutionPolicy Unrestricted");
PowerShell ps = PowerShell.Create();
ps.Runspace = runspace;
string script = System.IO.File.ReadAllText(#"D:\Export-Pipeline-Script.txt");
ps.AddScript(script);
ps.Invoke();
ps.Commands.Clear();
ps.AddCommand("Test2");
Collection<PSObject> results1 = ps.Invoke();
foreach (PSObject outputItem in results1)
{
if (outputItem != null)
{
Console.WriteLine(outputItem.ToString());
}
}
In AddCommand the Test2 is a function in which I have written the custom powershell command. In the above code results1 always written count as "0" whereas when I changed the custom powershell command to windows powershell command like Get-Process, it works.
Here is an example that worked for me. I created a PS module and a custom PS script and used them in your C# code. this might give you some clue as to how you can make yours work.
C:\Temp\PowerShell.Module.psm1
here is the custom powerhell. C:\Temp\PS\GetProc.ps1
worked with this code:
InitialSessionState initial = InitialSessionState.CreateDefault();
initial.ImportPSModule(new string[] { #"C:\Temp\PowerShell.Module.psm1" });
Runspace runspace = RunspaceFactory.CreateRunspace(initial);
initial.ThrowOnRunspaceOpenError = true;
runspace.Open();
RunspaceInvoke runSpaceInvoker = new RunspaceInvoke(runspace);
runSpaceInvoker.Invoke("Set-ExecutionPolicy Unrestricted");
PowerShell ps = PowerShell.Create();
ps.Runspace = runspace;
string script = System.IO.File.ReadAllText(#"C:\Temp\Export-Pipeline-Script.txt");
ps.AddScript(script);
ps.Invoke();
ps.Commands.Clear();
ps.AddCommand("GetProc");
Collection<PSObject> results1 = ps.Invoke();
foreach (PSObject outputItem in results1)
{
if (outputItem != null)
{
Console.WriteLine(outputItem.ToString());
}
}
result:
I'm trying to run powershell commands using C# but I keep getting errors when I invoke the pipeline. I was wondering if anyone know why I keep getting add-windowsfeature is not recognized. Thanks in advance.
private static void RunScript(string name)
{
InitialSessionState initial = InitialSessionState.CreateDefault();
initial.ImportPSModule(new[] { "ServerManager"});
Runspace runspace = RunspaceFactory.CreateRunspace(initial);
// create Powershell runspace
runspace.Open();
RunspaceInvoke runSpaceInvoker = new RunspaceInvoke(runspace);
runSpaceInvoker.Invoke("Set-ExecutionPolicy Unrestricted");
Pipeline pipeline = runspace.CreatePipeline();
Command cm = new Command("Import-module");
cm.Parameters.Add("name","ServerManager");
pipeline.Commands.Add(cm);
Command command = new Command("add-windowsfeature");
command.Parameters.Add(null, name);
pipeline.Commands.Add(command);
var a = pipeline.Invoke();
foreach (var psObject in a)
{
Console.WriteLine(psObject);
}
runspace.Close();
}
ServerManager is a 64-bit only module (it doesn't exist under C:\Windows\SysWOW64\WindowsPowerShell\v1.0\Modules but will exist under C:\Windows\System32\WindowsPowerShell\v1.0\Modules). Compile as x64 and your code should work.
I have the following code that I have tested and works:
using (new Impersonator("Administrator", "dev.dev", #########"))
{
RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();
Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration);
runspace.Open();
RunspaceInvoke scriptInvoker = new RunspaceInvoke(runspace);
scriptInvoker.Invoke("Set-ExecutionPolicy Unrestricted");
Pipeline pipeline = runspace.CreatePipeline();
Command myCmd = new Command(#"C:\test.ps1");
myCmd.Parameters.Add(new CommandParameter("upn", upn));
myCmd.Parameters.Add(new CommandParameter("sipAddress", sipAddress));
pipeline.Commands.Add(myCmd);
// Execute PowerShell script
Collection<PSObject> results = pipeline.Invoke();
}
However, when I try to include the function in a different project so that it is called from a webservice it throws an execption:
System.Management.Automation.CmdletInvocationException: Access to the registry key 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell' is denied. ---> System.UnauthorizedAccessException: Access to the registry key 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell' is denied.
I have no idea why this is happening. Any help would be appreciated.
What's happening is that Impersonator only impersonates on the thread, and PowerShell's Runspace is running on another thread.
To make this work, you need to add:
runspace.ApartmentState = System.Threading.ApartmentState.STA;
runspace.ThreadOptions = System.Management.Automation.Runspaces.PSThreadOptions.UseCurrentThread;
just before you open the runspace.
This will force the runspace to run on the same thread as the impersonated token.
Hope this helps,
Use these namespaces :
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Threading;
Create Runspace with InitialSessionState
InitialSessionState initialSessionState = InitialSessionState.CreateDefault();
initialSessionState.ApartmentState = ApartmentState.STA;
initialSessionState.ThreadOptions = PSThreadOptions.UseCurrentThread;
using ( Runspace runspace = RunspaceFactory.CreateRunspace ( initialSessionState ) )
{
runspace.Open();
// scripts invocation
runspace.Close();
}