Why won't Variable update? - powershell

I'm currently editing our new employee script for AD and I am running into an issue, I added 2 radio buttons for the home folder, one radio button will update the BaseHomeFolderPath to Path1 and the second will update it to Path2, I export the variable to a csv to verify that it works but it keeps showing up blank. I don't know if it is a scope issue or what I am doing wrong, any help will be greatly appreciated! Below is a copy of the code.
I tried using $script: and it didn't work either.
$BaseHomeFolderPath = ''
Set-Variable -Name $BaseHomeFolderPath -Scope Global
$radiobuttonAtlas_MouseClick = [System.Windows.Forms.MouseEventHandler]{
#Event Argument: $_ = [System.Windows.Forms.MouseEventArgs]
#TODO: Place custom script here
$BaseHomeFolderPath = '\\path1\users'
}
$radiobuttonCerberus_MouseClick=[System.Windows.Forms.MouseEventHandler]{
#Event Argument: $_ = [System.Windows.Forms.MouseEventArgs]
#TODO: Place custom script here
$BaseHomeFolderPath = '\\path2\users'
}
$buttonRun_MouseClick=[System.Windows.Forms.MouseEventHandler]{
#Event Argument: $_ = [System.Windows.Forms.MouseEventArgs]
#TODO: Place custom script here
$TestValue = New-Object System.Object
$TestValue | Add-Member -MemberType NoteProperty -Name "Path" -Value $BaseHomeFolderPath
$TestValue | Export-CSV -NoTypeInformation -Path "C:\Users\testuser\Desktop\Testcsv.csv"
}
I expect the BaseHomeFolderPath variable to be updated to whichever radio button is selected.

tl;dr
In order to update the $BaseHomeFolderPath variable in the script scope (or any scope other than the local one), you must reference it in that scope explicitly:
$script:BaseHomeFolderPath = '\\path1\users'
Otherwise, without a scope specifier such as $script:, you'll implicitly create a new variable by that name in the current scope, which in your case is the child scope in which your event handlers run.
In PowerShell, when you assign to a variable with $var = ..., you either:
update a preexisting variable that was created in the same scope.
or implicitly create a new variable in the current scope.
The tricky part is that even though child scopes see variables created in parent scopes and can get their value by name only, assigning to them by name only creates a new, scope-local variable, and that new variable then shadows the original one in the current scope and all child scopes.
A simple demonstration, using call operator & to execute a script block ({ ... }) in a child scope:
$var = 'parent'
"in parent: before: $var"
& {
"in child: before: $var" # sees $var from parent scope
$var = 'child' # creates new $var in current scope
"in child: after: $var" # sees new $var, which shadows the parent's
}
"in parent: after: $var" # still has original value
This prints:
in parent: before: parent
in child: before: parent
in child: after: child
in parent: after: parent
Note:
The behavior also applies to the implicit assignment that occurs with the ++ and -- operators (given that, e.g., ++$var is the same as $var += 1)
See this answer for an example of where this can constitute a pitfall.
In addition to fixed-target scope specifiers $script: and $global:, you can use the Get-Variable / Set-Variable cmdlets with the -Scope parameter to target variables in scopes relative to the current one (up the call stack; e.g., -Scope 1 refers to the parent scope).
To promote modularity and maintainability, it's generally better to avoid accessing variables across scope boundaries - best to pass values around instead.
For more information, see:
Get-Help about_Scopes
The last section of this answer, which provides a concise summary.

Related

Get-Variable defined in a scriptblock from a psm function

I have the following piece of code:
$x = 'xyz'
& {
$y = 'abc'
foo
}
The foo function is defined in the foo.psm1 module which is imported before the script block is started.
Inside the foo function, I call Get-Variable which shows me x but it doesn't show y. I tried playing with the -Scope parameter: Local, Script, Global, 0 - which is the local scope from what I understood from the docs, 1 - which is the parent scope.
How could I get the y variable inside the foo function?
I'm not looking for a solution such as passing it as an argument. I want something as Get-Variable but sadly it doesn't see it for some reason.
UP
Based on the comments received, probably more context is needed.
Say that foo receives a ScriptBlock which is using the $using: syntax.
$x = 'xyz'
& {
$y = 'abc'
foo -ScriptBlock {
Write-Host $using:x
Write-Host $using:y
}
}
I'm 'mining' these variables as follows:
$usingAsts = $ScriptBlock.Ast.FindAll( { param($ast) $ast -is [System.Management.Automation.Language.UsingExpressionAst] }, $true) | ForEach-Object { $_ -as [System.Management.Automation.Language.UsingExpressionAst] }
foreach ($usingAst in $usingAsts) {
$varAst = $usingAst.SubExpression -as [System.Management.Automation.Language.VariableExpressionAst]
$var = Get-Variable -Name $varAst.VariablePath.UserPath -ErrorAction SilentlyContinue
}
This is how I'm using Get-Variable and in the case presented above, y cannot be found.
Modules run in their own scope domain (aka session state), which means they generally do not see the caller's variables - unless (a module-external) caller runs directly in the global scope.
For an overview of scopes in PowerShell, see the bottom section of this answer.
However, assuming that you define the function in your module as an advanced one, there is a way to access the caller's state, namely via the automatic $PSCmdlet variable.
Here's a simplified example, using a dynamic module created via the New-Module cmdlet:
# Create a dynamic module that defines function 'foo'
$null = New-Module {
function foo {
# Make the function and advanced (cmdlet-like) one, via
# [CmdletBinding()].
[CmdletBinding()] param()
# Access the value of variable $bar in the
# (module-external) caller's scope.
# To get the variable *object*, use:
# $PSCmdlet.SessionState.PSVariable.Get('bar')
$PSCmdlet.GetVariableValue('bar')
}
}
& {
$bar = 'abc'
foo
}
The above outputs verbatim abc, as desired.

String failing to concatenate in PowerShell [duplicate]

I'm currently editing our new employee script for AD and I am running into an issue, I added 2 radio buttons for the home folder, one radio button will update the BaseHomeFolderPath to Path1 and the second will update it to Path2, I export the variable to a csv to verify that it works but it keeps showing up blank. I don't know if it is a scope issue or what I am doing wrong, any help will be greatly appreciated! Below is a copy of the code.
I tried using $script: and it didn't work either.
$BaseHomeFolderPath = ''
Set-Variable -Name $BaseHomeFolderPath -Scope Global
$radiobuttonAtlas_MouseClick = [System.Windows.Forms.MouseEventHandler]{
#Event Argument: $_ = [System.Windows.Forms.MouseEventArgs]
#TODO: Place custom script here
$BaseHomeFolderPath = '\\path1\users'
}
$radiobuttonCerberus_MouseClick=[System.Windows.Forms.MouseEventHandler]{
#Event Argument: $_ = [System.Windows.Forms.MouseEventArgs]
#TODO: Place custom script here
$BaseHomeFolderPath = '\\path2\users'
}
$buttonRun_MouseClick=[System.Windows.Forms.MouseEventHandler]{
#Event Argument: $_ = [System.Windows.Forms.MouseEventArgs]
#TODO: Place custom script here
$TestValue = New-Object System.Object
$TestValue | Add-Member -MemberType NoteProperty -Name "Path" -Value $BaseHomeFolderPath
$TestValue | Export-CSV -NoTypeInformation -Path "C:\Users\testuser\Desktop\Testcsv.csv"
}
I expect the BaseHomeFolderPath variable to be updated to whichever radio button is selected.
tl;dr
In order to update the $BaseHomeFolderPath variable in the script scope (or any scope other than the local one), you must reference it in that scope explicitly:
$script:BaseHomeFolderPath = '\\path1\users'
Otherwise, without a scope specifier such as $script:, you'll implicitly create a new variable by that name in the current scope, which in your case is the child scope in which your event handlers run.
In PowerShell, when you assign to a variable with $var = ..., you either:
update a preexisting variable that was created in the same scope.
or implicitly create a new variable in the current scope.
The tricky part is that even though child scopes see variables created in parent scopes and can get their value by name only, assigning to them by name only creates a new, scope-local variable, and that new variable then shadows the original one in the current scope and all child scopes.
A simple demonstration, using call operator & to execute a script block ({ ... }) in a child scope:
$var = 'parent'
"in parent: before: $var"
& {
"in child: before: $var" # sees $var from parent scope
$var = 'child' # creates new $var in current scope
"in child: after: $var" # sees new $var, which shadows the parent's
}
"in parent: after: $var" # still has original value
This prints:
in parent: before: parent
in child: before: parent
in child: after: child
in parent: after: parent
Note:
The behavior also applies to the implicit assignment that occurs with the ++ and -- operators (given that, e.g., ++$var is the same as $var += 1)
See this answer for an example of where this can constitute a pitfall.
In addition to fixed-target scope specifiers $script: and $global:, you can use the Get-Variable / Set-Variable cmdlets with the -Scope parameter to target variables in scopes relative to the current one (up the call stack; e.g., -Scope 1 refers to the parent scope).
To promote modularity and maintainability, it's generally better to avoid accessing variables across scope boundaries - best to pass values around instead.
For more information, see:
Get-Help about_Scopes
The last section of this answer, which provides a concise summary.

Why using command switch with a variable inside returns empty variable when using a windows form? [duplicate]

I'm currently editing our new employee script for AD and I am running into an issue, I added 2 radio buttons for the home folder, one radio button will update the BaseHomeFolderPath to Path1 and the second will update it to Path2, I export the variable to a csv to verify that it works but it keeps showing up blank. I don't know if it is a scope issue or what I am doing wrong, any help will be greatly appreciated! Below is a copy of the code.
I tried using $script: and it didn't work either.
$BaseHomeFolderPath = ''
Set-Variable -Name $BaseHomeFolderPath -Scope Global
$radiobuttonAtlas_MouseClick = [System.Windows.Forms.MouseEventHandler]{
#Event Argument: $_ = [System.Windows.Forms.MouseEventArgs]
#TODO: Place custom script here
$BaseHomeFolderPath = '\\path1\users'
}
$radiobuttonCerberus_MouseClick=[System.Windows.Forms.MouseEventHandler]{
#Event Argument: $_ = [System.Windows.Forms.MouseEventArgs]
#TODO: Place custom script here
$BaseHomeFolderPath = '\\path2\users'
}
$buttonRun_MouseClick=[System.Windows.Forms.MouseEventHandler]{
#Event Argument: $_ = [System.Windows.Forms.MouseEventArgs]
#TODO: Place custom script here
$TestValue = New-Object System.Object
$TestValue | Add-Member -MemberType NoteProperty -Name "Path" -Value $BaseHomeFolderPath
$TestValue | Export-CSV -NoTypeInformation -Path "C:\Users\testuser\Desktop\Testcsv.csv"
}
I expect the BaseHomeFolderPath variable to be updated to whichever radio button is selected.
tl;dr
In order to update the $BaseHomeFolderPath variable in the script scope (or any scope other than the local one), you must reference it in that scope explicitly:
$script:BaseHomeFolderPath = '\\path1\users'
Otherwise, without a scope specifier such as $script:, you'll implicitly create a new variable by that name in the current scope, which in your case is the child scope in which your event handlers run.
In PowerShell, when you assign to a variable with $var = ..., you either:
update a preexisting variable that was created in the same scope.
or implicitly create a new variable in the current scope.
The tricky part is that even though child scopes see variables created in parent scopes and can get their value by name only, assigning to them by name only creates a new, scope-local variable, and that new variable then shadows the original one in the current scope and all child scopes.
A simple demonstration, using call operator & to execute a script block ({ ... }) in a child scope:
$var = 'parent'
"in parent: before: $var"
& {
"in child: before: $var" # sees $var from parent scope
$var = 'child' # creates new $var in current scope
"in child: after: $var" # sees new $var, which shadows the parent's
}
"in parent: after: $var" # still has original value
This prints:
in parent: before: parent
in child: before: parent
in child: after: child
in parent: after: parent
Note:
The behavior also applies to the implicit assignment that occurs with the ++ and -- operators (given that, e.g., ++$var is the same as $var += 1)
See this answer for an example of where this can constitute a pitfall.
In addition to fixed-target scope specifiers $script: and $global:, you can use the Get-Variable / Set-Variable cmdlets with the -Scope parameter to target variables in scopes relative to the current one (up the call stack; e.g., -Scope 1 refers to the parent scope).
To promote modularity and maintainability, it's generally better to avoid accessing variables across scope boundaries - best to pass values around instead.
For more information, see:
Get-Help about_Scopes
The last section of this answer, which provides a concise summary.

Cannot modify a script-scoped variable from inside a function

I am currently making a script which is supposed to connect to 42 different local servers and getting the Users of a specific group (fjärrskrivbordsanvändare(Remote desktop users in swedish :D)) from active directory. After it has gotten all the users from the server it has to export the users to a file on MY desktop
The csv file has to look like this:
Company;Users
LawyerSweden;Mike
LawyerSweden;Jennifer
Stockholm Candymakers;Pedro
(Examples)
etc.
Here's the code as of now:
cls
$MolnGroup = 'fjärrskrivbordsanvändare'
$ActiveDirectory = 'activedirectory'
$script:CloudArray
Set-Variable -Name OutputAnvandare -Value ($null) -Scope Script
Set-Variable -Name OutputDomain -Value ($null) -Scope Script
function ReadInfo {
Write-Host("A")
Get-Variable -Exclude PWD,*Preference | Remove-Variable -EA 0
if (Test-Path "C:\file\frickin\path.txt") {
Write-Host("File found")
}else {
Write-Host("Error: File not found, filepath might be invalid.")
Exit
}
$filename = "C:\File\Freakin'\path\super.txt"
$Headers = "IPAddress", "Username", "Password", "Cloud"
$Importedcsv = Import-csv $filename -Delimiter ";" -Header $Headers
$PasswordsArray += #($Importedcsv.password)
$AddressArray = #($Importedcsv | ForEach-Object { $_.IPAddress } )
$UsernamesArray += #($Importedcsv.username)
$CloudArray += #($Importedcsv.cloud)
GetData
}
function GetData([int]$p) {
Write-Host("B")
for ($row = 1; $row -le $UsernamesArray.Length; $row++)
{
# (If the customer has cloud-service on server, proceed)
if($CloudArray[$row] -eq 1)
{
# Code below uses the information read in from a file to connect pc to server(s)
$secstr = New-Object -TypeName System.Security.SecureString
$PasswordsArray[$row].ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $UsernamesArray[$row], $secstr
# Runs command on server
$OutputAnvandare = Invoke-Command -computername $AddressArray[$row] -credential $cred -ScriptBlock {
Import-Module Activedirectory
foreach ($Anvandare in (Get-ADGroupMember fjärrskrivbordsanvändare))
{
$Anvandare.Name
}
}
$OutputDomain = Invoke-Command -computername $AddressArray[$row] -credential $cred -ScriptBlock {
Import-Module Activedirectory
foreach ($Anvandare in (Get-ADGroupMember fjärrskrivbordsanvändare))
{
gc env:UserDomain
}
}
$OutputDomain + $OutputAnvandare
}
}
}
function Export {
Write-Host("C")
# Variabler för att bygga up en CSV-fil genom Out-File
$filsökväg = "C:\my\file\path\Coolkids.csv"
$ColForetag = "Company"
$ColAnvandare = "Users"
$Emptyline = "`n"
$delimiter = ";"
for ($p = 1; $p -le $AA.Length; $p++) {
# writes out columns in the csv file
$ColForetag + $delimiter + $ColAnvandare | Out-File $filsökväg
# Writes out the domain name and the users
$OutputDomain + $delimiter + $OutputAnvandare | Out-File $filsökväg -Append
}
}
ReadInfo
Export
My problem is, I can't export the users or the domain. As you can see i tried to make the variables global to the whole script, but $outputanvandare and $outputdomain only contains the information i need inside of the foreach loop. If I try to print them out anywhere else, they're empty?!
This answer focuses on variable scoping, because it is the immediate cause of the problem.
However, it is worth mentioning that modifying variables across scopes is best avoided to begin with; instead, pass values via the success stream (or, less typically, via by-reference variables and parameters ([ref]).
To expound on PetSerAl's helpful comment on the question: The perhaps counter-intuitive thing about PowerShell variable scoping is that:
while you can see (read) variables from ancestral (higher-up) scopes (such as the parent scope) by referring to them by their mere name (e.g., $OutputDomain),
you cannot modify them by name only - to modify them you must explicitly refer to the scope that they were defined in.
Without scope qualification, assigning to a variable defined in an ancestral scope implicitly creates a new variable with the same name in the current scope.
Example that demonstrates the issue:
# Create empty script-level var.
Set-Variable -Scope Script -Name OutputDomain -Value 'original'
# This is the same as:
# $script:OutputDomain = 'original'
# Declare a function that reads and modifies $OutputDomain
function func {
# $OutputDomain from the script scope can be READ
# without scope qualification:
$OutputDomain # -> 'original'
# Try to modify $OutputDomain.
# !! Because $OutputDomain is ASSIGNED TO WITHOUT SCOPE QUALIFICATION
# !! a NEW variable in the scope of the FUNCTION is created, and that
# !! new variable goes out of scope when the function returns.
# !! The SCRIPT-LEVEL $OutputDomain is left UNTOUCHED.
$OutputDomain = 'new'
# !! Now that a local variable has been created, $OutputDomain refers to the LOCAL one.
# !! Without scope qualification, you cannot see the script-level variable
# !! anymore.
$OutputDomain # -> 'new'
}
# Invoke the function.
func
# Print the now current value of $OutputDomain at the script level:
$OutputDomain # !! -> 'original', because the script-level variable was never modified.
Solution:
There are several ways to add scope qualification to a variable reference:
Use a scope modifier, such as script in $script:OutputDomain.
In the case at hand, this is the simplest solution:
$script:OutputDomain = 'new'
Note that this only works with absolute scopes global, script, and local (the default).
A caveat re global variables: they are session-global, so a script assigning to a global variable could inadvertently modify a preexisting global variable, and, conversely, global variables created inside a script continue to exist after the script terminates.
Use Get/Set-Variable -Scope, which - in addition to supporting the absolute scope modifiers - supports relative scope references by 0-based index, where 0 represents the current scope, 1 the parent scope, and so on.
In the case at hand, since the script scope is the next higher scope,
Get-Variable -Scope 1 OutputDomain is the same as $script:OutputDomain, and
Set-Variable -Scope 1 OutputDomain 'new' equals $script:OutputDomain = 'new'.
(A rarely used alternative available inside functions and trap handlers is to use [ref], which allows modifying the variable in the most immediate ancestral scope in which it is defined: ([ref] $OutputDomain).Value = 'new', which, as PetSerAl points out in a comment, is the same as (Get-Variable OutputDomain).Value = 'new')
For more information, see:
Get-Help about_Variables
Get-Help about_Scopes
Finally, for the sake of completeness, Set-Variable -Option AllScope is a way to avoid having to use scope qualification at all (in all descendent scopes), because effectively then only a single variable by that name exists, which can be read and modified without scope qualification from any (descendent) scope.
# By defining $OutputDomain this way, all descendent scopes
# can both read and assign to $OutpuDomain without scope qualification
# (because the variable is effectively a singleton).
Set-Variable -Scope Script -Option AllScope -Name OutputDomain
However, I would not recommend it (at least not without adopting a naming convention), as it obscures the distinction between modifying local variables and all-scope variables:
in the absence of scope qualification, looking at a statement such as $OutputDomain = 'new' in isolation, you cannot tell if a local or an all-scope variable is being modified.
Since you've mentioned that you want to learn, I hope you'll pardon my answer, which is a bit longer than normal.
The issue that's impacting you here is PowerShell Variable Scoping. When you're commiting the values of $outputAvandare and $outputDomain, they only exist for as long as that function is running.
Function variables last until the function ends.
Script variables last until the script ends.
Session/global variables last until the session ends.
Environmental variable persist forever.
If you want to get the values out of them, you could make them Global variables instead, using this syntax:
$global:OutputAnvandare = blahblahblah
While that would be the easiest fix for your code, Global variables are frowned upon in PowerShell, since they subvert the normal PowerShell expectations of variable scopes.
Much better solution :)
Don't be dismayed, you're actually almost there with a really good solution that conforms to PowerShell design rules.
Today, your GetData function grabs the values that we want, but it only emits them to the console. You can see this in this line on GetData:
$OutputDomain + $OutputAnvandare
This is what we'd call emitting an object, or emiting data to the console. We need to STORE this data instead of just writing it. So instead of simply calling the function, as you do today, do this instead:
$Output = GetData
Then your function will run and grab all the AD Users, etc, and we'll grab the results and stuff them in $output. Then you can export the contents of $output later on.

Difference between Clear-Variable and setting variable to NULL

I often use variables which are declared in the script scope to avoid problems with functions and their scopes. I am declaring these variables like this:
New-Variable -Name test -Option AllScope -Value $null
... or sometimes I switch existing variables like this to use them comprehensively:
$script:test = $test
When I want to clear them I either use this:
Clear-Variable test -Scope Script
... or I simply use this:
$test = $null
Is there a difference? What should I prefer and why?
From the get-Help:
The Clear-Variable cmdlet deletes the data stored in a variable, but it does not delete the variable. As a result,
the value of the variable is NULL (empty). If the variable has a specified data or object type, Clear-Variable
preserves the type of the object stored in the variable.
So Clear-Variable and $var=$null are nearly equivalents (with the exception of the typing which is retained). An exact equivalent would be to do $var=[mytype]$null.
You can test it yourself:
$p = "rrrr"
Test-Path variable:/p # => $true
$p = $null
Get-Member -InputObject $p # => error
$p = [string]$null
Get-Member -InputObject $p # => it is a string
And to answer what may be the next question: how to completely remove a variable (since an absent variable is different from a null-valued variable)? Simply do
rm variable:/p
Test-Path variable:/p => $false
To complement Marcanpilami's helpful answer:
Note: In order to remove (undefine) a variable altogether, use Remove-Variable <name> [-Scope <scope>].
Unless $test is defined with Set-Variable -Option AllScope,
Clear-Variable test -Scope Script
and
$test = $null
are NOT generally equivalent.
(With Set-Variable -Option AllScope they are, but then the -Scope argument becomes irrelevant, because then only one instance of the variable exists (conceptually), across all scopes.)
$test = $null - unless executed in the same scope as when variable test was originally created - will implicitly create a test variable in the current scope (and assign $null to it), and leave the original variable untouched. For more on variable scoping in PS, see this answer
Note that variable-assignment syntax offers scoping too, via a scope prefix, but it is limited to global, script, and local (the default): $global:test = $null, $script:test = $null, $local:test = $null
There's also the private scope: a variation of local that prevents descendant scopes from seeing a variable - again, see this answer.
If you've ensured that you are targeting the same scope, the two forms above are functionally equivalent: they assign $null to the target variable.[1]
However, using Clear-Variable allows you to do two things that $<scope>:testing = ... doesn't:
the -Scope parameter also accepts a numeric value that indicates the scope relative to the current scope: 0 is the current scope, 1 is the parent scope, and so on.
you can target multiple variables (either as an array of names or using wildcards)
[1] Pitfall:
Note that if the target variable is type-constrained (was assigned with "cast notation"; e.g., [int] $i = 1), the type is retained - whether using $testing = $null or Clear-Variable - and an implicit type conversion may occur, which can have unexpected results or fail altogether:
[int] $i = 1 # type-constrain $i as an integer
Clear-Variable i # equivalent of $i = $null
$i # !! $i is now 0 (!), because [int] $null yields 0
[datetime] $d = 1 # type-constrain $d as DateTime
Clear-Variable d # !! FAILS, because `$d = $null` fails, given that
# !! $null cannot be converted to [datetime]