I can execute a powershell command on a remote machine, and I can execute powershell exchange snapin commands, but I can't figure out how to do both. The issue resides in the RunspaceFactory.CreateRunspace() method.
A WSManConnectionInfo object lets me target a remote host like so:
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(
new Uri(ConfigurationManager.AppSettings["ExchangeServerURI"]),
"http://schemas.microsoft.com/powershell/Microsoft.Exchange",
new PSCredential(Username, secureString));
And a RunspaceConfugation + PSSnapInInfo lets me target a snapin like so:
RunspaceConfiguration rsConfig = RunspaceConfiguration.Create();
PSSnapInException snapInException = null;
PSSnapInInfo info = rsConfig.AddPSSnapIn("Microsoft.Exchange.Management.PowerShell.Admin", out snapInException);
But I can only feed one or the other to CreateRunspace(). The Runtime object it returns has properties for ConnectionInfo and RunspaceConfiguration, but they're both readonly. Is this an intentional design that you can't remotely execute code with powershell snapins, or is there a way to do this that I'm missing?
One thing you can do is define a remoting endpoint (on the remote machine) that configures itself to have the desired pssnapin loaded e.g.:
Register-PSSessionConfiguration -Name ExAdminShell -StartupScript InitScript.ps1
Then you would connect to that endpoint.
Another thought, does it not work if you just add the Add-PSSnapin to the top of the script you're going to run on the remote machine? Keep in mind that snapins are bit-specific so if you are connecting to a 32-bit endpoint the snapin better be 32-bit and registered. Ditto for a 64-bit snapin.
I experienced the same problem recently that the RunspaceFactory.CreateRunspace() constructor only allows either ConnectionInfo or RunspaceConfiguration parameters and not both. I considered optional arguments but later noticed that these properties where readonly.
I don't know if it's possible for sure yet, still testing myself, but possible making the remote connection first and then calling the PSSnapIn under the PowerShell runspace might work...
public static Collection<PSObject> remoteExchangePowerShell(string domain, string username, SecureString password, string remoteFQDNServer)
{
PSCredential remoteCredential = new PSCredential(string.Format("{0}\\{1}", domain, username), password);
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri(string.Format("http://{0}/Powershell?serializationLevel=Full", remoteFQDNServer)),
"http://schemas.microsoft.com/powershell/Microsoft.Exchange", remoteCredential);
/*Just for PowerShell
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri(string.Format("http://{0}/Powershell?serializationLevel=Full", remoteFQDNServer) + ":5985/wsman"),
"http://schemas.microsoft.com/powershell/Microsoft.PowerShell", remoteCredential);*/
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Kerberos;
using (Runspace remoteRunspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
//remoteRunspace.Open();
using (PowerShell powershell = PowerShell.Create())
{
PSSnapInException psException;
powershell.Runspace.RunspaceConfiguration.AddPSSnapIn("Microsoft.Exchange.Management.PowerShell.E2010", out psException);
....
}
}
}
I would be interested to hear back on your results.
Related
I am currently trying to make an IIS based application with asp.net and C# to serve as a notification system that someone is here to see someone at our front desk so we don't have to go chase them down. I keep getting an error that reads "One or more computer names are not valid. If you are trying to pass a URI, use the -ConnectionUri parameter, or pass URI objects instead of strings" in the debugger for visual studio.
I have tried using credentials thinking it was a permission to remote issue, I have tried making the it a var as seen below. I tried a string.format also thinking it may not like the type.
Variable for ComputerName is just the basic computer name the computer is given not the full computername going to be trying that next.
var ComputerName = c.Attribute("CN");
InitialSessionState initial = InitialSessionState.CreateDefault();
Runspace runspace = RunspaceFactory.CreateRunspace(initial);
runspace.Open();
PowerShell ps = PowerShell.Create();
ps.Runspace = runspace;
ps.AddCommand("invoke-command");
ps.AddParameter("ComputerName", ComputerName);
ScriptBlock calldown = ScriptBlock.Create("Get-childitem C:\\windows");
ps.AddParameter("ScriptBlock", calldown);
foreach (PSObject obj in ps.Invoke()){
string message = string.Format("You currently have a client waiting for you at the front desk, please check the lobby system to find out more");
string title = string.Format ("Lobby Alert");
MessageBox.Show(message, title);
}
The results should be a message box on a computer in the back that is not the user "frontdesk" computer.
Solved it by using the value object instead since it registers as type string to pass through this error now have a different issue but that isn't Powershell related so I am posting a more accurate question in the C# section for that error I am having.
var ComputerName = c.Attribute("CN");
InitialSessionState initial = InitialSessionState.CreateDefault();
Runspace runspace = RunspaceFactory.CreateRunspace(initial);
runspace.Open();
PowerShell ps = PowerShell.Create();
ps.Runspace = runspace;
ps.AddCommand("invoke-command");
ps.AddParameter("ComputerName", ComputerName.Value);
ScriptBlock calldown = ScriptBlock.Create("Get-childitem C:\\windows");
ps.AddParameter("ScriptBlock", calldown);
foreach (PSObject obj in ps.Invoke()){
string message = string.Format("You currently have a client waiting for you at the
front desk, please check the lobby system to find out more");
string title = string.Format ("Lobby Alert");
MessageBox.Show(message, title);}
I have a simple way to connect to a remote windows machine from a local windows machine using winrm.
Here is the powershell code that is working:
Set-Item WSMan:\localhost\Client\TrustedHosts -Value $ip -Force
$securePassword = ConvertTo-SecureString -AsPlainText -Force 'mypass'
$cred = New-Object System.Management.Automation.PSCredential 'Administrator', $securePassword
$cmd = {ls C:\temp}
Invoke-Command -ComputerName $ip -Credential $cred -ScriptBlock $cmd
I want to figure out how to do the exact thing in c#.
Also, it would be additionally helpful if someone tell me whether there is a method to send files in c# winrm.
Note: the is only a c# code needed on my local machine. The remote machine is already setup.
well, I figured out one way as I shall post below, but while it works fine on windows 8, it encounters the error "Strong name validation failed" on windows 7 so I should keep looking into this.
Still please feel free to post other ideas.
--> add System.Management.Automation.dll to your project.
WSManConnectionInfo connectionInfo = new WSManConnectionInfo();
connectionInfo.ComputerName = host;
SecureString securePwd = new SecureString();
pass.ToCharArray().ToList().ForEach(p => securePwd.AppendChar(p));
connectionInfo.Credential = new PSCredential(username, securePwd);
Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo);
runspace.Open();
Collection<PSObject> results = null;
using (PowerShell ps = PowerShell.Create())
{
ps.Runspace = runspace;
ps.AddScript(cmd);
results = ps.Invoke();
// Do something with result ...
}
runspace.Close();
foreach (var result in results)
{
txtOutput.AppendText(result.ToString() + "\r\n");
}
I've got an article that describes an easy way to run Powershell through WinRM from .NET at http://getthinktank.com/2015/06/22/naos-winrm-windows-remote-management-through-net/.
The code is in a single file if you want to just copy it and it's also a NuGet package that includes the reference to System.Management.Automation.
It auto manages trusted hosts, can run script blocks, and also send files (which isn't really supported but I created a work around). The returns are always the raw objects from Powershell.
// this is the entrypoint to interact with the system (interfaced for testing).
var machineManager = new MachineManager(
"10.0.0.1",
"Administrator",
MachineManager.ConvertStringToSecureString("xxx"),
true);
// will perform a user initiated reboot.
machineManager.Reboot();
// can run random script blocks WITH parameters.
var fileObjects = machineManager.RunScript(
"{ param($path) ls $path }",
new[] { #"C:\PathToList" });
// can transfer files to the remote server (over WinRM's protocol!).
var localFilePath = #"D:\Temp\BigFileLocal.nupkg";
var fileBytes = File.ReadAllBytes(localFilePath);
var remoteFilePath = #"D:\Temp\BigFileRemote.nupkg";
machineManager.SendFile(remoteFilePath, fileBytes);
Please mark as answer if this helps. I've been using this for a while with my automated deployments. Please leave comments if you find issues.
I am facing a problem that cause me headaches (literally), I hope if you can help me with it :
Given that my Powershell is on an other server than my application, in c#, you can create a "Powershell remote session" by defining an WSManConnectionInfo and using this during the "runspace" creation.
Somthing like :
var runspace = RunspaceFactory.CreateRunspace(WSManConnectionInfo);
But the problem is :
When we are working with the remote session, we can only use some commands(Not all the commands are available). So, you can't use the "Import-Module" command directly in the remote session.
So I am asking you if you can help me to find a solution in c# (or just some hint) to use imported module in the remote session.
I know there's a lot of solution out there (Pure Powershell command), but I am just not good enough to convert these solutions in c#.
Hmm, I am able to use Import-Module in a remote session e.g.:
var connectionInfo = new WSManConnectionInfo(new Uri("http://foo.acme.com:5985"));
var runspace = RunspaceFactory.CreateRunspace(connectionInfo);
runspace.Open();
using (var powershell = PowerShell.Create())
{
powershell.Runspace = runspace;
powershell.AddScript("Import-Module PSCX");
var results = powershell.Invoke();
powershell.AddScript("Get-Uptime | Out-String");
results = powershell.Invoke();
foreach (var result in results)
{
Console.WriteLine(result);
}
runspace.Close();
}
That outputs:
Uptime LastBootUpTime
------ --------------
10.20:21:06.6432615 6/26/2014 6:29:00 PM
Is it possible that your module is bit-specific, perhaps a 32-bit module that only loads into an x86 PowerShell session? The default session will be 64-bit (assuming the remote OS is 64-bit). If it turns out that your module is 32-bit specific, connect to the Microsoft.Powershell32 endpoint.
var connectionInfo = new WSManConnectionInfo(new Uri("http://foo.acme.com:5985"), "http://schemas.microsoft.com/powershell/Microsoft.PowerShell32", PSCredential.Empty);
I want to execute some Active directory queries on remote machine using c# language.It is the query
PowerShell ps = PowerShell.Create();;
ps.AddScript(#"$Session = New-PSsession -Computername com1");
ps.AddScript(#"Invoke-Command -Command {Import-Module ActiveDirectory} -Session $Session");
ps.Invoke();
I want to skip the above command execution after the first execution and should maintain the session until the program exit.Please help me to execute more script with the same session established.
In exact i want to create the session only once in the whole program.Thanks
Finally i got the solution for my question ..its very simple one.
Make the Powershell object as a static variable and clear the command after script invoke function.
ps.commands.clear();
Now without affecting the current session we can execute the query easily..Let me know if any other efficient way for it.
Thanks.
int iRemotePort = 5985;
string strShellURI = #"http://schemas.microsoft.com/powershell/Microsoft.PowerShell";
string strAppName = #"/wsman";
AuthenticationMechanism auth = AuthenticationMechanism.Negotiate;
WSManConnectionInfo ci = new WSManConnectionInfo(
false,
sRemote,
iRemotePort,
strAppName,
strShellURI,
creds);
ci.AuthenticationMechanism = auth;
Runspace runspace = RunspaceFactory.CreateRunspace(ci);
runspace.Open();
PowerShell psh = PowerShell.Create();
psh.Runspace = runspace;
Make Powershell object as a static variable in the class.
I having issues with passing arguments to PowerShell via C#
I am getting the following exception:
"A command that prompts the user failed because the host program or
the command type does not support user interaction. Try a host program
that supports user interaction, such as the Windows PowerShell Console
or Windows PowerShell ISE, and remove prompt-related commands from
command types that do not support user interaction, such as Windows
PowerShell workflows"
cs:
private static void RunPowershellScript(string scriptFile, string scriptParameters)
{
string scriptParameters = "param1 param2";
RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();
Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration);
runspace.Open();
RunspaceInvoke scriptInvoker = new RunspaceInvoke(runspace);
Pipeline pipeline = runspace.CreatePipeline();
Command scriptCommand = new Command(scriptFile);
Collection<CommandParameter> commandParameters = new Collection<CommandParameter>();
foreach (string scriptParameter in scriptParameters.Split(' '))
{
CommandParameter commandParm = new CommandParameter(null, scriptParameter);
commandParameters.Add(commandParm);
scriptCommand.Parameters.Add(commandParm);
}
pipeline.Commands.Add(scriptCommand);
Collection<PSObject> psObjects;
psObjects = pipeline.Invoke();
}
ps:
Function Foo ($arg1, $arg2)
{
Write-Host $arg1
Write-Host $arg2
}
Foo $args[0] $args[1]
What am i missing here? how can i make this work?
The exception is not about arguments. Either do not use commands that require host UI implemented (Write-Host included) or implement you own custom host (PSHost) and this UI (PSHostUserInterface). Here is the example of a simple host (and there is much more on MSDN about this, if you choose this way):
http://msdn.microsoft.com/en-us/library/windows/desktop/ee706559(v=vs.85).aspx
For simple tasks implementing a host with UI is too much, perhaps. You may consider simply to define a function Write-Host with the same arguments and implement it so that it works in your specific case (e.g. does [Console]::WriteLine(...)). This function should be defined in the script or better made available for it in a different way, e.g. invoke another script with it defined in the global scope.
P.S. And if you have your custom host then use one of the CreateRunspace() overloads that takes a host instance as an argument in order to link the host and the runspace.