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

时间:2016-07-29 14:29:30

标签: powershell scope powershell-v2.0 powershell-v3.0 powershell-remoting

我目前正在创建一个脚本,它应该连接到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循环内部所需的信息。如果我试图在其他任何地方打印出来,它们就是空的?!

2 个答案:

答案 0 :(得分:3)

既然您已经提到过想要学习的内容,我希望您能原谅我的答案,这比平时要长一些。

这里影响你的问题是PowerShell Variable Scoping。当您提交$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的内容。

答案 1 :(得分:3)

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

阐述PetSerAl's对该问题的有用评论:关于PowerShell变量范围可能反直觉的是:

  • 虽然您可以从祖先(更高层)范围(例如父作用域)中查看(读取)变量,方法是通过< em>仅仅是名称(例如,$OutputDomain),

  • 您无法仅按名称修改 - 要修改它们,您必须明确引用它们中定义的范围

如果没有范围限定,分配给祖先范围中定义的变量会隐式创建同名 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 中使用范围修饰符,例如$script:OutputDomain

    • 在目前的情况下,这是最简单的解决方案:
      $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'

  • 相同

有关详细信息,请参阅:

最后,为了完整起见, 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'之类的语句,您无法判断是否正在修改本地或全范围变量。