Variable contents changed by function it is passed to - what am I forgetting?

I recently (re)wrote a coworker’s script into a slightly more properly constructed function. One of the function’s parameters takes an object, a collection/array of psobjects to be specific. The function does some filtering, and then tweaks the value of one property on certain elements of the collection. In testing, I’m importing a CSV to a variable, and then calling the function as such:

$collection = $import-csv c:\temp\blah.csv
get-hcpatchlists -list $collection

I’m not sure I’ve written a script at all like this before and so I was quite surprised to find that after running the function, the array in $collection had been modified. As the title of the topic says, am I forgetting something fundamental?

please forgive the gratuitous Write-Verboses. I was trying to figure out why the function was behaving differently the second time I ran it. I’m not a fan of what the script is doing in terms of output, but that’s a battle for another day.

function Get-HCPatchLists {
    param (
        # The object representing the list SIDv1
        # A path to save the files to
        [ValidateScript({Test-Path $_})]
        $netpath = "\\myserver\somepath\ServerPatchList\"

begin {

    $excludes = ("Manual-Snowflakes","Linux","Shutdown Pending","Exempt","HCLIB","ESX Server","Departmental","Lab patching","Special Requirements","M 3,6,9,12")
    # Make sure that $netpath ends with a backslash
    If (-not ($netpath.endswith("\"))){
        $netpath += "\"   
    write-verbose $netpath
process {
    # Filter out the patching schedules we don't want to deal with.
    $list = $list | Where-Object {$excludes -notcontains $_.PatchSchedule} | Where-Object {$_.Decommissioned -ne $true} 
    Write-Verbose "$($list.count) entries after filtering"

    foreach ($server in $list) {
        if ($server.PatchSchedule -eq "") {
            Write-Verbose "$($server.title) not categorized"
            $server.PatchSchedule = "ZZ-to be categorized"
        if ($server.domain -eq "") {
            Write-verbose "De-nulling $($server.title)'s domain"
            $server.domain = "Unknown"
        #Write-verbose "old domain: $($server.domain)"
        if ($server.domain -eq "ECOM") {
        #Write-Verbose $server.Cluster
		    $server.cluster = ($server.Cluster).replace("Cluster","")
        #Write-Verbose $server.Cluster
			$server.domain += "-$($server.cluster)"
        $path = "$netpath$($server.domain)-$($server.patchschedule).txt"
        try {
		    #write-output "$name $patchstr"
			out-file -filepath $path -append -inputobject $($server.title)
		catch {
            write-verbose "error with $name : $_" 

Is this expected behavior?

Yep, that’s normal. There are two different ways you could attempt to modify a parameter. First, there’s assigning to the variable itself:

function Test-Something
    param ($TheParameter)

    $TheParameter = 'A new value'

In this case, the “A new value” string would not be seen by the caller. However, another option is that you could modify the object referred to BY the parameter:

function Test-Something
    param ([hashtable] $Hashtable)

    $hashtable['SomeNewKey'] = 'Some New Value'

In this case, the caller would see the new key / value pair added to their hashtable. This is because the parameter variable inside the function and the variable that the caller passes to the function refer to the same object in memory, rather than a copy. This is true for all reference types (pretty much everything except for numeric values, Booleans, enums, with a few other exceptions that you may not run into in PowerShell.)

Thanks! That make sense!

The simple solution is to leave the object alone, and perform the text manipulation with a variable that is only in the function’s scope.

Put another way, variables that “contain” objects, are really variables that “point to” objects.