1

首先,我为这篇冗长冗长的帖子道歉。这是一个有趣的问题,我想尽可能详细。我已尝试浏览该站点上的任何相关 PowerShell 帖子,但找不到任何可以帮助我解决此问题的内容。

我一直在与一个团队一起研究一个 PowerShell 脚本,该团队可以将局域网唤醒数据包发送到一组计算机。它通过读取在两列中包含主机名和 MAC 的 .csv 文件来工作,然后为每台计算机创建 WOL 数据包并将它们广播到网络上。发送 WOL 数据包后,它会等待一分钟,然后 ping 计算机以验证它们是否在线,如果任何计算机没有响应,它将显示一个窗口,显示哪些机器没有响应 ping。直到最后的 If/Else 语句都可以正常工作,所以我不会对脚本的那部分进行太多详细说明(当然,如果您想要/需要更多详细信息,请随时询问)。

我遇到的问题是最后的 If/Else 语句。脚本应该工作的方式是在脚本中间的 ForEach 循环中,变量 $PingResult 的值是真还是假,这取决于计算机是否响应 ping。如果 ping 失败,则 $PingResult 为 $false,然后它将主机名添加到 $PingResult2 变量中。

理论上,如果所有机器都响应,If 语句就会触发,消息框会显示它成功,然后脚本会停止。如果任何机器没有响应,Else 语句就会运行,它将 $PingResult2 变量中的所有项目连接在一起,并在窗口中显示列表。实际发生的情况是,即使所有机器都响应 ping,If 语句完全被跳过,而运行 Else 语句。但是,此时 $PingResult2 变量为空白,因此它不显示任何未能响应的计算机的计算机名称。在我的测试中,我从未见过脚本无法唤醒计算机的情况(假设它已插入等),但 Else 语句仍然运行。在 Else 语句运行的情况下,

为了给问题增加另一个问题,我想回到 $PingResult2 变量。我必须将变量创建为通用列表,以便它支持 Add 方法以允许变量根据需要增长。作为测试,我们修改了脚本以通过使用 += 运算符而不是使 $PingResult2 成为列表来将结果连接在一起,虽然如果机器出现故障,这并没有在最终显示窗口中提供非常可读的视觉结果,但确实如此实际上偶尔可以正常工作。如果所有计算机都成功响应,则 If 语句将按预期运行并显示成功消息。就像我说的那样,它有时会起作用,有时会不起作用,没有其他改变会对结果产生影响。

知道什么可能导致这个问题吗?我和我的团队在这一点上完全没有想法,我们很想弄清楚是什么导致了这个问题。我们已经在 Windows 7、8.1 和 Windows 10 的最新预览版上进行了尝试,但没有成功。提前感谢您的任何帮助。

PS 如果您能解释第 29 行上的正则表达式被称为什么以及它是如何工作的,那么您可以加分。我在一个网络帖子上发现了它,该帖子解决了在每两个字符之间添加一个冒号的问题,但该帖子没有解释它的名称。(原文链接http://powershell.org/wp/forums/topic/add-colon-between-every-2-characters/

我们构建其余脚本的原始 WOL 脚本由 John Savill 编写(链接http://windowsitpro.com/networking/q-how-can-i-easily-send-magic-packet-wake-machine-my-subnet )

脚本

Add-Type -AssemblyName Microsoft.VisualBasic,System.Windows.Forms

$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.ShowDialog() | Out-Null

$FileVerify = Get-Content -Path $OpenFileDialog.FileName -TotalCount 1
$FileVerify = ($FileVerify -split ',')

If($FileVerify[0] -ne "Machine Name" -or $FileVerify[1] -ne "MAC")

{

    $MsgBox = [System.Windows.Forms.MessageBox]::Show("The CSV File's headers must be Machine Name and MAC.",'Invalid CSV File headers!',0,48)
    Break

}

$ComputerList = Import-Csv -Path $OpenFileDialog.FileName |
Out-GridView -PassThru -Title "Select Computers to Wake up"

ForEach($Computer in $ComputerList)

    {

        If($Computer.'MAC' -notmatch '([:]|[-])')

            {

                $Computer.'MAC' = $Computer.'MAC' -replace '(..(?!$))','$1:'

            }

        $MACAddr = $Computer.'MAC'.split('([:]|[-])') | %{ [byte]('0x' + $_) }
        $UDPclient = new-Object System.Net.Sockets.UdpClient
        $UDPclient.Connect(([System.Net.IPAddress]::Broadcast),4000)
        $packet = [byte[]](,0xFF * 6)
        $packet += $MACAddr * 16
        [void] $UDPclient.Send($packet, $packet.Length)
        write "Wake-On-Lan magic packet sent to $($Computer.'Machine Name'.ToUpper())"

    }

Write-Host "Pausing for sixty seconds before verifying connectivity."
Start-Sleep -Seconds 60

$PingResult2 = New-Object System.Collections.Generic.List[System.String]

ForEach($Computer in $ComputerList)

    {

        Write-Host "Pinging $($Computer.'Machine Name')"
        $PingResult = Test-Connection -ComputerName $Computer.'Machine Name' -Quiet

        If ($PingResult -eq $false)

            {

                $PingResult2.Add($Computer.'Machine Name')

            }

    }

If($PingResult2 -eq "")

    {

        [System.Windows.Forms.MessageBox]::Show("All machines selected are online.",'Success',0,48)
        Break

    }

Else

    {

        $PingResult2 = ($PingResult2 -join ', ')
        [System.Windows.Forms.MessageBox]::Show("The following machines did not respond to a ping: $PingResult2",'Unreachable Machines',0,48)

    }
4

3 回答 3

1

您的If语句中的比较不正确,因为您正在将$PingResult2aList<string>与字符串进行比较。相反,尝试

If ($PingResult2.Count -eq 0)
{
    # Show the message box
}
Else
{
    # Show the other message box
}

或这个主题的无数其他变体之一。

有问题的正则表达式使用反向引用将两个字符替换为相同的两个字符加上一个冒号字符。不过,我不确定您到底想“定义”什么。

于 2015-04-29T21:30:41.457 回答
0

另一个答案很好地解释了为什么您的代码不起作用。我不去那里。相反,我会给出一些我认为会改进你的脚本的建议,并解释我为什么这么认为。让我们从函数开始。你做的一些事情是我手头的功能,因为,它们工作得很好,而且经常使用,我喜欢把它们放在手边。

首先,获取 CSV 文件路径的对话框。它有效,不要误会我的意思,但它可能会更好......因为它是你弹出一个没有参数的打开文件对话框。此功能允许您根据需要使用一些不同的参数,或者在非常通用的“打开文件”对话框中不使用任何参数,但我认为这里有一点改进:

Function Get-FilePath{
[CmdletBinding()]
Param(
    [String]$Filter = "|*.*",
    [String]$InitialDirectory = "C:\")

    [void][System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")
    $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
    $OpenFileDialog.initialDirectory = $InitialDirectory
    $OpenFileDialog.filter = $Filter
    [void]$OpenFileDialog.ShowDialog()
    $OpenFileDialog.filename
}

然后就这样称呼它:

$CSVFile = Get-FilePath -Filter "Comma Separated Value (.CSV)|*.CSV" -InitialDirectory "$env:USERPROFILE\Desktop"

这将打开仅针对 CSV 文件的对话框过滤,并开始查看他们的桌面(我发现很多人将内容保存到他们的桌面)。那只会获得路径,因此您可以像以前一样运行验证。其实,不像你。你真的似乎把这一切都复杂化了。稍后我会谈到这个,首先,另一个函数!您经常调用消息框,然后输入一堆选项,然后每次调用类型和所有内容。如果你要做不止一次,让自己轻松一点,做一个功能。在这里,检查一下:

Function Show-MsgBox ($Text,$Title="",[Windows.Forms.MessageBoxButtons]$Button = "OK",[Windows.Forms.MessageBoxIcon]$Icon="Information"){
[Windows.Forms.MessageBox]::Show("$Text", "$Title", [Windows.Forms.MessageBoxButtons]::$Button, $Icon) | ?{(!($_ -eq "OK"))}
}

然后,您可以根据需要指定尽可能多或尽可能少的内容。此外,它使用 Type'd 参数,因此选项卡完成工作,或者在 ISE 中(如果您正在编写脚本,就像我一样)它会弹出有效选项,您只需从按钮或图标列表中选择以显示。另外,如果它是一个简单的“OK”响应,它不会返回任何内容,以保持清洁,但会返回 Yes/No/Cancel 或您为按钮选择的任何其他选项。

好的,这就是函数,让我们进入脚本的核心。你的文件验证......好吧,你拉文件的第一行,所以它应该只是一个字符串,我不确定你为什么要拆分它并单独验证每个标题。只需将字符串作为一个整体进行匹配。我建议不区分大小写,因为我们在这里并不真正关心大小写。此外,根据 CSV 文件的生成方式,您可能需要考虑标题周围的引号。Using-Match将执行更宽容的 RegEx 匹配。

If((Get-Content $CSVFile -TotalCount 1) -match '^"?machine name"?,"?mac"?$'){
    Show-MsgBox "The CSV File's headers must be Machine Name and MAC." 'Invalid CSV File headers!' -Icon Warning
    break
}

所以现在我们有两个函数和 5 行代码。是的,这些功能比您以前拥有的功能占用更多空间,但使用起来更友好,而且 IMO 功能更强大。就我而言,您的 MAC 地址更正和 WOL 发送部分都是王牌。没有理由改变那部分。现在,为了验证计算机是否已恢复...在这里我们可以进行一些改进。而不是[List]只为每个对象添加一个成员,然后根据下面的过滤器进行过滤。整个剧本会更长一些,但我认为会更好。

Add-Type -AssemblyName Microsoft.VisualBasic,System.Windows.Forms

Function Get-FilePath{
[CmdletBinding()]
Param(
    [String]$Filter = "|*.*",
    [String]$InitialDirectory = "C:\")

    [void][System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")
    $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
    $OpenFileDialog.initialDirectory = $InitialDirectory
    $OpenFileDialog.filter = $Filter
    [void]$OpenFileDialog.ShowDialog()
    $OpenFileDialog.filename
}

Function Show-MsgBox ($Text,$Title="",[Windows.Forms.MessageBoxButtons]$Button = "OK",[Windows.Forms.MessageBoxIcon]$Icon="Information"){
[Windows.Forms.MessageBox]::Show("$Text", "$Title", [Windows.Forms.MessageBoxButtons]::$Button, $Icon) | ?{(!($_ -eq "OK"))}
}

#Get File Path
$CSVFile = Get-FilePath -Filter "Comma Separated Value (.CSV)|*.CSV" -InitialDirectory "$env:USERPROFILE\Desktop"

#Validate Header
If((Get-Content $CSVFile -TotalCount 1) -match '^"?machine name"?,"?mac"?$'){
    Show-MsgBox "The CSV File's headers must be Machine Name and MAC." 'Invalid CSV File headers!' -Icon Warning
    break
}

$ComputerList = Import-Csv -Path $CSVFile |
Out-GridView -PassThru -Title "Select Computers to Wake up"

ForEach($Computer in $ComputerList)

    {

        If($Computer.'MAC' -notmatch '([:]|[-])')

            {

                $Computer.'MAC' = $Computer.'MAC' -replace '(..(?!$))','$1:'

            }

        $MACAddr = $Computer.'MAC'.split('([:]|[-])') | %{ [byte]('0x' + $_) }
        $UDPclient = new-Object System.Net.Sockets.UdpClient
        $UDPclient.Connect(([System.Net.IPAddress]::Broadcast),4000)
        $packet = [byte[]](,0xFF * 6)
        $packet += $MACAddr * 16
        [void] $UDPclient.Send($packet, $packet.Length)
        write "Wake-On-Lan magic packet sent to $($Computer.'Machine Name'.ToUpper())"

    }

Write-Host "Pausing for sixty seconds before verifying connectivity."
Start-Sleep -Seconds 60

$ComputerList|ForEach

    {

        Write-Host "Pinging $($_.'Machine Name')"
        Add-Member -InputObject $_ -NotePropertyName "PingResult" -NotePropertyValue (Test-Connection -ComputerName $Computer.'Machine Name' -Quiet)

    }

If(($ComputerList|Where{!($_.PingResult)}).Count -gt 0)

    {

        Show-MsgBox "All machines selected are online." 'Success'

    }

Else

    {

        Show-MsgBox "The following machines did not respond to a ping: $(($ComputerList|?{!($_.PingResult)}) -join ", ")" 'Unreachable Machines' -Icon Asterisk

    }

好的,我要离开我的肥皂盒回家了,我的班已经结束了,该换个冷的了。

于 2015-04-30T06:25:35.643 回答
0

您正在检查列表是否具有空字符串值,而不是检查列表中的项目数。

如果您将 if 语句更改为以下内容,它应该可以正常工作:

If($PingResult2.count -eq 0)

我猜正则表达式试图在字符串的每两个字符之间插入一个冒号以表示0123456789ab01:23:45:67:89:ab.

该代码表示​​如果 MAC 中没有连字符或冒号,则在每个字符中加上一个冒号,然后使用冒号作为分隔符拆分地址,然后将每个字符表示为一个字节:

If($Computer.'MAC' -notmatch '([:]|[-])')

        {

            $Computer.'MAC' = $Computer.'MAC' -replace '(..(?!$))','$1:'

        }

    $MACAddr = $Computer.'MAC'.split('([:]|[-])') | %{ [byte]('0x' + $_) }
于 2015-04-29T21:49:59.727 回答