I am creating PowerShell cmdlets in C# by extending the PSCmdlet class.
I need to use the same parameter for pipeline input and normal parameter input. Eg
[Parameter(Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
public Object Connection;
Here the Connection parameter can take both pipeline input
$connectionValue | Cmdlet-Name
and also normal parameter using
Cmdlet-Name -Connection $connectionValue
Is there a way in C# by which I can find out if the parameter value is pipelined to the cmdlet or provided using -Connection?
In PowerShell this can be done by checking if $input is empty or not. Is there any parameter property that can indicate the input type?
You can check by seeing if its set when beginprocessing is called or if it only is set during process record. Non pipeline properties are set before begin processing is called.
Related
I have the following Powershell script.
param([String]$stepx="Not Working")
echo $stepx
I then try using the following C# to pass a parameter to this script.
using (Runspace space = RunspaceFactory.CreateRunspace())
{
space.Open();
space.SessionStateProxy.SetVariable("stepx", "This is a test");
Pipeline pipeline = space.CreatePipeline();
pipeline.Commands.AddScript("test.ps1");
var output = pipeline.Invoke();
}
After the above code snippet is run, the value "not working" is in the output variable. It should be "This is a test". Why is that parameter ignored?
Thanks
You're defining $stepx as a variable, which is not the same as passing a value to your script's $stepx parameter.
The variable exists independently of the parameter, and since you're not passing an argument to your script, its parameter is bound to its default value.
Therefore, you need to pass an argument (parameter value) to your script's parameter:
Somewhat confusingly, a script file is invoked via a Command instance, to which you pass arguments (parameter values) via its .Parameters collection.
By contrast, .AddScript() is used to add a string as the contents of an in-memory script (stored in a string), i.e., a snippet of PowerShell source code.
You can use either technique to invoke a script file with parameters, though if you want to use strongly typed arguments (whose values cannot be unambiguously inferred from their string representations), use the Command-based approach (the .AddScript() alternative is mentioned in comments):
using (Runspace space = RunspaceFactory.CreateRunspace())
{
space.Open();
Pipeline pipeline = space.CreatePipeline();
// Create a Command instance that runs the script and
// attach a parameter (value) to it.
// Note that since "test.ps1" is referenced without a path, it must
// be located in a dir. listed in $env:PATH
var cmd = new Command("test.ps1");
cmd.Parameters.Add("stepx", "This is a test");
// Add the command to the pipeline.
pipeline.Commands.Add(cmd);
// Note: Alternatively, you could have constructed the script-file invocation
// as a string containing a piece of PowerShell code as follows:
// pipeline.Commands.AddScript("test.ps1 -stepx 'This is a test'");
var output = pipeline.Invoke(); // output[0] == "This is a test"
}
In a PowerShell profile, one can identify the PowerShell host in order to do appropriate setup for that host's environment. For example:
if ($host.Name -eq 'ConsoleHost')
{
Import-Module PSReadline
# differentiate verbose from warnings!
$privData = (Get-Host).PrivateData
$privData.VerboseForegroundColor = "cyan"
}
elseif ($host.Name -like '*ISE Host')
{
Start-Steroids
Import-Module PsIseProjectExplorer
}
I would like to be able to do the equivalent identification from a C# context primarily because PowerShell ISE does not support Console.ReadLine so I want to know if it is safe to use it in the current PS host's environment.
I first explored trying to get the output of the Get-Host cmdlet from within C# (per Invoking a cmdlet within a cmdlet). After I located the Microsoft.PowerShell.Commands.Utility assembly (under C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\3.0) I could compile this but it yielded null...
var cmd = new Microsoft.PowerShell.Commands.GetHostCommand();
var myHost = cmd.Invoke();
...while this would not compile due to the InternalHost class being (ironically!) internal:
var cmd = new Microsoft.PowerShell.Commands.GetHostCommand();
var myHost = cmd.Invoke<System.Management.Automation.Internal.Host.InternalHost>();
Next, I then modified my cmdlet to inherit from PSCmdlet rather than Cmdlet (to allow access to the SessionState), so I could then access the PS host object like this:
var psVarObject = SessionState.PSVariable.GetValue("Host");
Of course, that returns a pure Object, which I then needed to cast to... oh, wait... it's still internal!... so this would not compile:
string psHost = ((System.Management.Automation.Internal.Host.InternalHost)psVarObject).Name;
Leaving me no alternative but to use reflection on a foreign assembly (horrors!):
string psHost = (string)psVarObject.GetType().GetProperty("Name").GetValue(psVarObject, null);
That works, but is less than ideal, because reflecting upon any 3rd-party assembly is a fragile thing to do.
Any alternative ideas on either (a) identifying the host or, (b) backing up a bit, being able to use the host's own Read-Host cmdlet to get a typed input from a user?
You can just use Host property from PSCmdlet class. And if you want to do Read-Host:
Host.UI.ReadLine()
When getting
var psVarObject = SessionState.PSVariable.GetValue("Host");
You can cast it to System.Management.Automation.Host.PSHost instead of InternalHost
Exchange management requires you to pass $null as a parameter to remove forwarding from a mailbox:
Set-Mailbox -ForwardingAddress $null -DeliverToMailboxAndForward $false
This (IMHO) translates to:
command = new PSCommand().AddCommand("Set-Mailbox");
command.AddParameter("Identity",task.TargetUser);
command.AddParameter("FowardingAddress", null);
command.AddParameter("DeliverToMailboxAndForward", false);
ps.Commands=command;
var results = ps.Invoke();
Unfortunately, the Invoke chokes on the "ForwardingAddress" setting: A parameter cannot be found that matches parameter name 'FowardingAddress'.
How can I pass a parameter that ends up as $null?
If that parameter is of type string then you should be able to pass this value:
System.Management.Automation.Language.NullString.Value
or include
using System.Management.Automation.Language;
and then later
command.AddParameter("ForwardingAddress",NullString.Value);
Another option is to use $null via the AddScript method:
var script = String.Format("Set-Mailbox -Identity {0} -ForwardingAddress $null -DeliverToMailboxAndForward $false", task.TargetUser);
new PSCommand().AddScript(script);
Passing null should work fine. As this question shows, C#'s nulls are mapped to $null.
Assuming the code you're showing is copy-pasted directly from your solution, I'm guessing the problem is that you're passing a value to the parameter "FowardingAddress", rather than "ForwardingAddress" (note the missing 'r'). A simple typo, not a type mismatch. :)
I want to examine a MS service (with display name 'MyService', say) on a failover cluster and to this end I want to evaluate powershell commands in C#.
The commands I have in mind are
$a = Get-ClusterResource "MyService"
$b = Get-ClusterGroup $a.OwnerGroup.Name | Get-ClusterResource | Where-Object {$_.ResourceType -eq "Network Name"}
I already figured out how to load the FailoverClusters module in to the power shell instance. I'm creating the shell using
the following code:
InitialSessionState state = InitialSessionState.CreateDefault();
state.ImportPSModule(new[] { "FailoverClusters" });
PowerShell ps = PowerShell.Create(state);
With this psinstance I can now successfully execute single cluster evaluation commands.
Now my understanding is that if I'm using ps.AddCommand twice, first with Get-ClusterResource and then with the commands from the next line, I will pipe the result of Get-ClusterResource into the next command, which I don't want to do since the -Name parameter of Get-ClusterResource does not accept results from a pipe. (Rather the second line would be build using AddCommand)
My question is, how do I pass the variable $a to the second line in a c# powershell invoke? Do I have to create two power shell instances and evaluate the first line first, passing it's result somehow to a second call, or is it possible to define a variable in a programmatic powershell instance?
I'm pretty sure you just need to use AddParameter or AddArgument after adding the Get-ClusterResource command to the pipeline. AddParameter on MSDN.
Once you have the first pipeline added (only a single command in this case), use var result = ps.Invoke();, yank the required info from the result.Members collection, and use it to AddParameter or AddArgument after adding the Get-ClusterGroup
Then continue to use addCommand to fill in the rest of the pipeline.
The Powershell Invoke method has an example on msdn
(copy and pasted for posterity):
// Using the PowerShell object, call the Create() method
// to create an empty pipeline, and then call the methods
// needed to add the commands to the pipeline. Commands
// parameters, and arguments are added in the order that the
// methods are called.
PowerShell ps = PowerShell.Create();
ps.AddCommand("Get-Process");
ps.AddArgument("wmi*");
ps.AddCommand("Sort-Object");
ps.AddParameter("descending");
ps.AddArgument("id");
Console.WriteLine("Process Id");
Console.WriteLine("------------------------");
// Call the Invoke() method to run the commands of
// the pipeline synchronously.
foreach (PSObject result in ps.Invoke())
{
Console.WriteLine("{0,-20}{1}",
result.Members["ProcessName"].Value,
result.Members["Id"].Value);
} // End foreach.
I am trying to run Exchange cmdlets using System.Automation dll in C#.
In http://technet.microsoft.com/en-us/library/dd315325.aspx, they have said that for escaping single quotes, we basically need to append it with another single quote
For example,
[PS] C:\Windows\system32>Write-Host 'Live and let le''arn'
Live and let le'arn
However, when I try to do the same thing with my cmdlet,
New-Mailbox -Name 'user1.''.cn'
The new mailbox is actually created with name as user.''.cn. We would like it to be user.'.cn
Code to execute this cmdlet is as follows:
AutomatedRunspace.Command command = new AutomatedRunspace.Command(cmdlet.Command);
foreach (CmdletParameter param in cmdlet.GetParameters())
{
command.Parameters.Add(param.Name, param.Value);
}
pipeline.Commands.Add(command);
Is there anything we can do to correctly escape it?
When you're invoking cmdlets in C#, you need to also worry about the C# string quoting behavior. It's not clear in your question what exactly you're setting the Name parameter to in C# code. I would try this:
string nameArg = "user1.'.cn";
That is, the PowerShell API should be bypassing the parameter parsing phase since you're supplying the argument directly via the API.