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



我目前正在制作一个脚本,该脚本应该连接到42个不同的本地服务器,并从活动目录中获取特定组的用户(fjärrskrivbordsanvändare(瑞典语:D中的远程桌面用户((。从服务器获取所有用户后,它必须将用户导出到MY 桌面上的文件

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:filefrickinpath.txt") {
    Write-Host("File found")
}else {
    Write-Host("Error: File not found, filepath might be invalid.")
    Exit
}

$filename = "C:FileFreakin'pathsuper.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:myfilepathCoolkids.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循环中需要的信息。如果我尝试将它们打印到其他任何地方,它们是空的?!

此答案侧重于变量范围,因为它是问题的直接原因。
但是,值得一提的是,最好避免跨范围修改变量;相反,通过成功流传递值(或者,不太典型的是通过按引用变量和参数 ([ref] (。

为了阐述PetSerAl对这个问题的有用评论:关于PowerShell变量范围可能违反直觉的事情是:

  • 虽然您可以通过仅通过名称(例如$OutputDomain(引用它们来查看(读取(祖先(上级(作用域(例如父作用域(中的变量

  • 不能仅按名称修改它们 - 要修改它们,必须显式引用定义它们的范围

如果没有作用域

限定,则分配给在祖先作用域中定义的变量会隐式创建当前作用域中具有相同名称的新变量。

演示问题的示例

  # 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(默认值(。

    • 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' 相同(

有关详细信息,请参阅:

  • Get-Help about_Variables
  • Get-Help about_Scopes

最后,为了完整起见, 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'之类的语句,您无法判断是正在修改局部变量还是全作用域变量。

既然你提到你想学习,我希望你能原谅我的回答,这比平时长一点。

这里影响你的问题是PowerShell变量范围。 当你提交 $outputAvandare$outputDomain 的值时,它们只存在于该函数运行时。

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的内容。

最新更新