Exchange and PowerShell - c#

I'm currently developing a Exchange connector and using PowerShell scripts in C#, like this:
public void Connect(string exchangeFqdn_, PSCredential credential_)
{
var wsConnectionInfo = new WSManConnectionInfo(new Uri("http://" + exchangeFqdn_ + "/powershell"),
"http://schemas.microsoft.com/powershell/Microsoft.Exchange", credential_);
wsConnectionInfo.AuthenticationMechanism = AuthenticationMechanism.Default;
Runspace = RunspaceFactory.CreateRunspace(wsConnectionInfo);
Runspace.Open();
}
Then, I execute my script using the Powershell object:
public override List<PSObject> ExecuteCommand(Command command_)
{
List<PSObject> toreturn;
PowerShell powershell = null;
try
{
powershell = PowerShell.Create();
powershell.Commands.AddCommand(command_);
powershell.Runspace = Runspace;
toreturn = new List<PSObject>(powershell.Invoke());
}
finally
{
if (powershell != null)
powershell.Dispose();
}
return toreturn;
}
I can add a mail box like with this command:
Command command = new Command("New-Mailbox");
command.Parameters.Add("Name", name_);
command.Parameters.Add("OrganizationalUnit", ou_);
command.Parameters.Add("UserPrincipalName", upn_);
command.Parameters.Add("FirstName", firstname_);
command.Parameters.Add("Initials", initials_);
command.Parameters.Add("LastName", lastname_);
command.Parameters.Add("ResetPasswordOnNextLogon", false);
command.Parameters.Add("Password", secureString_);
But I'm facing an issue when I try to remove this mailbox (or another one):
Command command = new Command("Remove-Mailbox");
command.Parameters.Add("Identity", identity_);
command.Parameters.Add("Permanent", true);
System.Management.Automation.RemoteException: Cannot invoke this function because the current host does not implement it.
I do not understand. Why can I add a user, but not delete it ?
Am i missing something ?

I've found the answer, thanks to this topic.
I changed the Command object like that:
Command command = new Command("Remove-Mailbox");
command.Parameters.Add("Identity", identity_);
command.Parameters.Add("Permanent", true);
command.Parameters.Add("Confirm", false);
And it works like a charm.
Thanks !
I hope that help someone !

Related

Running powershell through c# recently getting error Access is denied

This program has been running successfully for over 4 years. Just recently (8/4/2022), the pscommand version of the program has failed. We are trying to figure out what changed.
We are getting the error "Connecting to remote server outlook.office365.com failed with the following error message : Access is denied. For more information, see the about_Remote_Troubleshooting Help topic." ONLY when dealing with pssession / PSCommands.
Code:
public Collection<PSObject> runPSCommand(PSCommand _command, string _commandName, PSCommand _secondCommand = null)
{
PSCredential credential = new PSCredential(this.emailLogin, this.emailPass);
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri(this.WSManConnectionURI), this.MSSchema, credential);
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Basic;
try
{
using (Runspace runspace = System.Management.Automation.Runspaces.RunspaceFactory.CreateRunspace(connectionInfo))
{
PowerShell powershell = PowerShell.Create();
PSCommand remoteSigned = new PSCommand();
runspace.Open();
powershell.Runspace = runspace;
wsmanconnectionURI: https://outlook.office365.com/PowerShell-LiveID
MSSchema: http://schemas.microsoft.com/powershell/Microsoft.Exchange
Fails at runspace.Open().
We run multiple different type of commands on this program (Connect-ExchangeOnline, Connect-AzureAD, Connect-MSOLService) that are ALL working, it is JUST running the PSCommands that fail.
Tried with powershell as well and it is also failing:
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Authentication Basic -AllowRedirection -Credential Get-Credential
With error: "New-PSSession : [outlook.office365.com] Connecting to remote server outlook.office365.com failed with the following error message : Access is denied.
For more information, see the about_Remote_Troubleshooting Help topic."
Again, this was working for multiple years and JUST started failing. We checked passwords, check logins, tried multiple users.
Thank you for any assistance.
So, Darin lead me to the correct answer, and it was probably deprecation per https://learn.microsoft.com/en-us/powershell/exchange/exchange-online-powershell-v2?view=exchange-ps. Apparently PSSession was deprecated, and we had to move the commands over to exchange online.
For those that it might help, our new code is:
public Collection<PSObject> runExchangeCommand(Command _command, string _commandName)
{
InitialSessionState initialSession = InitialSessionState.CreateDefault();
initialSession.ImportPSModule(new[] { "MSOnline" });
//initialSession.ImportPSModule(new[] { "ExchangeOnlineManagement" });
PSCredential credential = new PSCredential(this.emailLogin, this.emailPass);
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri("https://outlook.office365.com/PowerShell-LiveID"), "http://schemas.microsoft.com/powershell/Microsoft.Exchange", credential);
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Basic;
connectionInfo.MaximumConnectionRedirectionCount = 10;
Command connectCommand = new Command("Connect-ExchangeOnline");
connectCommand.Parameters.Add(new CommandParameter("Credential", credential));
try
{
using (Runspace runspace = RunspaceFactory.CreateRunspace(initialSession))
{
runspace.Open();
Pipeline pipe = runspace.CreatePipeline();
pipe.Commands.Add(connectCommand);
var results = pipe.Invoke();
var error = pipe.Error.ReadToEnd();
if (error.Count > 0)
{
foreach (PSObject err in error)
{
//more logging not sharing that code
}
}
pipe = runspace.CreatePipeline();
pipe.Commands.Add(_command);
var results2 = pipe.Invoke();
var error2 = pipe.Error.ReadToEnd();
if (error2.Count > 0)
{
foreach (PSObject er in error2)
{
//more logging, not sharing that code
}
}
return results2;
}
}
I left out logging / catch code because that included some identifying things.
As for creating the command, it boils down to what command you want to run. Here is one example of the runExchangeCommand:
public bool removeCalendarInvites(string email)
{
Command removeMeetings = new Command("Remove-CalendarEvents");
removeMeetings.Parameters.Add("Identity", email);
removeMeetings.Parameters.Add("CancelOrganizedMeetings");
removeMeetings.Parameters.Add("QueryWindowInDays", 365);
removeMeetings.Parameters.Add("Confirm", false);
Collection<PSObject> results = runExchangeCommand(removeMeetings, "removeCalendarEvents");
The command name is only used for logging / identifying what errors were caught in.
Thanks for looking

Executing 3rd Party Powershell SDK commands with C#

This is an incredibly frustrating problem that nobody else seems to have the answer to so I'm trying to break my question down into the simplest thing possible. Surely there are other 3rd party Powershell SDK's out there that people have tried to access and use via C#. Does anybody know why this would get an error saying the following?
The term 'add-PSSnapin Citrix*.Admin.V* is not recognized as the name of a cmdlet, function, script file, or operable program.
The same goes for other commands that this PSSnapin provides at the command prompt.
I can type this command in manually at a powershell command prompt and it works. So do the other commands. What's the deal?
public void testPS()
{
using (Runspace runspace = RunspaceFactory.CreateRunspace())
{
runspace.Open();
PowerShell ps = PowerShell.Create();
ps.Runspace = runspace;
ps.AddCommand("add-PSSnapin Citrix.*.Admin.V*");
ps.Invoke();
//Commented sections below don't work either, same error.
//ps.AddCommand("Get-BrokerSession");
//ps.AddParameter("AdminAddress");
//ps.AddParameter("SERVERNAME");
//ps.Invoke();
//Collection<PSObject> psr = ps.Invoke();
//foreach (PSObject x in psr)
//{
// MessageBox.Show(x.ToString());
//}
}
}
UPDATE:
This new code as suggested in the answer below gets a new error: 'System.Management.Automation.ParameterBindingException' occurred in System.Management.Automation.dll
public void testPS()
{
using (Runspace runspace = RunspaceFactory.CreateRunspace())
{
runspace.Open();
PowerShell ps = PowerShell.Create();
ps.Runspace = runspace;
PSSnapInException psex;
runspace.RunspaceConfiguration.AddPSSnapIn("Citrix.Broker.Admin.V2", out psex);
Pipeline pipeline = runspace.CreatePipeline();
Command getSession = new Command("Get-BrokerSession");
getSession.Parameters.Add("AdminAddress");
getSession.Parameters.Add("MYSERVERNAME");
//also tried the above with this code
//getSession.Parameters.Add("-AdminAddress MYSERVERNAME");
// and
//getSession.Parameters.Add("AdminAddress MYSERVERNAME");
// and other methods as well
pipeline.Commands.Add(getSession);
//This line below is where the exception occurs.
Collection<PSObject> output = pipeline.Invoke();
foreach (PSObject x in output)
{
MessageBox.Show(x.ToString());
}
}
}
UPDATE 2:
I also get this same error above trying to set the execution policy.
UPDATE 3:
Fixed, see comments in answer below. The syntax of the parameters line was incorrect.
Use RunspaceConfiguration.AddPSSnapIn to add PSSnapin and then add a command:
Runspace runSpace = RunspaceFactory.CreateRunspace();
runSpace.Open();
PSSnapInException psex;
runSpace.RunspaceConfiguration.AddPSSnapIn("Citrix.Broker.Admin.V2", out psex);
Pipeline pipeline = runSpace.CreatePipeline();
Command getSession = new Command("Get-BrokerSession");
getSession.Parameters.Add("AdminAddress", "SERVERNAME");
pipeline.Commands.Add(getSession);
Collection<PSObject> output = pipeline.Invoke();
You need to make sure that you split the command from its argument. In your case, it would be something like:
ps.AddCommand("add-PSSnapin");
Then you can always just post-append Citrix.*.Admin.V* as an argument for the command above.
For those still stuck with this like me, even as of the latest Microsoft.Powershell.Sdk 6.2.0 nuget package I was running into this issue. A colleague of mine ended up getting it to work by doing the following:
public string RunPowershell(string param1)
{
var outputString = "";
var startInfo = new ProcessStartInfo
{
FileName = #"powershell.exe",
Arguments = "C:\\path\\to\\script.ps1" + " -param1 " + param1,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
using (var process = new Process())
{
process.StartInfo = startInfo;
process.Start();
var output = process.StandardOutput.ReadToEnd();
var errors = process.StandardError.ReadToEnd();
if (!string.IsNullOrEmpty(output))
{
outputString = output;
}
if (!string.IsNullOrEmpty(errors))
{
Console.WriteLine($"Error: {errors}");
}
}
return outputString;
}
"C:\path\to\script.ps1" can have Add-PSSnapin commands in it and still run just fine.
I couldn't find a bug filed for Microsoft.Powershell.Sdk to handle this, but if someone creates one and links it here, thanks!
Hope this helps

Powershell Import-Module command doesn't work with c# api

Import-Module command work fine with powershell windows console but same command doesn't work on c# api.
i'm using this project for execute powershell script: http://www.codeproject.com/Articles/18229/How-to-run-PowerShell-scripts-from-C
it execute many of them commands but it doesn't execute "Import-Module 'c:\vm\vm.psd1'" command. i try import microsoft modules but it doesn't work too. How can i execute "Import-Module" command with c# api?
Also add-pssnapin 'virtualmachinemanager' doesn't work too.
Try load module in this way:
PowerShell powershell = PowerShell.Create();
powerShell.Commands.AddCommand("Import-Module").AddParameter("Name", "c:\vm\vm.psd1'");
or
PowerShell powershell = PowerShell.Create();
powershell.Commands.AddCommand("Add-PsSnapIn").AddParameter("Name", "virtualmachinemanager");
With a pipeline try create an InitialSessionState
InitialSessionState iss = InitialSessionState.CreateDefault();
iss.ImportPSModule(new string[] { #"C:\vm\vm.psd1"});
Runspace runSpace = RunspaceFactory.CreateRunspace(iss);
runSpace.Open();
then use your code with pipeline to run cmdlet from module loaded
Try something like this for loading the snapin and executing your commands:
using System.Management.Automation.Runspaces;
//...
var rsConfig = RunspaceConfiguration.Create();
using (var myRunSpace = RunspaceFactory.CreateRunspace(rsConfig))
{
PSSnapInException snapInException = null;
var info = rsConfig.AddPSSnapIn("FULL.SNAPIN.NAME.HERE", out snapInException);
myRunSpace.Open();
using (var pipeLine = myRunSpace.CreatePipeline())
{
Command cmd = new Command("YOURCOMMAND");
cmd.Parameters.Add("PARAM1", param1);
cmd.Parameters.Add("PARAM2", param2);
cmd.Parameters.Add("PARAM3", param3);
pipeLine.Commands.Add(cmd);
pipeLine.Invoke();
if (pipeLine.Error != null && pipeLine.Error.Count > 0)
{
//check error
}
}
}

c# remote powershell Hyperv cmdlet start-vm -Vm

i am trying to pass the VM object of that powershell command:
Start-Vm -Vm <VirtualMachine>
I would like to do this by c# code.
So i create a remote runspace and so on:
class RemotePowershell
{
private const string SHELL_URI = "http://schemas.microsoft.com/powershell/Microsoft.PowerShell";
private WSManConnectionInfo connectionInfo = null;
public RemotePowershell(string hostname, string username, string livepassword)
{
SecureString password = new SecureString();
foreach (char c in livepassword.ToCharArray()) { password.AppendChar(c); }
password.MakeReadOnly();
PSCredential creds = new PSCredential(string.Format("{0}\\{1}", hostname, username), password);
var targetWsMan = new Uri(string.Format("http://{0}:5985/wsman", hostname));
connectionInfo = new WSManConnectionInfo(targetWsMan, SHELL_URI, creds);
connectionInfo.OperationTimeout = 4 * 60 * 1000; // 4 minutes.
connectionInfo.OpenTimeout = 1 * 60 * 1000; // 1 minute.
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Negotiate;
}
public void RunScript(string scriptText, Collection<CommandParameter> parametters)
{
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
runspace.Open();
using (PowerShell ps = PowerShell.Create())
{
ps.Runspace = runspace;
ps.AddCommand(scriptText);
ps.AddParameters(parametters);
Collection<PSObject> results = ps.Invoke();
}
runspace.Close();
}
I run this with an extention method like this:
public static class PowershellExtentionMethods
{
private static RemotePowershell powerShellSession = new RemotePowershell("HOSTNAME", "USERNAME", "PASSWORD");
public static void PowershellExec(this string commands, Collection<CommandParameter> parameters)
{
powerShellSession.RunScript(commands, parameters);
}
}
var cmd = "Start-VM";
Collection<CommandParameter> cpc = new Collection<CommandParameter>();
cpc.Add(new CommandParameter("Vm",this.vm));
cmd.PowershellExec(cpc);
And nothing append the vm don't start and code run without exception.
So i would like to know if i use the right technique to pass object to cmdlet...
If someone as an idea, he's welcome ;)
A couple of thoughts.. Your example looks ok, but what type of virtualization are you using? I'm guessing based on the example that you are using Hyper-V.
Things to check:
Server OS, if 2008 or 2008 R2, where is the command coming from System Center or a third party library? In either case, I didn't see a call to load the module with the Start-VM command. Make sure the cmdlet or function is available before you call it. If it is Server 2012, autoloading should handle loading the command, but you'll want to make sure the Hyper-V module is available on the box and loads into the session.
What is the type of "this.VM" that you are passing to Start-VM? Depending on which module you are using to manage VMs, the type of the object will matter.
What is the VM storage like? Is it local or on an SMB share? If it is on an SMB share, is the correct credential delegation in place?

Problems using literals and codeblocks with c# to interact with powershell 2.0

If I try to run a Powershell Command through c# I get the following error:
"The term 'select' 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."
If the Command was executed directly with Powershell(.exe) all works fine!
The command I try to run looks like i.g:
"Get-Mailbox -Organization 'CoolOrganizationNameGoesHere' | select ServerName"
It seems that there is a problem with the "Pipe" |,
I have wasted hours on searching at major search engines with the wildest keyword combinations,
but I've found nothing that works.
The last thing I have tried is setting the PSLanguageMode property of the published IIS-Application for Powershell, The result is still the same as written before.
Maybe there is WinRM wrong configured? Or my local Powershell configuration is corrupted?
Is there any well written documentation on C# (or any other .Net language) using Powershell with remote access
and using the Pipe | "command"?
Can anybody tell me what is wrong, that's like to find the needle in a haystack!
Thanks!
Trick could be in the way you create your command. If you are running script as a string, you should use:
Command myCommand = new Command(script, true);
But if you want to run it as a filename - you should use:
Command myCommand = new Command(script, false);
So:
Command myCommand = new Command("Get-Date", true);
Command myCommand = new Command("c:\test.ps1", false);
In case you need full method:
private static IEnumerable<PSObject> ExecutePowerShellScript(string script, bool isScript = false, IEnumerable<KeyValuePair<string, object>> parameters = null)
{
RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();
Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration);
runspace.Open();
Command myCommand = new Command(script, isScript);
if (parameters != null)
{
foreach (var parameter in parameters)
{
myCommand.Parameters.Add(new CommandParameter(parameter.Key, parameter.Value));
}
}
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.Add(myCommand);
var result = pipeline.Invoke();
// Check for errors
if (pipeline.Error.Count > 0)
{
StringBuilder builder = new StringBuilder();
//iterate over Error PipeLine until end
while (!pipeline.Error.EndOfPipeline)
{
//read one PSObject off the pipeline
var value = pipeline.Error.Read() as PSObject;
if (value != null)
{
//get the ErrorRecord
var r = value.BaseObject as ErrorRecord;
if (r != null)
{
//build whatever kind of message your want
builder.AppendLine(r.InvocationInfo.MyCommand.Name + " : " + r.Exception.Message);
builder.AppendLine(r.InvocationInfo.PositionMessage);
builder.AppendLine(string.Format("+ CategoryInfo: {0}", r.CategoryInfo));
builder.AppendLine(string.Format("+ FullyQualifiedErrorId: {0}", r.FullyQualifiedErrorId));
}
}
}
throw new Exception(builder.ToString());
}
return result;
}

Categories