Add Powershell commands to the pipeline - c#

I'm trying to get the lower store from a 2010 Exchange server, and the function will run in a WCF container.
The problem I'm facing is that I'm unable to run multiple PowerShell commands in the pipeline.
I've tried the following (based on this, how to invoke the powershell command with "format-list" and "out-file" pipeline from c#?):
string strCommand = #"Get-MailboxDatabase -Status | select ServerName,Name,DatabaseSize | Sort-Object DatabaseSize";
string CommandLine = string.Format("&{{{0}}}", strCommand);
pipeLine.Commands.AddScript(CommandLine);
But I get:
Unhandled Exception: System.Management.Automation.RemoteException: Script block literals are not allowed in restricted language mode or a Data section.
Also I tried,
Command getMailbox = new Command("Get-MailboxDatabase");
getMailbox.Parameters.Add("Status", null);
Command sort = new Command("Sort-Object");
pipeLine.Commands.Add(getMailbox);
pipeLine.Commands.Add(sort);
Collection<PSObject> commandResults = pipeLine.Invoke();
But not luck:
Unhandled Exception: System.Management.Automation.RemoteException: The term 'Sort-Object' is not recognized as the name of a cmdlet
I wonder if I should use multiple pipelines (one pipeline per cmdlet), but I am not sure.

It sounds like the problem is the runspace. If that's an Exchange server, and you're running that in the remote management session provided by Exchange, the only thing you can do in that session is run the Exchange cmdlets. The Select-Object and Sort-Object cmdlets and other PowerShell language elements just aren't there to use.

Considering that Sort-Object is a command which is not recognized by the schema named 'http://schemas.microsoft.com/powershell/Microsoft.Exchange" then I proceed to develop a function using Snap-Ins and it's working fine.
Notice I'm taking the first database because the default sort mode is ascending. Also I'd like to comment that if you compile on Framework 4.0 you're going to get a "Value cannot be null error message" so you have to change to 3.5.
Keep in mind that it is being used by a WCF Service so no problem with Snap-Ins. If you like to use it on any other application, like a console-based application then you should install EMS 2010 on that computer.
This function basically execute the following PowerShell command, Get-MailboxDatabase -Status | Sort-Object DatabaseSize
private static string getLowServerStoreDN_SnapIn(string ExchangeSite)
{
string strResult = string.Empty;
RunspaceConfiguration rsConfig = RunspaceConfiguration.Create();
PSSnapInException snapInException = null;
PSSnapInInfo info = rsConfig.AddPSSnapIn("Microsoft.Exchange.Management.PowerShell.E2010", out snapInException);
Runspace runspace = RunspaceFactory.CreateRunspace(rsConfig);
try
{
runspace.Open();
Command getMailbox = new Command("Get-MailboxDatabase");
getMailbox.Parameters.Add(new CommandParameter("Status", null));
Command sort = new Command("Sort-Object");
sort.Parameters.Add("Property", "DatabaseSize");
Pipeline commandPipeLine = runspace.CreatePipeline();
commandPipeLine.Commands.Add(getMailbox);
commandPipeLine.Commands.Add(sort);
Collection<PSObject> getmailboxResults = commandPipeLine.Invoke();
if (getmailboxResults.Count > 0)
{
PSObject getMailboxResult = getmailboxResults[0];
strResult = getMailboxResult.Properties["Name"].Value.ToString();
//foreach (PSObject getMailboxResult in getmailboxResults)
//{
// strResult = getMailboxResult.Properties["Name"].Value.ToString();
//}
}
}
catch (ApplicationException e)
{
//Console.WriteLine(e.Message);
throw new FaultException("function getLowServerStoreDN_SnapIn(" + ExchangeSite + "): " + e.Message,
FaultCode.CreateReceiverFaultCode("BadExchangeServer", "http://example.com"));
}
return strResult;
}

Related

Common Language Runtime detected an invalid program

The wpf app is working well on a computer where it was developed. The files from bin/Release are used to run the app.
There is System.Management.Automation.dll used to work with powershell remotely on the Exchange Server 2010 to create new mailboxes. It's setup with Local = True so the dll is added into the files in the bin/release.
However, if the same files from bin/release are copied on another computer then all functionalities on Active Directory are working well except the mail-box creation part as it gives Error: Common Language Runtime detected an invalid program.
I could find a few advises as to uncheck 'Optimize Code' and rebuild it in VS but it seems as it's not helping.
As I said before all is working well on developer's computer. So, I run the app on another computer where VS 2013 was also installed and got an error:"The term 'Enable-Mailbox' 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."
Here is the code:
public static class ManageMailBox
{
public static bool CreateMailBox(string strUserID, string admin_user, string pword) //, string str_upn)
{
try
{
string strExchangeServer = Constants.ExchangeServer;
Uri uri = new Uri(#"http://" + strExchangeServer + #"/powershell?serializationLevel=Full"); // orig works
/// must pass secure string
char[] passwordChars = pword.ToCharArray();
SecureString password = new SecureString();
foreach (char c in passwordChars)
{
password.AppendChar(c);
}
PSCredential credential = new PSCredential("DOMAIN\\" + admin_user, password);
Runspace runspace = RunspaceFactory.CreateRunspace();
PowerShell powershell = PowerShell.Create();
PSCommand command = new PSCommand();
command.AddCommand("New-PSSession");
command.AddParameter("ConfigurationName", "Microsoft.Exchange");
command.AddParameter("ConnectionUri", uri);
command.AddParameter("Credential", credential);
command.AddParameter("Authentication", "Default");
PSSessionOption sessionOption = new PSSessionOption();
sessionOption.SkipCACheck = true;
sessionOption.SkipCNCheck = true;
sessionOption.SkipRevocationCheck = true;
command.AddParameter("SessionOption", sessionOption);
powershell.Commands = command;
try
{
// open the remote runspace
runspace.Open();
// associate the runspace with powershell
powershell.Runspace = runspace;
// invoke the powershell to obtain the results
Collection<PSSession> result = powershell.Invoke<PSSession>();
foreach (ErrorRecord current in powershell.Streams.Error)
{
throw new Exception("Exception: " + current.Exception.ToString());
throw new Exception("Inner Exception: " + current.Exception.InnerException);
}
if (result.Count != 1)
throw new Exception("Unexpected number of Remote Runspace connections returned.");
// Set the runspace as a local variable on the runspace
powershell = PowerShell.Create();
command = new PSCommand();
command.AddCommand("Set-Variable");
command.AddParameter("Name", "ra");
command.AddParameter("Value", result[0]);
powershell.Commands = command;
powershell.Runspace = runspace;
powershell.Invoke();
// First import the cmdlets in the current runspace (using Import-PSSession)
powershell = PowerShell.Create();
command = new PSCommand();
//command.AddScript("Set-ExecutionPolicy -Unrestricted");
command.AddScript("Import-PSSession -Session $ra");
powershell.Commands = command;
powershell.Runspace = runspace;
powershell.Invoke();
// Now run get-ExchangeServer
powershell = PowerShell.Create();
command = new PSCommand();
command.AddCommand("Enable-Mailbox");
command.AddParameter("Identity", strUserID);
command.AddParameter("Alias", strUserID);
command.AddParameter("Database", "IAP Mailbox Database 0948752629");
powershell.Commands = command;
powershell.Runspace = runspace;
powershell.Invoke(); // ERROR !!! The term 'Enable-Mailbox' is not recognized as the name of a cmdlet, function
return true;
}
catch(Exception ex) {
throw new Exception(ex.Message.ToString()); }
finally
{
// dispose the runspace and enable garbage collection
runspace.Dispose();
runspace = null;
// Finally dispose the powershell and set all variables to null to free
// up any resources.
powershell.Dispose();
powershell = null;
}
}
catch (Exception argex)
{
throw new ArgumentException(argex.Message.ToString());
}
}
}
It seems like this could be a few things, it is hard to know what is the cause in your scenario. Firstly i would try running as Admin, failing that i would try these things:
reinstall the .net framework (make sure the version is correct)
Common Language Runtime detected an invalid program in Visual Studio
For C# projects go to the project properties and under the Build tab un-check "Optimize Code".
Common Language Runtime detected an invalid program?
Try enabling 32-bit applications in your application pool advanced settings.
or
I finally managed to solve this issue. I unchecked code optimization
in C# Express and that solved the issues.
InvalidProgramException / Common Language Runtime detected an invalid program
It's solved. It was a matter of permissions for PowerShell script to use cmdlet commands remotely.
Edit: while it could be the case why mail-box could not be created this error was on another computer with a different configuration.
On the computer where we received the “Common language runtime error” the problem was the older versuion of PowerShell.
I experienced this in a Xamarin Forms app because I had a button click event on an event that didn't exist. Removing the click event from the button resolved the issue.
I had this issue, published with "Optimize Code" checked in Project > Properties > Build and worked fine then re-published and "Optimize Code" unchecked and work OK again.

Restarting App Pool via c# using powershell script

I'm trying to restart an iis app pool on a remote server. First I would like to stop the app pool however I am getting an
Cannot convert the "{ Stop-WebAppPool -Name "BaymentPool" }" value of
type "System.String" to type
"System.Management.Automation.ScriptBlock".
exception
public void StopAppPool()
{
Runspace runSpace = RunspaceFactory.CreateRunspace();
runSpace.Open();
Pipeline pipeline = runSpace.CreatePipeline();
Command invokeCmd = new Command("Invoke-Command");
invokeCmd.Parameters.Add("ComputerName","IISC02");
invokeCmd.Parameters.Add("ScriptBlock","{ Stop-WebAppPool -Name \"BaymentPool\" }");
pipeline.Commands.Add(invokeCmd);
Collection<PSObject> output = pipeline.Invoke();
foreach (PSObject psObject in output)
{
Process process = (Process)psObject.BaseObject;
Console.WriteLine("Process name: " + process.ProcessName);
}
}
I can't quite fathom out what I am doing wrong. I know I need to add each param separately, which I think I'm doing, however it still complains.
A simple solution could be:
var serverManager = ServerManager.OpenRemote("000.000.000.000"); // Ip Address of Remote server
var appPool = serverManager.ApplicationPools["MyAppPool"];
if (appPool == null) return;
if (appPool.State == ObjectState.Stopped)
{
appPool.Start();
}
else
{
appPool.Recycle();
}
The scriptblock parameter should be of type ScriptBlock
invokeCmd.Parameters.Add("ScriptBlock", ScriptBlock.Create("{ Stop-WebAppPool -Name \"BaymentPool\" }"));
Your code that checks result is not correct, that cast to a Process object will not work. Have a look here for examples of how to inspect the result of your invoke:
http://blogs.msdn.com/b/kebab/archive/2014/04/28/executing-powershell-scripts-from-c.aspx

Execute PowerShell Script from C# MVC Web Application

I need to execute a powershell script from my asp.net MVC Web application. My requirement is to create site collections dynamically. I have the script for it and it works perfectly.There are no arguments which are to be passed to the script. The code which I have been using has been displayed below:
RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();
Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration);
runspace.Open();
RunspaceInvoke scriptInvoker = new RunspaceInvoke(runspace);
Pipeline pipeline = runspace.CreatePipeline();
//Here's how you add a new script with arguments
Command myCommand = new Command(scriptfiellocation);
pipeline.Commands.Add(myCommand);
pipeline.Commands.Add("Out-String");
// Execute PowerShell script
var result = pipeline.Invoke();
On executing the code, when I check the count of variable result it gives the count as 1. However on checking my site, there is no site collection that has been created. I am not able to identify where I am going wrong as there is no run time error and the Invoke command also seems to be running properly.
Could anyone tell me where I might be going haywire ? Considering that the PowerShell script works perfectly when running through Management shell.
I had to forego the pipeline approach as I was not able to figure out what the issue was. Also another problem with that approach is that it threw the error: "Get-SPWbTemplate is not recognized as an cmdlet". The following code worked perfectly fine for me and created the required site collections:
PowerShell powershell = PowerShell.Create();
//RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();
//Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration)
using (Runspace runspace = RunspaceFactory.CreateRunspace())
{
runspace.Open();
//RunspaceInvoke scriptInvoker = new RunspaceInvoke(runspace);
//scriptInvoker.Invoke("Set-ExecutionPolicy Unrestricted");
powershell.Runspace = runspace;
//powershell.Commands.AddScript("Add-PsSnapin Microsoft.SharePoint.PowerShell");
System.IO.StreamReader sr = new System.IO.StreamReader(scriptfilepath);
powershell.AddScript(sr.ReadToEnd());
//powershell.AddCommand("Out-String");
var results = powershell.Invoke();
if (powershell.Streams.Error.Count > 0)
{
// error records were written to the error stream.
// do something with the items found.
}
}
Also there was no requirement to set the execution policy.
well don't know if its help but i never use pipeline to run Command shell not sure how that work.
But here a quick example
Runspace RS = RunspaceFactory.CreateRunspace(myConnection);
PowerShell PS = PowerShell.Create();
PSCommand PScmd = new PSCommand();
string cmdStr = "Enable-Mailbox -Identity " + username + " -Database DB01 -Alias " + aliasexample;
PScmd.AddScript(cmdStr);
try
{
RS.Open();
PS.Runspace = RS;
PS.Commands = PScmd;
PS.Invoke();
}
catch (Exception ex)
{
ex.ToString();
}
finally
{
RS.Dispose();
RS = null;
PS.Dispose();
PS = null;
}
with the try catch you can catch the error with debugging if something goes wrong.
If i remember correctly i had to put ACL.exe for permission to file system so i can execute the commandshell you can do a quick search on google for it.
Hope this help.

Running powershell in C#, not having much luck!

I have created a asp.net web application for internal use that allows certain users to start and stop Virtual machines that are linked to there QA testing environments, the code behind page runs a powershell script that starts the selected server once a button is pressed on an ASP.net page.
I have reserched and implimented alot of the code from this site but i am coming up against a few problems.
everytime i click the button on the main web page the error that is fed back from the powershell script says"You cannot call a method on a null-valued expression." the only problem is if i run it from a powershell prompt like this ". \script\test.ps1 'W7EA9'" it works fine.
This is the class that calls the powershell script.
public String Startserver(String Servername)
{
String scriptText =". \\scripts\\test.ps1 " + Servername + "";
// create Powershell runspace
Runspace runspace = RunspaceFactory.CreateRunspace();
// open it
runspace.Open();
// create a pipeline and feed it the script text
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript(scriptText);
// execute the script
Collection<PSObject> results = new Collection<PSObject>();
try
{
results = pipeline.Invoke();
}
catch (Exception ex)
{
results.Add(new PSObject((object)ex.Message));
}
// close the runspace
runspace.Close();
// convert the script result into a single string
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
return stringBuilder.ToString();
//return scriptText;
}
and here is the powershell script it is trying to run
Param ($server = $arg[0])
$Core = get-wmiobject -namespace root\virtualization -class Msvm_Computersystem -filter "ElementName = '$server'"
$status = $Core.RequestStateChange(2) `
It may be somthing really obvious but im just not seeing it and any help would be great.
thanks
Chris
Here is a best step-by-step guide to running PowerShell from ASP.NET.
http://devinfra-us.blogspot.com/2011/02/using-powershell-20-from-aspnet-part-1.html
HTH
I don't see where you're providing a parameter to the script anywhere.
i am passing the paramater from a button press on an asp.net page the code behind looks like this
Hypervserver Start = new Hypervserver();
String result = Start.Startserver("W7EA9");
Label1.Visible = true;
Label1.Text = result;
Below is how I ended up doing this.
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
Pipeline pipeline = runspace.CreatePipeline();
Command myCommand = new Command("C:\\Scripts\\Test.ps1");
//I used full path name here, not sure if you have to or not
CommandParameter myParam1 = new CommandParameter("-ServerName", "myServer");
myCommand.Parameters.Add(myParam1);
//You can add as many parameters as you need to here
pipeline.Commands.Add(myCommand);
Collection<PSObject> results = pipeline.Invoke();
runspace.Close();
StringBuilder stringBuilder = new StringBuilder()
foreach (PSObject obj in results) {
stringBuilder.AppendLine(obj.ToString());
}
string thestring = stringBuilder.ToString();
A few notes. The scripts first line that is not a comment or blank line should be the parameter list formatted like this:
param([string]$ServerName,[string]$User)
You have this, I just wanted to acknowledge the fact that I could not get this working when my script file was a function with parameters.
For certain commands you may need additional privileges, in my case all of my scripts worked this way except for creating a mailbox for which I had to add credentials onto the connection.
Greg
In powershell, the default arguments collection is called $args, with an 's'; I'm pretty sure that's why $server is null when you run it via code, and thus the Get-WmiObject call returns null, causing the error when you attempt to call the RequestStateChange method on it.
I'm guessing it works fine in your normal powershell window because you already have a $server variable in the session.

Powershell in C# Return Command Output

I'm new to bringing C# and Powershell together, but am hoping to create a web page that leverages Powershell in the back end. I realize that what I am doing can be done solely with C#, but would like to figure this out for other applications.
Essentially, I am taking the name of a new web application from a web form and getting the authenticated user's username for physical path mapping.
My Powershell code works correctly (even when copying it from Pipeline.Commands[0] directly), but it does not appear to do anything when I run it. I get parameter errors in the result variable if I force one (ex: make -physicalpath a non-existent path), but with all parameters correct, the variable result only contains one blank item.
I see many similar questions to this one, but do not see s definitive answer.
Does this sound like a C# or IIS Powershell module issue? Any ideas how I get more information returned from my command?
protected void Button1_Click(object sender, System.EventArgs e)
{
String username = getUser();
String physicalPath = "S:\\WebSites\\" + username + "\\public_html\\" + TextBox1.Text;
// Create Powershell Runspace
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
// Create pipeline and add commands
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript(
"Import-Module WebAdministration; set-psdebug -trace 1; " +
"New-WebApplication -Site MySite" +
" -Name " + TextBox1.Text +
" -PhysicalPath " + physicalPath +
" -ApplicationPool WebSites -Verbose -force");
pipeline.Commands.Add("Out-String");
// Execute Script
Collection<PSObject> results = new Collection<PSObject>();
try
{
results = pipeline.Invoke();
}
catch (Exception ex)
{
results.Add(new PSObject((object)ex.Message));
}
// Close runspace
runspace.Close();
//Script results to string
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
}
Thanks!
That looks like it should work. You should check the error stream and see if there are messages there (ie: "Destination element already exists").
I'd also suggest you consider using the PowerShell 2 APIs as in this blog post:
http://huddledmasses.org/how-to-invoke-powershell-and-use-the-results-from-csharp/
If you're using that, you can check the ps.Streams.Error to make sure it's empty...

Categories