Just because something is a daft idea, doesn't mean it can't be done. We sometimes need to do some housekeeping - like delete old files or list items or… yes, well, whatever you use Powershell for in a SharePoint world. Or it could be that your solution has "issues" for which you have Powershell solutions, but not the budget to transform into proper bug fixes. So you create a "how to" for the ITPro guys. Idea: What if we keep the scripts in a list, and have SharePoint execute the scripts on demand? An announcements list (because of the multiline body field). Warning! Let us be clear. This list needs to be locked down; if somebody creates a malicious script and you run it, I cannot help you. First; we need to figure out how to start Powershell scripts from C#. Hit teh interwebs and the Googlie, and you may find jpmik's post: http://www.codeproject.com/Articles/18229/How-to-run-PowerShell-scripts-from-C. (Or MS' official answer at http://msdn.microsoft.com/en-us/library/ee706563(v=vs.85).aspx)
public string RunPowershell(string powershellText, SPWeb web, string param1, string param2) {
// Powershell ~= RunspaceFactory - i.e. Create a powershell context
var runspace = RunspaceFactory.CreateRunspace();
var resultString = new StringBuilder();
try
{
// load the SharePoint snapin - Note: you cannot do this in the script itself (i.e. add-pssnapin etc does not work)
PSSnapInException snapInError;
runspace.RunspaceConfiguration.AddPSSnapIn("Microsoft.SharePoint.PowerShell", out snapInError);
runspace.Open();
// set a web variable.
runspace.SessionStateProxy.SetVariable("webContext", web);
// and some user defined parameters
runspace.SessionStateProxy.SetVariable("param1", param1);
runspace.SessionStateProxy.SetVariable("param2", param2);
var pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript(powershellText);
// add a "return" variable
pipeline.Commands.Add("Out-String");
// execute!
var results = pipeline.Invoke();
// convert the script result into a single string
foreach (PSObject obj in results)
{
resultString.AppendLine(obj.ToString());
}
}
finally
{
// close the runspace
runspace.Close();
}
// consider logging the result. Or something.
return resultString.ToString();
}
Ok. We've written some code. Let us test it.
var runner = new PowershellRunner();
runner.RunPowershellScript(@"
$web = Get-SPWeb 'http://server/web' # or $webContext
$web.Title = $param1
$web.Update()
$web.Dispose()
", null, "New title", "not used");
Next step: Connect
the code to the list, or more specifically, have the code execute on one (or
several) list items. As there are more options than readers, I'll leave this as
an exercise for the reader. Some alternatives:
Create a ribbon
button that calls RunPowershell with the body of the selected itemsAdd a layout pageSpecify
list item from query string (possibly coupled with content editor webpart with
html that links directly to this page with querystring)WebpartListing
with an "execute" columnList
with multiselect and an execute button
Etc!Now
that you have the code for executing powershell scripts, you can easily expand
this into a timer job, which executes scripts at regular intervals. But if the
previous solution was dangerous, this is even worse - the scripts will usually
be run with one of the admin accounts, and can do pretty much anything...One more thing...
Note that as this is running "consoleless" calls to Write-Host will
fail. Two solutions; remove all output, or check if the script is run in a
console-window or not.
if ($host.Name -eq "ConsoleHost")
{
Write-Host 'If I agreed with you we'd both be wrong'
}