添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
label~opt~ labeled-statement function-statement flow-control-statement statement-terminator trap-statement try-statement data-statement inlinescript-statement parallel-statement sequence-statement pipeline statement-terminator statement-terminator: new-line-character

statement 指定了要执行的某种操作。 除非在此子句中另行指明,否则语句将按词法顺序执行。

statement-block 允许将一组语句组合成一个语法单元。

8.1.1 标记语句

labeled-statement:
    switch-statement
    foreach-statement
    for-statement
    while-statement
    do-statement

迭代语句 (§8.4) 或 switch 语句 (§8.6) 可以选择性在前面紧接一个语句标签 label。 语句标签用作 break (§8.5.1) 或 continue (§8.5.2) 语句的可选目标。 但是,标签不会改变控制流。

冒号 (:) 与后跟的标记之间不允许有空格。

:go_here while ($j -le 100) {
    # ...
:labelA
for ($i = 1; $i -le 5; ++$i) {
    :labelB
    for ($j = 1; $j -le 3; ++$j) {
        :labelC
        for ($k = 1; $k -le 2; ++$k) {
            # ...

8.1.2 语句值

语句的值是它写入管道的一组累计值。 如果语句写入一个标量值,则该值是语句的值。 如果语句写入多个值,则该语句的值是一组按写入顺序存储在不受约束的一维数组的元素中的值。 请看下面的示例:

$v = for ($i = 10; $i -le 5; ++$i) { }

循环没有迭代,也没有向管道写入任何内容。 语句的值为 $null。

$v = for ($i = 1; $i -le 5; ++$i) { }

尽管循环迭代了五次,但没有向管道写入任何内容。 语句的值为 $null。

$v = for ($i = 1; $i -le 5; ++$i) { $i }

循环迭代了五次,每次向管道写入 int 值 $i。 语句的值是值为 Length 5 的 object[]。

$v = for ($i = 1; $i -le 5; ) { ++$i }

尽管循环迭代了五次,但没有向管道写入任何内容。 语句的值为 $null。

$v = for ($i = 1; $i -le 5; ) { (++$i) }

循环迭代了五次,将每个值写入管道。 语句的值是值为 Length 5 的 object[]。

$i = 1; $v = while ($i++ -lt 2) { $i }

循环只迭代一次。 语句的值是值为 2 的 int。

下面是一些其他示例:

# if $count is not currently defined then define it with int value 10
$count = if ($count -eq $null) { 10 } else { $count }
$i = 1
$v = while ($i -le 5) {
    $i                   # $i is written to the pipeline
    if ($i -band 1) {
        "odd"            # conditionally written to the pipeline
    ++$i                 # not written to the pipeline
# $v is object[], Length 8, value 1,"odd",2,3,"odd",4,5,"odd"

8.2 管道语句

pipeline:
    assignment-expression
    expression redirections~opt~ pipeline-tail~opt~
    command verbatim-command-argument~opt~ pipeline-tail~opt~
assignment-expression:
    expression assignment-operator statement
pipeline-tail:
    | new-lines~opt~ command
    | new-lines~opt~ command pipeline-tail
command:
    command-name command-elements~opt~
    command-invocation-operator command-module~opt~ command-name-expr command-elements~opt~
command-invocation-operator: one of
command-module:
    primary-expression
command-name:
    generic-token
    generic-token-with-subexpr
generic-token-with-subexpr:
    No whitespace is allowed between ) and command-name.
    generic-token-with-subexpr-start statement-list~opt~ )
command-namecommand-name-expr:
    command-name
primary-expressioncommand-elements:
    command-element
    command-elements command-element
command-element:
    command-parameter
    command-argument
    redirection
command-argument:
    command-name-expr
verbatim-command-argument:
    --% verbatim-command-argument-chars

redirections 在 §7.12 中有所介绍;assignment-expression 在 §7.11 中有所介绍;command-invocation-operator 点 (.) 在 §3.5.5 中有所介绍。 有关命令调用中的自变量到参数映射的讨论,请参阅 §8.14。

pipeline 中的第一个命令是一个表达式或一个命令调用。 通常,命令调用以 command-name 开头,它一般是一个裸标识符。 command-elements 表示命令的自变量列表。 换行符或 n 非转义分号将终止管道。

命令调用包含命令的名称,后跟零个或多个自变量。 用于控制自变量的规则如下所示:

  • 如果一个自变量不是表达式,但包含任意文本,没有未转义的空格,则将其视为已由双引号引起来。 保留字母大小写。

  • 变量替换和子表达式扩展 (§2.3.5.2) 在 expandable-string-literal 和 expandable-here-string-literal 中进行。

  • 用引号引住文本后,就可以在自变量的值中包含前导空格、尾随空格和嵌入空格。 [注意:如果带引号的自变量中存在空格,则不会将单个自变量转换为多个自变量。 说明结束]

  • 用括号将自变量括起来会导致计算该表达式的值,并且传递的是计算结果,而不是原始表达式的文本。

  • 若要传递一个看起来像(但并非真的是)开关参数 (§2.3.4) 的自变量,请将该自变量用引号引起来。

  • 当指定的自变量与具有 [switch] 类型约束的参数 (§8.10.5) 匹配时,自变量本身有名称会导致该参数设置为 $true。 但是,可以通过向自变量追加一个后缀来显式设置参数的值。 例如,给定一个类型约束参数 p,自变量 -p:$true 将 p 设置为 True,而 -p:$false 将 p 设置为 False。

  • 自变量 -- 指示其后面的所有自变量都按其实际格式传递,就如同在它们周围添加了双引号一样。

  • 自变量 --% 指示它后面的所有自变量都将通过最小分析和处理进行传递。 此自变量称为逐字参数。 逐字参数后面的自变量不是 PowerShell 表达式,即使它们是语法上有效的 PowerShell 表达式也是如此。

    如果命令类型为 Application,则不会将参数 --% 传递给命令。 --% 后面的自变量将展开任何环境变量(用 % 括住的字符串)。 例如:

    echoargs.exe --% "%path%" # %path% is replaced with the value $env:path
    

    没有指定自变量的计算顺序。

    有关参数绑定的信息,请参阅 §8.14。 有关名称查找的信息,请参阅 §3.8。

    一旦自变量处理完成,命令就会被调用。 如果被调用的命令正常终止 (§8.5.4),控制将回到脚本或函数中紧跟命令调用之后的位置。 有关异常终止行为的说明,请参阅 break (§8.5.1)、continue (§8.5.2)、throw (§8.5.3)、exit (§8.5.5)、try (§8.7) 和 trap (§8.8)。

    通常情况下,调用一个命令时要使用它的名称,后面要加上任何自变量。 不过,可以使用 command-invocation 运算符 &。 如果命令名称包含非转义空格,则必须将其用引号引起来,并使用此运算符调用。 由于脚本块没有名称,因此也必须使用此运算符来调用。 例如,对命令调用 Get-Factorial 的以下调用是等效的:

    Get-Factorial 5
    & Get-Factorial 5
    & "Get-Factorial" 5
    

    允许直接和间接递归函数调用。 例如,

    function Get-Power([int]$x, [int]$y) {
        if ($y -gt 0) { return $x * (Get-Power $x (--$y)) }
        else { return 1 }
    
    New-Object 'int[,]' 3,2
    New-Object -ArgumentList 3,2 -TypeName 'int[,]'
    dir e:\PowerShell\Scripts\*statement*.ps1 | Foreach-Object {$_.Length}
    dir e:\PowerShell\Scripts\*.ps1 | Select-String -List "catch" | Format-Table path,linenumber -AutoSize
    

    8.3 if 语句

    if-statement:
        if new-lines~opt~ ( new-lines~opt~ pipeline new-lines~opt~ ) statement-block
            elseif-clauses~opt~ else-clause~opt~
    elseif-clauses:
        elseif-clause
        elseif-clauses elseif-clause
    elseif-clause:
        new-lines~opt~ elseif new-lines~opt~ ( new-lines~opt~ pipeline new-lines~opt~ ) statement-block
    else-clause:
        new-lines~opt~ else statement-block
    

    pipeline 控制表达式必须具有 bool 类型或可以隐式转换为该类型。 else-clause 是可选的。 可能有零个或多个 elseif-clause。

    如果顶级 pipeline 测试为 True,则会执行其 statement-block,语句的执行终止。 否则,如果存在 elseif-clause,在其 pipeline 测试为 True 的情况下,会执行 statement-block,语句的执行终止。 否则,如果存在 else-clause,则会执行其 statement-block。

    $grade = 92
    if ($grade -ge 90) { "Grade A" }
    elseif ($grade -ge 80) { "Grade B" }
    elseif ($grade -ge 70) { "Grade C" }
    elseif ($grade -ge 60) { "Grade D" }
    else { "Grade F" }
    

    8.4 迭代语句

    8.4.1 while 语句

    while-statement:
        while new-lines~opt~ ( new-lines~opt~ while-condition new-lines~opt~ ) statement-block
    while-condition:
        new-lines~opt~ pipeline
    

    控制表达式 while-condition 必须具有 bool 类型或可以隐式转换为该类型。 循环体由 statement-block 组成,会重复执行直到控制表达式测试为 False。 控制表达式的值是在每次执行循环体之前进行计算的。

    $i = 1
    while ($i -le 5) {                     # loop 5 times
        "{0,1}`t{1,2}" -f $i, ($i*$i)
    

    8.4.2 do 语句

    do-statement:
        do statement-block new-lines~opt~ while new-lines~opt~ ( while-condition new-lines~opt~ )
        do statement-block new-lines~opt~ until new-lines~opt~ ( while-condition new-lines~opt~ )
    while-condition:
        new-lines~opt~ pipeline
    

    控制表达式 while-condition 必须具有 bool 类型或可以隐式转换为该类型。 在 while 窗体中,当控制表达式测试为 True 时,由 statement-block 组成的循环体将重复执行。 在 until 窗体中,循环体将重复执行,直到控制表达式测试为 True。 控制表达式的值是在每次执行循环体之后进行计算的。

    $i = 1
        "{0,1}`t{1,2}" -f $i, ($i * $i)
    while (++$i -le 5)                 # loop 5 times
    $i = 1
        "{0,1}`t{1,2}" -f $i, ($i * $i)
    until (++$i -gt 5)                 # loop 5 times
    

    8.4.3 for 语句

    for-statement:
        for new-lines~opt~ (
            new-lines~opt~ for-initializer~opt~ statement-terminator
            new-lines~opt~ for-condition~opt~ statement-terminator
            new-lines~opt~ for-iterator~opt~
            new-lines~opt~ ) statement-block
        for new-lines~opt~ (
            new-lines~opt~ for-initializer~opt~ statement-terminator
            new-lines~opt~ for-condition~opt~
            new-lines~opt~ ) statement-block
        for new-lines~opt~ (
            new-lines~opt~ for-initializer~opt~
            new-lines~opt~ ) statement-block
    for-initializer:
        pipeline
    for-condition:
        pipeline
    for-iterator:
        pipeline
    

    控制表达式 for-condition 必须具有 bool 类型或可以隐式转换为该类型。 当控制表达式测试为 True 时,由 statement-block 组成的循环体将重复执行。 控制表达式的值是在每次执行循环体之前进行计算的。

    表达式 for-initializer 的值是在首次计算控制表达式的值之前进行计算的。 计算表达式 for-initializer 的值仅为了解其副作用;它生成的任何值都将被丢弃,并且不会被写入管道。

    for-iterator 的值是在每次执行循环体之后进行计算的。 计算表达式 for-iterator 的值仅为了解其副作用;它生成的任何值都将被丢弃,并且不会被写入管道。

    如果省略了 for-condition 表达式,则控制表达式测试为 True。

    for ($i = 5; $i -ge 1; --$i) { # loop 5 times
        "{0,1}`t{1,2}" -f $i, ($i * $i)
    $i = 5
    for (; $i -ge 1; ) { # equivalent behavior
        "{0,1}`t{1,2}" -f $i, ($i * $i)
    

    8.4.4 foreach 语句

    foreach-statement:
        foreach new-lines~opt~ foreach-parameter~opt~ new-lines~opt~
            ( new-lines~opt~ variable new-lines~opt~ *in* new-lines~opt~ pipeline
            new-lines~opt~ ) statement-block
    foreach-parameter:
        -parallel
    

    循环体由 statement-block 组成,针对 pipeline 指定的集合中的变量 variable 指定的每个元素执行。 variable 的作用域不限于 foreach 语句。 因此,它在循环体执行完成后保留其最终值。 如果 pipeline 指定一个标量(值 $null 除外)而非集合,则该标量被视为一个元素的集合。 如果 pipeline 指定值 $null,则 pipeline 被视为零个元素的集合。

    如果指定了 foreach-parameter -parallel,则行为是实现定义的。

    foreach-parameter ‑parallel 只被允许在工作流中使用 (§8.10.2)。

    每个 foreach 语句都有自己的枚举器 $foreach(§2.3.2.2、§4.5.16),它仅在执行该循环时存在。

    pipeline 生成的对象是在 statement-block 开始执行之前收集的。 但是,对于 ForEach-Object cmdlet,当每个对象生成时,会对其执行 statement-block。

    $a = 10, 53, 16, -43
    foreach ($e in $a) {
    $e # the int value -43
    foreach ($e in -5..5) {
    foreach ($t in [byte], [int], [long]) {
        $t::MaxValue # get static property
    foreach ($f in Get-ChildItem *.txt) {
    $h1 = @{ FirstName = "James"; LastName = "Anderson"; IDNum = 123 }
    foreach ($e in $h1.Keys) {
        "Key is " + $e + ", Value is " + $h1[$e]
    

    8.5 流控制语句

    flow-control-statement:
        break label-expression~opt~
        continue label-expression~opt~
        throw pipeline~opt~
        return pipeline~opt~
        exit pipeline~opt~
    label-expression:
        simple-name
        unary-expression
    

    流控制语句会导致将控制无条件转移到其他位置。

    8.5.1 break 语句

    带 label-expression 的 break 语句被称为“已标记的 break 语句”。 不带 label-expression 的 break 语句被称为“未标记的 break 语句”。

    在 trap 语句之外,直接在一个迭代语句 (§8.4) 中使用未标记的 break 语句将终止执行最小的封闭迭代语句。 直接在一个 switch 语句 (§8.6) 中使用未标记的 break 语句将终止当前开关的 switch-condition 的模式匹配。 有关在 trap 语句中使用 break 的详细信息,请参阅 (§8.8)。

    迭代语句或 switch 语句可以选择在前面紧接一个语句标签 (§8.1.1)。此类语句标签可以用作已标记的 break 语句的目标,在这种情况下,该语句将终止执行目标封闭迭代语句。

    无需在任何本地作用域内解析已标记的 break;搜索匹配标签也可以继续在调用堆栈中进行,甚至跨脚本和函数调用边界。 如果找不到匹配的标签,当前的命令调用将被终止。

    label-expression 指定的标签的名称不需要具有常量值。

    如果 label-expression 是一个 unary-expression,则会被转换为字符串。

    $i = 1
    while ($true) { # infinite loop
        if ($i * $i -gt 100) {
            break # break out of current while loop
    $lab = "go_here"
    :go_here
    for ($i = 1; ; ++$i) {
        if ($i * $i -gt 50) {
            break $lab # use a string value as target
    :labelA
    for ($i = 1; $i -le 2; $i++) {
        :labelB
        for ($j = 1; $j -le 2; $j++) {
            :labelC
            for ($k = 1; $k -le 3; $k++) {
                if (...) { break labelA }
    

    8.5.2 continue 语句

    带 label-expression 的 continue 语句被称为“已标记的 continue 语句”。 不带 label-expression 的 continue 语句被称为“未标记的 continue 语句”。

    在 trap 语句内使用 continue 的情况在 §8.8 中进行了介绍。

    循环中的未标记 continue 语句将终止执行当前循环,并将控制转移到最小封闭迭代语句 (§8.4) 的右括号。 开关中的未标记 continue 语句将终止执行当前 switch 迭代,并将控制转移到最小封闭 switch 的 switch-condition (§8.6)。

    迭代语句或 switch 语句 (§8.6) 可以选择在前面紧接一个语句标签 (§8.1.1)。 这种语句标签可以用作封闭的已标记 continue 语句的目标,在这种情况下,该语句将终止执行当前循环或 switch 迭代,并将控制转移到目标封闭迭代或 switch 语句标签。

    无需在任何本地作用域内解析已标记的 continue;搜索匹配标签也可以继续在调用堆栈中进行,甚至跨脚本和函数调用边界。 如果找不到匹配的标签,当前的命令调用将被终止。

    label-expression 指定的标签的名称不需要具有常量值。

    如果 label-expression 是一个 unary-expression,则会被转换为字符串。

    $i = 1
    while (...) {
        if (...) {
            continue # start next iteration of current loop
    $lab = "go_here"
    :go_here
    for (...; ...; ...) {
        if (...) {
            continue $lab # start next iteration of labeled loop
    :labelA
    for ($i = 1; $i -le 2; $i++) {
        :labelB
        for ($j = 1; $j -le 2; $j++) {
            :labelC
            for ($k = 1; $k -le 3; $k++) {
                if (...) { continue labelB }
    

    8.5.3 throw 语句

    异常是处理系统级或应用程序级错误条件的一种方法。 throw 语句引发异常。 (有关异常处理的介绍,请参阅 §8.7。)

    如果省略 pipeline 且 throw 语句不在 catch-clause 子句中,则行为是实现定义的。 如果存在 pipeline 且 throw 语句存在于 catch-clause 中,则在执行与 catch-clause 关联的任何 finally-clause 后,将重新引发该 catch-clause 捕获的异常。

    如果存在 pipeline,则引发的异常的类型是实现定义的。

    引发异常时,控制将转移到可处理异常的封闭 try 语句中的第一个 catch 子句。 最初引发异常的位置称为“引发点”。 一旦引发一个异常,§8.7 中所述的步骤将重复执行,直到找到与异常匹配的 catch 子句或找不到任何匹配项。

    throw
    throw 100
    throw "No such record in file"
    

    如果省略 pipeline且 throw 语句不在 catch-clause 中,则文本“ScriptHalted”将被写入管道,并且所引发异常的类型为 System.Management.Automation.RuntimeException。

    如果存在 pipeline,则引发的异常被包装在一个类型为 System.Management.Automation.RuntimeException 的对象中,其中包括异常相关信息作为 System.Management.Automation.ErrorRecord 对象(可通过 $_ 访问)。

    示例 1:throw 123 导致类型为 RuntimeException 的异常。 在 catch 块中,$_.TargetObject 包含包装在其中的对象(本例中为具有值 123 的 System.Int32)。

    示例 2:throw "xxx" 导致类型为 RuntimeException 的异常。 在 catch 块中,$_.TargetObject 包含包装在其中的对象(本例中为具有值“xxx”的 System.String)。

    示例 3:throw 10,20 导致类型为 RuntimeException 的异常。 在 catch 块中,$_.TargetObject 包含包装在其中的对象(本例中是 System.Object[],它是一个由 System.Int32` 值为 10 和 20 的两个元素组成的无约束数组)。

    8.5.4 return 语句

    return 语句向管道写入由 pipeline 指定的一个或多个值(如果有),并返回对函数或脚本调用方的控制。 函数或脚本可以有零个或多个 return 语句。

    如果执行到达函数的右括号,则假定隐含的 return 没有 pipeline。

    return 语句是一种“语法糖”,让程序员可以像在其他语言中那样表达自己的内容;但是,从函数或脚本返回的值实际上是该函数或脚本写入管道的所有值,加上由 pipeline 指定的任何值。 如果只向管道写入一个标量值,则其类型为返回的值的类型;否则,返回类型是一个无约束的一维数组,其中包含写入管道的所有值。

    function Get-Factorial ($v) {
        if ($v -eq 1) {
            return 1 # return is not optional
        return $v * (Get-Factorial ($v - 1)) # return is optional
    

    调用 Get-Factorial 的调用方将获得一个 int。

    function Test {
        "text1" # "text1" is written to the pipeline
        # ...
        "text2" # "text2" is written to the pipeline
        # ...
        return 123 # 123 is written to the pipeline
    

    调用 Test 的调用方将获得一个由三个元素组成的无约束一维数组。

    8.5.5 exit 语句

    exit 语句终止当前脚本,将控制和 exit 代码返回给主机环境或调用脚本。 如果提供了 pipeline,它指定的值将根据需要被转换为 int。 如果不存在此类转换,或者如果省略 pipeline,则返回 int 值零。

    exit $count # terminate the script with some accumulated count
    

    8.6 switch 语句

    switch-statement:
        switch new-lines~opt~ switch-parameters~opt~ switch-condition switch-body
    switch-parameters:
        switch-parameter
        switch-parameters switch-parameter
    switch-parameter:
        -regex
        -wildcard
        -exact
        -casesensitive
        -parallel
    switch-condition:
        ( new-lines~opt~ pipeline new-lines~opt~ )
        -file new-lines~opt~ switch-filename
    switch-filename:
        command-argument
        primary-expression
    switch-body:
        new-lines~opt~ { new-lines~opt~ switch-clauses }
    switch-clauses:
        switch-clause
        switch-clauses switch-clause
    switch-clause:
        switch-clause-condition statement-block statement-terimators~opt~
    switch-clause-condition:
        command-argument
        primary-expression
    

    如果 switch-condition 指定单个值,控制将被传递给一个或多个匹配的模式语句块。 如果没有模式匹配,可以执行一些默认操作。

    开关必须包含一个或多个 switch-clause,每个都以模式(非默认 switch 子句)或关键字 default(默认 switch 子句)开头。 开关必须包含零个或一个 default switch 子句,以及零个或多个非默认 switch 子句。 可以按任意顺序编写 switch 子句。

    多个模式可能具有相同的值。 模式不需要是文本,且开关可以具有不同类型的模式。

    如果 switch-condition 的值与模式值一致,则执行该模式的 statement-block。 如果多个模式值与 switch-condition 的值一致,则按词法顺序执行每个匹配的模式的 statement-block,除非任何这些 statement-block 包含 break 语句 (§8.5.1)。

    如果 switch-condition 的值不匹配任何模式值,则在存在 default switch 子句的情况下,会执行其 statement-block;否则,会终止该 switch-condition 的模式匹配。

    开关可以是嵌套的,且每个开关都有自己的一组 switch 子句。 在这种情况下,switch 子句属于当前作用域内最内部的开关。

    进入每个 statement-block 时,会自动为 $_ 分配 switch-condition 的值,该操作导致将控制转到该 statement-block。 $_ 也可以在该 statement-block 的 switch-clause-condition 中使用。

    非字符串的匹配情况是通过测试相等性 (§7.8.1) 来完成的。

    如果匹配涉及字符串,则默认情况下,比较不区分大小写。 存在 switch-parameter -casesensitive 时,比较区分大小写。

    模式可能包含通配符 (§3.15),在这种情况下,将执行通配符字符串比较,但仅在存在 switch-parameter -wildcard 时执行。 默认情况下,比较不区分大小写。

    模式可能包含正则表达式 (§3.16),在这种情况下,将执行正则表达式字符串比较,但仅在存在 switch-parameter -regex 时执行。 默认情况下,比较不区分大小写。 如果存在 -regex 且匹配一个模式,则在该模式的 switch-clause statement-block 中定义 $matches。

    switch-parameter 可以是缩写形式;可以使用参数的任何非重复前导部分。 例如,‑regex、‑rege、‑reg、‑re 和 ‑r 是等效的。

    如果指定的若干个 switch-parameter 发生冲突,则以词法顺序中最后的那个为准。 存在 ‑exact 时会禁用 -regex 和 -wildcard,但它对 ‑case 没有任何影响。

    如果指定了 switch-parameter ‑parallel,则行为是实现定义的。

    switch-parameter ‑parallel 只允许在工作流 (§8.10.2) 中使用。

    如果模式是 script-block-expression,将计算该块的值,并在必要时将结果转换为 bool。 如果结果具有值 $true,则执行相应的 statement-block;否则不执行。

    如果 switch-condition 指定多个值,则使用上述针对指定单个值的 switch-condition 的规则,按照词法顺序将开关应用于每个值。 每个 switch 语句都有自己的枚举器 $switch(§2.3.2.2、§4.5.16),它仅在执行该开关时存在。

    switch 语句可以有一个标签,且可以包含已标记和未标记的 break (§8.5.1) 和 continue (§8.5.2) 语句。

    如果 switch-condition 是 -file switch-filename,则开关将循环访问 switch-filename 指定的文件中的值,而不是循环访问表达式中的值。对于该文件,一次读取一行,每行包含一个值。 值中不包含行终止符字符。

    $s = "ABC def`nghi`tjkl`fmno @#$"
    $charCount = 0; $pageCount = 0; $lineCount = 0; $otherCount = 0
    for ($i = 0; $i -lt $s.Length; ++$i) {
        ++$charCount
        switch ($s[$i]) {
            "`n" { ++$lineCount }
            "`f" { ++$pageCount }
            "`t" { }
            " " { }
            default { ++$otherCount }
    switch -wildcard ("abc") {
        a* { "a*, $_" }
        ?B? { "?B? , $_" }
        default { "default, $_" }
    switch -regex -casesensitive ("abc") {
        ^a* { "a*" }
        ^A* { "A*" }
    switch (0, 1, 19, 20, 21) {
        { $_ -lt 20 } { "-lt 20" }
        { $_ -band 1 } { "Odd" }
        { $_ -eq 19 } { "-eq 19" }
        default { "default" }
    

    8.7 try/finally 语句

    try-statement:
        try statement-block catch-clauses
        try statement-block finally-clause
        try statement-block catch-clauses finally-clause
    catch-clauses:
        catch-clause
        catch-clauses catch-clause
    catch-clause:
        new-lines~opt~ catch catch-type-list~opt~
        statement-block
    catch-type-list:
        new-lines~opt~ type-literal
        catch-type-list new-lines~opt~ , new-lines~opt~
    type-literalfinally-clause:
        new-lines~opt~ finally statement-block
    

    try 语句提供了一种机制,用于捕获块执行过程中发生的异常。 try 语句还提供指定在控制离开 try 语句时始终执行的代码块的功能。 通过 throw 语句引发异常的过程在 §8.5.3 中进行了介绍。

    try 块是与 try 语句关联的 statement-block。 catch 块是与 catch-clause 关联的 statement-block。 finally 块是与 finally-clause 关联的 statement-block。

    没有 catch-type-list 的 catch-clause 被称为“常规 catch 子句”。

    每个 catch-clause 都是一个异常处理程序,而 catch-type-list 包含所引发异常的类型的 catch-clause 是匹配的 catch 子句。 常规 catch 子句匹配所有异常类型。

    尽管 catch-clauses 和 finally-clause 是可选的,但必须至少有一个存在。

    对引发的异常的处理包括重复计算以下步骤的值,直到找到与异常匹配的 catch 子句。

  • 在当前作用域中,将检查包含引发点的每个 try 语句。 对于每个 try 语句 S,从最内部的 try 语句到最外层的 try 语句,将计算以下步骤的值:

  • 如果 S 的 try 块包含引发点,且 S 具有一个或多个 catch 子句,则会按词法顺序检查 catch 子句,以找到合适的异常处理程序。 指定异常类型或异常类型的基类型的第一个 catch 子句被视为匹配项。 常规 catch 子句被视为任何异常类型的匹配项。 如果找到匹配的 catch 子句,则异常处理是通过将控制转移给该 catch 子句的块来完成的。 在匹配的 catch 子句中,变量 $_ 包含当前异常的说明。

  • 否则,如果 S 的 try 块或 catch 块包含引发点,并且 S 具有 finally 块,则控制将转移到 finally 块。 如果 finally 块引发另一个异常,则终止处理当前异常。 否则,如果控制到达 finally 块的末尾,将继续处理当前异常。

  • 如果异常处理程序不在当前作用域中,将针对其中的引发点对应于调用当前作用域的语句的封闭作用域重复执行上述步骤。

  • 如果异常处理最终终止所有作用域,并指明不存在任何处理程序用于处理异常,则未指定该行为。

    为了防止在 try 块中无法到达 catch 子句,catch 子句不能指定等于或派生自同一 try 块中早期 catch 子句中指定的类型的异常类型。

    当控制离开 finally 语句时,try 块的语句会始终执行。 无论控制转移是正常执行的结果、执行 break、continue 或 return 语句的结果,还是从 try 语句引发的异常的结果,都是如此。

    如果在 finally 块执行过程中引发异常,则异常将引发到下一个封闭 try 语句。 如果正在处理另一个异常,该异常将丢失。 throw 语句的描述中进一步讨论了生成异常的过程。

    try 语句可以与 trap 语句共存;有关详细信息,请参阅 §8.8。

    $a = new-object 'int[]' 10
    $i = 20 # out-of-bounds subscript
    while ($true) {
        try {
            $a[$i] = 10
            "Assignment completed without error"
            break
        catch [IndexOutOfRangeException] {
            "Handling out-of-bounds index, >$_<`n"
            $i = 5
        catch {
            "Caught unexpected exception"
        finally {
            # ...
    

    引发的每个异常都是作为 System.Management.Automation.RuntimeException 引发的。 如果 try 块中具有特定于类型的 catch-clause,则检查异常的 InnerException 属性以尝试查找匹配项,例如与上述类型 System.IndexOutOfRangeException 匹配。

    8.8 trap 语句

    trap-statement:
        *trap* new-lines~opt~ type-literal~opt~ new-lines~opt~ statement-block
    

    包含和不包含 type-literal 的 trap 语句分别类似于包含和不包含 catch-type-list 的 catch 块 (§8.7),只不过 trap 语句一次只能捕获一种类型。

    可以在同一 statement-block 中定义多个 trap 语句,它们的定义顺序并不重要。 如果在同一作用域内定义了两个包含相同 type-literal 的 trap 语句,则词法上的第一个语句用于处理匹配类型的异常。

    与 catch 块不同,trap 语句与异常类型完全匹配;不会执行派生类型匹配。

    发生异常时,如果当前作用域中没有匹配的 trap 语句,则会在封闭的作用域中搜索匹配的 trap 语句,这可能涉及在调用脚本、函数或筛选器中查找,然后在其调用方中查找,等等。 如果查找最终终止所有作用域,并指明不存在任何处理程序用于处理异常,则未指定该行为。

    执行 trap 语句的 statement-body 仅用于处理相应的异常;否则执行过程会忽略它。

    如果 trap 的 statement-body 正常退出,默认情况下,错误对象将被写入错误流,异常被视为已处理,并继续执行紧跟在包含使异常可见的 trap 语句的作用域中的语句之后的语句。 异常的原因可能是在由包含 trap 语句的命令调用的命令中。

    如果在 trap 的 statement-body 中执行的最后一个语句是 continue (§8.5.2),则将错误对象写入错误流的过程被禁止,并继续执行紧跟在包含使异常可见的 trap 语句的作用域中的语句之后的语句。 如果在 trap 的 statement-body 中执行的最后一个语句是 break (§8.5.1),则将错误对象写入错误流的过程被禁止,并再次引发异常。

    在 trap 语句中,变量 $_ 包含当前错误的描述。

    考虑一种情况,从 try 块中引发的异常没有匹配的 catch 块,但匹配的 trap 语句存在于较高的块级别。 执行 try 块的 finally 子句后,即使任何父作用域具有匹配的 catch 块,trap 语句也会获得控制。 如果 trap 语句是在 try 块内定义的,并且该 try 块具有匹配的 catch 块,则 trap 语句将获得控制。

    在下面的示例中,将写入错误对象,并继续执行紧跟在导致 trap 的语句之后的语句;即,将“Done”写入管道。

    $j = 0; $v = 10/$j; "Done"
    trap { $j = 2 }
    

    在下面的示例中,将禁止写入错误对象,并继续执行紧跟在导致 trap 的语句之后的语句;即,将“Done”写入管道。

    $j = 0; $v = 10/$j; "Done"
    trap { $j = 2; continue }
    

    在下面的示例中,将禁止写入错误对象,并再次引发异常。

    $j = 0; $v = 10/$j; "Done"
    trap { $j = 2; break }
    

    在下面的示例中,trap 语句和生成异常的语句在同一作用域内。 捕获并处理异常后,执行过程将恢复,并向管道中写入 1。

    &{trap{}; throw '\...'; 1}
    

    在下面的示例中,trap 语句和生成异常的语句在不同作用域内。 捕获并处理异常后,执行过程将恢复,并向管道中写入 2(而非 1)。

    trap{} &{throw '\...'; 1}; 2
    

    8.9 data 语句

    data-statement:
        data new-lines~opt~ data-name data-commands-allowed~opt~ statement-block
    data-name:
        simple-name
    data-commands-allowed:
        new-lines~opt~ -supportedcommand data-commands-list
    data-commands-list:
        new-lines~opt~ data-command
        data-commands-list , new-lines~opt~ data-command
    data-command:
        command-name-expr
    

    data 语句创建一个数据部分,使该部分的数据与代码分开。 这种分开操作支持像文本的独立字符串资源文件(如错误消息和帮助字符串)等工具。 它还有助于支持国际化,因为它可以更轻松地隔离、查找并处理要被翻译成不同语言的字符串。

    脚本或函数可以具有零个或多个数据部分。

    数据部分的 statement-block 仅限于包含以下 PowerShell 功能:

  • 除 -match 之外的所有运算符
  • if 语句
  • 以下自动变量:$PsCulture、$PsUICulture、$true、$false 和 $null。
  • 用分号 (;) 分隔的语句
  • 对 ConvertFrom-StringData cmdlet 的调用
  • 通过 supportedcommand 参数标识的任何其他 cmdlet
  • 如果使用 ConvertFrom-StringData cmdlet,则可以使用任何形式的字符串文本来表示键/值对。 但是,expandable-string-literal 和 expandable-here-string-literal 不得包含任何变量替换或子表达式扩展。

    SupportedCommand 参数指示,给定的 cmdlet 或函数仅生成数据。 例如,以下数据部分包括用户编写的 cmdlet ConvertTo-XML,它用于格式化 XML 文件中的数据:

    data -supportedCommand ConvertTo-XML {
        Format-XML -strings string1, string2, string3
    

    请看以下示例,其中数据部分包含一个 ConvertFrom-StringData 命令,该命令将字符串转换为一个哈希表,其值被分配给 $messages。

    $messages = data {
        ConvertFrom-StringData -stringdata @'
        Greeting = Hello
        Yes = yes
        No = no
    

    可分别使用 $messages.Greeting、$messages.Yes 和 $messages.No 访问哈希表的键和值。

    现在,这可以被保存为一个英语资源。 德语和西班牙语资源可以在单独的文件中创建,并包含以下数据部分:

    $messages = data {
        ConvertFrom-StringData -stringdata @"
        Greeting = Guten Tag
        Yes = ja
        No = nein
    $messagesS = data {
        ConvertFrom-StringData -stringdata @"
        Greeting = Buenos días
        Yes = sí
        No = no
    

    如果存在 dataname,它将命名数据语句的值要存储到其中的变量(不使用前导 $)。 具体而言,$name = data { ... } 等效于 data name { ... }。

    8.10 函数定义

    function-statement:
        function new-lines~opt~ function-name function-parameter-declaration~opt~ { script-block }
        filter new-lines~opt~ function-name function-parameter-declaration~opt~ { script-block }
        workflow new-lines~opt~ function-name function-parameter-declaration~opt~ { script-block }
    function-name:
        command-argument
    command-argument:
        command-name-expr
    function-parameter-declaration:
        new-lines~opt~ ( parameter-list new-lines~opt~ )
    parameter-list:
        script-parameter
        parameter-list new-lines~opt~ , script-parameter
    script-parameter:
        new-lines~opt~ attribute-list~opt~ new-lines~opt~ variable script-parameter-default~opt~
    script-block:
        param-block~opt~ statement-terminators~opt~ script-block-body~opt~
    param-block:
        new-lines~opt~ attribute-list~opt~ new-lines~opt~ param new-lines~opt~
            ( parameter-list~opt~ new-lines~opt~ )
    parameter-list:
        script-parameter
        parameter-list new-lines~opt~ , script-parameter
    script-parameter-default:
        new-lines~opt~ = new-lines~opt~ expression
    script-block-body:
        named-block-list
        statement-list
    named-block-list:
        named-block
        named-block-list named-block
    named-block:
        block-name statement-block statement-terminators~opt~
    block-name: one of
        dynamicparam   begin   process   end
    

    函数定义指定要定义的函数、筛选器或工作流的名称及其参数的名称(如果有)。 它还包含零个或多个为实现该函数而执行的语句。

    每个函数都是 System.Management.Automation.FunctionInfo 类的一个实例。

    8.10.1 筛选器函数

    普通函数在管道中运行一次,并通过 $input 访问输入集合,而 filter 是一种特殊类型的函数,它针对输入集合中的每个对象执行一次。 当前正在处理的对象可通过变量 $_ 获取。

    没有命名块的筛选器 (§8.10.7) 等效于具有进程块、但没有任何开始块或结束块的函数。

    来看看以下筛选器函数定义和调用:

    filter Get-Square2 { # make the function a filter
        $_ * $_ # access current object from the collection
    -3..3 | Get-Square2 # collection has 7 elements
    6, 10, -3 | Get-Square2 # collection has 3 elements
    

    每个筛选器都是类 System.Management.Automation.FilterInfo 的一个实例 (§4.5.11)。

    8.10.2 工作流函数

    工作流函数与具有实现定义的语义的普通函数类似。 工作流函数将转换为一系列 Windows Workflow Foundation 活动,并在 Windows Workflow Foundation 引擎中执行。

    8.10.3 自变量处理

    对于名为 Get-Power的函数,来看看以下定义:

    function Get-Power ([long]$base, [int]$exponent) {
        $result = 1
        for ($i = 1; $i -le $exponent; ++$i) {
            $result *= $base
        return $result
    

    此函数有两个参数:$base 和 $exponent。 它还包含一组语句,用于为非负指数值计算 $base^$exponent^ 并将结果返回给 Get-Power 的调用方。

    当脚本、函数或筛选器开始执行时,每个参数被初始化为其相应的自变量值。 如果没有相应的自变量,并且提供了默认值 (§8.10.4),则使用该值;否则,使用值 $null。 这样的话,每个参数都是一个新变量,就像它在 script-block 的开始部分通过赋值初始化一样。

    如果 script-parameter 包含类型约束(如上述 [long] 和 [int]),则相应自变量的值将被转换为该类型(如有必要);否则不会发生转换。

    当脚本、函数或筛选器开始执行时,变量 $args 在它内部定义为一个无约束一维数组,其中包含所有未以词法顺序按名称或位置绑定的自变量。

    请看以下函数定义和调用:

    function F ($a, $b, $c, $d) { ... }
    F -b 3 -d 5 2 4       # $a is 2, $b is 3, $c is 4, $d is 5, $args Length 0
    F -a 2 -d 3 4 5       # $a is 2, $b is 4, $c is 5, $d is 3, $args Length 0
    F 2 3 4 5 -c 7 -a 1   # $a is 1, $b is 2, $c is 7, $d is 3, $args Length 2
    

    有关参数绑定的详细信息,请参阅 §8.14。

    8.10.4 参数初始化表达式

    参数 p 的声明可能包含一个初始化表达式,在这种情况下,该初始化表达式的值用于初始化 p,前提是 p 未绑定到调用中的任何自变量。

    请看以下函数定义和调用:

    function Find-Str ([string]$str, [int]$start_pos = 0) { ... }
    Find-Str "abcabc" # 2nd argument omitted, 0 used for $start_pos
    Find-Str "abcabc" 2 # 2nd argument present, so it is used for $start_pos
    

    8.10.5 [switch] 类型约束

    在传递开关参数时,命令中的相应参数必须受类型开关约束。 类型开关有两个值:True 和 False。

    请看以下函数定义和调用:

    function Process ([switch]$trace, $p1, $p2) { ... }
    Process 10 20                # $trace is False, $p1 is 10, $p2 is 20
    Process 10 -trace 20         # $trace is True, $p1 is 10, $p2 is 20
    Process 10 20 -trace         # $trace is True, $p1 is 10, $p2 is 20
    Process 10 20 -trace:$false  # $trace is False, $p1 is 10, $p2 is 20
    Process 10 20 -trace:$true   # $trace is True, $p1 is 10, $p2 is 20
    

    8.10.6 管道和函数

    在管道中使用脚本、函数或筛选器时,值的集合将被传递到该脚本或函数。 脚本、函数或筛选器通过枚举器 $input(§2.3.2.2、§4.5.16)(在脚本、函数或筛选器的条目上定义)访问该集合。

    请看以下函数定义和调用:

    function Get-Square1 {
        foreach ($i in $input) {   # iterate over the collection
            $i * $i
    -3..3 | Get-Square1            # collection has 7 elements
    6, 10, -3 | Get-Square1        # collection has 3 elements
    

    8.10.7 命名块

    script-block 中的语句可以属于一个大型未命名块,也可以分布在一个或多个命名块中。 命名块允许自定义处理来自管道的集合;命名块可以按任何顺序定义。

    begin 块中的语句(即,使用关键字 begin 标记的语句)在传递第一个管道对象之前执行一次。

    process 块中的语句(即,使用关键字 process 标记的语句)对传递的每个管道对象执行一次。 ($_ 提供对来自管道的输入集合中的当前正在处理的对象的访问。)这意味着,如果通过管道发送零个元素的集合,则完全不会执行 process 块。 但是,如果在管道上下文外部调用脚本或函数,则此块刚好执行一次,且 $_ 设置为 $null,因为当前没有集合对象。

    end 块中的语句(即,使用关键字 end 标记的语句)在传递最后一个管道对象之后执行一次。

    8.10.8 dynamicParam 块

    到目前为止,§8.10 的子部分处理静态参数,这些参数被定义为源代码的一部分。 还可以通过 dynamicParam 块(另一种形式的命名块)(§8.10.7) 定义动态参数,该块使用关键字 dynamicParam 进行标记。 这种机制的大部分都是实现定义的。

    动态参数是 cmdlet、函数、筛选器或脚本的参数,仅在特定条件下可用。 其中一种情况是 Set-Item cmdlet 的 Encoding 参数。

    在 statement-block 中,使用 if 语句指定参数在函数中可用的条件。 使用 New-Object cmdlet 创建一个实现定义的类型的对象来表示参数,并指定其名称。 此外,使用 New-Object 创建另一个实现定义的类型的对象,以表示参数的实现定义的属性。

    下面的示例展示了一个函数,该函数具有名为 Name 和 Path 的标准参数,以及一个名为 DP1 的可选动态参数。 DP1 参数位于 PSet1 参数集内,其类型为 Int32。 仅当 Path 参数的值包含“HKLM:”,指示它正在 HKEY_LOCAL_MACHINE 注册表驱动器中使用时,DP1 参数才在 Sample 函数中可用。

    function Sample {
        Param ([String]$Name, [String]$Path)
        DynamicParam {
            if ($path -match "*HKLM*:") {
                $dynParam1 = New-Object System.Management.Automation.RuntimeDefinedParameter("dp1", [Int32], $attributeCollection)
                $attributes = New-Object System.Management.Automation.ParameterAttribute
                $attributes.ParameterSetName = 'pset1'
                $attributes.Mandatory = $false
                $attributeCollection = New-Object -Type System.Collections.ObjectModel.Collection``1[System.Attribute]
                $attributeCollection.Add($attributes)
                $paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
                $paramDictionary.Add("dp1", $dynParam1)
                return $paramDictionary
    

    用于创建对象以表示动态参数的类型为 System.Management.Automation.RuntimeDefinedParameter。

    用于创建对象以表示参数属性的类型为 System.Management.Automation.ParameterAttribute。

    参数的实现定义的属性包括 Mandatory、Position和 ValueFromPipeline。

    8.10.9 param 块

    param-block 提供了一种声明参数的替代方法。 例如,下面一组参数声明是等效的:

    function FindStr1 ([string]$str, [int]$start_pos = 0) { ... }
    function FindStr2 {
        param ([string]$str, [int]$start_pos = 0) ...
    

    param-block 允许在 param-block 上有 attribute-list,但 function-parameter-declaration 不允许。

    脚本可以有 param-block,但不能有 function-parameter-declaration。 函数或筛选器定义可以有 function-parameter-declaration 或 param-block,但不能同时具有这两者。

    请看下面的示例:

    param ( [Parameter(Mandatory = $true, ValueFromPipeline=$true)]
            [string[]] $ComputerName )
    

    一个参数 $ComputerName 的类型为 string[],它是必需参数,从管道接受输入。

    有关 Parameter 属性的介绍和更多示例,请参阅 §12.3.7。

    8.11 parallel 语句

    parallel-statement:
        *parallel* statement-block
    

    parallel 语句包含零个或多个以实现定义的方式执行的语句。

    parallel 语句只允许在工作流 (§8.10.2) 中使用。

    8.12 sequence 语句

    sequence-statement:
        *sequence* statement-block
    

    sequence 语句包含零个或多个以实现定义的方式执行的语句。

    sequence 语句只允许在工作流 (§8.10.2) 中使用。

    8.13 inlinescript 语句

    inlinescript-statement:
        inlinescript statement-block
    

    inlinescript 语句包含零个或多个以实现定义的方式执行的语句。

    inlinescript 语句只允许在工作流 (§8.10.2) 中使用。

    8.14 参数绑定

    调用脚本、函数、筛选器或 cmdlet 时,可以将每个自变量按位置绑定到相应的参数,第一个参数的位置为零。

    请看以下名为 Get-Power 的函数的定义片段,以及对它的调用:

    function Get-Power ([long]$base, [int]$exponent) { ... }
    Get-Power 5 3       # argument 5 is bound to parameter $base in position 0
                        # argument 3 is bound to parameter $exponent in position 1
                        # no conversion is needed, and the result is 5 to the power 3
    Get-Power 4.7 3.2   # double argument 4.7 is rounded to int 5, double argument
                        # 3.2 is rounded to int 3, and result is 5 to the power 3
    Get-Power 5         # $exponent has value $null, which is converted to int 0
    Get-Power           # both parameters have value $null, which is converted to int 0
    

    调用脚本、函数、筛选器或 cmdlet 时,可以将自变量按名称绑定到相应的参数。 为此,将参数与自变量一起使用,它是一个自变量,具体格式为带前导短划线 (-) 的参数名称,后跟该自变量的关联值。 使用的参数名称可以具有任何不区分大小写的拼写,且可以使用唯一指定相应参数的任何前缀。 选择参数名称时,请避免使用常用参数的名称。

    请看下面对函数 Get-Power 的调用:

    Get-Power -base 5 -exponent 3   # -base designates $base, so 5 is
                                    # bound to that, exponent designates
                                    # $exponent, so 3 is bound to that
    Get-Power -Exp 3 -BAs 5         # $base takes on 5 and $exponent takes on 3
    Get-Power -e 3 -b 5             # $base takes on 5 and $exponent takes on 3
    

    另一方面,调用以下函数

    function Get-Hypot ([double]$side1, [double]$side2) {
        return [Math]::Sqrt($side1 * $side1 + $side2 * $side2)
    

    必须使用参数 -side1 和 -side2 ,因为没有用于唯一指定参数的前缀。

    同一参数名称不能多次使用,无论是否有不同的关联自变量值。

    参数可以有属性 (§12)。 有关各个属性的信息,请参阅 §12.3 中的相应部分。 有关参数集的信息,请参阅 §12.3.7。

    脚本、函数、筛选器或 cmdlet 可以通过调用命令行和/或管道接收自变量。 以下是用于解析参数绑定的步骤,按顺序排列:

  • 绑定所有命名参数,然后
  • 绑定位置参数,然后
  • 使用完全匹配按值 (§12.3.7) 从管道绑定,然后
  • 使用转换按值 (§12.3.7) 从管道绑定,然后
  • 使用完全匹配按名称 (§12.3.7) 从管道绑定,然后
  • 使用转换按名称 (§12.3.7) 从管道绑定
  • 其中几个步骤涉及转换,如 §6. 中所述。 但是,绑定中使用的转换集与语言转换中使用的转换集不完全相同。 具体而言:

  • 尽管值 $null 可以强制强制转换为 bool,但不能将 $null 绑定到 bool。
  • 将值 $null 传递给 cmdlet 的开关参数时,它被视为已传递 $true。 但是,当传递给函数的开关参数时,它被视为已传递 $false。
  • bool 或 switch 类型的参数只能绑定到数值或布尔自变量。
  • 如果参数类型不是一个集合,但自变量是某种集合,则不会尝试转换,除非参数类型为 object 或 PsObject。 (此限制的要点是禁止将集合转换为字符串参数。)否则,将尝试进行常规转换。
  • 如果参数类型为 IList 或 ICollection<T>,则仅尝试通过构造函数 op_Implicit 和 op_Explicit 进行转换。 如果不存在此类转换,则对“collection”类型的参数(包括 IList、ICollection<T> 和数组)使用特殊转换。

    如果可能的话,位置参数最好在没有类型转换的情况下进行绑定。 例如,

    function Test {
        [CmdletBinding(DefaultParameterSetname = "SetB")]
        param([Parameter(Position = 0, ParameterSetname = "SetA")]
            [decimal]$dec,
            [Parameter(Position = 0, ParameterSetname = "SetB")]
            [int]$in
        $PsCmdlet.ParameterSetName
    Test 42d   # outputs "SetA"
    Test 42    # outputs "SetB"
    			即将发布:在整个 2024 年,我们将逐步淘汰作为内容反馈机制的“GitHub 问题”,并将其取代为新的反馈系统。 有关详细信息,请参阅:https://aka.ms/ContentUserFeedback。

    提交和查看相关反馈

  •