Going through some more advanced PowerShell topics I bumped into the passing of variables by reference few times. Although I used it just a few times there was one thing that always bothered me about it: You have to define the referenced variable first. So I decided to create a handy function that enables me to pass a variable by reference without caring if it is already defined.

What is passing argument by reference?

Argument that would be normally passed by value, like an integer or a string, is passed to function by reference, enabling the function to manipulate the data and save the directly back to the object not just reading a copy of them and outputting the result on its output. Passing by reference is marked by cast to [ref] which is type accelerator for [System.Management.Automation.PSReference].

Normally if you call a function you pass only the value of the variable like so:

function Abs ([double]$Number)
{
	[math]::Abs($Number)
}
$one = -1
$result = Abs -Number $one

"one is: $one"
"result is: $result"
one is: -1
result is: 1

The function ‘Abs’ takes ‘-1′ from the variable ‘one’ as the input. The value is passed to the static method ‘Abs’ of the Math class. The method returns absolute value of the value ‘-1′ and outputs it on the standard output. The output value is saved to the ‘result’ variable. As you can see in the final output the value of the variable ‘one’ is not affected by the operation, because only the value is passed.

If you pass the variable by reference, you do not pass just the value, you pass the whole object. You usually do it because you want to store the output directly to the variable as in this case:

function AbsRef ([ref]$Number)
{
	$Number.value = [math]::Abs($Number.value)
}
$one = -1
Abs -Number ([ref]$one)

"one is: $one"
one is: 1

The variable ‘one’ is passed to the function by reference enabling you to save the output of the static ‘Abs’ function of the Math class directly to the variable.

Note: It maybe seems like a good idea to do this but it is not. The value types are passed by value for a reason. Don't do this in your code unless you absolutely have to.

If you wonder if it would work without the ‘ref’ then you should know it would not because then the ‘Number’ variable is created in the scope of the function and does not affect the value of the ‘one’ variable.

function Abs ($Number)
{
	$Number = [math]::Abs($Number)
}
$one = -1
Abs -Number ($one)

"one is: $one"
one is: -1

For further reference you can go to the Microsoft documentation on the ref (it is for C# but the idea is the same) which is found here.

Implementing the function

The method used in the examples in this topic is taken from a Hey, Scripting Guy! Blog article describing how to use PowerShell tokenizer method. Also the error produced while calling the cast to ‘ref’ without the variable initialized is described there. I really recommend you to read it, amazing stuff.

function Reference-Variable
{
	<#
	.SYNOPSIS
	Reference defined or undefined variable while passing
	it by reference.

	.DESCRIPTION
	The function enables user to easily reference defined
	or undefined variable while calling a method that
	requires usage of the [ref] cast. If the variable is
	defined in the calling scope do not take any action.
	If the variable is not defined it is created in the
	calling scope and passed to the output as
	System.Management.Automation.PSReference object.
	.NOTES
	Author	: Jakub Jares - me@jakubjares.com

	.LINK

http://powershell.cz/2013/01/07/reference-variable/

	.EXAMPLE
	PS> [system.management.automation.psparser]::Tokenize('"""',(Reference-Variable TokenizingError))
	The tokenize method accepts string to tokenize as first
	parameter and reference to a variable where errors will
	be stored as second.
	Output the errors by otputing the TokenizingError variable
	using this command:
	PS> $TokenizingError | fl 

	Token   : System.Management.Automation.PSToken
	Message : The string starting:
          At line:1 char:1
          +  <<<< """
          is missing the terminator: ".

	.PARAMETER	Name
	Defines name of the variable. The name is specifies
	without the '$' sign. As with Set-Variable or Get-Variable.
	.PARAMETER Clear
	If the variable specified by the name parameter exists it
	is set to $null. Use to prevent conversion issues if the
	variable is already set.
	#>

	[CmdletBinding()]
	param	(
		[Parameter(Position=1, Mandatory=$true, ValueFromPipeline=$false)]
		[string]$Name,
		[Parameter(Position=2, ValueFromPipeline=$false)]
		[switch]$Clear
		)

	Write-Verbose "Checking if the variable '$Name' exists."
	#refer to the Parent (calling) scope by number 1
	$var = Get-Variable -Name $name -Erroraction SilentlyContinue -Scope 1

	if ($var)
	{
		Write-Verbose "Variable '$Name' exists in the parent scope."
		if ($Clear)
		{
			Write-Verbose "Null parameter specified, nulling the '$Name' variable."
			$var.Value = $null
		}
	}
	else
	{
		Write-Verbose "Variable '$Name' does not exist in the parent scope, creating one."
		$var = New-Variable -Name $Name -Scope 1
	}
	Write-Verbose "Reference to variable '$Name' returned."
	#return reference to the variable
	[ref](Get-Variable $Name -Scope 1)
}

The function is pretty simple. It checks if the variable with the ‘name’ exists in the parent scope by specifying the Scope parameter (Do you wonder what sorcery is this? Refer to this article by @Jeffrey Snover.) If the variable is not defined, it is created in the parent scope. It is also casted to the PSReference type and passed to output of the function.

The biggest catch of the function is the scoping. If the Reference-Variable function is called a new scope is created which becomes the current scope, but I need to create the variable in the parent scope (in the scope that called the function) because my current scope is destroyed after the function ends. Also by operating only in the parent scope (and not in script or other scope) I minimize the impact that nulling the variable can have on the environment, because creating the variable there does not affect any other variable of the same name in any grandparent etc. scope. Keep in mind the function is meant to avoid defining the variable by us in the current scope hence, not changing the variables in other scopes is necessary. To prove it works I created also a small function called ‘Test-ReferenceVariable’ to test the scoping. Call it like this:

function Test-ReferenceVariable ($name)
{
	[system.management.automation.psparser]::Tokenize('"""',
		(Reference-Variable -Name $Name -Verbose -Clear))
	get-variable $Name -ValueOnly
}
$TokenizingError = "defined"

Test-ReferenceVariable -name TokenizingError

Remove-Variable TokenizingError
Podrobnosti (verbose): Checking if the variable 'TokenizingError' exists.
Podrobnosti (verbose): Variable 'TokenizingError' does not exist in the parent scope, creating one.
Podrobnosti (verbose): Reference to variable 'TokenizingError' returned.

Token                                   Message
-----                                   -------
System.Management.Automation.PSToken    The string starting:...

First I define the ‘TokenizingError’ variable in the current scope, and then call the function ‘Test-ReferenceVariable’. Calling the function creates another scope which becomes child to the scope where the variable is defined. Then I call the ‘Reference-Variable’ function (again new child scope is created) which checks if the ‘TokenizingError’ is defined in the parent scope (scope of the ‘Test-ReferenceVariable function) which is not, as you can see on the second line of the verbose output. The variable is created and passed to the tokenizer function. Not affecting the original variable.