Executing scripts with Powershell via C# - powershell

I need to execute the following script:
Get-MailboxDatabase -Status | select ServerName,Name,DatabaseSize
I tried a few solutions with Powershell and Command classes, but they doesn't work.
Error that I received:
Value parameters cannot be null.

I think this will do what you're looking for:
private string RunLocalExchangePowerShell(string script)
{
// create the runspace and load the snapin
RunspaceConfiguration rsConfig = RunspaceConfiguration.Create();
PSSnapInException snapInException = null;
Runspace runSpace = RunspaceFactory.CreateRunspace(rsConfig);
runSpace.Open();
rsConfig.AddPSSnapIn("Microsoft.Exchange.Management.PowerShell.E2010", out snapInException);
Pipeline pipeLine = runSpace.CreatePipeline();
// load the script and convert the output to a string
pipeLine.Commands.AddScript(script);
pipeLine.Commands.Add("out-string");
// get the results
Collection<PSObject> results;
results = pipeLine.Invoke();
// loop through the results and build the output
StringBuilder sb = new StringBuilder();
foreach (PSObject obj in results)
{
sb.AppendLine(obj.ToString());
}
// close the pipeline and runspace
pipeLine.Dispose();
runSpace.Close();
return sb.ToString();
}
Example usage:
Console.WriteLine(prog.RunLocalExchangePowerShell("Get-MailboxDatabase -Status | select ServerName,Name,DatabaseSize"));

Related

How to check current status of a Data Factory pipelinerun with just Pipeline name?

Is it possible to check the current status of a Azure Data Factory pipelinerun with just Pipeline name either through Powershell or API ?
I have seen that you can use Get-AzDataFactoryV2PipelineRun but that requires you to have the pipelinerunid.
My goal is to build a script that will first check if a pipeline run is running and if not trigger it. I want to avoid the script to trigger a pipelinerun so that there will be multiple pipeline runs running at the same time.
I've done it with PowerShell, although I'm sure there are better ways, this is the solution I've come up with.
It basically gets all the pipeline run ids from a week ago to this day, iterates through each one and if it finds one that is "InProgress", it will not do anything. If no pipeline run id is in that state, then execute it.
You obviously need to be authenticated previously to run this:
$before = get-date
$after = (get-date).AddDays(-7)
$runIds = Get-AzDataFactoryV2PipelineRun -DataFactoryName $DataFactoryName -ResourceGroupName $ResourceGroupName -LastUpdatedAfter $after -LastUpdatedBefore
$before
$shouldRun = 1
$runIds | ForEach-Object {
## Check for all statuses
if($_.Status -eq "InProgress"){
$shouldRun = 0
}
}
if($shouldRun){
## Logic to run pipeline, this is just an example.
Invoke-AzDataFactoryV2Pipeline -PipelineName $PipelineName -ResourceGroupName $ResourceGroupName -DataFactoryName $DataFactoryName
}
Using .Net Core SDK
ServiceClientCredentials cred = new TokenCredentials(accessToken);
using (var client = new DataFactoryManagementClient(cred) { SubscriptionId = subscriptionId })
{
RunQueryFilter filter1 = new RunQueryFilter("PipelineName", "Equals", new List<string> { "ActualPipelineName" });
RunQueryFilter filter2 = new RunQueryFilter("Status", "Equals", new List<string> { "Queued" });
DateTime before = DateTime.UtcNow;
DateTime after = before.AddHours(-4);
RunFilterParameters param = new RunFilterParameters(after, before, null, new List<RunQueryFilter> { filter1, filter2 }, null);
PipelineRunsQueryResponse pipelineResponse = client.PipelineRuns.QueryByFactory(resourceGroupName, azureDataFactoryName, param);
int? QueuedPipelines = pipelineResponse?.Value?.Count;
}
In filter 1, you can use "In" operator to query for more than one PipelineName.
In filter2, you can use any valid status of ADF (Success, InProgress, Queued etc.)
This same thing can also be achieved using REST API.
P.S- you might need to use Continuation token if the count of pipelines is more than 100 (for that particular status).

C# and Powershell Runspaces creating massive unmanaged memory leak

I have a strange issue with a windows service that I'm developing in .Net 4. After an hour of running the service the memory usage is at 274MB.
I have tried to find the cause of the leak by removing the functions one by one to find out where the leak occurs and at the moment because of this all the service does is run a PowerShell command Get-WBSummary every 10 seconds and capture the output to a string.
I am trying to track down what is hogging all the memory using Red Gate .Net Profiler 10. When I look at the biggest hog of memory after an hour the only thing that sticks out is
Namespace Class name Live size (bytes) Unmanaged size (bytes)
System.Reflection RuntimeAssembly 1,400 1,462,189
Which cant be right as that comes out to 1.462189mb.
Here is the code I am using which runs every time the service timer hits 10 seconds (For testing I set it to a low value)
var Config = RunspaceConfiguration.Create();
PSSnapInException Warning;
if (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor == 1)
{
Config.AddPSSnapIn("Windows.ServerBackup", out Warning);
}
using (var Runspace = RunspaceFactory.CreateRunspace(Config))
{
Runspace.Open();
PowerShell Ps;
using (Ps = PowerShell.Create())
{
Ps.Runspace = Runspace;
Ps.AddCommand("Get-WBSummary");
var Output = Ps.Invoke();
var Sb = new StringBuilder();
foreach (var PsObject in Output)
{
foreach (var PsPropertyInfo in PsObject.Properties)
{
Sb.AppendLine(PsPropertyInfo.Name + " " + PsPropertyInfo.Value);
}
}
PowershellOutput = Sb.ToString();
}
Runspace.Close();
Runspace.Dispose();
Ps.Dispose();
Config = null;
PowerShellSession.Stop();
PowerShellSession.Dispose();
GC.Collect();
Can anyone suggest what I might be doing wrong?
Edit:
The problem here has nothing to do with the C# code but the way I was using PowerShell. The solution is instead of using one PowerShell runspace and entering the same command again and again in it (Which causes the PowerShell memory to increase to infinity) but to create a new PowerShell process every time and then close it when you are done
using (PowerShellProcessInstance ExternalPowerShellProccess = new PowerShellProcessInstance())
{
ExternalPowerShellProccess.Process.Start();
var Config = RunspaceConfiguration.Create();
if (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor == 1)
{
Config.AddPSSnapIn("Windows.ServerBackup", out PSSnapInException Warning);
}
Runspace Runspace = RunspaceFactory.CreateOutOfProcessRunspace(TypeTable.LoadDefaultTypeFiles(), ExternalPowerShellProccess);
Runspace.Open();
using (PowerShell Ps = PowerShell.Create())
{
Ps.Runspace = Runspace;
Ps.AddCommand("Get-WBSummary");
var Output = Ps.Invoke();
var Sb = new StringBuilder();
foreach (var PsObject in Output)
{
foreach (var PsPropertyInfo in PsObject.Properties)
{
Sb.AppendLine(PsPropertyInfo.Name + " " + PsPropertyInfo.Value);
}
}
PowershellOutput = Sb.ToString();
Ps.Dispose();
Runspace.Close();
Runspace.Dispose();
}

Execute PowerShell Script from C#

I need to make an application which will run Power-Shell and run command Get-Dedupjob, but I received the following error:
the term "Get-Dedupjob" is not recognized as the name of a cmdlet,
function, script file, or operable program.
Although mentioned command works properly when I run this in power-shell (show deduplication jobs).
I suppose that problem could be that program doesn't run command as administrator.
Is there some solution ho to run this as admin? Or where could be the problem?
private void button5_Click(object sender, EventArgs e)
{
// Get-Dedupjob
// 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("Get-Dedupjob");
// execute the script
Collection<PSObject> results = pipeline.Invoke();
// 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());
}
textBox1.Text = Convert.ToString(stringBuilder);
}
You should import the corresponding psd1 file
private void button5_Click(object sender, EventArgs e)
{
// Get-Dedupjob
// create Powershell runspace
Runspace runspace = RunspaceFactory.CreateRunspace();
// open it
runspace.Open();
PowerShell ps = PowerShell.Create();
ps.Runspace = rs;
ps.AddCommand("Get-Dedupjob");
// execute the script
Collection<PSObject> results = ps.Invoke();
// convert the script result into a single string
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
textBox1.Text = Convert.ToString(stringBuilder);
// close the runspace
runspace.Close();
}
You will need to create powershell object to run the powershell commands. Following are the using statement imports you need to have for the above to work.
using System.Management.Automation; // Windows PowerShell namespace.
using System.Management.Automation.Runspaces; // Windows PowerShell namespace.

How to update "HasMultipleValues" for Sharepoint 2013 Metadata Managed Property through Powershell

I need to add new Managed Metadata Property into Sharepoint 2013 using Powershell + Sharepoint Powershell Extensions.
I did this using C#.
To get all Sharepoint Managed properties I made this:
private static string GetAllSPManagedProperties(string searchApplication)
{
RunspaceConfiguration config = RunspaceConfiguration.Create();
PSSnapInException OExSnapIn = null;
PSSnapInInfo pssnap = config.AddPSSnapIn("Microsoft.SharePoint.PowerShell", out OExSnapIn);
//create powershell runspace
Runspace cmdlet = RunspaceFactory.CreateRunspace(config);
cmdlet.Open();
RunspaceInvoke scriptInvoker = new RunspaceInvoke(cmdlet);
// set powershell execution policy to unrestricted
scriptInvoker.Invoke("Set-ExecutionPolicy Unrestricted");
// create a pipeline and load it with command object
Pipeline pipeline = cmdlet.CreatePipeline();
Command cmd = new Command("Get-SPEnterpriseSearchMetadataManagedProperty");
pipeline.Commands.Add(cmd);
CommandParameter cmdParam = new CommandParameter("SearchApplication", searchApplication);
cmd.Parameters.Add(cmdParam);
//pipeline.Commands.Add("Out-String");
// this will format the output
IEnumerable<PSObject> output = pipeline.Invoke();
pipeline.Stop();
cmdlet.Close();
// process each object in the output and append to stringbuilder
StringBuilder results = new StringBuilder();
foreach (PSObject obj in output)
{
var typeNames = obj.TypeNames;
var p1 = obj.Properties["ID"].Value; // "AboutMe" object {string}
var p2 = obj.Properties["ManagedType"].Value; // Text object {Microsoft.Office.Server.Search.Administration.ManagedDataType}
var p3 = obj.Properties["PID"].Value; // 26 object {int}
var p4 = obj.Properties["Name"].Value; // "AboutMe" object {string}
var p5 = obj.Properties["HasMultipleValues"].Value; // true object {bool}
string managedTypeName = (string)p2.ToString();
results.AppendLine(obj.ToString());
}
return results.ToString();
}
The problem is that I am trying to set this flag "HasMultipleValues" of the selected Managed Metadata Property programmatically.
obj.Properties["HasMultipleValues"].Value = true;
I do not know how to do that. I was hoping to find some Update method of the PSObject (returned by the pipeline.Invoke() but unfortunately didn't found anything useful.
My question is, Is it possible to set the properties of any ManagedMetadataProperty and how ?
It looks like that I found a solution myself.
I didn't found the way on how to achieve the solution through setting object properties but have found equally acceptable solution through scripting.
public static void UpdateSharepointManagedMetadataProperty(string searchApplication, string propertyName, bool HasMultipleValues)
{
RunspaceConfiguration config = RunspaceConfiguration.Create();
PSSnapInException OExSnapIn = null;
PSSnapInInfo pssnap = config.AddPSSnapIn("Microsoft.SharePoint.PowerShell", out OExSnapIn);
//create powershell runspace
Runspace cmdlet = RunspaceFactory.CreateRunspace(config);
cmdlet.Open();
RunspaceInvoke scriptInvoker = new RunspaceInvoke(cmdlet);
bool set = HasMultipleValues;
string script = "";
if (set)
{
script =
string.Format("$prop = Get-SPEnterpriseSearchMetadataManagedProperty -Identity {0} -SearchApplication \"{1}\" \r\n", propertyName, searchApplication) +
"$prop.HasMultipleValues = $true \r\n" +
"$prop.Queryable = $true \r\n" +
"$prop.Refinable = $true \r\n" +
"$prop.Searchable = $true \r\n" +
"$prop.Retrievable = $true \r\n" +
"$prop.Update() \r\n";
}
else
{
script =
string.Format("$prop = Get-SPEnterpriseSearchMetadataManagedProperty -Identity {0} -SearchApplication \"{1}\" \r\n", propertyName, searchApplication) +
"$prop.HasMultipleValues = $false \r\n" +
"$prop.Queryable = $false \r\n" +
"$prop.Refinable = $false \r\n" +
"$prop.Searchable = $false \r\n" +
"$prop.Retrievable = $false \r\n" +
"$prop.Update() \r\n";
}
var result = scriptInvoker.Invoke(script);
cmdlet.Close();
}

How to troubleshoot a 'System.Management.Automation.CmdletInvocationException'

Does anyone know how best to determine the specific underlying cause of this exception?
Consider a WCF service that is supposed to use Powershell 2.0 remoting to execute MSBuild on remote machines. In both cases the scripting environments are being called in-process (via C# for Powershell and via Powershell for MSBuild), rather than 'shelling-out' - this was a specific design decision to avoid command-line hell as well as to enable passing actual objects into the Powershell script.
An abridged version of the Powershell script that calls MSBuild is shown below:
function Run-MSBuild
{
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Build.Engine")
$engine = New-Object Microsoft.Build.BuildEngine.Engine
$engine.BinPath = "C:\Windows\Microsoft.NET\Framework\v3.5"
$project = New-Object Microsoft.Build.BuildEngine.Project($engine, "3.5")
$project.Load("deploy.targets")
$project.InitialTargets = "DoStuff"
# Process the input object
while ($input.MoveNext())
{
# Set MSBuild Properties & Item
}
# Optionally setup some loggers (have also tried it without any loggers)
$consoleLogger = New-Object Microsoft.Build.BuildEngine.ConsoleLogger
$engine.RegisterLogger($consoleLogger)
$fileLogger = New-Object Microsoft.Build.BuildEngine.FileLogger
$fileLogger.Parameters = "verbosity=diagnostic"
$engine.RegisterLogger($fileLogger)
# Run the build - this is the line that throws a CmdletInvocationException
$result = $project.Build()
$engine.Shutdown()
}
When running the above script from a PS command prompt it all works fine. However, as soon as the script is executed from C# it fails with the above exception.
The C# code being used to call Powershell is shown below (remoting functionality removed for simplicity's sake):
// Build the DTO object that will be passed to Powershell
dto = SetupDTO()
RunspaceConfiguration runspaceConfig = RunspaceConfiguration.Create();
using (Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfig))
{
runspace.Open();
IList errors;
using (var scriptInvoker = new RunspaceInvoke(runspace))
{
// The Powershell script lives in a file that gets compiled as an embedded resource
TextReader tr = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("MyScriptResource"));
string script = tr.ReadToEnd();
// Load the script into the Runspace
scriptInvoker.Invoke(script);
// Call the function defined in the script, passing the DTO as an input object
var psResults = scriptInvoker.Invoke("$input | Run-MSBuild", dto, out errors);
}
}
NOTE: The overload of the Invoke() method allows you to pass in an IEnumerable object and it takes care of instantiating an enumerator to in the Powershell variable '$input' - this then gets passed into the script via the pipeline. Here are some supporting links:
http://msdn.microsoft.com/en-us/library/ms569104(VS.85).aspx
http://knicksmith.blogspot.com/2007/03/managing-exchange-2007-recipients-with.html (jump to the 'Passing an Input Object to the Runspace' section)
Assuming that the issue was related to MSBuild outputting something that the Powershell runspace can't cope with, I have also tried the following variations to the second .Invoke() call:
var psResults = scriptInvoker.Invoke("$input | Run-MSBuild | Out-String", dto, out errors);
var psResults = scriptInvoker.Invoke("$input | Run-MSBuild | Out-Null", dto, out errors);
var psResults = scriptInvoker.Invoke("Run-MSBuild | Out-String");
var psResults = scriptInvoker.Invoke("Run-MSBuild | Out-String");
var psResults = scriptInvoker.Invoke("Run-MSBuild | Out-Null");
var psResults = scriptInvoker.Invoke("Run-MSBuild");
Note how the underlying issue still occurs irrespective of whether an input object is used.
I've also looked at using a custom PSHost (based on this sample: http://blogs.msdn.com/daiken/archive/2007/06/22/hosting-windows-powershell-sample-code.aspx), but during debugging I was unable to see any 'interesting' calls to it being made.
Do the great and the good of Stackoverflow have any insight that might save my sanity?
I can get the following code to work but I get a warning that MSBUILD engine wants to be run on a STA thread. Unfortunately the thread created by the PowerShell engine to execute the script is MTA. That said, my little sample works:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Management.Automation;
using System.Collections;
namespace RunspaceInvokeExp
{
class Program
{
static void Main()
{
var script = #"
function Run-MSBuild
{
[System.Reflection.Assembly]::LoadWithPartialName(""Microsoft.Build.Engine"")
$engine = New-Object Microsoft.Build.BuildEngine.Engine
$engine.BinPath = ""C:\Windows\Microsoft.NET\Framework\v3.5""
$project = New-Object Microsoft.Build.BuildEngine.Project($engine, ""3.5"")
$project.Load(""deploy.targets"")
$project.InitialTargets = ""DoStuff""
# Process the input object
while ($input.MoveNext())
{
# Set MSBuild Properties & Item
}
# Optionally setup some loggers (have also tried it without any loggers)
$consoleLogger = New-Object Microsoft.Build.BuildEngine.ConsoleLogger
$engine.RegisterLogger($consoleLogger)
$fileLogger = New-Object Microsoft.Build.BuildEngine.FileLogger
$fileLogger.Parameters = ""verbosity=diagnostic""
$engine.RegisterLogger($fileLogger)
# Run the build - this is the line that throws a CmdletInvocationException
$result = $project.Build()
$engine.Shutdown()
}
";
using (var invoker = new RunspaceInvoke())
{
invoker.Invoke(script);
IList errors;
Collection<PSObject> results = invoker.Invoke(#"$input | Run-MSBuild", new[] {0}, out errors);
Array.ForEach<PSObject>(results.ToArray(), Console.WriteLine);
}
}
}
}
Which line of your C# code fails? Also, can you post some of the specifics from the exception. You can work around the MTA thread issue by doing something like this:
using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
namespace RunspaceInvokeExp
{
class Program
{
[STAThread]
static void Main()
{
var script = #"
function Run-MSBuild
{
[System.Reflection.Assembly]::LoadWithPartialName(""Microsoft.Build.Engine"")
$engine = New-Object Microsoft.Build.BuildEngine.Engine
$engine.BinPath = ""C:\Windows\Microsoft.NET\Framework\v3.5""
$project = New-Object Microsoft.Build.BuildEngine.Project($engine, ""3.5"")
$project.Load(""deploy.targets"")
$project.InitialTargets = ""DoStuff""
# Process the input object
while ($input.MoveNext())
{
# Set MSBuild Properties & Item
}
# Optionally setup some loggers (have also tried it without any loggers)
$consoleLogger = New-Object Microsoft.Build.BuildEngine.ConsoleLogger
$engine.RegisterLogger($consoleLogger)
$fileLogger = New-Object Microsoft.Build.BuildEngine.FileLogger
$fileLogger.Parameters = ""verbosity=diagnostic""
$engine.RegisterLogger($fileLogger)
# Run the build - this is the line that throws a CmdletInvocationException
$result = $project.Build()
$engine.Shutdown()
}
Run-MSBuild
";
Runspace runspace = RunspaceFactory.CreateRunspace();
Runspace.DefaultRunspace = runspace;
runspace.Open();
EngineIntrinsics engine = runspace.SessionStateProxy.
GetVariable("ExecutionContext") as EngineIntrinsics;
ScriptBlock scriptblock =
engine.InvokeCommand.NewScriptBlock(script);
Collection<PSObject> results = scriptblock.Invoke(new[] { 0 });
Array.ForEach<PSObject>(results.ToArray(), Console.WriteLine);
runspace.Close(); // Really should be in a finally block
}
}
}