I'm building a Powershell CmdLet for awesome printing. It should function just like Out-Print but with all the bells and whistles of winprint.
PS> get-help out-winprint
NAME
Out-WinPrint
SYNTAX
Out-WinPrint [[-Name] <string>] [-SheetDefintion <string>] [-ContentTypeEngine <string>]
[-InputObject <psobject>] [<CommonParameters>]
ALIASES
wp
For this to work, I need to take the input stream (InputObject) of my PSCmdLet implementation and pass it through Out-String so it's all expanded and formatted. I'm thinking the best way to do this is to use CommandInvocationIntrinsics.InvokeScript to invoke out-string, which should give me the output as a string.
var text = this.SessionState.InvokeCommand.InvokeScript(@"Out-String", true, PipelineResultTypes.None, InputObject, null);
this.WriteObject(text, false);
If I understand how this is supposed to work, the var text above should be a string and WriteObject call should display what out-string would display (namely the result of get-help out-winprint).
PS> get-help out-winprint -full | out-winprint
NAME
Out-WinPrint
SYNTAX
Out-WinPrint [[-Name] <string>] [-SheetDefintion <string>] [-ContentTypeEngine <string>]
[-InputObject <psobject>] [<CommonParameters>]
ALIASES
wp
However, in reality text is string[] = { "" } (an array of strings with one element, an empty string).
PS> get-help out-winprint -full | out-winprint
Name Value
---- -----
PSVersion 7.0.0
PSEdition Core
GitCommitId 7.0.0
OS Microsoft Windows 10.0.18362
Platform Win32NT
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0鈥
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
Perhaps I'm reading the docs incorrectly?
SO question here: https://stackoverflow.com/questions/60712580/invoking-cmdlet-from-a-c-based-pscmdlet-providing-input-and-capturing-output
If you ever figure this one out... do let me know! I've been considering adding something like this to one of my own cmdlets. /cc @SeeminglyScience I know we've discussed some of this, so I'm hoping you might be able to point out what's wrong here / if there's a bug here we need to fix 馃挅
And, FWIW, I've tried probably a dozen other ways to run Out-String from w/in a PSCmdLet. None worked (probably because I don't know what I'm doing).
I've also tried to find other ways to format the input...
@tig Try this:
[Cmdlet(VerbsData.Out, "WinPrint")]
[OutputType(typeof(string))]
public class OutWinPrintCommand : System.Management.Automation.Cmdlet
{
[Parameter(ValueFromPipelineByPropertyName = true, ValueFromPipeline = true)]
public PSObject InputObject { get; set; }
protected override void ProcessRecord()
{
var commandInfo = new CmdletInfo("Out-String", typeof(Microsoft.PowerShell.Commands.OutStringCommand));
using var ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace);
ps.AddCommand(commandInfo);
ps.AddParameter("InputObject", this.InputObject);
var result = ps.Invoke<string>();
this.WriteObject(result, true);
}
}
results in following:
PS C:\> Get-Help Out-WinPrint | Out-WinPrint
NAME
Out-WinPrint
SYNTAX
Out-WinPrint [-InputObject <psobject>] [<CommonParameters>]
ALIASES
None
Woot! That works.

However, it doesn't explain why InvokeScript is not working. Thanks a ton Andrew.
The script Out-String doesn't do anything with input. You'd need to do $input | Out-String.
The example in the OP is the equivalent of $input | & { Out-String }. The example that works is equivalent to Out-String -InputObject $input. The latter will work just fine depending on what you're trying to do. If you wanted to do this in ProcessRecord and capture table formatting iteratively you would want to use the SteppablePipeline API.
The _script_
Out-Stringdoesn't do anything with input. You'd need to do$input | Out-String.
I don't understand this. The implementation of out-string, from what I can see, absolutely uses InputObject when -InputObject is not specified.
The example that works is equivalent to
Out-String -InputObject $input. The latter will work just fine depending on what you're trying to do. If you wanted to do this inProcessRecordand capture table formatting iteratively you would want to use theSteppablePipelineAPI.
I get now why @anmenaga's suggestion works. However, I'm seeing behavior after further testing that has me concerned about creating a new runspace here.
Thus, I want to figure out how to make InvokeScript work.
I tried this, but it is not working either. I may still be missing something on how this is all supposed to work:
var result = this.SessionState.InvokeCommand.InvokeScript(@"Out-String -InputObject $input", true, PipelineResultTypes.None, null, _psObjects);
I think this matches the pattern used in the create new CmdLetInfo example, doesn't i?
The _script_
Out-Stringdoesn't do anything with input. You'd need to do$input | Out-String.I don't understand this. The implementation of
out-string, from what I can see, absolutely usesInputObjectwhen-InputObjectis not specified.
Yes, the command Out-String does handle input. InvokeScript doesn't construct a single command processor with the command you specify, it invokes a script with the contents you provide. That's why it's functionally equivalent to $input | & { Out-String } (which does not actually handle input).
The example that works is equivalent to
Out-String -InputObject $input. The latter will work just fine depending on what you're trying to do. If you wanted to do this inProcessRecordand capture table formatting iteratively you would want to use theSteppablePipelineAPI.I get now why @anmenaga's suggestion works. However, I'm seeing behavior after further testing that has me concerned about creating a new runspace here.
Use PowerShell.Create(RunspaceMode.CurrentRunspace) instead. Make sure to test piping multiple objects.
Thus, I want to figure out how to make
InvokeScriptwork.I tried this, but it is not working either. I may still be missing something on how this is all supposed to work:
var result = this.SessionState.InvokeCommand.InvokeScript(@"Out-String -InputObject $input", true, PipelineResultTypes.None, null, _psObjects);I think this matches the pattern used in the create
new CmdLetInfoexample, doesn't i?
null needs to be the input you're trying to pass, right now you're specifying the parameter args which would put it in $args. Or leave it in args and use the variable $args instead. Though you probably need to pipe, I don't think Out-String's InputObject handles enumerating arrays. Also again make sure to test passing multiple objects.
Also PipelineResultTypes.None should probably be Output. It doesn't actually make a difference right now since it doesn't look like that's hooked up properly, but if it's ever fixed your code will break.
Ah... This works equivalently to the CmdletInfo example:
var result = this.SessionState.InvokeCommand.InvokeScript(@"$input | Out-String", true, PipelineResultTypes.None, _psObjects, null);
string text = result[0].ToString();
Since my ProcessRecord expands arrays, this works as hoped when $input has multiple items:
protected override void ProcessRecord() {
if (InputObject == null || InputObject == AutomationNull.Value) {
return;
}
IDictionary dictionary = InputObject.BaseObject as IDictionary;
if (dictionary != null) {
// Dictionaries should be enumerated through because the pipeline does not enumerate through them.
foreach (DictionaryEntry entry in dictionary) {
ProcessObject(PSObject.AsPSObject(entry));
}
}
else {
ProcessObject(InputObject);
}
}
So, false-alarm. InvokeScript works as advertised (although the documentation is horrible). I'm closing this issue and thanking y'all profusely for your patience.