【问题标题】:PowerShell Avoid using script wide variablePowerShell 避免使用脚本范围的变量
【发布时间】:2014-09-10 22:52:55
【问题描述】:

我写了一个函数来检查主机是在线还是离线并返回$true$false。此功能完美运行,我想通过查看来改进它 是否可以删除脚本范围的变量,例如 $Script:arrayCanPingResult$script:tmpPingCheckServers

我为什么要这个? 当我在foreach 循环中调用该函数时,我通常使用开关-Remember,因此它不会检查同一主机两次。为了能够正确使用它,我必须通过将两个变量都声明为空($Script:arrayCanPingResult=$script:tmpPingCheckServers=@{})来开始我使用此函数的所有脚本。而且我可以想象人们忘记将第一行放在他们的脚本中,并且在 PowerShell ISE 编辑器中进行多次测试时,当主机已经在 ISE 中检查过一次(F5)时,它不会在第二次运行时再次进行测试.

在这种情况下,有没有办法避免使用脚本范围的变量?所以我们不需要在新脚本的开头声明它们为空?如果这是可能的,那就太好了,因为我们可以在自定义模块中包含这个函数。

一如既往,感谢您的建议或帮助。在你们这里​​我真的学到了很多东西。

# Function to check if $Server is online
Function Can-Ping ($Server,[switch]$Remember) {

    $PingResult = {
          # Return $true or $false based on the result from script block $PingCheck
          foreach ($_ in $Script:arrayCanPingResult) { 
                   # Write-Host "$(Get-TimeStamp) $Server > Function Can-Ping: $_ " -ForegroundColor Green
                   if ($Server -eq $($_.Split(",")[0])) {

                   #Write-Host "$(Get-TimeStamp) $Server > Function Can-Ping: We will return $($_.Split(",")[1])" -ForegroundColor Green
                    return $($_.Split(",")[1])  
                   } 
          }
    }

    $PingCheck = {

        $Error.Clear()

        if (Test-Connection -ComputerName $Server -BufferSize 16 -Count 1 -ErrorAction 0 -quiet) { # ErrorAction 0 doesn't display error information when a ping is unsuccessful

            Write-Host "$(Get-TimeStamp) $Server > Function Can-Ping: Ping test ok" -ForegroundColor Gray; $Script:arrayCanPingResult+=@("$Server,$true"); return
        } 
        else {
            $Error.Clear()
            Write-Host "$(Get-TimeStamp) $Server > Function Can-Ping: Ping test FAILED" -ForegroundColor Gray

            Write-Host "$(Get-TimeStamp) $Server > Function Can-Ping: Flushing DNS" -ForegroundColor Gray
            ipconfig /flushdns | Out-Null

            Write-Host "$(Get-TimeStamp) $Server > Function Can-Ping: Registering DNS" -ForegroundColor Gray
            ipconfig /registerdns | Out-Null

            Write-Host "$(Get-TimeStamp) $Server > Function Can-Ping: NSLookup" -ForegroundColor Gray
            nslookup $Server | Out-Null # Suppressing error here is not possible unless using '2> $null', but if we do this, we don't get $true or $false for the function so '| Out-Null' is an obligation
            if (!$?) {
                Write-Host "$(Get-TimeStamp) $Server > Function Can-Ping: NSlookup can't find the host '$Server', DNS issues or hostname incorrect?" -ForegroundColor Yellow
                # Write-Host $Error -ForegroundColor Red
                if ($SendMail) {
                    Send-Mail $MailTo "FAILED Ping test" "$(Get-TimeStamp) NSlookup can't find the host '$Server', hostname incorrect or DNS issues?" "<font color=`"red`">$error</font>"
                }
                $script:arrayCanPingError += "ERROR | $(Get-TimeStamp) Ping test failed: NSlookup can't find the host '$Server', hostname incorrect or DNS issues?$error"
                $script:HTMLarrayCanPingError += "ERROR | $(Get-TimeStamp) Ping test failed:<br>NSlookup can't find the host '$Server', hostname incorrect or DNS issues?<br><font color=`"red`">$error</font>"
                $Script:arrayCanPingResult+=@("$Server,$false")
                return
                }
            else {
                Write-Host "$(Get-TimeStamp) $Server > Function Can-Ping: Re-pinging '$Server'" -ForegroundColor Gray
                if (Test-Connection -ComputerName $Server -BufferSize 16 -Count 1 -ErrorAction 0 -Quiet) {
                   Write-Host "$(Get-TimeStamp) $Server > Function Can-Ping: Ping test ok, problem resolved" -ForegroundColor Gray
                   $Script:arrayCanPingResult+=@("$Server,$true")
                   return
                }
                else {
                      Write-Host "$(Get-TimeStamp) $Server > Function Can-Ping: DNS Resolving is ok but can't connect, server offline?" -ForegroundColor Yellow
                      if ($SendMail) {
                          Send-Mail $MailTo "FAILED Ping test" "$error" "DNS Resolving is ok but can't connect to $Server, server offline?"
                      } 
                      $script:arrayCanPingError += "ERROR Ping test failed: DNS Resolving is ok but can't connect to $Server, server offline?$error"
                      $script:HTMLarrayCanPingError += "ERROR Ping test failed: DNS Resolving is ok but can't connect to $Server, server offline?<br><font color=`"red`">$error</font>"
                      $Script:arrayCanPingResult+=@("$Server,$false")
                      return
                }
            }
        }
    }

    # Call the script block $PingAction every time, unless the switch $Remember is provided, than we only check each server once
    if ($Remember) {
        Write-Host "$(Get-TimeStamp) $Server > Function Can-Ping: Switch '-Remember' detected" -ForegroundColor Gray
        While ($tmpPingCheckServers -notcontains $Server) { 
                  &$PingCheck
                  $script:tmpPingCheckServers = @($tmpPingCheckServers+$Server) #Script wide variable, otherwise it stays empty when we leave the function / @ is used to store it as an Array (table) instead of a string
        }
        &$PingResult
    } 
    else {
          &$PingCheck
          &$PingResult
    }
}

【问题讨论】:

  • 简答:PSCustomObject。 IE。您想使用 object 作为参数,然后返回具有修改属性的相同对象。长答案取决于您的功能实际上应该做什么。你调用Test-Connection 一次,剩下的就是逻辑(这太复杂以至于无法阅读)和Write-Host 调用(Write-Host 是邪恶的,在生产代码中避免它)。您能否提供输入和预期输出的示例?
  • 感谢您的回复。该函数像这样使用Can-Ping -Remember SERVER1,如果SERVER1 在线,则返回$true,如果不在线,则返回$false。我第二次运行Can-Ping -Remember SERVER1 它根本不会做任何测试并返回该服务器的最后一个已知结果。在我的脚本中,我每次在 foreach 循环中使用新的 $Server 名称调用此函数。然后根据结果检查PSRemoting 功能和其他内容。

标签: variables powershell scope


【解决方案1】:

我会这样做:

function Get-PingStatus {
    param(
        # Server object.
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        $Server,
        [Parameter(Mandatory=$false)]
        [Bool]$UseLastPingResult = $false
    )

    if ( ($UseLastPingResult) `
    -and (! [String]::IsNullOrEmpty($Server.LastPingResult)) ) {
        # Return unmodified object if LastPingResult property is not empty.
        return $Server 
    }

    try {
        $oPingResult = Test-Connection -ComputerName $Server.Name `
        -BufferSize 16 -Count 1 -ErrorAction Stop

        $Server.LastPingResult = "success"
        # And just in case.
        $Server.IPV4Address = $oPingResult.IPV4Address
    }
    catch {
        $Server.LastPingResult = "failure"
    }

    return $Server
}

对象输入,对象输出。一般来说,这是编写 PowerShell 函数的最佳方法,因为:1) 它与常用 cmdlet 的功能一致;2) 它可以帮助您保持代码的简单和可读性。

使用 ErrorAction -Stop 和 try...catch...finally 也比 ErrorAction -SilentlyContinue 之后检查一些变量更好。

现在让我们假设 $cServerNames 是服务器名称或任何可以解析为 IP 地址的集合。示例:@("server1", "server2", "1.2.3.4", "webserver.example.com")

# Converting strings to objects.
$cServers = @()
foreach ($sServerName in $cServerNames) {
    $oServer = New-Object PSObject -Property @{
        "Name" = $sServerName;
        "IPV4Address" = $null;
        "LastPingResult" = $null;
    }

    $cServers += $oServer
}

# Now we can iterate objects and update their properties as necessary.
foreach ($oServer in $cServers) {
    $oServer = Get-PingStatus -Server $oServer -UseLastPingResult

    if ($oServer.LastPingResult -eq "success") {
        # Do something.
    } else {
        # Do something else like an error message.
    }
}

您可以添加任何您想要的诊断输出,但我建议您使用 Write-Output 和/或 Write-Error 而不是 Write-Host。

最后,请注意,在大多数情况下,ping 在生产代码中几乎没有用处。这是多余的,无论主机是否回复,都无法证明。例如,ping 可能没问题,但由于某种原因,您在后续的 Get-WmiObject 查询中遇到异常。如果出于性能原因(以节省时间)执行 ping,则应考虑通过 background jobsworkflows 并行运行脚本块。

【讨论】:

  • 谢谢亚历山大。你的帖子让我重新思考了我对$true$false 编码的完整设置。我从其他示例中选择了这种方法,你是对的,最好使用对象,因为它更灵活。我将尝试更改我的脚本以实现object-way,因为它看起来好多了。再次感谢您的帮助:)
  • @DarkLite1 不客气! ) 当我开始使用 PowerShell 时,我主要是 bash/python 背景,所以我也尝试过广泛使用其他东西(如哈希表)。但后来我转向物体,再也没有回头。
  • 我今天一直在玩你的代码,我偶然发现了一些东西。开关LastPingResult 未按设计工作(请与verbose 核对)。因为每次通过重复的服务器名称时它仍在进行所有测试。这可能是因为函数的scope。也许最好收集函数外的对象并完全删除开关UseLastPingResult,因为它什么也没做..
  • 它应该可以工作。尝试使用 [bool] 而不是 [switch] 可能。我以为它们是一样的,但显然有区别。我需要仔细阅读。
  • 没问题,它永远不会在函数中工作。当 PowerShell 将函数留给下一个对象时,它会忘记函数内的变量内容。解决这个问题的最好方法是在函数外部创建一个包含函数结果的数组。稍后我会试试的。无论如何,谢谢。
猜你喜欢
  • 1970-01-01
  • 2017-06-20
  • 2012-03-08
  • 1970-01-01
  • 1970-01-01
  • 2020-04-24
  • 2015-05-02
  • 1970-01-01
  • 2020-11-05
相关资源
最近更新 更多