Powershell: Write-Verbose throws error for unassigned variable or empty array

Created on 13 Sep 2017  Â·  18Comments  Â·  Source: PowerShell/PowerShell

Steps to reproduce

Write-Verbose $variableThatWasNotAssigned
Write-Verbose @()

Expected behavior

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.

Actual behavior

> 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
```

Area-Cmdlets-Utility Issue-Enhancement

Most helpful comment

It is definitely an enhancement, but something I think could be useful

All 18 comments

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:

  • anything that evaluates to $null
  • any array-valued expression

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.

    • It makes sense that 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.
    • As an aside: Curiously, 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

    • This might be convenient for the other cmdlets too, and the consistency would help.

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

Was this page helpful?
0 / 5 - 0 ratings