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...
Related
I am creating an exchange user (new-mailbox) and then setting some AD parameters on them after the user is created in the same runspace with commands that will not run in the Exchange runspace unless import-module 'activedirecty' is ran. Is there a way to import the module after the runspace is created as I can do with the Powershell prompt?
inside the same runspace session I want to run:
new-mailbox
set-mailbox
set-user
set-aduser
The last one is what requires me to import the AD module I can successfully run the command inside of Powershell directly, but can't seem to figure out how to add the module mid runspace session? I'd tried
powershell.AddParameter("import-module -name 'activedirectory'; set-aduser xxxx")
and
powershell.AddParameter("import-module -name 'activedirectory'")
powershell.AddParameter("set-aduser xxxx")
and
powershell.AddScript("import-module -name 'activedirectory'; set-aduser xxxx")
This works below
public void SetPasswordNeverExpiresProperty(bool PasswordNeverExpires, string alias)
{
string dn = "CN=xxx,OU=xxx,OU=xxx=xxx=xxx=xxx,DC=xx,DC=xx,DC=xxx,DC=xxx"
DirectoryEntry objRootDSE = new DirectoryEntry("LDAP://" + dn);
ArrayList props = new ArrayList();
int NON_EXPIRE_FLAG = 0x10000;
int EXPIRE_FLAG = 0x0200;
int valBefore = (int) objRootDSE.Properties["userAccountControl"].Value;
objRootDSE.Properties["userAccountControl"].Value = EXPIRE_FLAG;
objRootDSE.CommitChanges();
string valAfter = objRootDSE.Properties["userAccountControl"].Value.ToString();`
And I'm out of guesses, any help would be appreciated.
PSCredential ExchangeCredential = new PSCredential(PSDomain + #"\" + PSUsername, PSpwd);
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri("xxxxxx/powershell"), "http://schemas.microsoft.com/powershell/Microsoft.Exchange", ExchangeCredential);
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Kerberos;
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
PowerShell powershell = PowerShell.Create();
if (runspace.RunspaceStateInfo.State == RunspaceState.Opened)
{
// do nothing
}
else
{
runspace.Open();
powershell.Runspace = runspace;
}
try
{
psobjs = powershell.Invoke();
}
catch (Exception ex)
{
result = "Failed: " + ex.Message;
}
powershell.Commands.Clear();
}
I'll sum up my comments in an answer, since it seems I was unexpectedly helpful :)
I also had found that you can't use Import-Module when using remote PowerShell like that. It's kind of annoying, but such is life.
Years ago, I implemented an automatic account creation service in our environment for AD and Exchange 2010. I found I had to do the AD account manipulation with DirectoryEntry and then only the Exchange stuff with PowerShell.
The problem is making sure that both things happen on the same domain controller so you don't run into replication problems.
So you have two options: Use New-Mailbox to create the mailbox and AD account in one shot. As you pointed out, the OriginatingServer property of the result has the domain controller. But there is also a DistinguishedName property there too! (I just found this when you mentioned the server property) Then you can create a DirectoryEntry object against the same domain controller like this:
new DirectoryEntry($"LDAP://{domainController}/{distinguishedName}")
Or, what I did (I think because I didn't realize at the time that I could get the DC from the result of New-Mailbox), is create the AD object first with DirectoryEntry, pull the domain controller it got created on from .Options.GetCurrentServerName(), then pass that in the DomainController parameter to Enable-Mailbox.
I am looking for almost similar solution. The scenario is same. I have parallel for each loop in c#.net. It calls 2 functions before actually hits the powershell code. The code snippet is below.
My question is, what is correct approach to call powershell script file in parallel using run space from c# code. Currently the issue is that powershell is in wait state indefinitely.
Please let me know improved or right way. I am not clear on the code you gave as example above, especially creating runspace pool and assigning to PS object. How or when do you call RemoteCommandInvoker(). I understand its a constructor, is that suffice. ?
My code snippet. The code is called from Prallel.ForEach. Here we pass IPdress and machine name. The script file is same.
public string ExecuteCommandDirect(string IpAddress, string machineName, string logFilePath, string powerShellPath, string PSLogFilePath) { String FullPsFilePath = powerShellPath; // path of powershell script file; String PsParameters = FullPsFilePath + " " + IpAddress + " " + machineName + " " + PSLogFilePath;
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
//Console.WriteLine("Create pipeline & add commands....");
// Create pipeline and add commands
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript(PsParameters);
// Execute Script
Collection<PSObject> results = new Collection<PSObject>();
AWSlogging.WriteLog("Invoking Script....", logFilePath);
try
{
results = pipeline.Invoke();
}
catch (Exception ex)
{
results.Add(new PSObject((object)ex.Message));
}
// Close runspace
runspace.Close();
runspace.Dispose();
//Console.WriteLine("Completed.Run Space closed...");
AWSlogging.WriteLog("Completed.Run Space closed...", logFilePath);
//Script results to string
StringBuilder stringBuilder = new StringBuilder();
//Console.WriteLine("Output....Start");
AWSlogging.WriteLog("Output....Start", logFilePath);
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
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;
}
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.