Is 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
}
f2Alright gang, bear with me...this is a short question but there's a LOT of supporting documentation. What it boils down to is: if I define a variable's value within a function, how do I pass that to other functions that run subsequently?
I've constructed a rather lengthy script that breaks things down into a series of functions so they can be easily mixed and matched, but I noticed that if I didn't define a specific variable outside the functions (specifically, $nextName, which in this version is defined right at the start of the ForEach-Object loop), it wasn't reaching the last function that was running.
I'd post the script that I have, but it's WAY too long, so we'll have to just talk in theory. This is the sequence of commands I want to run:
$num = 1
$moveThese | ForEach-Object {
$relativePath = $_.FullName.Substring($sourceDir.Length)
$nextName = Join-Path -Path $targetDir -ChildPath $relativePath
$lastSize = $TotalSize;
$TotalSize += $_.Length
ADDSIZE
#HOLDFAST
TOTALSTATS
#HOLDFAST
DISPLAYSTATS
#HOLDFAST
STATSCHECK
#HOLDFAST
IFEXIST
#HOLDFAST
if ($existFile -eq "Y") {
SIZECHECK
HOLDFAST
}
if ($sizeMatch -eq "Y") {
HASHCEHCK
HOLDFAST
}
if ($hashMatch -eq "Y") {
REMOVEITEM
HOLDFAST
}
ElseIf ($hashMatch -eq "N") {
RENAMEITEM
HOLDFAST
}
if ($fileDeleted -ne "Y") {
COPYITEM
#HOLDFAST
}
$num += 1
}So you see that in this version I'm defining the variable $nextName right at the start of the loop, but originally the first two lines after ForEach-Object were part of the function IFEXIST. Trouble was, when we got to the end and COPYITEM ran, $nextName was empty, so the value wasn't getting passed down.
And the issue is, IFEXIST defines a variable $existFile...and what I'm thinking is that if $nextName wasn't getting passed from IFEXIST, then $existFile isn't getting passed on either.
So...how do I get one function to read a variable defined in another in a setup like this?
EDIT: adding verbiage of function IFEXIST
function IFEXIST
{
Write-Host `n
Write-Host "FUNCTION IFEXIST" -BackgroundColor DarkRed -ForegroundColor Yellow
Write-Host `n
$script:relativePath = $_.FullName.Substring($sourceDir.Length)
$script:nextName = Join-Path -Path $targetDir -ChildPath $relativePath
Write-Host "THISFILE: "
Write-Host $_.FullName -ForegroundColor Yellow
Write-Host "`nTARGETFILE:"
Write-Host $nextName -ForegroundColor Yellow
Write-Host `n
If(Test-Path -Path $nextName) {
$script:existFile = "Y"
Write-Host "This file exists in TARGETDIR."
} else {
$script:existFile = "N"
Write-Host "This file does not exist in TARGETDIR."
}
}Videos
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.
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"
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.
To aid future maintenance, I’ve got my user management script calling another script to get the address information for different offices, which should pass back the right info to be used in the AD user management.
So, if add-user.ps1 gets the variable $ADOffice=“New York” it calls get-OfficeAddress.ps1 where a switch statement passes back the correct street address, state, and zip code.
Then add-user.ps1 goes on to make the appropriate changes to the target user in the AD.
It looks like this:
add-user.ps1
Param(
[Parameter( Mandatory = $false)]
[string]$ADoffice = "New York"
)
&("C:\Scripts\User Mgmt Automation\get-OfficeAddress.ps1") $ADoffice
Then get-OfficeAddress.ps1 picks that up like so:
switch ($Office)
{
"Albany" {
$ADstreetAddress="123 Main Street"
$ADcity="Albany"
$ADstate="NY"
$ADpostalCode="123456"
}
"New York"{
$ADstreetAddress="1313 Mockingbird Lane"
$ADcity="New York"
$ADstate="NY"
$ADpostalCode="131313"
}
}
Write-Host $ADoffice,$ADstreetAddress,$ADcity,$ADstate,$ADpostalCode
The screen output shows the results on the screen fine, but I don’t know how to pass those variables back into ad-user.ps1 just after the call to get-OfficeAddress.ps1
Could you point me in the right direction, please?
You need to assign a variable name to the output of your called script. You might be able to call it by using
$address = &("C:\Scripts\User Mgmt Automation\get-OfficeAddress.ps1") $ADoffice
The better way would be to re-write the get-OfficeAddress.ps1 into a grouping of related functions which can be called using ‘dot-Sourcing’
You would add the script and its functions into your calling script as so:
.C:\folder\ScriptWithFunctions.ps1
and then just call the functions inside it the same as you would if you wrote the functions inside the script itself. Try this page for more details/examples.
Microsoft Certified Professional Magazine Online
Exploring Dot-Sourcing in PowerShell -- Microsoft Certified Professional...
Dot-sourcing allows for you to keep your functions modular.
edited for clarity
Hello
In my form, I declare my radio buttons, textboxes etc.
In my group, I have a series of radiobuttons, so I am using the .Add_Click{whichoption} on the group to indicate that if a group item is clicked (radiobutton), it will run a function.
The function simply says that :
function whichoption{
#Check that there is a computername entered before assigning a value
If($Compname = $txtsetUACComp.Text) {
If($rdoOption1.checked) {$UACoption = 1}
If($rdoOption2.checked) {$UACoption = 2}
If($rdoOption3.checked) {$UACoption = 3}
If($rdoOption4.checked) {$UACoption = 4}
}
}
Once the whichoption function has run, I call another function that requires parameters (-computername and -option). This option also requires credentials So I have use the following in order to prompt for Credentials (inside the function).
$c=Get-credential
$Username = $c.UserName
Im using:
[System.Windows.MessageBox]::Show(($CompName) + ", " + $UACOption)
to see what values have been passed, and it is always blank ($compName and $UACOption do not have values).
So this leads me to believe it is a scoping issue.
I have attempted to use:
Function whichoption() {
If($txtsetUACComp.Text) {
$grpOptions.Enabled = $True #don't enable radiobuttons until a computername is typed in.
$Global:CompName = $Script:txtsetUACComp.Text + $txtsetUACComp.Text
if($rdoOption1.checked) {$Global:UACOption = 1 + $UACOption}
if($rdoOption2.checked) {$Global:UACOption = 2 + $UACOption}
if($rdoOption3.checked) {$Global:UACOption = 3 + $UACOption}
if($rdoOption4.checked) {$Global:UACOption = 4 + $UACOption}
return $UACOption
}
}
But the same results.
The function I am calling has the following parameters:
Param ([String] $Script:CompName, [int] $Script:UACOption)
And the command to call the function is:
$btnUAC.Add_Click({ChangeUAC -Computername $Script:CompName -Option $Script:UACOption})
Any suggestions?
Thank You
Terry
By default, variables within a function operate within the scope of the function and do not exist outside of the function. You can change the scope of the variable.
learn.microsoft.com
about Scopes - PowerShell
Explains the concept of scope in PowerShell and shows how to set and change the scope of elements.
Parameters in calls to functions in PowerShell (all versions) are space-separated, not comma-separated. Also, the parentheses are entirely unnecessary and will cause a parse error in PowerShell 2.0 (or later) if Set-StrictMode -Version 2 or higher is active. Parenthesised arguments are used in .NET methods only.
function foo($a, $b, $c) {
"a: $a; b: $b; c: $c"
}
Execute:
foo 1 2 3
Output:
a: 1; b: 2; c: 3
The correct answer has already been provided, but this issue seems prevalent enough to warrant some additional details for those wanting to understand the subtleties.
I would have added this just as a comment, but I wanted to include an illustration—I tore this off my quick reference chart on PowerShell functions. This assumes function f's signature is f($a, $b, $c):

Thus, one can call a function with space-separated positional parameters or order-independent named parameters. The other pitfalls reveal that you need to be cognizant of commas, parentheses, and white space.
For further reading, see my article Down the Rabbit Hole: A Study in PowerShell Pipelines, Functions, and Parameters. The article contains a link to the quick reference/wall chart as well.
The $arg[] array appears to lose scope inside the ForEach-Object.
function foo($arg1, $arg2)
{
echo $arg1
echo $arg2.FullName
}
echo "0: $($args[0])"
echo "1: $($args[1])"
$zero = $args[0]
$one = $args[1]
$items = get-childitem $args[1]
$items | foreach-object {
echo "inner 0: $($zero)"
echo "inner 1: $($one)"
}
The reason that $args[0] is not returning anything in the foreach-object is that $args is an automatic variable that takes unnamed, unmatched parameters to a command and the foreach-object is a new command. There aren't any unmatched parameters for the process block, so $args[0] is null.
One thing that can help is that your scripts can have parameters, just like functions.
param ($SomeText, $SomePath)
function foo($arg1, $arg2)
{
echo $arg1
echo $arg2.FullName
}
echo "0: $SomeText"
echo "1: $SomePath"
$items = get-childitem $SomePath
$items | foreach-object -process {foo $SomeText $_}
As you start to want more functionality from your parameters, you might want to check out a blog post I wrote on the progression of parameters from $args to the current advanced parameters we can use now.
I would like to simplify a large script by breaking it into several smaller scripts.
What do you think of this idea for exchanging variables?
Call a script using:
$results = . c:\path\other-script.ps1
This should give the called script everything in the calling script’s scope, and prepare to receive outputs.
At the end of the called script, bundle everything I want into a custom object, then:
return $object
Back in the calling script I can access everything like:
$results.this
$results.that