第 6 章 - 流控制

脚本编写

当你从编写 PowerShell 单行命令到编写脚本时,实际上并没有看上去那么复杂。 脚本只不过是在 PowerShell 控制台中以交互方式运行的相同或类似命令,只是将它们保存为.ps1脚本文件。 可以使用一些脚本构造,例如 foreach 循环而不是 ForEach-Object cmdlet。 考虑到这既是语言关键字,又是 foreach cmdlet 的 ForEach-Object 别名,初学者可能会感到困惑。

循环

PowerShell 的最佳方面之一是其可伸缩性。 一旦你学会了如何为单个项目执行任务,将相同的操作应用于数百个项目几乎同样简单。 在 PowerShell 中使用不同类型的循环之一来遍历项。

ForEach-Object

ForEach-Object 是一个 cmdlet,用于遍历管道中的项,例如使用 PowerShell 单行命令。 ForEach-Object 通过管道流式传输对象。

尽管 模块 参数 Get-Command 接受多个字符串值,但它仅通过属性名称通过管道输入接受它们。 在以下方案中,如果要通过管道将两个字符串值 Get-Command 传递给 Module 参数,则需要使用 ForEach-Object cmdlet。

'ActiveDirectory', 'SQLServer' |
    ForEach-Object {Get-Command -Module $_} |
    Group-Object -Property ModuleName -NoElement |
    Sort-Object -Property Count -Descending
Count Name
----- ----
  147 ActiveDirectory
   82 SqlServer

在上一个示例中, $_ 是当前对象。 从 PowerShell 版本 3.0 开始, $PSItem 可以使用而不是 $_。 大多数经验丰富的 PowerShell 用户更喜欢使用 $_ ,因为它向后兼容且不太适合键入。

使用 foreach 关键字时,必须先将项存储在内存中,然后才能循环访问它们,如果不知道正在使用的项数,这可能很困难。

$ComputerName = 'DC01', 'WEB01'
foreach ($Computer in $ComputerName) {
    Get-ADComputer -Identity $Computer
}
DistinguishedName : CN=DC01,OU=Domain Controllers,DC=mikefrobbins,DC=com
DNSHostName       : dc01.mikefrobbins.com
Enabled           : True
Name              : DC01
ObjectClass       : computer
ObjectGUID        : c38da20c-a484-469d-ba4c-bab3fb71ae8e
SamAccountName    : DC01$
SID               : S-1-5-21-2989741381-570885089-3319121794-1001
UserPrincipalName :

DistinguishedName : CN=WEB01,CN=Computers,DC=mikefrobbins,DC=com
DNSHostName       : web01.mikefrobbins.com
Enabled           : True
Name              : WEB01
ObjectClass       : computer
ObjectGUID        : 33aa530e-1e31-40d8-8c78-76a18b673c33
SamAccountName    : WEB01$
SID               : S-1-5-21-2989741381-570885089-3319121794-1107
UserPrincipalName :

很多时候,例如 foreachForEach-Object 这样的循环是必需的。 否则会收到错误消息。

Get-ADComputer -Identity 'DC01', 'WEB01'
Get-ADComputer : Cannot convert 'System.Object[]' to the type
'Microsoft.ActiveDirectory.Management.ADComputer' required by parameter
'Identity'. Specified method is not supported.
At line:1 char:26
+ Get-ADComputer -Identity 'DC01', 'WEB01'
+                          ~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Get-ADComputer], Parame
   terBindingException
    + FullyQualifiedErrorId : CannotConvertArgument,Microsoft.ActiveDirecto
   ry.Management.Commands.GetADComputer

其他情况下,可以在消除循环的同时获得相同的结果。 请参阅 cmdlet 帮助以了解选项。

'DC01', 'WEB01' | Get-ADComputer
DistinguishedName : CN=DC01,OU=Domain Controllers,DC=mikefrobbins,DC=com
DNSHostName       : dc01.mikefrobbins.com
Enabled           : True
Name              : DC01
ObjectClass       : computer
ObjectGUID        : c38da20c-a484-469d-ba4c-bab3fb71ae8e
SamAccountName    : DC01$
SID               : S-1-5-21-2989741381-570885089-3319121794-1001
UserPrincipalName :

DistinguishedName : CN=WEB01,CN=Computers,DC=mikefrobbins,DC=com
DNSHostName       : web01.mikefrobbins.com
Enabled           : True
Name              : WEB01
ObjectClass       : computer
ObjectGUID        : 33aa530e-1e31-40d8-8c78-76a18b673c33
SamAccountName    : WEB01$
SID               : S-1-5-21-2989741381-570885089-3319121794-1107
UserPrincipalName :

如前面的示例所示, 标识 参数 Get-ADComputer 仅在通过参数输入提供时接受单个值。 但是,通过使用管道,可以将多个值发送到命令,因为一次处理一个值。

循环 for 在满足指定条件时循环。 我不经常使用 for 循环,但它有使用。

for ($i = 1; $i -lt 5; $i++) {
    Write-Output "Sleeping for $i seconds"
    Start-Sleep -Seconds $i
}
Sleeping for 1 seconds
Sleeping for 2 seconds
Sleeping for 3 seconds
Sleeping for 4 seconds

在上一示例中,循环从数字 1 开始循环,只要计数器变量 $i 小于 5,该循环将执行四次。 它总共睡眠了 10 秒。

去做

PowerShell 中有两个不同的 do 循环: do untildo whiledo until 在指定条件为 false 之前运行。

下面的示例是一个数字游戏,它一直持续到你猜到的值等于 cmdlet 生成的相同数字 Get-Random

$number = Get-Random -Minimum 1 -Maximum 10
do {
    $guess = Read-Host -Prompt "What's your guess?"
    if ($guess -lt $number) {
        Write-Output 'Too low!'
    } elseif ($guess -gt $number) {
        Write-Output 'Too high!'
    }
}
until ($guess -eq $number)
What's your guess?: 1
Too low!
What's your guess?: 2
Too low!
What's your guess?: 3

Do While 是相反的。 只要指定条件的计算结果为 true,它就运行。

$number = Get-Random -Minimum 1 -Maximum 10
do {
    $guess = Read-Host -Prompt "What's your guess?"
    if ($guess -lt $number) {
        Write-Output 'Too low!'
    } elseif ($guess -gt $number) {
        Write-Output 'Too high!'
    }
}
while ($guess -ne $number)
What's your guess?: 1
Too low!
What's your guess?: 2
Too low!
What's your guess?: 3
Too low!
What's your guess?: 4

相同的结果可以通过将测试条件反转为不相等的 Do While 循环来实现。

do 循环始终至少运行一次,因为条件是在循环结束时计算的。

do while与循环一样,只要指定的条件为 true,循环while就运行。 但是,区别在于 while 循环在运行任何代码之前,会先在循环顶部评估条件。 因此,如果条件的计算结果为 false,则不会运行。

下面的示例计算美国感恩节的日期。 它总是在11月的第四个星期四。 循环从 11 月的第 22 天开始,并添加一天,而一周中的日期不等于星期四。 如果 22 号是星期四,循环根本不会运行。

$date = Get-Date -Date 'November 22'
while ($date.DayOfWeek -ne 'Thursday') {
    $date = $date.AddDays(1)
}
Write-Output $date
Thursday, November 23, 2017 12:00:00 AM

break、continue 和 return

关键字 break 旨在退出循环,通常用于 switch 语句。 在以下示例中, break 导致循环在第一次迭代后结束。

for ($i = 1; $i -lt 5; $i++) {
    Write-Output "Sleeping for $i seconds"
    Start-Sleep -Seconds $i
    break
}
Sleeping for 1 seconds

关键字 continue 旨在跳到循环的下一次迭代。

以下示例输出数字 1、2、4 和 5。 它跳过数字 3,并继续执行循环的下一次迭代。 如同break一样,continue打破循环,但仅限于当前的迭代。 执行将继续进行下一次迭代,而不是完全中断循环并停止。

while ($i -lt 5) {
    $i += 1
    if ($i -eq 3) {
        continue
    }
    Write-Output $i
}
1
2
4
5

关键字 return 旨在退出现有范围。

请注意,在以下示例中输出 return 第一个结果,然后退出循环。

$number = 1..10
foreach ($n in $number) {
    if ($n -ge 4) {
        return $n
    }
}
4

可在我的博客文章之一中找到对结果语句的更彻底的解释: PowerShell 返回关键字

概要

在本章中,你了解了 PowerShell 中存在的不同类型的循环。

回顾

  1. ForEach-Object cmdlet 和foreach语句之间有什么区别?
  2. 使用 while 循环而不是 do whiledo until 循环的主要优势是什么?
  3. 语句 breakcontinue 语句有何不同?

参考文献