function test() {
write-Host "Called by: $($MyInvocation.Line)"
}
$a = test
Which outputs
Called by: $a = test
?
Answer from TessellatingHeckler on serverfault.comIs there a way to pass variables between functions in PowerShell? I did some googling but was surprised to come up short, all of the information was pretty messy.
Is there a way I could get f2 in the below example to actually output the value of $test ?
Function f1 {
$test = "Hello world"
}
Function f2 {
echo $test
}
f2Function return value in PowerShell - Stack Overflow
powershell - Use values from a function and pass it to another function - Stack Overflow
how get the value from a function outside itself?
In Powershell, how to set variable values within a function and have that value available in the parent scope? - Stack Overflow
function test() {
write-Host "Called by: $($MyInvocation.Line)"
}
$a = test
Which outputs
Called by: $a = test
?
I don't think this is possible at all, nor would it make much sense.
It's like expecting the integer 42 to know if it has been stored in the variable $answer.
Once a function returns, only its return value (if any) is stored in a variable (if an assignment is actually used). If Get-Answer returns 42 and you issue the command $answer = Get-Answer, then $answer will indeed contain 42, but no record will be kept of the fact that it was stored there by having it returned from a function; for all intents and purposes, the end results of $answer = 42 and $answer = Get-Answer are identical, if Get-Answer does indeed return 42.
Also, the assignement (if any) only happens after a function returns; the function only provides a return value (if it does); it doesn't and shouldn't care what PowerShell is going to do with this value after it returns; and its return value could very well be discarded instead of being assigned to anything. There is no direct link between $answer and Get-Answer: what PowerShell does when faced with a command like $answer = Get-Answer is:
- Execute
Get-Answer - Grab the return value from
Get-Answer - Store this value in
$answer
None of the players has any knowledge of this link; the function doesnt know what its return value will be used for, and the variable doesn't know where its assigned value comes from.
And even if some record was kept of this assignment having ever happened, it definitely wouldn't have happened yet while Get-Answer was still being executed.
PowerShell has really wacky return semantics - at least when viewed from a more traditional programming perspective. There are two main ideas to wrap your head around:
- All output is captured, and returned
- The return keyword really just indicates a logical exit point
Thus, the following two script blocks will do effectively the exact same thing:
$a = "Hello, World"
return $a
$a = "Hello, World"
$a
return
The $a variable in the second example is left as output on the pipeline and, as mentioned, all output is returned. In fact, in the second example you could omit the return entirely and you would get the same behavior (the return would be implied as the function naturally completes and exits).
Without more of your function definition I can't say why you are getting a PSMethod object. My guess is that you probably have something a few lines up that is not being captured and is being placed on the output pipeline.
It is also worth noting that you probably don't need those semicolons - unless you are nesting multiple expressions on a single line.
You can read more about the return semantics on the about_Return page on TechNet, or by invoking the help return command from PowerShell itself.
This part of PowerShell is probably the most stupid aspect. Any extraneous output generated during a function will pollute the result. Sometimes there isn't any output, and then under some conditions there is some other unplanned output, in addition to your planned return value.
So, I remove the assignment from the original function call, so the output ends up on the screen, and then step through until something I didn't plan for pops out in the debugger window (using the PowerShell ISE).
Even things like reserving variables in outer scopes cause output, like [boolean]$isEnabled which will annoyingly spit a False out unless you make it [boolean]$isEnabled = $false.
Another good one is $someCollection.Add("thing") which spits out the new collection count.
Variables in powershell are context sensitive. If I define a function like:
$bar = "Hi"
function foo {
$bar = "Hey!"
}
$bar <-- returns "Hi"
Then the $bar variable is not available to me outside that function. To make variables available outside of functions then you can control the scope of a function. If I set a variable in a function using the script or global prefix then the variable will be available for the whole script or globally in powershell runspace. See here:
function foo {
$script:fooVar = "world"
}
function bar {
foo
$global:barVar = "Hello " + $fooVar
}
The variable $fooVar in the foo function will be available to all other functions within the script due to the scope prefix script of the variable $fooVar. The barVar function will be available globally in the runspace. I.e. when your script has finished the variable is still present back at the command line and even to other scripts.
As you can see in the bar function I first call foo and then use the foovVar variable. When I use the $fooVar variable I don't have to specify the $script:fooVar, I can if I want to but it's not necessary.
These are all valid variable assignments:
script:bbb = 123
$global:ccc = 123
So in your case use $script:source and $script:target or $global:source and $global:target. For more info run the following command:
Help About_Scope
the hackish way is to add this to the top:
$Gobal:source = ""
$Gobal:target = ""
and search and replace $source with $Gobal:source and $target with $Gobal:target - then you can use these new global variables at any point in the script.
As suggested you could protect them in another function but for a simple job\automated task that might be overkill. Depends what it's for.
The variables are created in your function's local-scope. Those variables are deleted when your function is done.
Global: The scope that is in effect when Windows PowerShell starts. Variables and functions that are present when Windows PowerShell starts have been created in the global scope. This includes automatic variables and preference variables. This also includes the variables, aliases, and functions that are in your Windows PowerShell profiles. Local: The current scope. The local scope can be the global scope or any other scope. Script: The scope that is created while a script file runs. Only the commands in the script run in the script scope. To the commands in a script, the script scope is the local scope.
Source: about_Scopes
If you need the variables to be available for the script, then write them to the script scope.
$BackupFile = $null
$TaskSequenceID = $null
$OSDComputerName = $null
$capturedWimPath = $null
Function Set-OsToBuild
{
switch ($OsToBuild)
{
"Win7x64"
{
$script:BackupFile = "Win7x64-SP1.wim"
$script:TaskSequenceID = "WIN7X64BC"
$script:OSDComputerName = "Ref-Win7x64"
$script:capturedWimPath = "$($PathToMdtShare)\Captures\$BackupFile"
}
}
}
If you would like to keep the values for whole sessions (until you close the powershell-process), then you should use the global scope.
$global:BackupFile = $null
$global:TaskSequenceID = $null
$global:OSDComputerName = $null
$global:capturedWimPath = $null
Function Set-OsToBuild
{
switch ($OsToBuild)
{
"Win7x64"
{
$global:BackupFile = "Win7x64-SP1.wim"
$global:TaskSequenceID = "WIN7X64BC"
$global:OSDComputerName = "Ref-Win7x64"
$global:capturedWimPath = "$($PathToMdtShare)\Captures\$BackupFile"
}
}
}
The powershell about_scope help document is what you want to read for this.
Specifically this section:
Windows PowerShell Scopes
Scopes in Windows PowerShell have both names and numbers. The named scopes specify an absolute scope. The numbers are relative and reflect the relationship between scopes. Global: The scope that is in effect when Windows PowerShell starts. Variables and functions that are present when Windows PowerShell starts have been created in the global scope. This includes automatic variables and preference variables. This also includes the variables, aliases, and functions that are in your Windows PowerShell profiles. Local: The current scope. The local scope can be the global scope or any other scope. Script: The scope that is created while a script file runs. Only the commands in the script run in the script scope. To the commands in a script, the script scope is the local scope. Private: Items in private scope cannot be seen outside of the current scope. You can use private scope to create a private version of an item with the same name in another scope. Numbered Scopes: You can refer to scopes by name or by a number that describes the relative position of one scope to another. Scope 0 represents the current, or local, scope. Scope 1 indicates the immediate parent scope. Scope 2 indicates the parent of the parent scope, and so on. Numbered scopes are useful if you have created many recursive scopes.
So depending on your exact needs you could use any one of the following I believe.
$global:BackupFile = "Win7x64-SP1.wim"$script:BackupFile = "Win7x64-SP1.wim"$1:BackupFile = "Win7x64-SP1.wim"
I created a PowerShell module to make RESTful API calls to an application. For simplicity, let's say that this module has three functions:
-
Function getProperty($name)
-
Function createProperty($name, $value, $description)
-
Function updateProperty($name, $value, $description)
Whenever createProperty or updateProperty functions are called, those functions in their current form will call getProperty before and after execution. I basically want to verify a property exists before attempting to update or verify it doesn't exist before attempting to create a new one. In order to do that, I've created a boolean variable in the script scope, so it would do something like this:
-
Call createProperty
-
createProperty calls getProperty and sets $Exist to $true or $false, depending on whether the property is found.
-
createProperty checks value of $Exist
-
createProperty creates if $Exists -eq $false, else error out and log it
All that stuff outlined above works great when using just the script. Inside the module, I cannot get the boolean variable to pass from function to function. How do I do that in the module? Am I missing a fundamental best practice here?
I wanted to share my code, but don't want to go through the hassle of sanitizing it just now, but will do it if it becomes necessary. Thanks in advance for any insight.
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*
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
this is a function that gets user input, asks if it is correct, repeats if NOT correct, and finally sends the text out to the caller.
function Get-ExchangeGroupName
{
$Prompt = 'Please enter the Group name '
$Choice = ''
while ($Choice -eq '')
{
$Choice = Read-Host $Prompt
Write-Host ('You entered [ {0} ].' -f $Choice)
$YesNo = Read-Host ' Is that correct? [n/y]'
if ($YesNo -eq 'y')
{
# send the result out to the caller
$Choice
}
else
{
$Choice = ''
}
}
} # function Get-ExchangeGroupName
$ChosenGroupName = Get-ExchangeGroupName
''
$ChosenGroupName
output with n & y responses to "is that correct?" ...
Please enter the Group name : qwerty
You entered [ qwerty ].
Is that correct? [n/y]: n
Please enter the Group name : AlfaBravo
You entered [ AlfaBravo ].
Is that correct? [n/y]: y
AlfaBravo
Figured it out. I forgot to add the $Script: in front of the lines where the function was asking for input. Here is the corrected code:
function getGroup {
$Script:dggroup = Read-Host -Prompt 'Enter the name of the group to add a member'
write-host "You entered $dggroup"
$Script:confirmdggroup = Read-Host -Prompt 'If the group name is correct enter Y'
write-host "You entered $confirmdggroup when asked to confirm group name"
}
If ($confirmdggroup -ne "Y") {getGroup}
write-host "Group name to be modified is $dggroup"
write-host "the confirmdggroup variable is: $confirmdggroup"