Powershell ignores parameter passed via SessionStateProxy.SetVariable - c#

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"
}

Related

C# pass parameter to power shell script file throws error

C# code has to pass parameter value to powershell script file. The code is working fine if I m not paasing any parameter. When I use .AddParameter or AddArgument it throws error.
while using AddArgument it throws error as 'A positional parameter cannot be found that accepts argument 'Test 111'.'
while using AddParameter I am getting erro as : 'A parameter cannot be found that matches parameter name 'FilePrefix'.'
Please find my C# code below
using (PowerShell ps = PowerShell.Create())
{
var scriptfile = #"\\cbc0056\work\Powershell\Scenarios\Test.ps1";
ps.AddCommand("Set-ExecutionPolicy")
.AddParameter("ExecutionPolicy", "RemoteSigned")
.AddParameter("Scope", "Process")
.AddParameter("Force");
ps.AddScript(scriptfile).AddCommand("Out-String");
//ps.AddArgument("Test 222");
ps.AddParameter("FilePrefix", "Test 222");
Collection<PSObject> results = ps.Invoke();
foreach (PSObject item in results)
{
_logger.LogInformation("Power Shell returned Values as given below " + "\r\n"+item.BaseObject.ToString());
// write some business logic
}
PowerShelll script Test.ps1 file as given below
Param(
[Parameter(Position=1)]
[string]$FilePrefix
)
$test = $FilePrefix
Write-Host "hello this is a test " | Out-String
Write-Host $test| Out-String
$test
Get-Process | Out-String
What is wrong in passing parameter ? Any help would be highly appreciated.
Use .AddCommand() to execute a script file (.ps1); only use .AddScript() to execute a script block, i.e. a piece of PowerShell code.
As Mathias notes, your .AddParameter() call must come before adding another pipeline segement with .AddCommand("Out-String").
ps.AddCommand(scriptfile).AddParameter("FilePrefix", "Test 222").AddCommand("Out-String");
Also note that there's an easier way to set the execution policy, via an object specifying the initial session state: see this answer.

$global: is set in a PowerShell session but not in the System.Management.Automation.PowerShell instance

I'm moving the execution of a PowerShell script (StartBackup.ps1) that we would normally run in a standalone PowerShell session into a C# application. The script executes normally directly in PowerShell, imports modules/DLLs, calls into other scripts and sets a ton of variables.
In the C# application, I have:
using (PowerShell powerShell = PowerShell.Create())
{
powerShell.AddCommand("Set-ExecutionPolicy");
powerShell.AddParameter("Scope", "Process");
powerShell.AddParameter("ExecutionPolicy", "RemoteSigned");
powerShell.AddCommand("Set-Location");
powerShell.AddParameter("Path", "E:\\BackupTools");
powerShell.AddCommand("E:\\BackupTools\\StartBackup.ps1", false);
powerShell.AddParameter("Type", "Closed");
Collection<PSObject> results = powerShell.Invoke();
foreach (var resultItem in results)
{
...
}
}
The above runs just fine up until the point where $global: stuff gets set, and that's where it starts to throw errors. All of those values are null/empty.
I added a couple of powerShell.AddCommands to check whether or not those values are set after the script executes, and they are indeed all null in the PowerShell instance. In the standalone shell they're all set just fine.
What is the issue here? Why is the PowerShell instance different from an actual shell?
EDIT: The intention is not to just fire-and-forget the script. The intention is to have it do its job and then continue working with whatever artifacts it leaves behind in the PowerShell instance just as I normally would if this was powershell.exe.
If you want to just execute an existing PowerShell script, the simplest way would be to use the Process class. You can build the command line and run it.
The C# PowerShell Class is required if you want to build your script itself in your C# code.
Also, your AddCommand will chain the commands. Is that your requirement ?
MSDN post
Call AddCommand() methods to add this content to the execution pipeline.
using (PowerShell PowerShellInstance = PowerShell.Create())
{
// use "AddScript" to add the contents of a script file to the end of the execution pipeline.
// use "AddCommand" to add individual commands/cmdlets to the end of the execution pipeline.
PowerShellInstance.AddScript("param($param1) $d = get-date; $s = 'test string value'; " +
"$d; $s; $param1; get-service");
// use "AddParameter" to add a single parameter to the last command/script on the pipeline.
PowerShellInstance.AddParameter("param1", "parameter 1 value!");
}

Call PS Script, Save String Returned

My PS script returns a string.
Function GetData {
Param(
[string]$id
)
Process
{
return "Value is $id"
}
GetData -id $arg
The below is the C# that calls the PS script:
PowerShell ps = PowerShell.Create();
string psScript = "GetData.ps1";
ps.AddScript(psScript);
// only takes one parameter
ps.AddParameter("25");
Collection<PSObject> results = ps.Invoke();
foreach (PSObject r in results)
{
Console.WriteLine(r.ToString());
}
Console.ReadLine();
Nothing returns.
I double checked the script and it does return a value when I pass in the path manually when calling the script directly in PowerShell. I also made sure that in the Properties of the project the Platform target is x64 (based on another question's error). I also tried to directly save the result in the Invoke method, but it gave an error, which showed that I have to actually save it in a collection, even though it's one record.
Forgot, also tried:
psParam = "25";
string psScript = "GetData.ps1 -arg'" + psParam + "'";
And no result on the console.
Tested this:
PowerShell ps = PowerShell.Create();
string psScript = ".\\GetData.ps1";
ps.AddCommand(psScript);
ps.AddArgument("25");
Collection<PSObject> results = ps.Invoke();
foreach (PSObject r in results)
{
Console.WriteLine(r.ToString());
}
Console.ReadLine();
And used most of the above and this errors because it says GetData.ps1 is not recognized as the name of a cmdlet, function, script file, or operable program. If I point directly to it by placing it on my C drive (C:\GetData.ps1), it does nothing.
Double check; inside the script I am calling the function on the last line:
GetData -id $arg
Is this correct?
Pay attention to your function. It gets an open curly brace '{', but not the matching close curly brace '}'. Your defective PowerShell code will emit an error not caught by your code.
After you correct this simple error, notice how you're calling your function. What is $arg? I assure you it's not any automatic variable. Have a look in about_automatic_variables...
P.S.: you'd better off asking enormously difficult questions like this one in social . technet . microsoft . com / Forums / windowsserver / en-US / home?forum=winserverpowershell . If you ha did it, the answer would have been posted many hours ago.
The problem is that "return" is not what you think.
In Powershell, the "return value" is the last value on the stack when execution ends. In your case, just omit the "return" keyword, and the string will come out as you expect.
Function return value in PowerShell
Alternatively, you can use Write-Output which would explicitly send the data to the output like a C-style return statement.
Note: Do NOT use Write-Host, as it writes directly to the powershell host, skipping the pipeline and never giving you a chance to see the value.

Using variable in c# invocation of powershell

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.

C# PowerShell pipeline input flag/property

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.

Categories

Resources