I want to catch the output exactly as I get it when I run commands in PowerShell.
For instance when I type LS, I get:
Yet when I use this code:
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
PowerShell ps = PowerShell.Create(); // Create a new PowerShell instance
ps.Runspace = runspace; // Add the instance to the runspace
ps.Commands.AddScript("ls"); // Add a script
Collection<PSObject> results = ps.Invoke();
runspace.Close();
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
Console.WriteLine(obj.ToString());
}
I get the following output:
Microsoft.Management.Infrastructure.dll
System.Management.Automation.dll
System.Management.Automation.xml
WpfApp1.exe
WpfApp1.exe.config
WpfApp1.pdb
Although this output may come in handy, with an other application I dont get the right output in return, so I would prefer the exact output as I see it in PowerShell itself.
Is there a way to read the output as I get it in PowerShell, line for line?
If you want the exact text that powershell produces you then you can use Out-String in the powershell command:
ps.Commands.AddScript("ls | Out-String");
You can also read the values by accessing the Properties of the PSObject:
foreach (PSObject obj in results)
{
var name = obj.Properties["Name"]?.Value.ToString()
var mode = obj.Properties["Mode"]?.Value.ToString();
var length = obj.Properties["Length"]?.Value.ToString();
var lastMod = (DateTime?)obj.Properties["LastWriteTime"]?.Value;
Console.WriteLine(string.Format("{0} {1} {2} {3}", mode, lastMod, length, name));
}
Note, as mentioned in mklement0's answer, you don't need to use Runspace to execute this powershell. Consider using Get-ChildItem rather than ls.
Note: This answer also recommends what part of haldo's helpful answer shows, in a more focused manner and with supplementary information.
Modify your script to pipe your command to the Out-String
cmdlet, which uses PowerShell's formatting system to render to a string, the same way that output renders to the console.
ps.AddScript("ls | Out-String"); // Add a script
Note:
Windows PowerShell assumes a fixed line width of 120 characters and with (implied) tabular (Format-Table) or wide (Format-Wide) formatting, truncates lines that are longer (except if the output object is of type [string]), with the point of truncation indicated with ...
PowerShell [Core] 7+ exhibits the same behavior fundamentally, but only uses default width 120 as a fallback: when the hosting (console-subsystem) executable is running in a console (terminal), the console window's width is used instead, which is the same behavior you get in a regular PowerShell console window (see this answer).
To fix that, pass a large-enough line width to -Width; e.g.:
ps.AddScript("ls | Out-String -Width 200");
Note:
In Windows PowerShell, do not use -Width ([int]::MaxValue-1), because every line is then padded to that width, which will result in excessively large output.
PowerShell [Core] 7+, this padding is no longer performed, and you can safely use
-Width ([int]::MaxValue-1)
A few asides:
For robustness, I suggest avoiding the use of aliases (such as ls for Get-ChildItem) in scripts and compiled code.
In the case at hand, ls wouldn't work on Unix-like platforms, because the alias isn't defined there, so as not to conflict with the platform-native ls utility.
It's best to wrap PowerShell ps = PowerShell.Create(); in a using block to ensure that the PowerShell instance is disposed of: using (PowerShell ps = PowerShell.Create()) { ... }
There is generally no need to create a runspace explicitly - PowerShell.Create() will create one for you.
The System.Management.Automation.PowerShell instance returned by PowerShell.Create() directly exposes methods such as .AddScript() - no need to use the .Commands property.
You can get compressed json output from powershell with this command
ls | ConvertTo-Json -Compress
Then deserialize. Also this command provide extra info than see in powershell output.
Related
Hi I am trying to create some sort of hybrid using power shell and c# as managing tool, so simple script in powershell that checks repo status
$git_status = git status
Write-Output $git_status
Now using Pipeline and Run
using (Runspace runspace = RunspaceFactory.CreateRunspace())
{
runspace.Open();
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript(powershellScript);
pipeline.Commands.Add("Out-String");
Collection<PSObject> results = pipeline.Invoke();
runspace.Close();
StringBuilder output = new StringBuilder();
foreach (PSObject obj in results)
{
output.AppendLine(obj.ToString());
}
return output.ToString();
}
But I am getting empty result, when I run script in IDE power shell result looks like for example:
Write-Host "$git_status"
On branch master Your branch is up-to-date with 'origin/master'. Changes to be committed:
(use "git reset HEAD ..." to unstage) modified: DeviceDatabase/Platforms.xml
What can i do to get that response back to c# procedure?
Write-Host will only print output to terminal, but it will not pass it to stdout. If you want to get the output - use Write-Output.
Write-Host is there to build UX: change output text color, etc. For anything else use Write-Output if you want to have your scripts as tools that can be combined to solve larger problems.
You can find a better explanation here. I'll use example code from the link to expand a little:
function Receive-Output
{
process
{
# catch input from pipe and print it in green
Write-Host $_ -ForegroundColor Green
}
}
# this will print green text, because it was passed to Receive-Output
Write-Output "this is a test" | Receive-Output
# this will print white text, because it was not passed to Receive-Output
# there's a good chance "this is a test" will already be printed by the time Receive-Output gets called
Write-Host "this is a test" | Receive-Output
Edit:
As git status prints a colorful text we can safely assume it's using Write-Host in PowerShell. Based on how Posh-Git reads git status into an object I've compiled a little example code that should solve your problem:
> $status = (git -c color.status=false status)
> Write-Output $status
This is the output I get (Windows 10, PowerShell Core 6.0):
For various reason I need to query the mailbox auto-reply configuration for a user using a remote PowerShell command from C# code instead of using the EWS API.
I'm pretty much using this article as a template on how to do this and I'm running into an issue that I can't wrap my head around. Specifically it looks like there is some information being lost through the serialization/deserialization process for remote PowerShell commands. So I'm not able to cast it to another type and use it in the C# code. Would anyone have an idea how to either find a workaround or avoid this?
Down below you can see the code that runs the PowerShell code and returns the objects and tries to do stuff with it. The problem is that the BaseObject type is PSCustomObject and as such the cast/check doesn't work. I am not sure how I'd access the attributes that are exposed by the custom object either. With the debugging tools in VS I'm able to see that it actually kind of has all the data. If I run the code directly in PowerShell I can see that the data type for $configuration would be Deserialized.Microsoft.Exchange.Data.Storage.Management.MailboxAutoReplyConfiguration. So I guess it actually looses some of the information for that object during serialization?
An alternative problem I haven't checked yet (as I'd really like to avoid it) would be that the system I'm running this code on doesn't have the Exchange assemblies installed. That's also why I'm using the clunky BaseObject.GetType().ToString() method in order to check the type as I'm not able to reference the type and use is. But I'd actually kind of expect to get a data structure that is self sufficient from the PowerShell object. Am I wrong about how this would work?
using (PowerShell PowerShellInstance = PowerShell.Create())
{
// add a script that creates a new instance of an object from the caller's namespace
PowerShellInstance.AddScript(#"
$session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionURI <URI>
Import-PSSession $session
$configuration = Get-MailboxAutoReplyConifguration -identity <E-Mail>
# Put it on the output stream
$configuration
");
// invoke execution on the pipeline (collecting output)
Collection<PSObject> PSOutput = PowerShellInstance.Invoke();
// loop through each output object item
foreach (PSObject outputItem in PSOutput)
{
if (outputItem != null)
{
if(outputItem.BaseObject.GetType().ToString() == "Microsoft.Exchange.Data.Storage.Management.MailboxAutoReplyConfiguration"){
# We have a decrepancy here as the above is the Exchange API class and
# below would be the EWS API class. As they expose the same attributes I'd expect it to work.
OofSettings settings = outputItem.BaseObject as OofSettings
}
}
}
}
That's exactly the problem: The Deserialization destroys the original Powershell-Object your script generated and create a new one, with the data of the origin object, but not the methods afaik (Type PSObject).
The workaround is to do the task you have to do in a powershell-script either or directly in the first script (whatever fits better to your needs).
For your example, i mean this:
using (PowerShell PowerShellInstance = PowerShell.Create())
{
// add a script that creates a new instance of an object from the caller's namespace
PowerShellInstance.AddScript(#"
$session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionURI <URI>
Import-PSSession $session
$configuration = Get-MailboxAutoReplyConifguration -identity <E-Mail>
***INSERT HERE Powershell-Cmdlets to do the things you need***
# Put it on the output stream
$configuration
");
// invoke execution on the pipeline (collecting output)
Collection<PSObject> PSOutput = PowerShellInstance.Invoke();
// loop through each output object item
foreach (PSObject outputItem in PSOutput)
{
if (outputItem != null)
{
if(outputItem.BaseObject.GetType().ToString() == "Microsoft.Exchange.Data.Storage.Management.MailboxAutoReplyConfiguration"){
# We have a decrepancy here as the above is the Exchange API class and
# below would be the EWS API class. As they expose the same attributes I'd expect it to work.
OofSettings settings = outputItem.BaseObject as OofSettings
}
}
}
}
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.
I have a PSScript that I call from C#. The problem that I am encountering, is that even though script has loaded, the Get-RDUserSession is not being executed. The function which contains this cmdlet is being called, because if I include the Get-Process, I am getting results.
Therefore, I am at a loss as to why and how I get this particular cmdlet to execute from C# - it works in Powershell directly.
My code is:
using (PowerShell ps = PowerShell.Create())
{
ps.Runspace = rs;
ps.AddScript(myscript);
ps.Invoke();
ps.AddCommand("MyFunction");
Collection<PSObject> output = ps.Invoke();
}
Does anyone have any ideas?
Make sure your C# Object is set correctly to either 64bit or 32bit depending upon your environment.
I'd like to pass an associative array from C# to Powershell. As an example I'd like to execute this powershell line of code:
PS C:\> get-command | select name, #{N="Foo";E={"Bar"}} -first 3
Name Foo
---- ---
Add-Content Bar
Add-History Bar
Add-Member Bar
I'd like to do this via a Pipeline of distinct Commands as opposed to a single command marked as a script. Here's the code:
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.Add("get-command");
Command c = new Command("select-object");
List properties = new List();
properties.Add("name");
properties.Add("#{N=\"Foo\";E={\"Bar\"}}");
c.Parameters.Add("Property", properties.ToArray());
c.Parameters.Add("First", 3);
pipeline.Commands.Add(c);
pipeline.Commands.Add("Out-String");
Collection retval = pipeline.Invoke();
runspace.Close();
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in retval)
Console.WriteLine(obj.ToString());
But that associative array being passed in as a parameter to Select-Object isn't being parsed correctly. This is what comes out the other side:
PS C:\test> c:\test\Bin\Debug\test.exe
Name #{N="Foo";E={"Bar"}}
---- --------------------
Add-Content
Add-History
Add-Member
What's wrong with how I'm setting up the Select-Object command parameters?
Creating a pipeline through c# and creating a pipeline with native powershell script have one major difference that is actually quite subtle: the parameter binder.
if I write a version of your code in pure script, I will get the same error: the hashtable literal is treated as a string value.
ps> $ps = $ps.Commands.Add("get-process")
ps> $ps = $ps.Commands.Add("select-object")
ps> $ps.Commands[1].Parameters.Add("Property", #("Name", '#{N="Foo";E={"Bar"}}'))
In this case, the command receives an array of two strings, the "name" and the hashtable literal string. This will be broken in exactly the same way as your C#. Now, take a look at the right way to do it (in script) - let me rewrite line 3:
ps> $ps.Commands[1].Parameters.Add("Property", #("Name", #{N="Foo";E={"Bar"}}))
So what changed? I removed the quotes around the hashtable -- I am passing a hashtable as the 2nd element of the object array! So, to get your C# example to work, you need to do what the parameter binder does for us at the command line (which is quite a lot!). Replace:
properties.Add("#{N=\"Foo\";E={\"Bar\"}}");
with
properties.Add(
new Hashtable {
{"N", "Foo"},
{"E", System.Mananagement.Automation.ScriptBlock.Create("\"Foo\"")}
}
);
I hope this clears it up for you. The parameter binder is probably the least visible but most powerful part of the powershell experience.
-Oisin