How to preserve output format of Powershell commands that are executed from an object?

0

0

Scenario

I would like to see the output of powershell commands that are executed inside a method of an object, in the same way I see them "live/when they are happening" when they are not inside the object.

Examples

To illustrate the undesired difference, I will show 2 scripts, the first one shows the output of the commnad, the 2nd does not. Both are run by opening powershell, browsing to their directory and ran with ./<scriptname>.ps1

  1. Consider scriptA.ps1with content:
lxrun /install /y

Which produces the desired outcome of:

Warning: lxrun.exe is only used to configure the legacy Windows
Subsystem for Linux distribution. Distributions can be installed by
visiting the Windows Store: https://aka.ms/wslstore

This will install Ubuntu on Windows, distributed by Canonical and
licensed under its terms available here: https://aka.ms/uowterms

The legacy Windows Subsystem for Linux distribution is already
installed. 
  1. scriptB.ps1 with content:
# runs single command
Class Object{
    runCommand(){
        lxrun /install /y
    }
}

# create object
[Object] $object = [Object]::new()

# execute command in method runCommand() of [Object] object
$object.runCommand()

Which does not show any output at all.

Attempts

All attempts are made inside the following script, at line: <command here>

# runs single command
Class Object{
    runCommand(){
        `<command here>`
    }
}
  1. Line:
Write-Output (lxrun /install /y)

Resulting output:(no output)


  1. Line:
Write-Host (lxrun /install /y)

Resulting output:Correct content, but lost/changed legible text formatting:

W a r n i n g :   l x r u n . e x e   i s   o n l y   u s e d   t o  
c o n f i g u r e   t h e   l e g a c y   W i n d o w s   S u b s y s
t e m   f o r   L i n u x   d i s t r i b u t i o n .       D i s t r
i b u t i o n s   c a n   b e   i n s t a l l e d   b y   v i s i t i
n g   t h e   W i n d o w s   S t o r e :       h t t p s : / / a k a
. m s / w s l s t o r e             T h i s   w i l l   i n s t a l l 
U b u n t u   o n   W i n d o w s ,   d i s t r i b u t e d   b y   C
a n o n i c a l   a n d   l i c e n s e d   u n d e r   i t s   t e r
m s   a v a i l a b l e   h e r e :         h t t p s : / / a k a . m
s / u o w t e r m s             T h e   l e g a c y   W i n d o w s  
S u b s y s t e m   f o r   L i n u x   d i s t r i b u t i o n   i s 
a l r e a d y   i n s t a l l e d . 
  1. Line:
Write-Host (lxrun /install /y) | Out-Host

Resulting output: same as in attempt 2.

  1. Line:
Write-Host (lxrun /install /y) | Out-Default

Resulting output: same as in attempt 2.

  1. Line:
Write-Host (lxrun /install /y) | Out-String

Resulting output: same as in attempt 2

  1. Line:
write-verbose (lxrun /install /y)

Resulting output:(no output)

Write-Verbose : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'Message'.
Specified method is not supported.
At F:\path to example script\example.ps1:30 char:23
+         write-verbose (lxrun /install /y)
+                       ~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Write-Verbose], ParameterBindingException
    + FullyQualifiedErrorId : CannotConvertArgument,Microsoft.PowerShell.Commands.WriteVerboseCommand
  1. Line:
write-verbose (lxrun /install /y) -Verbose

Resulting output:same as in 6 (except when the line at which the error occurs, the -Verbose is included)


  1. Line:
(lxrun /install /y) | Write-Host

Resulting output: Better readible but still not the nor

o   c o n f i g u r e   t h e   l e g a c y  W i n d o w s   S u b s y
s t e m   f o r   L i n u x   d i s t r i b u t i o n .


 D i s t r i b u t i o n s   c a n   b e   i n s t a l l e d   b y   v
i s i t i n g   t h e   W i n d o w s   S t o r e :


 h t t p s : / / a k a . m s / w s l s t o r e





 T h i s   w i l l   i n s t a l l   U b u n t u   o n   W i n d o w s
,   d i s t r i b u t e d   b y   C a n o n i c a l   a n d   l i c e
n s e d   u n d e r   i t s   t e r m s   a v a i l a b l e   h e r e
:


 h t t p s : / / a k a . m s / u o w t e r m s





 T h e   l e g a c y   W i n d o w s   S u b s y s t e m   f o r   L i
n u x   d i s t r i b u t i o n   i s   a l r e a d y   i n s t a l l
e d . ```
  1. Line:
Write-Verbose -Message (lxrun /install /y)

Resulting output:

Write-Verbose : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'Message'. Specified method is not supported.
At G:/path to file\example.ps1:35 char:32
 +         Write-Verbose -Message (lxrun /install /y)
 +                                ~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Write-Verbose], ParameterBindingException
    + FullyQualifiedErrorId : CannotConvertArgument,Microsoft.PowerShell.Commands.WriteVerboseCommand
  1. Line:
Write-Verbose -Message lxrun /install /y

Resulting output: same as in attempt 9

  1. Line:
Write-Verbose -Message (lxrun /install /y) -Verbose

Resulting output: same as in attempt 9 (except when the line at which the error occurs, the -Verbose is included)

  1. Based on the question and answers linked in the comments, I modified the script to:
# runs single command

#[CmdletBinding(SupportsShouldProcess=$true)]
[CmdletBinding()]
Param()
Class Object{    
    runCommand(){
        lxrun /install /y|Write-Verbose
    }
}

# create object
[Object] $object = [Object]::new()

# execute command in method runCommand() of [Object] object
$object.runCommand()

which is called with the -Verbose parameter when running it: ./example.ps1 -Verbose. It returns a black and yellow text:

VERBOSE: W a r n i n g :   l x r u n . e x e   i s   o n l y   u s e d   t o   >c o n f i g u r e   t h e
l e g a c y   W i n d o w s   S u b s y s t e m   f o r   L i n u x   d i s t >r i b u t i o n .
VERBOSE:
VERBOSE:
VERBOSE:  D i s t r i b u t i o n s   c a n   b e   i n s t a l l e d   b y   v >i s i t i n g   t h e
W i n d o w s   S t o r e :
VERBOSE:
VERBOSE:
VERBOSE:  h t t p s : / / a k a . m s / w s l s t o r e
VERBOSE:
VERBOSE:
VERBOSE:
VERBOSE:
VERBOSE:
VERBOSE:  T h i s   w i l l   i n s t a l l   U b u n t u   o n   W i n d o w s >,   d i s t r i b u t e d
b y   C a n o n i c a l   a n d   l i c e n s e d   u n d e r   i t s   t e r >m s   a v a i l a b l e
h e r e :
VERBOSE:
VERBOSE:
VERBOSE:  h t t p s : / / a k a . m s / u o w t e r m s
VERBOSE:
VERBOSE:
VERBOSE:
VERBOSE:
VERBOSE:
VERBOSE:  T h e   l e g a c y   W i n d o w s   S u b s y s t e m   f o r   L i >n u x
d i s t r i b u t i o n   i s   a l r e a d y   i n s t a l l e d .
VERBOSE:
VERBOSE:
VERBOSE:

Where from what I understood the Param() is included to pass/absorb the -Verbose argument, and the [CmdletBinding()] enables let binding the way a cmdlet does it, instead of the "normal" way a script does. I still need to understand what that difference is/means. And the the formatting is not yet correct/desired with this Write-Verbose implementation.

Question

How do I write the output of a command on the powershell terminal without delay, without losing it's original formatting (as with scriptA.ps1)?

Note

One could build a parser, but I think there might be more efficient and faster solutions which I have not yet found.

a.t.

Posted 2019-08-21T12:10:24.627

Reputation: 269

1

So which instance generated the output? The one with Write-Hostor the one with Write-Output? One Option would be to use Write-Verbose.

– Seth – 2019-08-22T09:44:31.427

1@Seth I apologize for the unclarity, I have restructured the attempts which show that the Write-Host option generated outputs at attempts 3,4,5,8 (In a "wrong"/different formatting), while the Write-Output at attempt 1, did not generate an output.Thank you for your suggestion, I incorporated the results of the attempts in the initial question, and will proceed debugging their respective error messages. – a.t. – 2019-08-22T15:56:13.513

1Write-Verbose didn't produce any output because you didn't change $VerbosePreference and actually made sure that you're supporting -Verbose for your class. about_Classes also points out that using Write-Host essentially violates PowerShell principals and why you're not seeing any output by default. – Seth – 2019-08-23T05:26:07.570

Answers

0

In the end it turned out writing a parser was a faster(building wise, not [necessarily] runtime-wise) solution, as my search procedure did not (yet) look into why and how the commands with write.. etc., changed the output (format).

# runs single command
Class Object{
    runCommand(){
        $output = (lxrun /install /y)
        Write-Host $this.parseOutput($output) # return formating back to "normal"
    }

    # removes null characters and then replaces triple spaces with enters.
    [String] parseOutput([String] $output){

        # first remove all nulls
        $output = $output.Replace(([char]0).ToString(),'')

        # then replace all triple occurrences of spaces with newlines
        $output = $output.Replace(([char]32+[char]32+[char]32).ToString(),"`n")

        return $output
    }

}

# create object
[Object] $object = [Object]::new()

# execute command in method runCommand() of [Object] object
$object.runCommand()

This "solves"/remedies the problem, but I prefer deeper understanding.Hence, if anyone can explain:

  1. Why the extra null and space characters are added when a command is executed from/in an object I would greatly appreciate it.
  2. How it can be done in the same line as the command, without calling a self-written method, a better solution is found.

a.t.

Posted 2019-08-21T12:10:24.627

Reputation: 269

1

They probably didn't change the output format. You're simply running a non PowerShell command so you can run into encoding issues. Using Start-Process might help in that situation. Also using Write-Host is usually a bad idea. You really might want to use something like Write-Verbose or simply return the output from the command.

– Seth – 2019-08-23T05:19:12.597

Thank you for your elaborations, could you perhaps specify how one could "return the output from the command"? – a.t. – 2019-08-23T08:08:38.570

By using a return statement, just like you do in your other method that returns $output. By returning that you would be able to ... well, return the output of the command. – Seth – 2019-08-23T09:25:49.510