## Friday, August 05, 2011

### What do you get when you iterate null?

In most cases, an exception; but in PowerShell you get `\$null`.

Suppose I have folders with 0, 1 and more than 1 file in (called `empty`, `full` and `two`, say) and I want to enumerate the names of the files contained in each, with some general purpose code.

That should be no problem --

```function Get-FileNames(\$dir) {
\$files = Get-ChildItem \$dir
foreach (\$file in \$files) {
Split-Path -Leaf \$file
}
}```

and let's write a little reporting function

```function Get-Report(\$arg) {
\$arg
\$arg.GetType().FullName
\$arg.Length
\$arg | % { Write-Host "++\$_++" }
Write-Host "-----"
}```

When we report on the folders in descending order of contained files we get

```test (2).txt
test.txt
System.Object[]
2
++test (2).txt++
++test.txt++
-----
test.txt
System.String
8
++test.txt++
-----
Split-Path : Cannot bind argument to parameter 'Path' because it is null.
At C:\Users\Steve\Documents\scratch\Untitled1.ps1:6 char:15
+     Split-Path <<<<  -Leaf \$file
+ CategoryInfo          : InvalidData: (:) [Split-Path], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.Split
PathCommand

You cannot call a method on a null-valued expression.
At C:\Users\Steve\Documents\scratch\Untitled1.ps1:12 char:15
+   \$arg.GetType <<<< ().FullName
+ CategoryInfo          : InvalidOperation: (GetType:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull

++++```

Not happy.

The first error is what you get when you work on the `foreach `over `\$null`, yielding `\$null`; and calling code has to special-case the single item return -- getting the .Length property of the return is not the number of files! For that replace `\$arg.Length` by `\$arg | Measure-Object | % {write-host \$_.Count}` .

So try this

```function Get-Array(\$list)
{
if (-not \$list) {
,(@())
} else {
if (\$list -is [array]) {
\$list
} else {
,(,\$list)
}
}
}

function Get-FileNamesX(\$dir)
{
\$files = Get-Array(Get-ChildItem \$dir)
foreach (\$file in \$files)
{
Split-Path -Leaf \$file
}
}

function Get-FileNames2(\$dir)
{
Get-Array(Get-FileNamesX(\$dir))
}```

where we fight PowerShell's fragmentation of sequences all the way. Now we get what we expected

```test (2).txt
test.txt
System.Object[]
2
++test (2).txt++
++test.txt++
-----
test.txt
System.Object[]
1
++test.txt++
-----
System.Object[]
0
-----```

Oh, yeah -- the more PowerShell way of doing all this:

```function Get-FileNames(\$dir)
{
Get-ChildItem \$dir | % {
Split-Path -Leaf \$_
}
}

function Get-Report {
PARAM (\$InputObject)
END {
\$arg = @(\$InputObject + \$Input)
\$arg
\$arg.GetType().FullName
\$arg.Length
\$arg | Measure-Object | % {write-host "Measure = \$(\$_.Count)"}
\$arg | % { Write-Host "++\$_++" }
Write-Host "-----"
}
}

Get-FileNames(".\two") | Get-Report
Get-FileNames(".\full") | Get-Report
Get-FileNames(".\empty") | Get-Report```