powershellfunction

Powershell function returning not just what was after the return call


The below code is annoyingly not just returning the contents of the $t variable, of even if the contents of $t are instead returned like return "!! Quitting...":

function AddADUser
{

....

  $Answer = [System.Windows.Forms.MessageBox]::Show("WARNING: The below account already exists in domain $domain. Continue to update it (yes), or quit and rerun should it be needed after investigation (no)?", "$ScriptFileName", "YesNo")

  if($Answer -eq "no")
  {
    write-host "Before return"
    $t = "!! Quitting script on user cancellation, due to not wanting to update existing user(s)   data with new details provided`r`n"
    write-host "$t"
    return $t
  }
}

Instead the console output before and after the function returns:

Before return

!! Quitting script on user cancellation, due to not wanting to update existing user(s) data with new details provided

 System.Management.Automation.PSCustomObject !! Quitting script on user cancellation, due to not wanting to update existing user(s) data with new details provided

$t contains nothing prior to being assigned after the first write-host

This must be a quirk to Powershell function return, but I can't figure it out where the darned System.Management.Automation.PSCustomObject is coming from, apart from maybe if the function returned at a later stage, it would contain an object (I have checked with a write-host it is not proceeding to that stage though). What is going on!?


Solution

  • While the question doesn't contain enough information to diagnose the source of the unwanted output, let's imagine a simple scenario:

    function AddUser {
      param(<# ... #>)
    
      # oops, accidentally leaking a custom object
      [pscustomobject]@{ lol = 123 }
    
      $t = 'Value we actually want'
    
      return $t
    }
    

    It's important to understand here that PowerShell functions can emit output at any time before the function returns - in fact, any pipeline or value expression that outputs anything during invocation will have its resulting output slipstreamed back up to the caller, as suggested by the inline comment above.

    What this means is that if we invoke AddUser as-is, it'll emit the custom object first, followed by $t, so any variable you assign the output of AddUser from will suddenly contain an array with $t in the last slot, rather than just the scalar value stored in $t on its own.

    Thankfully, there's a generic workaround you can use in this situation:

    $onlyT = & {
      $null = . AddUser @paramArgs
      return $t
    }
    

    Value of $t is returned and assigned to $onlyT outside the wrapper, whereas anything emitted during execution of AddUser is suppressed with the inner assignment to $null.

    The reason this works is that . invokes AddUser in the calling scope, meaning local variables like $t will linger inside the wrapper block.