PowerShell: Function doesn't have proper return value
Asked Answered
M

3

12

I wrote a powershell script to compare the content of two folders:

$Dir1 ="d:\TEMP\Dir1"
$Dir2 ="d:\TEMP\Dir2"

function Test-Diff($Dir1, $Dir2) {
    $fileList1 = Get-ChildItem $Dir1 -Recurse | Where-Object {!$_.PsIsContainer} | Get-Item | Sort-Object -Property Name
    $fileList2 = Get-ChildItem $Dir2 -Recurse | Where-Object {!$_.PsIsContainer} | Get-Item | Sort-Object -Property Name

    if($fileList1.Count -ne $fileList2.Count) {
        Write-Host "Following files are different:"
        Compare-Object -ReferenceObject $fileList1 -DifferenceObject $fileList2 -Property Name -PassThru | Format-Table FullName
        return $false
    }

    return $true
}

$i = Test-Diff $Dir1 $Dir2

if($i) { 
    Write-Output "Test OK" 
} else { 
    Write-Host "Test FAILED" -BackgroundColor Red
}

If I set a break point on Compare-Object, and I run this command in console, I get the list of differences. If I run the whole script, I don't get any output. Why?

I'm working in PowerGUI Script Editor, but I tried the normal ps console too.

EDIT:

The problem is the check on the end of the script.

$i = Test-Diff $Dir1 $Dir2

if($i) { 
   Write-Output "Test OK" 
...

If I call Test-Diff without $i = check, it works!

Test-Diff returns with an array of objects and not with an expected bool value:

[DBG]: PS D:\>> $i | ForEach-Object { $_.GetType() } | Format-Table -Property Name
 Name                                                                                                                        
 ----                                                                                                                        
 FormatStartData                                                                                                             
 GroupStartData                                                                                                              
 FormatEntryData                                                                                                             
 GroupEndData                                                                                                                
 FormatEndData                                                                                                               
 Boolean         

If I comment out the line with Compare-Object, the return value is a boolean value, as expected.

The question is: why?

Mclin answered 26/3, 2014 at 14:20 Comment(5)
Please show the command in the context of your script, since this command itself runs successfully.Behm
I extracted the problematic part of the script, it works from script also. I'm completely confused...Mclin
The if is the problem on the end. if($i) { Write-Output "Test OK" ...Mclin
Could you explain what the purpose of the script is? And does the test at the end fail for you?Behm
Purpose: comparing directories. No, the test fails never, if I check the return value.Mclin
M
16

I've found the answer here: http://martinzugec.blogspot.hu/2008/08/returning-values-from-fuctions-in.html

Functions like this:

Function bar {
 [System.Collections.ArrayList]$MyVariable = @()
 $MyVariable.Add("a")
 $MyVariable.Add("b")
 Return $MyVariable
}

uses a PowerShell way of returning objects: @(0,1,"a","b") and not @("a","b")

To make this function work as expected, you will need to redirect output to null:

Function bar {
 [System.Collections.ArrayList]$MyVariable = @()
 $MyVariable.Add("a") | Out-Null
 $MyVariable.Add("b") | Out-Null
 Return $MyVariable
}

In our case, the function has to be refactored as suggested by Koliat.

Mclin answered 27/3, 2014 at 9:6 Comment(2)
In my case, I figured if I want to return a failure from a function, a reliable way is to throw an error instead of returning $false.Waxwork
Who is Koliat? Found nobody with that name on this page.Birdsong
P
4

An alternative to adding Out-Null after every command but the last is doing this:

$i = (Test-Diff $Dir1 $Dir2 | select -last 1)

PowerShell functions always return the result of all the commands executed in the function as an Object[] (unless you pipe the command to Out-Null or store the result in a variable), but the expression following the return statement is always the last one, and can be extracted with select -last 1.

Partridgeberry answered 18/10, 2019 at 14:38 Comment(1)
The readability of having variables assigned and not using makes this approach problematic. | Out-Null seems to show intent much better.Torrence
M
1

I have modified the bit of your script, to make it run the way you want it. I'm not exactly sure you would want to compare files only by the .Count property though, but its not within the scope of this question. If that wasn't what you were looking after, please comment and I'll try to edit this answer. Basically from what I understand you wanted to run a condition check after the function, while it can be easily implemented inside the function.

$Dir1 ="C:\Dir1"
$Dir2 ="C:\Users\a.pawlak\Desktop\Dir2"

function Test-Diff($Dir1,$Dir2)
{
$fileList1 = Get-ChildItem $Dir1 -Recurse | Where-Object {!$_.PsIsContainer} | Get-Item | Sort-Object -Property Name
$fileList2 = Get-ChildItem $Dir2 -Recurse | Where-Object {!$_.PsIsContainer} | Get-Item | Sort-Object -Property Name

if ($fileList1.Count -ne $fileList2.Count)
{
Write-Host "Following files are different:"
Compare-Object -ReferenceObject $fileList1 -DifferenceObject $fileList2 -Property FullName -PassThru | Format-Table FullName
Write-Host "Test FAILED" -BackgroundColor Red

}
else 
{ 
return $true
Write-Output "Test OK" 
}
}

Test-Diff $Dir1 $Dir2

If there is anything unclear, let me know

AlexP

Malta answered 26/3, 2014 at 15:33 Comment(5)
Yes, it works, but why returns the original function an array rather then a bool value?Mclin
Test-Diff function consists of couple of commands rather than a simple Bool value. Test-Diff function is an array of 6 commands, where the last one is the bool type. Change the original code to if($i[5]) and see the results. $i | Get-Member and $i.GetType() to see the details. If you call for the last object in this array, you are returning the bool function you were looking for. Thanks for the question though, I did some research and expanded my own understanding :-)Malta
It's not the excepted behavior in my opinion, if I write 'return $false' explicitly. I think $i should be $true or $false and not an array of commands.Mclin
As far as I see $i is a function consisting of many types, rather than a simple $i = $true | $false. However, I am not experienced enough to state if something is working as designed or a bug. Is the solution I presented clear nonetheless?Malta
Yes, thanks, it's clear. Only the 'return $true' in the else branch is not clear. It may be a copy-paste mistake.Mclin

© 2022 - 2024 — McMap. All rights reserved.