Rob Traynere
Rob Traynere

Reputation: 704

get powershell variable name from actual variable

I am trying to figure out how to get the name of a powershell variable from the object, itself.

I'm doing this because I'm making changes to an object passed by reference into a function, so I don't know what the object will be and I am using the Set-Variable cmdlet to change that variable to read only.

# .__NEEDTOGETVARNAMEASSTRING is a placeholder because I don't know how to do that.

function Set-ToReadOnly{
  param([ref]$inputVar)
  $varName = $inputVar.__NEEDTOGETVARNAMEASSTRING
  Set-Variable -Name $varName -Option ReadOnly
}
$testVar = 'foo'
Set-ToReadOnly $testVar

I've looked through a lot of similar questions and can't find anything that answers this specifically. I want to work with the variable entirely inside of the function--I don't want to rely on passing in additional information.

Also, while there may be easier/better ways of setting read-only, I have been wanting to know how to reliably pull the variable name from a variable for a long time, so please focus solving that problem, not my application of it in this example.

Upvotes: 4

Views: 6632

Answers (2)

mklement0
mklement0

Reputation: 440092

Mathias R. Jessen's helpful answer explains why the originating variable cannot be reliably determined if you only pass its value.

The only robust solution to your problem is to pass a variable object rather than its value as an argument:

function Set-ToReadOnly {
  param([psvariable] $inputVar) # note the parameter type
  $inputVar.Options += 'ReadOnly'
}

$testVar = 'foo'
Set-ToReadOnly (Get-Variable testVar) # pass the variable *object*

If your function is defined in the same scope as the calling code - which is not true if you the function is defined in a (different) module - you can more simply pass just the variable name and retrieve the variable from the parent / an ancestral scope:

# Works ONLY when called from the SAME SCOPE / MODULE
function Set-ToReadOnly {
  param([string] $inputVarName)
  # Retrieve the variable object via Get-Variable.
  # This will implicitly look up the chain of ancestral scopes until
  # a variable by that name is found.
  $inputVar = Get-Variable $inputVarName
  $inputVar.Options += 'ReadOnly'
}

$testVar = 'foo'
Set-ToReadOnly testVar # pass the variable *name*

Upvotes: 6

Mathias R. Jessen
Mathias R. Jessen

Reputation: 174900

As noted in this answer to a similar question, what you're asking (resolving the identity of a variable based on its value) can not be done reliably:

The simple reason being that contextual information about a variable being referenced as a parameter argument will have been stripped away by the time you can actually inspect the parameter value inside the function.

Long before the function is actually called, the parser will have evaluated the value of every single parameter argument, and (optionally) coerced the type of said value to whatever type is expected by the parameter it's bound to.

So the thing that is ultimately passed as an argument to the function is not the variable $myVariable, but the (potentially coerced) value of $myVariable.

What you could do for reference types is simply go through all variables in the calling scope and check if they have the same value:

function Set-ReadOnlyVariable {
  param(
    [Parameter(Mandatory=$true)]
    [ValidateScript({ -not $_.GetType().IsValueType })]
    $value
  )

  foreach($variable in Get-Variable -Scope 1 |Where-Object {$_.Value -ne $null -and $_.Value.Equals($value)}){
    $variable.Options = $variable.Options -bor [System.Management.Automation.ScopedItemOptions]::ReadOnly
  }
}

But this will set every single variable in the callers scope with that value to readonly, not just the variable you referenced, and I'd strongly recommend against this kind of thing - you're most likely doing something horribly wrong if you need to do this

Upvotes: 3

Related Questions