无法从函数内部修改脚本范围的变量
Cannot modify a script-scoped variable from inside a function
我目前正在制作一个脚本,该脚本应该连接到 42 个不同的本地服务器并从活动目录中获取特定组的用户 (fjärrskrivbordsanvändare(瑞典语中的远程桌面用户 :D))。从服务器获取所有用户后,它必须将用户导出到我的桌面上的文件
csv 文件必须如下所示:
Company;Users
LawyerSweden;Mike
LawyerSweden;Jennifer
Stockholm Candymakers;Pedro
(Examples)
etc.
这是目前的代码:
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
我的问题是,我无法导出用户或域。如您所见,我试图使变量成为整个脚本的全局变量,但是 $outputanvandare 和 $outputdomain 只包含我在 foreach 循环中需要的信息。如果我尝试在其他地方打印它们,它们是空的?!
既然你提到了你想学习,我的回答比正常的要长一些,希望你能原谅。
这里影响您的问题是 PowerShell 变量作用域。当您提交 $outputAvandare
和 $outputDomain
的值时,它们仅在该函数为 运行ning.
时存在
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.
如果您想从中获取值,可以使用以下语法将它们设为全局变量:
$global:OutputAnvandare = blahblahblah
虽然这对您的代码来说是最简单的修复方法,但 PowerShell 不赞成使用全局变量,因为它们颠覆了 PowerShell 对变量作用域的正常预期。
更好的解决方案:)
不要灰心,您实际上几乎已经有了符合 PowerShell 设计规则的非常好的解决方案。
今天,您的 GetData
函数获取了我们想要的值,但它只将它们发送到控制台。您可以在 GetData
:
的这一行中看到这一点
$OutputDomain + $OutputAnvandare
这就是我们所说的发射对象,或发射数据到控制台。我们需要存储这些数据,而不是仅仅写入它。因此,与其像今天那样简单地调用函数,不如这样做:
$Output = GetData
然后您的函数将运行 并抓取所有AD 用户等,我们将抓取结果并将它们填充到$output
中。然后你可以稍后导出$output
的内容。
这个答案关注变量作用域,因为它是问题的直接原因。
不过,值得一提的是,跨作用域修改变量最好避免以开头;相反,通过成功流传递值(或者不太常见的是通过引用变量和参数([ref]
))。
阐述 PetSerAl's 对问题的有用评论:PowerShell 变量作用域可能违反直觉的是:
虽然您可以查看(读取)来自祖先(更高)范围的变量(例如父作用域)通过 纯名称 (例如 $OutputDomain
)、
来引用它们
您不能仅按名称修改它们 - 要修改它们您必须明确引用scope 它们定义于.
没有范围限定,分配到一个在祖先范围内定义的变量隐式创建一个new变量在当前范围内同名。
示例 演示了该问题:
# 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.
解法:
有几种向变量引用添加范围限定的方法:
使用范围修饰符,例如$script:OutputDomain
中的script
。
在手头的案例中,这是最简单的解决方案:
$script:OutputDomain = 'new'
请注意,这仅适用于 绝对 范围 global
、script
和 local
(默认值) .
A 警告 global
变量:它们是 session-global,因此脚本分配全局变量可能会无意中修改预先存在的全局变量,相反,在脚本中创建的全局变量在脚本终止后继续存在。
使用Get/Set-Variable -Scope
,除了支持绝对范围修饰符外,还支持相对 基于0的索引的范围引用,其中0
代表当前范围,1
父范围,依此类推。
- 在手头的例子中,由于脚本范围是下一个更高的范围,
Get-Variable -Scope 1 OutputDomain
与 $script:OutputDomain
相同,
Set-Variable -Scope 1 OutputDomain 'new'
等于 $script:OutputDomain = 'new'
.
(函数和陷阱处理程序内部很少使用的替代方法是使用 [ref]
,它允许在定义它的最直接祖先范围内修改变量:([ref] $OutputDomain).Value = 'new'
,正如 PetSerAl 在评论中指出的那样,与 (Get-Variable OutputDomain).Value = 'new'
)
相同
有关详细信息,请参阅:
最后,为了完整起见,Set-Variable -Option AllScope
是一种完全避免必须使用范围限定的方法(在所有后代范围中),因为这样有效只有一个 单个 同名变量存在,可以读取 和 修改而无需任何(后代)范围的范围限定。
# 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
但是,我不推荐它(至少在不采用命名约定的情况下),因为它模糊了修改局部变量和全范围变量之间的区别:
在没有范围限定的情况下,孤立地查看诸如 $OutputDomain = 'new'
的语句,您无法判断正在修改的是局部变量还是全范围变量。
我目前正在制作一个脚本,该脚本应该连接到 42 个不同的本地服务器并从活动目录中获取特定组的用户 (fjärrskrivbordsanvändare(瑞典语中的远程桌面用户 :D))。从服务器获取所有用户后,它必须将用户导出到我的桌面上的文件
csv 文件必须如下所示:
Company;Users
LawyerSweden;Mike
LawyerSweden;Jennifer
Stockholm Candymakers;Pedro
(Examples)
etc.
这是目前的代码:
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
我的问题是,我无法导出用户或域。如您所见,我试图使变量成为整个脚本的全局变量,但是 $outputanvandare 和 $outputdomain 只包含我在 foreach 循环中需要的信息。如果我尝试在其他地方打印它们,它们是空的?!
既然你提到了你想学习,我的回答比正常的要长一些,希望你能原谅。
这里影响您的问题是 PowerShell 变量作用域。当您提交 $outputAvandare
和 $outputDomain
的值时,它们仅在该函数为 运行ning.
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.
如果您想从中获取值,可以使用以下语法将它们设为全局变量:
$global:OutputAnvandare = blahblahblah
虽然这对您的代码来说是最简单的修复方法,但 PowerShell 不赞成使用全局变量,因为它们颠覆了 PowerShell 对变量作用域的正常预期。
更好的解决方案:)
不要灰心,您实际上几乎已经有了符合 PowerShell 设计规则的非常好的解决方案。
今天,您的 GetData
函数获取了我们想要的值,但它只将它们发送到控制台。您可以在 GetData
:
$OutputDomain + $OutputAnvandare
这就是我们所说的发射对象,或发射数据到控制台。我们需要存储这些数据,而不是仅仅写入它。因此,与其像今天那样简单地调用函数,不如这样做:
$Output = GetData
然后您的函数将运行 并抓取所有AD 用户等,我们将抓取结果并将它们填充到$output
中。然后你可以稍后导出$output
的内容。
这个答案关注变量作用域,因为它是问题的直接原因。
不过,值得一提的是,跨作用域修改变量最好避免以开头;相反,通过成功流传递值(或者不太常见的是通过引用变量和参数([ref]
))。
阐述 PetSerAl's 对问题的有用评论:PowerShell 变量作用域可能违反直觉的是:
虽然您可以查看(读取)来自祖先(更高)范围的变量(例如父作用域)通过 纯名称 (例如
$OutputDomain
)、 来引用它们
您不能仅按名称修改它们 - 要修改它们您必须明确引用scope 它们定义于.
没有范围限定,分配到一个在祖先范围内定义的变量隐式创建一个new变量在当前范围内同名。
示例 演示了该问题:
# 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.
解法:
有几种向变量引用添加范围限定的方法:
使用范围修饰符,例如
$script:OutputDomain
中的script
。在手头的案例中,这是最简单的解决方案:
$script:OutputDomain = 'new'
请注意,这仅适用于 绝对 范围
global
、script
和local
(默认值) .A 警告
global
变量:它们是 session-global,因此脚本分配全局变量可能会无意中修改预先存在的全局变量,相反,在脚本中创建的全局变量在脚本终止后继续存在。
使用
Get/Set-Variable -Scope
,除了支持绝对范围修饰符外,还支持相对 基于0的索引的范围引用,其中0
代表当前范围,1
父范围,依此类推。- 在手头的例子中,由于脚本范围是下一个更高的范围,
Get-Variable -Scope 1 OutputDomain
与$script:OutputDomain
相同,
Set-Variable -Scope 1 OutputDomain 'new'
等于$script:OutputDomain = 'new'
.
- 在手头的例子中,由于脚本范围是下一个更高的范围,
(函数和陷阱处理程序内部很少使用的替代方法是使用
[ref]
,它允许在定义它的最直接祖先范围内修改变量:([ref] $OutputDomain).Value = 'new'
,正如 PetSerAl 在评论中指出的那样,与(Get-Variable OutputDomain).Value = 'new'
) 相同
有关详细信息,请参阅:
最后,为了完整起见,Set-Variable -Option AllScope
是一种完全避免必须使用范围限定的方法(在所有后代范围中),因为这样有效只有一个 单个 同名变量存在,可以读取 和 修改而无需任何(后代)范围的范围限定。
# 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
但是,我不推荐它(至少在不采用命名约定的情况下),因为它模糊了修改局部变量和全范围变量之间的区别:
在没有范围限定的情况下,孤立地查看诸如 $OutputDomain = 'new'
的语句,您无法判断正在修改的是局部变量还是全范围变量。