无法从函数内部修改脚本范围的变量

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'

    • 请注意,这仅适用于 绝对 范围 globalscriptlocal(默认值) .

    • 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' 的语句,您无法判断正在修改的是局部变量还是全范围变量。