Home > Net >  PowerShell, cannot append a variable inside a function
PowerShell, cannot append a variable inside a function

Time:09-29

I'm a bit confused about something in PowerShell.

At the end of a function it appends some text to a variable so that I can log what happened. But when I do this, $x just contains the header and nothing else. What am I doing wrong?

$x = "Header`n=========="

function xxx ($z) {
    $z = "$z ... 1"
    $x  = "`noutput from $z"
}

xxx 123
xxx 234
xxx 345
xxx 456

CodePudding user response:

To summarise the comments, and lean on this answer by @mklement0 from a similar question - https://stackoverflow.com/a/38675054/3156906 - the default behaviour is for PowerShell to let you read variables from a parent scope, but if you assign a value it creates a new variable in the child scope which "hides" the parent variable until the child scope exists.

The about_Scopes documentation says something similar as well, but doesn't really lay it out in such specific detail unfortunately:

If you create an item in a scope, and the item shares its name with an item in a different scope, the original item might be hidden under the new item, but it is not overridden or changed.

You can assign values to the variable in the parent scope if you refer to it explicitly by scope number - e.g:

$x = "some value"

function Invoke-MyFunction
{
    # "-Scope 1" means "immediate parent scope"
    Set-Variable x -Value "another value" -Scope 1
}

Invoke-MyFunction

write-host $x

In your case though, you might want to wrap your logging logic into separate functions rather than litter your code with lots of Set-Variable x -Value ($x "another log line") -Scope 1 (which is an implementation detail of your logging approach).

Your current approach will degrade performance over time by creating a new (and increasingly long) string object every time you add some logging output, and it will also make it really hard to change your logging mechanism at a later date (e.g. what if you decide want to log to a file instead?) as you'll need to go back to every logging line and rewrite it to use the new mechanism.

What you could do instead is something like this:

Logging Code

$script:LogEntries = new-object System.Collections.ArrayList; 

function Write-LogEntry
{
    param( [string] $Value )
    $null = $script:LogEntries.Add($Value)
}

function Get-LogEntries
{
    return $script:LogEntries
}

Application Code

function Invoke-MyFunction
{
    Write-LogEntry -Value "another logging line"
}

and then your logging mechanism is abstracted away from your main application, and your application just has to call Write-LogEntry.

Note that $script:<varname> is another way of referencing variables in the containing script's root scope using Scope Modifiers - the link describes some other options including global, local and private.

  • Related