Pass parameters to Powershell using C# - c#

I have a C# function which actually invokes a powershell asynchronously
Below is my code:
using (PowerShell PowerShellInstance = PowerShell.Create())
{
PowerShellInstance.AddScript(scriptFile,false);
PowerShellInstance.AddParameter("var", "Value");
PSDataCollection <PSObject> outputCollection = new PSDataCollection<PSObject>();
IAsyncResult result = PowerShellInstance.BeginInvoke<PSObject, PSObject>(null, outputCollection);
while (result.IsCompleted == false)
{
foreach (PSObject outputItem in outputCollection)
{
System.Console.WriteLine(outputItem.BaseObject.ToString());
}
System.Console.WriteLine("Waiting for pipeline to finish...");
Thread.Sleep(1000);
}
}
while this works like i nee to to , the addition of the parameter doesn't seem to do any thing. I have made that parameter compulsory in powershell , no success nothing happens then. If its not compulsory it just works and doesn't process the parameter.
My Powershell is:
Param(
[Parameter(Mandatory=$False,Position=1)]
[string]$var
)
. "C:\Data\initiate.PS1"
write-output "Value is $var"
The output is:
In Initiation -- This is coming from the PS initation script
Value is
Waiting for pipeline to finish...
IF I make the parameter Required as true in Powershell the output is:
Waiting for pipeline to finish...
I have tried changing he scope in the PowerShellInstance.AddScript, no use.
This might look like a duplicate question but I couldn't find the solution hence asking here.
The msdn link i used to create this code is at:
https://blogs.msdn.microsoft.com/kebab/2014/04/28/executing-powershell-scripts-from-c/
Any help is really appreciated.

Please add/mark PetSerAl's comment as answer, worked for me. Thank you
AddScript -> AddCommand

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.

Capturing output from powershell commands run from C#

I'm creating a simple C# program used to check various settings on windows 10 builds we're testing. I need to capture the output from a CMD or Powershell invoke and display the result as a string (human-readable). Specifically, I'm trying to capture the output from get-bitlockervolume to check if the drives are encrypted. The program should be able to run without using admin creds.
Get Powershell command's output when invoked through code
unfortunately doesn't quite seem to work, so I thought I'd attempt to capture the output to a txt file and read it from there, but for some reason, the txt ends up being empty. For my latest attempts, I've ditched PowerShell and attempted to get it done using simple CMD.
Process bl = new Process();
bl.StartInfo.WindowStyle = ProcessWindowStyle.Hidden ;
bl.StartInfo.FileName = "cmd.exe";
bl.StartInfo.Arguments = #"/c manage-bde -status > C:\windows\temp\bitlockerstatus.txt";
bl.StartInfo.RedirectStandardOutput = true;
bl.StartInfo.UseShellExecute = false;
bl.Start();
This seems to create the output file I was looking for in the right location, but it always turns up empty. When running the command directly from cmd, there doesn't seem to be an issue recording the output.
I'm still quite new to C#, self-learning as I go along, so any suggestions for an alternate method/way of doing this would be appreciated.
Very late edit:
I've figured out a way by using a Collection.
code:
StringBuilder str = new Stringbuilder();
using (Powershell ps = Powershell.create())
{
ps.addscript ("manage-bde -status");
Collection<PSObject> psoutp = ps.Invoke();
foreach(PSObject outp in psoutp)
{
if (outp != null)
{
str.Append(outp);
}
else str.Append ("Error");
}
return str.ToString();
}
This for a Method that returns the output of manage-bde - status from powershell back to main. If there's no output at all (not even an errormessage) , it'll simply return "error" back to main.
Here's hoping this helps someone else one day.
I think the answer is here:
while (!proc.StandardOutput.EndOfStream)
{
string line = proc.StandardOutput.ReadLine();
// do something with line
}

$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.

"Write-Error" (write in Powershell, used in C#) works BUT Write-Debug doesn´t work - Why?

I have written a program with C#, that creates a logfile and fills this by using log4net. This program starts powershell-scripts. The scripts use log4net, too.
It works:
> C#:
> ps.Runspace.SessionStateProxy.SetVariable("myMethod",(Action<Level,string>)myMethod);
> ps.AddCommand(System.IO.Path.Combine(pathScripts, testSkripte[i].ToString()));
> ps.Invoke();
> Powershell:
> $ScriptLog.Invoke([log4net.Core.Level]::Debug, "TestFile_Debug")
> $ScriptLog.Invoke([log4net.Core.Level]::Warn, "TestFile_Warn") $ScriptLog
> $ScriptLog.Invoke([log4net.Core.Level]::Error, "TestFile_Error")
Now I want add to use the standard Write-Error, Write-Debug, etc. CMDlets in my Script.
(looks like here - answer of Hinek).
Powershell:
Write-Warning "Write-Warning"
AND
Write-Error "Write-Error"
works, but the following doesn´t work:
Write-Debug "Write-Debug" (I don´t get an item in my logfile) OR
Write-Debug "Write-Debug" -debug (for this I get an item in my logfile, but ...)
... I get an error in my logfile, too. The error looks like this:
[2010-10-22 13:10:58,097] DEBUG : Write-Debug
[2010-10-22 13:10:58,113] ERROR : Cannot invoke this function because the current
host does not implement it
(I think to have all namespaces.)
What the error-message means and what can I do again this?
thanks
The message “the current host does not implement it” tells that you should provide a host that implements missed features. Presumably you should implement your own PSHost and PSHostUserInterface, at least what you really need there. In the latter class you implement methods like WriteDebugLine and WriteErrorLine. Then cmdlets Write-Warning and Write-Debug trigger that methods internally.
Complete example of a host with user interface and those methods:
http://msdn.microsoft.com/en-us/library/ee706577(v=VS.85).aspx
(Perhaps you don’t need most of other methods, provide some dummies then)
Here is a workaround solution: override the default command Write-Debug (which is actually not implemented) with a function:
function global:Write-Debug
(
[string]
$Message,
[switch]
$Debug
)
{
# check both the $Debug switch and the $DebugPreference variable:
if ($Debug -or ($DebugPreference -ne 'SilentlyContinue')) {
# do job; in this demo just write to the console:
[Console]::WriteLine("DEBUG: $Message")
}
}
In C# put this code into a string and invoke it once in the same runspace where the main script will be invoked. This “profile” code installs the global function Write-Debug which is semantically the same as the original cmdlet. Then, when the main code calls Write-Debug that function is called, not the default cmdlet.
P.S. I have not tried this way, I prefer to use my own host (see my other answer). But this way should work fine in many cases (not all, perhaps).
Now I had found the answer myself:
C#-Code:
using (ps = PowerShell.Create())
{
int a = testSkripte.Count;
for (int i = 0; i < a; i++)
{
ps.Runspace.SessionStateProxy.SetVariable("portalpfad", pathExecuteScript);
ps.Runspace.SessionStateProxy.SetVariable("ScriptLog", (Action<Level, string>)ScriptLog);
//add following row:
ps.Runspace.SessionStateProxy.SetVariable("DebugPreference", "Continue");
ps.AddCommand(System.IO.Path.Combine(pathScripts, testSkripte[i].ToString()));
ps.Streams.Debug.DataAdded += new EventHandler<DataAddedEventArgs>(Debug_DataAdded);
ps.Streams.Warning.DataAdded += new EventHandler<DataAddedEventArgs>(Warning_DataAdded);
ps.Streams.Error.DataAdded += new EventHandler<DataAddedEventArgs>(Error_DataAdded);
ps.Invoke();
}
and this for write-debug:
Powershell-Code:
usings ...
#set variable
$DebugPreference
#Now Write (-Debug) without Error
Write-Debug "Write-Debug"
Write-Warning "Write-Warning"
Write-Error "Ende --> Write-Error"

Categories

Resources