I am trying to get information about my users' mailboxes using power shell integrated into C#, but i am getting below error:
This syntax is not supported by this run space. This might be because it is no-language mode.
Here is the code that I am using:
using System;
using System.Collections.ObjectModel;
using System.Collections;
using System.Linq;
using System.Text;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Threading;
using System.Globalization;
namespace Office365
{
class Program
{
static void Main(string[] args)
{
CultureInfo oldCI = Thread.CurrentThread.CurrentCulture;
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");
System.Security.SecureString secureString = new System.Security.SecureString();
string myPassword = "mySecretPassword";
foreach (char c in myPassword)
secureString.AppendChar(c);
PSCredential credential = new PSCredential("my#maildomain.com", secureString);
Console.WriteLine("Forbinder....");
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri("https://ps.outlook.com/PowerShell-LiveID?PSVersion=2.0"), "http://schemas.microsoft.com/powershell/Microsoft.Exchange", credential);
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Basic;
connectionInfo.SkipCACheck = true;
connectionInfo.SkipCNCheck = true;
string cmd2 = #"Get-Mailbox | Select-object Identity, displayname, ProhibitSendReceiveQuota, #{n='size';e={$MBXSTAT=Get-MailboxStatistics $_.Identity; $MBXSTAT.TotalItemSize}}";
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
runspace.Open();
using (Pipeline pipeline = runspace.CreatePipeline(cmd2))
{
pipeline.Commands.AddScript(cmd2);
Collection<PSObject> results = pipeline.Invoke();
foreach (PSObject obj in results)
{
// Do something with each result object
}
}
}
}
}
Any Suggestions on this? How can I overcome to this issue?
Normally i like to leave the dead alone but in this case i feel the need to resurrect this old post because i encountered the exact problem, need a place to keep this code and hope this prevents anybody else from wasting too much time.
I have found connecting to O365 with WSManConnectionInfo problematic and after wasting far too much time trying to get it to work i won't be using it unless i have no other choice.
That said the following code works for me and behaves the same as if i had opened a PowerShell prompt entered the commands i want to run :) it makes testing a lot simpler.
using (var _power_shell = PowerShell.Create()) {
_power_shell.AddScript("Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process");
_power_shell.Invoke();
var credential = new PSCredential(username, password.ToSecureString());
_power_shell.Runspace.SessionStateProxy.SetVariable("Credential", credential);
_power_shell.AddScript("$Session = New-PSSession –ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell -Credential
$Credential -Authentication Basic -AllowRedirection");
_power_shell.AddScript("Import-PSSession $Session");
_power_shell.Invoke();
_power_shell.AddScript("Get-Mailbox -RecipientTypeDetails RoomMailbox | Select-Object DisplayName, PrimarySmtpAddress");
var results = _power_shell.Invoke();
foreach (var obj in results)
{
/* Do something with the results */
}
_power_shell.AddScript("Remove-PSSession $Session");
_power_shell.Invoke();
}
The above code assumes username & password as string variables already in scope. Also an Extention method ToSecureString exists and is in scope.
As far as i understand PowerShell.Creste() provides an execution environment set to Restricted so you first need to change that. In this case becasue we scoped the Set-ExecutionPolicy to the process Admin rights are not required.
From what little i found on the subject it appears you need to remove the session when you are done or you may get throttled and lock yourself out of O365 for some time.
This is restriction from Exchange online and you need to use Commands instead of scripts in this case.
For example, running cmdlet Get-Mailbox -Identity user#mydomain.onmicrosoft.com will look like:
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri("https://ps.outlook.com/PowerShell-LiveID?PSVersion=2.0"), "http://schemas.microsoft.com/powershell/Microsoft.Exchange", credential);
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Basic;
Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo);
PowerShell shellInstance = PowerShell.Create();
shellInstance.Runspace = runspace;
PSObject mailbox = shellInstance.Runspace.AddCommand("Get-Mailbox").AddParameter("Identity","user#mydomain.onmicrosoft.com").Invoke().First();
I didn't use complex scripts and pipelines in my project so I'm not sure how it works if you need to run something rather tricky.
Related
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'm working on the some proof of concept code for a system that will manage a large number of Office365 accounts, however, I seem to be struggling at the first hurdle with a rather daft problem.
I'm using the RunspaceFactory to fire my Powershell commands at Office 365 and whilst the code appears to be running without any errors I never get a list of users back.
Firstly here's my code....
Runspace runSpace = RunspaceFactory.CreateRunspace();
runSpace.Open();
Pipeline pipeline = runSpace.CreatePipeline();
Command msolConnect = new Command("Connect-MsolService");
System.Security.SecureString securePassword = new System.Security.SecureString();
foreach (char pwdLetter in password)
{
securePassword.AppendChar(pwdLetter);
}
PSCredential credential = new PSCredential(username, securePassword);
msolConnect.Parameters.Add("Credential", credential);
pipeline.Commands.Add(msolConnect);
Command msolGetUser = new Command("Get-MsolUser");
msolGetUser.Parameters.Add("SearchString", "hayley");
pipeline.Commands.Add(msolGetUser);
Collection<PSObject> connectOutput = pipeline.Invoke();
foreach (PSObject psObject in connectOutput)
{
Console.WriteLine(psObject.Members["DisplayName"].Value.ToString());
}
The pipline.HadErrors is false and the connectOutput is always empty. It appears that code is succesfully running but without returning any results.
I have tried;
the same command in Windows Powershell and I get back a list of expected results.
mis-spelling SearchString (just to check that the command was running and the parameter was being passed) and an error is generated as I would expect I have also
using ImportPSModule(new[] { "MsOnline" }) to ensure the MSOnline module is available
other commands (e.g. Get-MsolGroup) and get back an empty list
I know find myself scratching my head on a Friday afternoon hoping that someone else may be able to help me...
Thanks in advance,
Darren
First you need to install SharePoint Online Management Shell from the following link: https://www.microsoft.com/en-us/download/details.aspx?id=35588. But you may also need to install Microsoft Online Services Sign-In Assistant version 7.0 or greater version from the following link: https://www.microsoft.com/en-my/download/details.aspx?id=39267 and then need to install Windows Azure Active Directory Module.
After that you can follow the following code:
InitialSessionState iss = InitialSessionState.CreateDefault();
iss.ImportPSModule(new[] { "MSOnline" });
using (Runspace myRunSpace = RunspaceFactory.CreateRunspace(iss))
{
Pipeline pipeline = myRunSpace.CreatePipeline();
myRunSpace.Open();
// Execute the Get-CsTrustedApplication cmdlet.
using (System.Management.Automation.PowerShell powershell = System.Management.Automation.PowerShell.Create())
{
powershell.Runspace = myRunSpace;
Command connect = new Command("Connect-MsolService");
System.Security.SecureString secureString = new System.Security.SecureString();
string myPassword = "Password";
foreach (char c in myPassword)
secureString.AppendChar(c);
connect.Parameters.Add("Credential", new PSCredential("admin#domain.microsoftonline.com", secureString));
powershell.Commands.AddCommand(connect);
Collection<PSObject> results = null;
Collection<ErrorRecord> errors = null;
results = powershell.Invoke();
errors = powershell.Streams.Error.ReadAll();
powershell.Commands.Clear();
Command getuser = new Command("Get-MsolUser");
getuser.Parameters.Add("MaxResults", 4);
powershell.Commands.AddCommand(getuser);
results = null;
errors = null;
results = powershell.Invoke();
foreach (PSObject psObject in results)
{
Console.WriteLine(psObject.Members["DisplayName"].Value.ToString());
}
}
}
Try appending the Get-MsolUser with -All
I tried running a script localwindows.ps1 from C# using the following Code :
PSCredential credential = new PSCredential(userName, securePassword);
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(false, "machineName", 5985, "/wsman", shellUri, credential);
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
runspace.Open();
String file = "C:\\localwindows.ps1";
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript(file);
pipeline.Commands.Add("Out-String");
Collection<PSObject> results = pipeline.Invoke();
}
But getting exception :'The term 'C:\localwindows.ps1' is not recognized as the name of a cmdlet, function, script file, or operable program.
So I tried the following :
PSCredential credential = new PSCredential(userName, securePassword);
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(false, "machineName", 5985, "/wsman", shellUri, credential);
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
runspace.Open();
using (PowerShell powershell = PowerShell.Create())
{
powershell.Runspace = runspace;
PSCommand new1 = new PSCommand();
String machinename = "machinename";
String file = "C:\\localwindows.ps1";
new1.AddCommand("Invoke-Command");
new1.AddParameter("computername", machinename);
new1.AddParameter("filepath", file);
powershell.Commands = new1;
Console.WriteLine(powershell.Commands.ToString());
Collection<PSObject> results = powershell.Invoke();
}
I am getting the error : "Cannot find path 'C:\localwindows.ps1' because it does not exist."
But using command 'Invoke-Command -ComputerName "machineName" -filepath C:\localwindows.ps1' ,from powershell in local machine created a new account in the remote machine.
How to call the script localwindows.ps1 from C#?
How to execute the command 'Invoke-Command -ComputerName "machineName" -filepath C:\localwindows.ps1' through C#?
The script localwindows.ps1 is
$comp = [adsi]“WinNT://machinename,computer”
$user = $comp.Create(“User”, "account3")
$user.SetPassword(“change,password.10")
$user.SetInfo()
Actually your invocation style should work. But in both of your examples, the script c:\localwindows.ps1 must reside on the local computer. In the Invoke-Command case, it will be copied from the local computer to the remote computer.
If, in the Invoke-Command case, the script already exists on the remote computer and you don't need to copy it over, remove the FilePath parameter and add this:
new1.AddParameter("Scriptblock", ScriptBlock.Create(file));
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);
// for your specific issue I think this would be easier
var results = machineManager.RunScript(
File.ReadAllText("C:\\LocalWindows.ps1"));
// 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 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.
I'm trying to figure out how to do this kind of thing, but using PowerShell calls (from C#). We are moving to Exchange 2010 and my old code doesn't want to work, hence the PowerShell.
IExchangeMailbox exMb = (IExchangeMailbox)userDe.NativeObject;
IADsSecurityDescriptor securityDescriptor = (IADsSecurityDescriptor)exMb.MailboxRights;
IADsAccessControlList acl = (IADsAccessControlList)securityDescriptor.DiscretionaryAcl;
AccessControlEntry ace = new AccessControlEntry();
...
...
I've got the mailbox OK using:
using (PowerShell powershell = PowerShell.Create())
{
powershell.AddCommand("Get-Mailbox");
powershell.AddParameter("Identity", username);
runspace.Open();
powershell.Runspace = runspace;
return powershell.Invoke()[0];
}
But if I then pass the result to a similar method to get the ACL so I can start modifying that, like this
using (PowerShell powershell = PowerShell.Create())
{
powershell.AddCommand("Get-Acl");
powershell.AddArgument(mailbox);
runspace.Open();
powershell.Runspace = runspace;
return powershell.Invoke()[0];
}
...I get
The term 'Get-Acl' is not recognized as the name of a cmdlet
...coming out in the logs. I also tried 'Get-ACL' in case it was case sensitive but I think the first version is correct.
I also tried Get-MailboxPermission but the docs for that say it doesn't even have a return type, so it wouldn't give me an object to manipulate afterwards.
Please help
Figured it out eventually:
powershell.AddCommand("Add-MailboxPermission");
powershell.AddParameter("Identity", mailboxIdentity);
powershell.AddParameter("User", groupName);
powershell.AddParameter("AccessRights", "FullAccess");
powershell.AddParameter("InheritanceType", "All");