A sad thing about PowerShell is that function and scriptblocks are dynamically scoped.
But there is another thing that surprised me is that variables behave as a cop
While other posts give lots of useful information they seem only to save you from RTFM.
The answer not mentioned is the one I find most useful!
([ref]$var).value = 'x'
This modifies the value of $var no matter what scope it happens to be in. You need not know its scope; only that it does in fact already exist. To use the OP's example:
$array=@("g")
function foo()
{
([ref]$array).Value += "h"
Write-Host $array
}
& {
([ref]$array).Value +="s"
Write-Host $array
}
foo
Write-Host $array
Produces:
g s
g s h
g s h
Explanation:
([ref]$var) gets you a pointer to the variable. Since this is a read operation it resolves to the most recent scope that actually did create that name. It also explains the error if the variable doesn't exist because [ref] can't create anything, it can only return a reference to something that already exists.
.value then takes you to the property holding the variable's definition; which you can then set.
You may be tempted to do something like this because it sometimes looks like it works.
([ref]$var) = "New Value"
DON'T!!!!
The instances where it looks like it works is an illusion because PowerShell is doing something that it only does under some very narrow circumstances such as on the command line. You can't count on it. In fact it doesn't work in the OP example.