Write-Verbose $variableThatWasNotAssigned
Write-Verbose @()
Similar behaviour to Write-Host
or Write-Output
: Those 2 cmdlets do not throw when being provided directly or via pipeline with a variable that was not assigned or an empty array.
> Write-Verbose $variableThatWasNotAssigned
Write-Verbose : Cannot bind argument to parameter 'Message' because it is null.
At line:1 char:15
+ Write-Verbose $variableThatWasNotAssigned
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Write-Verbose], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.WriteVerboseCommand
> Write-Verbose @()
Write-Verbose : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'Message'. Specified method is not supported.
At line:1 char:15
+ Write-Verbose @()
+ ~~~
+ CategoryInfo : InvalidArgument: (:) [Write-Verbose], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgument,Microsoft.PowerShell.Commands.WriteVerboseCommand
md5-a556cda69ad42901fd444751c1128e81
Name Value
---- -----
PSVersion 6.0.0-beta
PSEdition Core
GitCommitId v6.0.0-beta.6-39-g4313dbf19cea94a8d4d368f3b8c2c2acd79ed751
OS Microsoft Windows 10.0.15063
Platform Win32NT
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
```
Good find; to boil this down:
# (Effectively) passing *$null* fails.
> Write-Verbose $null
Write-Verbose : Cannot bind argument to parameter 'Message' because it is null.
...
# Passing *any array* fails:
> Write-Verbose ('ver', 'bose')
Write-Verbose : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'Message'. Specified method is not supported.
...
In other words: Write-Verbose
fails with:
$null
It's consistent with write-warning
and write-debug
. Is there a case where $null
makes sense that isn't a bug in the script? Handling an array or object makes sense to me, but perhaps that should be a different parameter for compatibility reasons.
If Write-Host $null
and Write-Output $null
output nothing, it makes sense to me that Write-Verbose
, Write-Warning
and Write-Debug
should at least not _fail_.
To me it make sense for these cmdlets to emit their _prefix only_ when passed $null
, i.e., VERBOSE:
, WARING:
and DEBUG:
; the specific behavior is debatable (perhaps output nothing at all?), but I hope there's agreement that there shouldn't be an _error_.
Is there a case where $null makes sense that isn't a bug in the script?
Any variable you're trying to write using one of these cmdlets could turn out to be unset, in which case it is better to have prefix-only output / no output rather than an error.
As for passing an _array_:
Given that PS generally converts arrays to $OFS
-separated lists when coercing to [string]
, all Write-*
cmdlets should do the same - which is the same behavior you get with an advanced function:
> function foo { param([string] $s) "[$s]" }; foo $null; foo @(); foo 'foo', 'bar'
[]
[]
[foo bar]
Currently all Write-* cmdlets accept an argument as simple string only - so it is "by design" Issue.
We could re-targeting the Issue as "Enhance all Write-* cmdlets to support object formatting".
Maybe it makes sense Write-Debug "Array values:", $array
It is definitely an enhancement, but something I think could be useful
So we need RFC :-)
Should be a short RFC. My team and the committee will refocus efforts on the RFC repo after we get 6.0.0 out.
Some initial thoughts:
Write-Output
's behavior differs - it is designed to accept an _array_ - but that difference is probably fine, because its purpose is not to generate _strings for display_ the way that (most) of the other Write-*
cmdlets do.
All the others, which accept a _single_ object, should act consistently, however, and I think Write-Host
should be the model here:
It accepts $null
and an _empty_ array, and treats it the same as an empty string (i.e.: no error, and a blank line by default; no output with -NoNewline
.
Write-Host
, Write-Debug
, and Write-Warning
(all of which currently _break_ with $null / empty-array input) should still print their usual prefix, however (VERBOSE:
, DEBUG:
, WARNING:
). Perhaps it is debatable whether it makes sense for Write-Error
to accept $null
/ an empty array, but note that _all_ of the cmdlets mentioned do support passing an _empty string_ explicitly.Write-Information
doesn't use a prefix when printing to the _console_ (or redirecting to a file), but does so in a _transcript_ (prefix INFO:
; see also: #4645)Write-Host
helpfully stringifies a _collection_ input object by creating a space-separated list by default (unless a different separator is specified with -Separator
).
Write-Host 1, 2 # -> '1 2'
Write-Verbose
, Write-Debug
, Write-Warning
, and Write-Error
currently _break_ with array-valued input.Write-Information
doesn't break, but simply calls .ToString()
on the collection, which results in unhelpful output:Write-Information -infa Continue 1, 2 # -> 'System.Object[]' !!
Write-Host
's input parameter is declared as a value-from-remaining-arguments parameter, which allows passing a collection as individual arguments:
Write-Host one two # -> 'one two'; same as: Write-Host one, two
@SteveL-MSFT @TravisEz13
This is still an issue, I've run into this in both older and current versions of PowerShell. The behavior for me is when I pass in array on the pipeline everything works as expected but when passing in the array to the parameter directly.
https://github.com/SchemaModule/PowerShell/issues/17
What I expect to happen
when passing an array on the pipeline or via the parameter where a write-verbose is outputting the same, I should not get an error.
What is happening
When passing an array on the pipeline no error is thrown from Write-Verbose, but when the array is passed via param, Write-Verbose is throwing an error.
In order to validate that I'm not programatically creating a faulty array I have passed a well-known array construct into the function.
@(1,2,3,4,5) |Get-SchemaProperty -Verbose
VERBOSE: 1
VERBOSE:
VERBOSE: Return all properties
VERBOSE: 2
VERBOSE:
VERBOSE: Return all properties
VERBOSE: 3
VERBOSE:
VERBOSE: Return all properties
VERBOSE: 4
VERBOSE:
VERBOSE: Return all properties
VERBOSE: 5
VERBOSE:
VERBOSE: Return all properties
# JeffreyPatton@FSTNQL1 | 12:08:22 | 09-09-2020 | [23.72GB] D:\CODE\Organizations\SchemaModule\PowerShell $ [dev ≡]
Get-SchemaProperty -SchemaDocument @(1,2,3,4,5)
Write-Verbose : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'Message'. Specified method is not supported.
At D:\CODE\Organizations\SchemaModule\PowerShell\schema\schema.psm1:135 char:19
+ Write-Verbose $SchemaDocument;
+ ~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Write-Verbose], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgument,Microsoft.PowerShell.Commands.WriteVerboseCommand
The last comment on this thread was 2yrs ago, and I'm unclear that my issue is DIRECTLY related to this Open issue, but it feels to be rather close.
@jeffpatton1971 if the proposed changes were implemented, your command would no longer emit an error but it would instead just write System.Object[]
. So it would only shift your bug a little. You want to either pipe to Write-Verbose
or construct the string you want to display (e.g. Write-Verbose ($SchemaDocument -join ', ')
)
@SeeminglyScience if the proposed changes were implemented I should be seeing system.object[] but that's obviously not happening here. Ultimately what will be passed in is a json array, so simply -join a comma i don't think is a workable solution for me. I'm curious to know if the change was implemented.
Again, making all Write-*
cmdlets (other than Write-Output
) exhibit the same behavior as Write-Host
strikes me as a reasonable solution:
PS> Write-Host 1,2
1 2 # space-separated list of .ToString()-stringified elements
PS> Write-Host @()
# no output
PS> Write-Host $null
# no output
This is certainly more sensible than _breaking_, and in cases where the .ToString()
stringification is insufficient, there's always piping via Out-String
:
PS> Get-Process -Id $PID | Out-String | Write-Verbose -vb
VERBOSE:
NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
------ ----- ----- ------ -- -- -----------
0 0.00 56.30 19.93 45714 …13 pwsh
@SeeminglyScience if the proposed changes were implemented I should be seeing system.object[] but that's obviously not happening here.
(...)
I'm curious to know if the change was implemented.
Nah nothing implemented yet.
Ultimately what will be passed in is a json array, so simply -join a comma i don't think is a workable solution for me.
What I mean is these examples aren't the same:
$objects | Write-Verbose
# vs
Write-Verbose $objects
They will act differently even if this is implemented. While it won't write System.Object[]
(ty for the correction @mklement0) -join ' '
doesn't seem like what you want either. You need to construct the string you want to send to verbose or change up how you're calling it.
@mklement0 consistency would be good, as that's what I'm striving for myself as stated in my comment, write-verbose WORKS as intended when I'm passing an array along the pipeline, but it fails when i pass the array via the named parameter.
It's possible that the issue I'm experiencing is NOT write-verbose but instead is how the pipeline is passing in the array. In the snip below you can see that when I pass in an array to the function write-verbose fails, but the second example i pass in the same array on the pipeline and write-verbose works.
I could potentially code around this, or simply OMIT the write-verbose and move on with my life, which I'm not opposed to either, but I'd like to at first see why there is this, what seems to me, glaring difference.
@SeeminglyScience I don't have the luxury of constructing the array, it's built, i happened to notice this in testing.
Get-SchemaProperty -SchemaDocument $SchemaDocument.properties.products.items.anyOf -Name dimensions -Verbose
Write-Verbose : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'Message'. Specified method is not supported.
At D:\CODE\Organizations\SchemaModule\PowerShell\schema\schema.psm1:135 char:19
+ Write-Verbose $PSBoundParameters['SchemaDocument'];
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Write-Verbose], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgument,Microsoft.PowerShell.Commands.WriteVerboseCommand
VERBOSE: dimensions
VERBOSE: Return specific Property
$id : #/properties/products/items/anyOf/0/properties/dimensions
type : object
title : The dimensions schema
description : An explanation about the purpose of this instance.
default :
examples : {@{width=5; height=10}}
required : {width, height}
properties : @{width=; height=}
additionalProperties : True
# JeffreyPatton@FSTNQL1 | 14:02:12 | 09-09-2020 | [23.72GB] D:\CODE\Organizations\SchemaModule\PowerShell $ [dev ≡ +0 ~1 -0 !]
$SchemaDocument.properties.products.items.anyOf |Get-SchemaProperty -Name dimensions -Verbose
VERBOSE: @{$id=#/properties/products/items/anyOf/0; type=object; title=The first anyOf schema; description=An explanation about the purpose of this instance.; default=; examples=System.Object[]; required=System.Object[]; properties=; additionalProperties=True}
VERBOSE: dimensions
VERBOSE: Return specific Property
$id : #/properties/products/items/anyOf/0/properties/dimensions
type : object
title : The dimensions schema
description : An explanation about the purpose of this instance.
default :
examples : {@{width=5; height=10}}
required : {width, height}
properties : @{width=; height=}
additionalProperties : True
@SeeminglyScience I don't have the luxury of constructing the array, it's built, i happened to notice this in testing.
Not the array, the string you're passing to Write-Verbose
. The point is that passing an array as a direct parameter is not the same as piping. You can pipe, you can do a string join, a foreach, etc. @mklement0's last example in this comment is another thing you can do.
To illustrate @SeeminglyScience's point:
# Argument
PS> & { [CmdletBinding()] param([Parameter(ValueFromPipeline)] $foo) process { "[$foo]" } } -foo 1, 2
[1 2]
# Pipeline
PS> 1, 2 | & { [CmdletBinding()] param([Parameter(ValueFromPipeline)] $foo) process { "[$foo]" } }
[1]
[2]
Perhaps #4242 also sheds some light on this.
@SeeminglyScience and @mklement0 I could use out-string as that seems to work for my needs, but it feels hack-ish. Especially, i get that there is a difference here, when the pipeline does the write-verbose of the array fine but the named param fails.
I agree that Write-Verbose $someArray
and Write-Verbose $null
_should_ work as-is, namely as described above.
To be clear: The powers that be need to agree with that, and someone has to implement it.
Most helpful comment
It is definitely an enhancement, but something I think could be useful