Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unpredictable behaviour of PowerShell function return value

We came accross some weird PowerShell behaviour when it comes to function return values.

Some context:

In our scripts and modules, we always want to set the $ErrorActionPreference and $DebugPreference to a global default that is retrievable with a function. Let's assume we want to set the $DebugPreference to "Continue" to have additional messages in our log.

So, we are doing somthing like this:

$DebugPreference = Get-GlobalDebugPreference

Whether this is a good approach is or not is not part of this question.

The problem:

On some machines with different versions of PowerShell this just didn't work as expected. When we called our function that returns the "Continue", $DebugPreference had indeed the correct value stored, but we did not have any additional log messages in our log file.

What we found out:

Whether Write-Debug works or not is somehow linked to how you return the value "Continue" from the function that manages our global defaults.

E.g. when the function looks like the following example, Write-Debug behaves as expected and prints debug messages in PowerShell 3. In PowerShell 2 however, the value is set to "Continue", but Write-Debug does not print any additional messages.

function Get-GlobalDebugPreference
{
    return "Continue"
}
$DebugPreference = Get-GlobalDebugPreference

However, if we just drop the value onto the shell and omit the return statement, it works for all versions of PowerShell v2+.

function Get-GlobalDebugPreference
{
    "Continue"
}
$DebugPreference = Get-GlobalDebugPreference

There are multiple ways to return a value from a function in PowerShell. For some ways it works for PS version 2, some work with v3. However using Write-Output to return the value "Continue" does not work up to v5.

In my opinion, all of the different approaches should work fine and should be interchangeable. That the behaviour differs for such basic things is concerning and makes PowerShell somewhat unpredictable.

I composed a small script that sets the $DebugPreference with different approaches of function returns that should all behave equally. If you run it with different versions of PowerShell you get different amounts of Debug output. Note that the variable $DebugPreference has the correct value and correct type after each step, but Write-Debug does only work after some of them.

Can someone explain what is going on here?

Running it with "powershell.exe -version 2 ..." gives me this output:

Starting to test return values. Current DebugPreference: SilentlyContinue
1 Obtained the value with return: Continue with Type string
2 Obtained the value from shell with return after: Continue with Type string
DEBUG: 2 After Get-GlobalDefaultWithReturnAfterwards
3 Obtained the value from shell with return after: Continue with Type System.Management.Automation.ActionPreference
DEBUG: 3 After Get-GlobalDefaultWithReturnAfterwardsActionPreference
4 Obtained the value without return: Continue with Type string
DEBUG: 4 After Get-GlobalDefaultWithOutReturn
5 Obtained the value with Write-Output: Continue with Type string
6 Obtained the value with Write-Output: Continue with Type System.Management.Automation.ActionPreference
7 Obtained piped value with Write-Output : Continue with Type string
8 Set the value directly: Continue with Type string
DEBUG: 8 After setting the value directly

Running it with "powershell.exe -version 5 ..." gives me this output:

Starting to test return values. Current DebugPreference: SilentlyContinue
1 Obtained the value with return: Continue with Type string
DEBUG: 1 After Get-GlobalDefaultWithReturn
2 Obtained the value from shell with return after: Continue with Type string
DEBUG: 2 After Get-GlobalDefaultWithReturnAfterwards
3 Obtained the value from shell with return after: Continue with Type System.Management.Automation.ActionPreference
DEBUG: 3 After Get-GlobalDefaultWithReturnAfterwardsActionPreference
4 Obtained the value without return: Continue with Type string
DEBUG: 4 After Get-GlobalDefaultWithOutReturn
5 Obtained the value with Write-Output: Continue with Type string
6 Obtained the value with Write-Output: Continue with Type System.Management.Automation.ActionPreference
DEBUG: 6 After Get-GlobalDefaultWriteOutputActionPreference
7 Obtained piped value with Write-Output : Continue with Type string
8 Set the value directly: Continue with Type string
DEBUG: 8 After setting the value directly

The script:

function Get-GlobalDefaultWithReturn
{
    return "Continue"
}

function Get-GlobalDefaultWithReturnAfterwards
{
    "Continue"
    return
}

function Get-GlobalDefaultWithReturnAfterwardsActionPreference
{
    ([System.Management.Automation.ActionPreference]::Continue)
    return
}

function Get-GlobalDefaultWithOutReturn
{
    "Continue"
}

function Get-GlobalDefaultWriteOutput
{
    Write-Output "Continue"
}

function Get-GlobalDefaultWriteOutputActionPreference
{
    Write-Output ([System.Management.Automation.ActionPreference]::Continue)
}


$DebugPreference = "SilentlyContinue"
Write-Host "Starting to test return values. Current DebugPreference: $DebugPreference"

$DebugPreference = Get-GlobalDefaultWithReturn
Write-Host "1 Obtained the value with return: $DebugPreference with Type $($DebugPreference.GetType())"
Write-Debug "1 After Get-GlobalDefaultWithReturn"

$DebugPreference = "SilentlyContinue"
$DebugPreference = Get-GlobalDefaultWithReturnAfterwards
Write-Host "2 Obtained the value from shell with return after: $DebugPreference with Type $($DebugPreference.GetType())"
Write-Debug "2 After Get-GlobalDefaultWithReturnAfterwards"

$DebugPreference = "SilentlyContinue"
$DebugPreference = Get-GlobalDefaultWithReturnAfterwardsActionPreference
Write-Host "3 Obtained the value from shell with return after: $DebugPreference with Type $($DebugPreference.GetType())"
Write-Debug "3 After Get-GlobalDefaultWithReturnAfterwardsActionPreference"

$DebugPreference = "SilentlyContinue"
$DebugPreference = Get-GlobalDefaultWithOutReturn
Write-Host "4 Obtained the value without return: $DebugPreference with Type $($DebugPreference.GetType())"
Write-Debug "4 After Get-GlobalDefaultWithOutReturn"

$DebugPreference = "SilentlyContinue"
$DebugPreference = Get-GlobalDefaultWriteOutput
Write-Host "5 Obtained the value with Write-Output: $DebugPreference with Type $($DebugPreference.GetType())"
Write-Debug "5 After Get-GlobalDefaultWriteOutput"

$DebugPreference = "SilentlyContinue"
$DebugPreference = Get-GlobalDefaultWriteOutputActionPreference
Write-Host "6 Obtained the value with Write-Output: $DebugPreference with Type $($DebugPreference.GetType())"
Write-Debug "6 After Get-GlobalDefaultWriteOutputActionPreference"

$DebugPreference = "SilentlyContinue"
Get-GlobalDefaultWriteOutput | % { $DebugPreference = $_ }
Write-Host "7 Obtained piped value with Write-Output : $DebugPreference with Type $($DebugPreference.GetType())"
Write-Debug "7 After Get-GlobalDefaultWriteOutput with pipe"

$DebugPreference = "SilentlyContinue"
$DebugPreference = "Continue"
Write-Host "8 Set the value directly: $DebugPreference with Type $($DebugPreference.GetType())"
Write-Debug "8 After setting the value directly"
like image 237
m4rkus Avatar asked Nov 08 '22 19:11

m4rkus


1 Answers

Earlier i stated that i did not have the issue but it seems that the way you run the code matters, excluding ISE, Console and Higher Powershell versions running as v2 this is what i found out:

Without problem:

  • powershell.exe -file c:\debugtest.ps1
  • Powershell.exe -command ."c:\debugtest.ps1"

With problem:

  • powershell.exe -command c:\debugtest.ps1

The workaround i suggested did work in this case:

You might want to try(Get-GlobalDefaultWithReturn).tostring() to force it to a string if that is the issue.

Powershell Version:

Major  Minor  Build  Revision
-----  -----  -----  --------
2      0      -1     -1      

It looks like a bug, but i don't know anything more about it yet. True it is a bit Unpredictable but you might want to consider the way you run your code, e.g. with FILE if it is a FILE.

like image 113
SteloNLD Avatar answered Nov 14 '22 23:11

SteloNLD