I've been attempting to create a site collection from a custom web service I have built in C#. I already had some methods in this that ran some powershell commands so I figured I would just create the site using powershell commands like the code below. This code runs fine without error but does not create a site collection, seems as if it is doing nothing. Is there a better way or can someone see what may be wrong below?
public string CreateSiteCollection(string urlroot, string urlname, string database, string primaryadmin, string secondadmin, string language, string description, string title, string template)
{
//Find language and template code
string lang_code = get_lang_code(language);
string temp_code = get_temp_code(template);
Call the PowerShell.Create() method to create an empty pipeline
PowerShell ps = PowerShell.Create();
// Place our script in the string myscript
string myscript = string.Format(#"
Add-PSSnapin Microsoft.SharePoint.Powershell -erroraction 'silentlycontinue'
$template = Get-SPWebTemplate ""{0}""
New-SPSite {1}{2} -OwnerAlias '{3}' -SecondaryOwnerAlias '{4}' -Language {5} -Description '{6}' -ContentDatabase {7} -Template $template -Name '{8}'
", temp_code, urlroot, urlname, primaryadmin, secondadmin, lang_code, description, database, title);
// Create PowerShell runspace
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
Pipeline pipeline = runspace.CreatePipeline(); // create pipepline then feed it myscript
pipeline.Commands.AddScript(myscript);
pipeline.Commands.Add("Out-String");
Collection<PSObject> results;
try
{
// Executing the script
results = pipeline.Invoke();
}
catch (Exception e)
{
// An error occurred, return the exception
return string.Format("Exception caught: {0}", e);
}
runspace.Close();
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
string output = stringBuilder.ToString().Trim();
if (output == "")
{
return "PowerShell Commands ran sucessfully with no output";
}
else
{
return String.Format("PowerShell Commands ran sucessfully and returned the following: {0}", output);
}
} // End of CreateSiteColleciton
I may use the Admin.asmx web service instead, but it would be easier if I could get this working because it allows me more customization.
Is there a reason why you can't call the SharePoint server side object model directly from your web service like this, http://msdn.microsoft.com/en-us/library/office/ms411953(v=office.14).aspx.
Or is there a requirement to go through a set of CmdLets?
I decided to go the with the object model just because it makes more sense. Below is the code for creating a new site collection:
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite site = new SPSite("http://servername:port/"))
{
using (SPWeb web = site.OpenWeb())
{
HttpContext.Current = null;
site.AllowUnsafeUpdates = true;
web.AllowUnsafeUpdates = true;
var newSite = site.WebApplication.Sites.Add(....);
}
}
});
Related
I'm trying to read my IIS local websites from C# using Powershell
But I always get an empty output
public static ManageWebsite GetWebSiteStatus(string websiteName)
{
ManageWebsite websiteState = new ManageWebsite();
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
PowerShell ps = PowerShell.Create(); // Create a new PowerShell instance
ps.Runspace = runspace; // Add the instance to the runspace
ps.Commands.AddScript(#"Get-Website -Name """ + websiteName + #""" | %{$_.state}"); // Add a script
Collection<PSObject> results = ps.Invoke();
string powershell_output = string.Empty;
runspace.Close();
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
powershell_output = obj.BaseObject.ToString();
}
websiteState.Status = powershell_output;
websiteState.WebSite = websiteName;
return websiteState;
}
I don't know why, I always use that function to invoke other things with powershell in C# and always works fine
a few things to check:
you instanciate a stringbuilder but never use it
StringBuilder stringBuilder = new StringBuilder();
you set a variable to an empty string, which is ok but you override it everytime you loop through your objects. IF you never get objects its still an empty string! An then you assign to Status websiteState.Status.
string powershell_output = string.Empty;
foreach (PSObject obj in results)
{
powershell_output = obj.BaseObject.ToString();
}
websiteState.Status = powershell_output;
So if your websiteName never gets an result from Get-Website you will always have an Object with no Properites set as expected. If you get an result from Get-Website you only get some stuff form BaseObject.ToString() from the last iterated object. You might want to do a little more checking if you get something back at all before you try to assign values.
Update to comments
you might want to build your string like
ps.Commands.AddScript($"Get-Website -Name {websiteName} | %{{$_.state}}");
and i am not sure if you get to access the pipe result with this like you expect it in powershell. so maybe try to store the array in a OutVariable that you return explict in the script.
Thank you to all.
I know I'm an idiot. My code ps.Commands.AddScript(#"Get-Website -Name """ + websiteName + #""" | %{$_.state}"); // Add a script
is acting in the IIS Express, that's why always return empty
In my C# app, I have to run some PowerShell scripts. I copied paste from this site code about how to run the scripts.
My question: suppose I want to use the code from the link, how can I extract the PowerShell output to some string or to some .txt file?
EDIT:
If you want to test this code for answering this post, you need:
add reference to System.Management.Automation dll
add requireAdministrator inside app.manifest
Based on this link, you can extract the output with Collection<PSObject> results = pipeline.Invoke();, here a code example, note this would extract the PowerShell output to StringBuiler and would hide the PowerShell window:
string RunScript(string pathToYourScript){
RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();
using (var runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration))
{
runspace.Open();
runspace.SessionStateProxy.SetVariable("prog", this);
using (Pipeline pipeline = runspace.CreatePipeline())
{
if (!string.IsNullOrEmpty(path))
pipeline.Commands.AddScript(string.Format("$env:path = \"{0};\" + $env:path", pathToYourScript));
pipeline.Commands.AddScript(path);
pipeline.Commands.Add("Out-String");
Collection<PSObject> results = pipeline.Invoke();
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
var outDefault = new Command("out-default");
outDefault.MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output);
pipeline.Commands.Add(outDefault);
return stringBuilder;
}
}
}
I have a C# application that queries Lync and Exchange PowerShell using Runspaces. If I execute the application on a server that has local access to the PowerShell commandlets i.e. the management tools are installed, it works; however, if I connect to the server remotely, my foreach logic fails with the following error:
Example loop - the first loop works fine, however it fails at the 2nd one as I drill down in the PS objects:
public Collection<PSObject> Workflows;
var wFFilter = _dataService.WorkFlows.ToList();
//foreach (PSObject workflowName in workflowNames)
foreach (dynamic workflowName in wFFilter)
{
var newWorkflow = new WorkFlowViewModel();
if (workflowName != null)
{
//GetDisplay Name
newWorkflow.Name = workflowName.Name;
//Populate IVR options
foreach (dynamic root in workflowName.DefaultAction.Question.AnswerList)
{
if (root.Action.QueueID != null)
{
//Do something
}
}
}
}
This leads me to believe there is something different in the way the PowerShell object is returned. Could this be the case? I just cant figure out why this is different, and how I can handle both local and remotely returned objects.
My PS code:
private RunspacePool rrp;
public Collection<PSObject> ExecuteSynchronously(string PSCommand, string RemoteMachineFqdn, int RemoteMachinePort, string RemoteMachinePath,
bool SslEnabled, string Username, SecureString Password)
{
Collection<PSObject> psResult;
if (rrp == null)
{
string shellUri = #"http://schemas.microsoft.com/powershell/Microsoft.PowerShell";
PSCredential remoteCredential = new PSCredential(Username, Password);
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(SslEnabled, RemoteMachineFqdn,
RemoteMachinePort, RemoteMachinePath, shellUri, remoteCredential);
connectionInfo.SkipRevocationCheck = true;
connectionInfo.SkipCACheck = true;
connectionInfo.SkipCNCheck = true;
rrp = RunspaceFactory.CreateRunspacePool(1, 10, connectionInfo);
rrp.Open();
}
using (PowerShell powershell = PowerShell.Create())
{
powershell.RunspacePool = rrp;
powershell.AddScript(PSCommand);
psResult = powershell.Invoke();
}
return psResult;
}
Thanks! Really appreciate some help on this :)
The are different because they've been serialized at the remote session and then deserialized in your local session. Serialization can result in loss of fidelity of object properties, and removal of methods from the objects.
I have an application which (among other things) needs to call the New-MailContact cmdlet and create contacts in Active Directory. I have followed a handful of tech articles to get as far as I have, but it is still not working.
I have verified my service account being used has the proper authentication based on this TechNet page. I am able to find and invoke the cmdlet from powershell, and I recieve no errors.
However, after running I inspect my OU and my contact was not created. I found this KB article which I think may be suspect, but since the cmdlet doesn't return any errors after the invoke, I can't be sure that this will solve my problem.
Here is a snippet of what I am doing:
public bool CreateMailContactObject(ADExchangeContact adExchangeContacts)
{
Collection<PSObject> results;
Pipeline pipeLine = null;
try
{
var runspaceConfiguration = RunspaceConfiguration.Create();
PSSnapInException snapInException;
var snapInInfo = runspaceConfiguration.AddPSSnapIn("Microsoft.Exchange.Management.PowerShell.Admin", out snapInException);
using (var runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration))
{
var newMailBoxContact = new Command("New-MailContact");
newMailBoxContact.Parameters.Add("Name", adExchangeContacts.DisplayName);
newMailBoxContact.Parameters.Add("ExternalEmailAddress", adExchangeContacts.ExternalEmailAddress);
newMailBoxContact.Parameters.Add("OrganizationalUnit", adExchangeContacts.OrganizationalUnit);
newMailBoxContact.Parameters.Add("Alias", adExchangeContacts.Alias);
runspace.Open();
pipeLine = runspace.CreatePipeline();
pipeLine.Commands.Add(newMailBoxContact);
results = pipeLine.Invoke();
_log.DebugFormat("results.Count = {0}", results.Count);
results.ForEach(x => x.Properties.ForEach(y => _log.DebugFormat("{0}: {1}", y.Name, y.Value)));
pipeLine.Stop();
runspace.Close();
}
return true;
}
catch (Exception ex)
{
// Add log statement
_log.ErrorFormat("Creation of Mail Contact in AD Failed. Error: {0}", ex);
return false;
}
}
I do not get any exceptions, and my result list is empty from the Pipeline invoke. Is there something I am missing? If the cmdlet fails due to permissions when creating the contact in AD, wouldn't I expect to recieve some sort of error in the result set from pipeLine.Invoke() ??
I am new with running Powershell, so if there is another issue (beyond the KB article) that could be at hand, please let me know.
if (pipeline.Error != null && pipeline.Error.Count > 0)
{
StringBuilder pipelineError = new StringBuilder();
pipelineError.AppendFormat("Error calling Add-MailboxPermission.");
foreach (object item in thisPipeline.Error.ReadToEnd())
{
pipelineError.AppendFormat("{0}\n", item.ToString());
}
ErrorText = ErrorText + "Error: " + pipelineError.ToString() + Environment.NewLine;
}
Please Put this code after pipeline.Invoke() and check if there is any error in there
Update:
I think this is erro with giving right permission to user,some solutions for this:
http://boardreader.com/thread/Microsoft_Exchange_2010_wont_allow_new_M_1w69j__37ad9f8a-cdcf-4d26-9384-00ad1a3d0f91.html
http://blogs.technet.com/b/richardroddy/archive/2010/07/12/exchange-2010-and-the-exchange-trusted-subsystem.aspx
I have the following sample Powershell script that is embedded in my C# application.
Powershell Code
$MeasureProps = "AssociatedItemCount", "ItemCount", "TotalItemSize"
$Databases = Get-MailboxDatabase -Status
foreach($Database in $Databases) {
$AllMBStats = Get-MailboxStatistics -Database $Database.Name
$MBItemAssocCount = $AllMBStats | %{$_.AssociatedItemCount.value} | Measure-Object -Average -Sum
$MBItemCount = $AllMBStats | %{$_.ItemCount.value} | Measure-Object -Average -Sum
New-Object PSObject -Property #{
Server = $Database.Server.Name
DatabaseName = $Database.Name
ItemCount = $MBItemCount.Sum
}
}
Visual Studio offers me the following embedding options:
Every PowerShell sample I've seen (MSDN on Exchange, and MSFT Dev Center) required me to chop up the Powershell command into "bits" and send it through a parser.
I don't want to leave lots of PS1 files with my application, I need to have a single binary with no other "supporting" PS1 file.
How can I make it so myapp.exe is the only thing that my customer sees?
Many customers are averse to moving away from a restricted execution policy because they don't really understand it. It's not a security boundary - it's just an extra hoop to jump through so you don't shoot yourself in the foot. If you want to run ps1 scripts in your own application, simply use your own runspace and use the base authorization manager which pays no heed to system execution policy:
InitialSessionState initial = InitialSessionState.CreateDefault();
// Replace PSAuthorizationManager with a null manager which ignores execution policy
initial.AuthorizationManager = new
System.Management.Automation.AuthorizationManager("MyShellId");
// Extract psm1 from resource, save locally
// ...
// load my extracted module with my commands
initial.ImportPSModule(new[] { <path_to_psm1> });
// open runspace
Runspace runspace = RunspaceFactory.CreateRunspace(initial);
runspace.Open();
RunspaceInvoke invoker = new RunspaceInvoke(runspace);
// execute a command from my module
Collection<PSObject> results = invoker.Invoke("my-command");
// or run a ps1 script
Collection<PSObject> results = invoker.Invoke("c:\temp\extracted\my.ps1");
By using a null authorization manager, execution policy is completed ignored. Remember - this is not some "hack" because execution policy is something for protecting users against themselves. It's not for protecting against malicious third parties.
http://www.nivot.org/nivot2/post/2012/02/10/Bypassing-Restricted-Execution-Policy-in-Code-or-in-Script.aspx
First of all you should try removing your customer's aversion To scripts. Read up about script signing, execution policy etc.
Having said that, you can have the script as a multiline string in C# code itself and execute it.Since you have only one simple script, this is the easiest approach.
You can use the AddScript ,ethos which takes the script as a string ( not script path)
http://msdn.microsoft.com/en-us/library/dd182436(v=vs.85).aspx
You can embed it as a resource and retrieve it via reflection at runtime. Here's a link from MSDN. The article is retrieving embedded images, but the principle is the same.
You sort of hovered the answer out yourself. By adding it as content, you can get access to it at runtime (see Application.GetResourceStream). Then you can either store that as a temp file and execute, or figure out a way to invoke powershell without the use of files.
Store your POSH scripts as embedded resources then run them as needed using something like the code from this MSDN thread:
public static Collection<PSObject> RunScript(string strScript)
{
HttpContext.Current.Session["ScriptError"] = "";
System.Uri serverUri = new Uri(String.Format("http://exchangsserver.contoso.com/powershell?serializationLevel=Full"));
RunspaceConfiguration rc = RunspaceConfiguration.Create();
WSManConnectionInfo wsManInfo = new WSManConnectionInfo(serverUri, SHELL_URI, (PSCredential)null);
using (Runspace runSpace = RunspaceFactory.CreateRunspace(wsManInfo))
{
runSpace.Open();
RunspaceInvoke scriptInvoker = new RunspaceInvoke(runspace);
scriptInvoker.Invoke("Set-ExecutionPolicy Unrestricted");
PowerShell posh = PowerShell.Create();
posh.Runspace = runSpace;
posh.AddScript(strScript);
Collection<PSObject> results = posh.Invoke();
if (posh.Streams.Error.Count > 0)
{
bool blTesting = false;
string strType = HttpContext.Current.Session["Type"].ToString();
ErrorRecord err = posh.Streams.Error[0];
if (err.CategoryInfo.Reason == "ManagementObjectNotFoundException")
{
HttpContext.Current.Session["ScriptError"] = "Management Object Not Found Exception Error " + err + " running command " + strScript;
runSpace.Close();
return null;
}
else if (err.Exception.Message.ToString().ToLower().Contains("is of type usermailbox.") && (strType.ToLower() == "mailbox"))
{
HttpContext.Current.Session["ScriptError"] = "Mailbox already exists.";
runSpace.Close();
return null;
}
else
{
HttpContext.Current.Session["ScriptError"] = "Error " + err + "<br />Running command " + strScript;
fnWriteLog(HttpContext.Current.Session["ScriptError"].ToString(), "error", strType, blTesting);
runSpace.Close();
return null;
}
}
runSpace.Close();
runSpace.Dispose();
posh.Dispose();
posh = null;
rc = null;
if (results.Count != 0)
{
return results;
}
else
{
return null;
}
}
}
The customer just can't see the PowerShell script in what you deploy, right? You can do whatever you want at runtime. So write it to a temporary directory--even try a named pipe, if you want to get fancy and avoid files--and simply start the PowerShell process on that.
You could even try piping it directly to stdin. That's probably what I'd try first, actually. Then you don't have any record of it being anywhere on the computer. The Process class is versatile enough to do stuff like that without touching the Windows API directly.