Friday, October 16, 2020

PowerShell: Inadvertently Returning Multiple Values from a Function

During a project (warning: do not write code while tired), I encountered a PowerShell function mysteriously returning three values. Most savvy PowerShell developers recognize that the following method returns an array of three items ([0]=Hi mom, [1]=<comma><space>, [2]=I miss you):

function ReturnThreeValues
{
  'Hi mom'
  ', '
  'I miss you.'
}

The return keyword can be used to (a.k.a being invoked with no return value):
  • exit a function
function ShowEmptyReturn
{
  return
}
  • exit a function and return a value from the function.
function ShowReturnWithValue
{
  'Hi mom, I miss you.'
}

The previous function's returns a string, "Hi mom, I miss you." unlike our first example that returns an array of three elements containing the same text.

The return keyword is not required to return a value from a function. The result of each statement is returned from a PowerShell function. I'm going to write the previous sentence again but this time I will make the entire sentence in boldface so you pay attention. The result of each statement is returned from a PowerShell function. To understand this consider the GetFullLogFilename function which performs the following task:
  • Takes a folder (directory) and filename as parameters
  • Verifies a directory exists and if it does not creates it
  • Concatenates the folder and the filename into a fully qualified filename
  • Verifies a fully qualified filename exists and if it does not creates it
  • The function's return value is the fully qualified filename
    • Or is it?
The GetFullLogFilename function is defined as following including code to invoke the function:

function Get-FullLogFilename
{
 Param(
   [Parameter(Mandatory=$true)]
   [ValidateNotNullOrEmpty()]
   [string] $folder,
   [Parameter(Mandatory=$true)]
   [ValidateNotNullOrEmpty()]
   [string] $filename
 )

 if (-Not (Test-Path $folder))
 {
   New-Item $folder -ItemType directory
 }

 [string] $qualifiedFilename =
   Join-Path -Path $folder -ChildPath $filename

 if (-Not (Test-Path -Path $qualifiedFilename -PathType leaf))
 {
   New-Item $qualifiedFilename -ItemType file
 }

 $qualifiedFilename
}


[int] $nameBase = Get-Random
[string] $folder =
   Join-Path -Path $PSScriptRoot -ChildPath "D$nameBase"
[string] $filename = "F$nameBase"

[string] $qualifiedFilenameMaybe =
   Get-FullLogFilename $folder $filename

$qualifiedFilenameMaybe

The last line of the function contains the variable, $qualifiedFilename, so the function's return value must be the fully qualified filename, Actually the return values is:
<value of $folder> +
<value of $qualifiedFilename> +
<value of $qualifiedFilename>

The GetFullLogFilename invokes New-Item twice and the first invocation of New-Item returns the $folder. The second invocation of New-Item returns the value of $qualifiedFilename. The last line of the function returns the value of $qualifiedFilename. Each of these steps is demarked by boldface in the function implementation above.

The solution to having the GetFullLogFilename return only the fully qualified filename is to insure that each invocation of New-Item does not return value. The solution is to use Out-Null which is defined in the documentation (Out-Null):



Insuring that GetFullLogFilename only returns $qualifiedFilename is implemented as follows (note the lines in boldface where Out-Null are used).

function Get-FullLogFilename
{
 Param(
   [Parameter(Mandatory=$true)]
   [ValidateNotNullOrEmpty()]
   [string] $folder,
   [Parameter(Mandatory=$true)]
   [ValidateNotNullOrEmpty()]
   [string] $filename
 )

 if (-Not (Test-Path $folder))
 {
   New-Item $folder -ItemType directory | Out-Null
 }

 [string] $qualifiedFilename =
   Join-Path -Path $folder -ChildPath $filename

 if (-Not (Test-Path -Path $qualifiedFilename -PathType leaf))
 {
   New-Item $qualifiedFilename -ItemType file | Out-Null
 }

 $qualifiedFilename
}

Appendix A: About Return

Microsoft's documentation on returning values from a function can be found at: About Return. This documentation contains as set of comprehensive examples that clarify the behavior of the return keyword and how variables/statements are returned from PowerShell functions: