简体   繁体   English

PowerShell-If / Else语句无法正常工作

[英]PowerShell - If/Else Statement Doesn't Work Properly

First off I apologize for the extremely long, wordy post. 首先,对于冗长而冗长的帖子,我深表歉意。 It's an interesting issue and I wanted to be as detailed as possible. 这是一个有趣的问题,我想尽可能详细。 I've tried looking through any related PowerShell posts on the site but I couldn't find anything that helped me with troubleshooting this problem. 我尝试浏览该站点上所有相关的PowerShell帖子,但找不到任何有助于解决该问题的信息。

I've been working on a PowerShell script with a team that can send Wake-On-Lan packets to a group of computers. 我一直在与一个可以将“局域网唤醒”数据包发送到一组计算机的团队一起开发PowerShell脚本。 It works by reading a .csv file that has the hostnames and MAC's in two columns, then it creates the WOL packets for each computer and broadcasts them out on the network. 它的工作方式是读取一个.csv文件,该文件在两列中包含主机名和MAC地址,然后为每台计算机创建WOL数据包,并将其在网络上广播出去。 After the WOL packets are sent, it waits a minute and then pings the computers to verify they are online, and if any don't respond it will display a window with what machines didn't respond to a ping. 发送WOL数据包后,它会等待一分钟,然后对计算机进行ping操作以验证它们是否在线,如果没有响应,它将显示一个窗口,其中显示哪些计算机未响应ping。 Up until the final If/Else statement works fine, so I won't be going into too much detail on that part of the script (but of course if you want/need further details please feel free to ask). 直到最终的If / Else语句正常为止,因此我不会在脚本的那一部分上介绍太多细节(但是,当然,如果您需要/需要更多细节,请随时询问)。

The problem I'm having is with the final If/Else statement. 我遇到的问题是最终的If / Else语句。 The way the script is supposed to work is that in the ForEach loop in the middle of the script, the value of variable $PingResult is true or false depending on whether or not the computer responds to a ping. 脚本应该起作用的方式是,在脚本中间的ForEach循环中,变量$ PingResult的值是true还是false,这取决于计算机是否响应ping。 If the ping fails, $PingResult is $false, and then it adds the hostname to the $PingResult2 variable. 如果ping失败,则$ PingResult为$ false,然后将主机名添加到$ PingResult2变量中。

In theory if all of the machines respond, the If statement fires and the message box displays that it was a success and then the script stops. 理论上,如果所有机器都响应,则触发If语句,并且消息框显示成功,然后脚本停止。 If any machines failed to respond, the Else statement runs and it joins all of the items together from the $PingResult2 variable and displays the list in a window. 如果有任何计算机未能响应,则运行Else语句,它将$ PingResult2变量中的所有项目连接在一起,并在窗口中显示该列表。 What actually happens is that even if all of the machines respond to a ping, the If statement is completely skipped and the Else statement runs instead. 实际发生的情况是,即使所有计算机都对ping作出响应,If语句也将被完全跳过,而Else语句将改为运行。 However, at that point the $PingResult2 variable is blank and hence it doesn't display any computer names of machines that failed to respond. 但是,此时$ PingResult2变量为空,因此它不会显示任何未能响应的计算机的计算机名称。 In my testing I've never seen a case where the script fails to wake a computer up (assuming it's plugged in, etc.), but the Else statement still runs regardless. 在我的测试中,我从未见过脚本无法唤醒计算机(假设计算机已插入等)的情况,但是Else语句仍然可以运行。 In situations where the Else statement runs, I've checked the value of the $PingResult2 variable and confirmed that it is blank, and typing $PingResult2 –eq “” returns $true. 在运行Else语句的情况下,我检查了$ PingResult2变量的值并确认其为空,然后键入$ PingResult2 -eq“”将返回$ true。

To add another wrinkle to the problem, I want to return to the $PingResult2 variable. 为了进一步解决该问题,我想返回$ PingResult2变量。 I had to create the variable as a generic list so that it would support the Add method to allow the variable to grow as needed. 我必须将变量创建为通用列表,以便它支持Add方法以允许变量根据需要增长。 As a test, we modified the script to concatenate the results together by using the += operator instead of making $PingResult2 a list, and while that didn't give a very readable visual result in the final display window if machines failed, it did actually work properly occasionally. 作为测试,我们修改了脚本,使用+ =运算符将结果串联在一起,而不是将$ PingResult2列为列表,尽管如果机器出现故障,在最终的显示窗口中视觉效果也不明显,但是它确实偶尔正常工作。 If all of the computers responded successfully the If statement would run as expected and display the success message. 如果所有计算机都成功响应,则If语句将按预期运行,并显示成功消息。 Like I said, it would sometimes work and sometimes not, with no other changes making a difference in the results. 就像我说的那样,它有时会起作用,有时却不会,因为没有其他更改会影响结果。 One other thing that we tried was taking out all of the references to the Visual Basic assembly and other GUI elements (besides the Out-GridView window) and that didn't work either. 我们尝试做的另一件事是取出对Visual Basic程序集和其他GUI元素的所有引用(除了Out-GridView窗口),而且也不起作用。

Any idea of what could be causing this problem? 任何可能导致此问题的想法? Me and my team are completely tapped out of ideas at this point and we'd love to figure out what's causing the issue. 此时,我和我的团队已经完全摆脱了想法,我们很想找出导致问题的原因。 We've tried it on Windows 7, 8.1, and the latest preview release of Windows 10 with no success. 我们已经在Windows 7、8.1和Windows 10的最新预览版上进行了尝试,但均未成功。 Thanks in advance for any assistance. 在此先感谢您的协助。

PS Extra brownie points if you can explain what the regular expression on line 29 is called and how it exactly works. PS Extra布朗尼蛋糕要点,如果您可以解释第29行的正则表达式是什么以及它如何正常工作。 I found out about it on a web posting that resolved the issue of adding a colon between every two characters, but the posting didn't explain what it was called. 我在网络发布中找到了相关信息,该发布解决了在每两个字符之间添加一个冒号的问题,但是该发布并没有解释它的名字。 (Original link http://powershell.org/wp/forums/topic/add-colon-between-every-2-characters/ ) (原始链接http://powershell.org/wp/forums/topic/add-colon-between-every-2-characters/

Original WOL Script we built the rest of the script around was by John Savill (link http://windowsitpro.com/networking/q-how-can-i-easily-send-magic-packet-wake-machine-my-subnet ) 我们围绕其余脚本构建的原始WOL脚本由John Savill(链接http://windowsitpro.com/networking/q-how-can-i-easily-send-magic-packet-wake-machine-my-subnet

Script 脚本

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)

    }

The comparison in your If statement is incorrect because you are comparing $PingResult2 , a List<string> , to a string. If语句中的比较不正确,因为您正在将$PingResult2 (一个List<string> )与一个字符串进行比较。 Instead, try 相反,尝试

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

or one of countless other variations on this theme. 或与此主题相关的其他众多变体之一。

The regular expression in question uses a backreference to replace exactly two characters with the same two characters plus a colon character. 有问题的正则表达式使用反向引用将两个相同的字符替换为两个相同的字符再加上一个冒号。 I am unsure what exactly you are attempting to "define," though. 不过,我不确定您到底要尝试“定义”什么。

You are checking if a list has a value of a null string, rather than checking the number of items in the list. 您正在检查列表是否具有空字符串值,而不是检查列表中的项目数。

If you change the if statement to the following it should work fine: 如果将if语句更改为以下内容,则应该可以正常工作:

If($PingResult2.count -eq 0)

I'm guessing the regex is trying to insert a colon between every two characters of a string to represent 0123456789ab as 01:23:45:67:89:ab . 我猜想正则表达式试图在字符串的每两个字符之间插入一个冒号,以将0123456789ab表示为01:23:45:67:89:ab

The code means if there is no hyphen or colon in the MAC, put in a colon every the characters, then split the address using colon as delimiter then represent each as a byte: 该代码意味着,如果MAC中没有连字符或冒号,则将每个字符放入冒号,然后使用冒号作为定界符分割地址,然后将每个字符表示为一个字节:

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

        {

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

        }

    $MACAddr = $Computer.'MAC'.split('([:]|[-])') | %{ [byte]('0x' + $_) }

The other answer have explained quite well why your code does not work. 另一个答案很好地解释了为什么您的代码不起作用。 I'm not going there. 我不去那 Instead I'll give some suggestions that I think would improve your script, and explain why I think so. 相反,我会给出一些建议,我认为这些建议会改善您的脚本,并解释为什么会这样。 Let's start with functions. 让我们从函数开始。 Some of the things you do are functions I keep on hand because, well, they work well and are used often enough that I like having them handy. 您要做的某些事情是我手头上的功能,因为它们很好用,经常使用,以至于我喜欢方便使用它们。

First, your dialog to get the CSV file path. 首先,您的对话框获取CSV文件路径。 It works, don't get me wrong, but it could probably be better... As it is you pop up an Open File dialog with no parameters. 它可以工作,请不要误会我,但可能会更好。。。因为它是弹出没有参数的“打开文件”对话框。 This function allows you to use a few different parameters as wanted, or none for a very generic Open File dialog, but I think it's a slight improvement here: 此功能允许您根据需要使用一些不同的参数,或者对于非常普通的“打开文件”对话框,可以不使用任何参数,但是我认为这里有一点改进:

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
}

Then just call it as such: 然后就这样称呼它:

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

That opens the dialog filtering for only CSV files, and starts them looking at their desktop (I find that a lot of people save things to their desktop). 这将打开仅过滤CSV文件的对话框,并启动它们查看其桌面(我发现很多人将内容保存到其桌面)。 That only gets the path, so you would run your validation like you were. 这样只会得到路径,因此您可以像以前一样运行验证。 Actually, not like you were. 实际上,与您不同。 You really seem to have over complicated that whole bit. 您似乎真的已经使整个过程变得复杂了。 Bit I'll get to that in a moment, first, another function! 首先,我要介绍一下另一个功能! You call message boxes fairly often, and type out a bunch of options, and call the type, and everything every single time. 您经常调用消息框,然后键入一堆选项,然后每次都调用该类型以及所有内容。 If you're going to do it more than once, make it easy on yourself, make a function. 如果您不止一次要这样做,请自己动手做一个函数。 Here, check this out: 在这里,检查一下:

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"))}
}

Then you can specify as much or as little as you want for it. 然后,您可以根据需要指定任意数量。 Plus it uses Type'd parameters, so tab completion works, or in the ISE (if that's where you're writing your script, like I do) it will pop up valid options and you just pick from a list for the buttons or icon to show. 加上它使用Type'd参数,因此可以完成制表符,或者在ISE(如果您是在编写脚本的地方,就像我一样),它将弹出有效选项,您只需从按钮或图标的列表中进行选择即可显示。 Plus it doesn't return anything if it's a simple 'OK' response, to keep things clean, but will return Yes/No/Cancel or whatever other option you choose for buttons. 另外,如果只是简单的“ OK”响应,它不会返回任何内容,以保持环境整洁,但会返回Yes / No / Cancel或您为按钮选择的任何其他选项。

Ok, that's the functions, let's get to the meat of the script. 好的,那就是函数,让我们来了解一下脚本的内容。 Your file validation... Ok, you pull the first line of the file, so that should just be a string, I'm not sure why you're splitting it and verifying each header individually. 您的文件验证...好吧,您拉出文件的第一行,所以它应该只是一个字符串,我不确定为什么要拆分它并分别验证每个标头。 Just match the string as a whole. 只匹配整个字符串。 I would suggest doing it case insensitive, since we don't really care about case here. 我建议不要区分大小写,因为我们实际上并不关心大小写。 Also, depending on how the CSV file was generated, there could be quotes around headers, which you may want to account for. 另外,根据CSV文件的生成方式,您可能要考虑标头周围的引号。 Using -Match will perform a RegEx match that is a bit more forgiving. 使用-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
}

So now we have two functions, and 5 lines of code. 因此,现在我们有两个功能和5行代码。 Yes, the functions take up more space than what you previously had, but they're friendlier to work with, and IMO more functional. 是的,这些功能比您以前拥有的空间要多,但是它们使用起来更友好,而且IMO的功能也更多。 Your MAC address correction, and WOL sending part are all aces so far as I'm concerned. 就我而言,您的MAC地址校正和WOL发送部分都是ace。 There's no reason to change that part. 没有理由更改该部分。 Now, for validating that computers came back up... here we could use some improvement. 现在,为了验证计算机是否已恢复...我们可以在此进行一些改进。 Instead of making a [List] just add a member to each object, then filter against that below. 无需创建[List]只需向每个对象添加一个成员,然后针对下面的对象进行过滤。 The script as a whole would be a little longer, but better off for it I think. 整个脚本会更长一些,但是我认为更好。

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

    }

Ok, I'm going to get off my soap box and go home, my shift's over and it's time for a cold one. 好的,我要下肥皂盒回家,我的班次结束了,是时候感冒了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM