Search code examples
functionpowershellparameter-passingpass-by-referencepowershell-3.0

Pass strings by reference in Powershell?


How can I pass strings by reference to the parent scope?

This doesn't work since strings are not acceptable "values".

function Submit([ref]$firstName){ 
    $firstName.value =  $txtFirstName.Text 
}

$firstName = $null
Submit([ref]$firstName)
$firstName

Error: "Property 'value' cannot be found on this object; make sure it exists and is settable"

Doing this doesn't give an error but it doesn't change the variable either:

$firstName = "nothing"

function Submit([ref]$firstName){ 
    $firstName =  $txtFirstName.Text 
} 

Submit([ref]$firstName)
$firstName

Edit:

Doing the first code block by itself works. However when trying to do it in my script it returns the error again. I fixed it enough for it to assign the variable and do what I want but it still throws up an error and I was wondering how to fix that. I think it's because it doesn't like variable;es changing during a running session. Here is a link to my script

https://github.com/InconspicuousIntern/Form/blob/master/Form.ps1


Solution

  • Your first snippet is conceptually correct and works as intended - by itself it does not produce the "Property 'Value' cannot be found on this object" error.

    You're seeing the error only as part of the full script you link to, because of the following line:

    $btnSubmit.Add_Click({ Submit })
    

    This line causes your Submit function to be called without arguments, which in turn causes the $firstName parameter value to be $null, which in turn causes the error quoted above when you assign to $firstName.Value.

    By contrast, the following invocation of Submit, as in your first snippet, is correct:

    Submit ([ref] $firstName)  # Note the recommended space after 'Submit' - see below.
    

    [ref] $firstName creates a (transient) reference to the caller's $firstName variable, which inside Submit binds to (local) parameter variable $firstName (the two may, but needn't and perhaps better not have the same name), where $firstName.Value can then be used to modify the caller's $firstName variable.


    Syntax note: I've intentionally placed a space between Submit and ([ref] $firstName) to make one thing clearer:

    The (...) (parentheses) here do not enclose the entire argument list, as they would in a method call, they enclose the single argument [ref] $firstName - of necessity, because that expression wouldn't be recognized as such otherwise.

    Function calls in PowerShell are parsed in so-called argument mode, whose syntax is more like that of invoking console applications: arguments are space-separated, and generally only need quoting if they contain special characters.

    For instance, if you also wanted to pass string 'foo', as the 2nd positional parameter, to Submit:

    Submit ([ref] $firstName) foo
    

    Note how the two arguments are space-separated and how foo needn't be quoted.


    As for an alternative approach:

    [ref]'s primary purpose is to enable .NET method calls that have ref / out parameters, and, as shown above, using [ref] is nontrivial.

    For calls to PowerShell functions there are generally simpler solutions.

    For instance, you can pass a custom object to your function and let the function update its properties with the values you want to return, which naturally allows multiple values to be "returned"; e.g.:

    function Submit($outObj){ 
        $outObj.firstName = 'a first name'
    }
    
    # Initialize the custom object that will receive values inside
    # the Submit function.
    $obj = [pscustomobject] @{ firstName = $null }
    
    # Pass the custom object to Submit.
    # Since a custom object is a reference type, a *reference* to it
    # is bound to the $outObj parameter variable.
    Submit $obj
    
    $obj.firstName # -> 'a first name'
    

    Alternatively, you can just let Submit construct the custom object itself, and simply output it:

    function Submit { 
        # Construct and (implicitly) output a custom
        # object with all values of interest.
        [pscustomobject] @{ 
            firstName = 'a first name' 
        } 
    }
    
    $obj = Submit
    
    $obj.firstName # -> 'a first name'