63、PowerShell 脚本编写与命令执行全解析

PowerShell 脚本编写与命令执行全解析

1. PowerShell 类型扩展

PowerShell 在其安装目录下的 types.ps1xml 文件中添加了多个类型扩展。这个文件是很好的示例来源,但不要直接修改它。建议创建一个新的文件,并使用 Update-TypeData cmdlet 加载自定义内容。以下命令从配置文件所在目录加载 Types.Custom.Ps1Xml

$typesFile = Join-Path (Split-Path $profile) "Types.Custom.Ps1Xml"
Update-TypeData -PrependPath $typesFile
2. 脚本、函数和脚本块的编写

当你想要打包和复用命令时,最佳的方式是将它们放在脚本、函数和脚本块中。
- 脚本 :脚本是包含一系列 PowerShell 命令的文本文件。编写脚本时,在文本编辑器中编写 PowerShell 命令,然后将文件保存为 .ps1 扩展名。
- 函数 :函数可以将紧密相关的命令打包成一个可通过名称访问的单元。函数的定义语法如下:

function SCOPE:name(parameters)
{
    statement block
}

或者使用过滤器形式:

filter SCOPE:name(parameters)
{
    statement block
}

有效的作用域名称包括 global (创建对整个 shell 可用的函数)、 script (创建仅对当前脚本可用的函数)、 local (创建仅对当前作用域和子作用域可用的函数)和 private (创建仅对当前作用域可用的函数),默认作用域是 local

函数支持 $args 数组、形式参数、 $input 枚举器、cmdlet 关键字、管道输出和等效的返回语义。常见的错误是像调用方法一样调用函数,例如:

$result = GetMyResults($item1, $item2)

正确的方式应该是:

$result = GetMyResults $item1 $item2

过滤器是一种特殊的函数,其语句被视为包含在 process 语句块中。

为了使大型脚本更易于理解,可以采用以下结构:

function Main
{
    (...)
    HelperFunction
    (...)
}
function HelperFunction
{
    (...)
}
. Main
  • 脚本块 :脚本块就像没有名称的函数和脚本。定义脚本块的语法如下:
$objectReference =
{
    statement block
}

脚本块支持 $args 数组、形式参数、 $input 枚举器、cmdlet 关键字、管道输出和等效的返回语义。可以直接调用脚本块( & { "Hello" } )或调用包含它的变量( & $objectReference )。

3. 命令的运行

执行命令(脚本、函数或脚本块)有两种方式:调用和点源。
- 调用 :调用命令会运行其中的命令。除非使用 GLOBAL 作用域关键字明确定义,否则脚本中定义的变量和函数在脚本退出后不会保留。默认情况下,PowerShell 的执行策略会阻止脚本运行,需要使用 Set-ExecutionPolicy cmdlet 更改设置,例如:

Set-ExecutionPolicy RemoteSigned

如果命令名没有空格,直接输入其名称;如果命令名有空格或命令是脚本块,则使用调用运算符 & 。示例如下:

c:\temp\Invoke-Commands.ps1 parameter1 parameter2 ...
Invoke-MyFunction parameter1 parameter2 ...
& "C:\Script Directory\Invoke-Commands.ps1" parameter1 parameter2 ...
$scriptBlock = { "Hello World" }
& $scriptBlock parameter1 parameter2 ...

如果要在模块的上下文中调用命令,需要提供对该模块的引用:

$module = Get-Module PowerShellCookbook
& $module Invoke-MyFunction parameter1 parameter2 ...
& $module $scriptBlock parameter1 parameter2 ...
  • 点源 :点源命令也会运行其中的命令,但与调用不同的是,脚本中定义的变量和函数在脚本退出后会保留。使用点运算符 . 进行点源操作,示例如下:
. "C:\Script Directory\Invoke-Commands.ps1" Parameters
. Invoke-MyFunction parameters
. $scriptBlock parameters

同样,如果要在模块的上下文中点源命令,也需要提供对该模块的引用。

4. 参数的使用

需要或支持用户输入的命令通过参数来实现。可以使用 Get-Command cmdlet 查看命令支持的参数,例如:

PS > Get-Command Stop-Process -Syntax
Stop-Process [-Id] <int[]> [-PassThru] [-Force] [-WhatIf] [-Confirm] [...]
Stop-Process -Name <string[]> [-PassThru] [-Force] [-WhatIf] [-Confirm] [...]
Stop-Process [-InputObject] <Process[]> [-PassThru] [-Force] [-WhatIf] [...]

为参数提供值的方式有多种:
- 使用连字符、参数名、空格和参数值,例如: Stop-Process -Id 1234
- 如果参数值包含空格,用引号括起来,例如: Stop-Process -Name "Process With Spaces"
- 如果变量包含参数值,使用 PowerShell 的常规变量引用语法,例如:

$name = "Process With Spaces"
Stop-Process -Name $name
  • 如果要使用其他 PowerShell 语言元素作为参数值,用括号括起来,例如: Get-Process -Name ("Power" + "Shell")
  • 可以只提供足够的参数名来区分它与其他参数,例如: Stop-Process -N "Process With Spaces"
  • 如果命令语法中参数名在方括号中(如 [-Id] ),则该参数是位置参数,可以省略参数名,只提供值,例如: Stop-Process 1234
  • 还可以使用哈希表定义参数,并使用展开运算符,例如:
$parameters = @{
    Path = "c:\temp"
    Recurse = $true
}
Get-ChildItem @parameters

可以通过为 PSDefaultParameterValues 哈希表赋值来定义命令参数的默认值,例如:

PS > $PSDefaultParameterValues["Get-Process:ID"] = $pid
PS > Get-Process
PS > $PSDefaultParameterValues["Get-Service:Name"] = {
    Get-Service -Name * | Foreach-Object Name | Get-Random }
PS > Get-Service
5. 命令输入的提供方式

PowerShell 提供了多种处理命令输入的选项。
- 参数数组 :要按位置访问命令行参数,可以使用 PowerShell 放在 $args 特殊变量中的参数数组,示例如下:

$firstArgument = $args[0]
$secondArgument = $args[1]
$argumentCount = $args.Count
  • 形式参数 :定义支持简单参数的命令语法如下:
param(
    [TypeName] $VariableName = Default,
    ...
)

定义支持高级功能的命令语法如下:

[CmdletBinding(cmdlet behavior customizations)]
param(
    [Parameter(Mandatory = $true, Position = 1, ...)]
    [Alias("MyParameterAlias")]
    [...]
    [TypeName] $VariableName = Default,
    ...
)

[CmdletBinding()] 属性的元素描述了脚本或函数与系统的交互方式,包括 SupportsShouldProcess DefaultParameterSetName ConfirmImpact 等。 [Parameter()] 属性的元素主要定义了参数与其他参数的关系,包括 Mandatory Position ParameterSetName 等。

PowerShell 还允许应用其他属性来为参数添加行为或验证约束,例如 [Alias()] [AllowNull()] [ValidateCount()] 等。

  • 管道输入 :要访问通过管道传递给命令的数据,可以使用 PowerShell 放在 $input 特殊变量中的输入枚举器,示例如下:
foreach($element in $input)
{
    "Input was: $element"
}

如果需要以非结构化方式访问管道输入,可以将输入枚举器转换为数组:

$inputArray = @($input)
6. 命令中的 cmdlet 关键字

当管道输入是命令的核心场景时,可以包含标记为 begin process end 的语句块:

param(...)
begin
{
    ...
}
process
{
    ...
}
end
{
    ...
}

PowerShell 在加载命令时执行 begin 语句,为管道传递的每个项目执行 process 语句,在处理完所有管道输入后执行 end 语句。在 process 语句块中, $_ (或 $PSItem )变量表示当前管道对象。

7. $MyInvocation 自动变量

$MyInvocation 自动变量包含脚本运行的上下文信息,包括命令的详细信息( MyCommand )、定义它的脚本( ScriptName )等。

8. 命令输出的检索

PowerShell 提供了三种主要方式来检索命令的输出:
- 管道输出 :脚本的返回值或输出是它生成但未捕获的任何数据。例如:

"Text Output"
5*5

将该命令的输出分配给变量会创建一个包含 Text Output 25 两个值的数组。
- 返回语句 return value 语句是管道输出的简写形式,例如:

return $false

以下是一个简单的 mermaid 流程图,展示命令执行的两种方式:

graph LR
    A[执行命令] --> B{调用}
    A --> C{点源}
    B --> D[变量和函数不保留]
    C --> E[变量和函数保留]

表格总结参数传递方式:
| 传递方式 | 示例 |
| ---- | ---- |
| 直接指定参数名和值 | Stop-Process -Id 1234 |
| 用引号括起含空格的值 | Stop-Process -Name "Process With Spaces" |
| 使用变量 | $name = "Process With Spaces"; Stop-Process -Name $name |
| 使用括号包含 PowerShell 元素 | Get-Process -Name ("Power" + "Shell") |
| 省略部分参数名 | Stop-Process -N "Process With Spaces" |
| 位置参数 | Stop-Process 1234 |
| 使用哈希表和展开运算符 | $parameters = @{Path = "c:\temp"; Recurse = $true}; Get-ChildItem @parameters |

PowerShell 脚本编写与命令执行全解析

9. 参数属性及验证属性详解

在使用 PowerShell 编写脚本和函数时,参数的属性和验证属性起着至关重要的作用,它们能确保输入的合法性和有效性,下面详细介绍这些属性。

9.1 [CmdletBinding()] 属性
属性名 作用 示例
SupportsShouldProcess 若为 $true ,启用 -WhatIf -Confirm 参数,提示用户命令会修改系统,可在实验模式下运行。使用时需在修改系统状态前调用 $psCmdlet.ShouldProcess() 方法。默认值为 $false [CmdletBinding(SupportsShouldProcess = $true)]
DefaultParameterSetName 定义命令的默认参数集名称,用于解决参数声明多个参数集且用户输入信息不足时的歧义问题。未指定时,命令无默认参数集名称。 [CmdletBinding(DefaultParameterSetName = "Set1")]
ConfirmImpact 定义命令的确认影响级别,有 Low Medium High 三种。当命令的影响级别大于偏好变量时,PowerShell 会自动生成确认消息。默认值为 Medium [CmdletBinding(ConfirmImpact = "High")]
9.2 [Parameter()] 属性
属性名 作用 示例
Mandatory 定义参数为必需参数,若用户未提供值,PowerShell 会自动提示输入。默认情况下,参数为可选参数。 [Parameter(Mandatory = $true)]
Position 定义参数的位置,适用于用户不指定参数名而直接提供值的情况。PowerShell 会按位置顺序将值分配给参数。 [Parameter(Position = 1)]
ParameterSetName 定义参数所属的参数集,参数的行为特定于该参数集,且仅存在于定义的参数集中。 [Parameter(ParameterSetName = "Set1")]
ValueFromPipeline 声明参数可直接接受管道输入,若用户通过管道传递数据,PowerShell 会将输入分配给该参数。 [Parameter(ValueFromPipeline = $true)]
ValueFromPipelineByPropertyName 声明参数可接受管道输入,前提是传入对象的属性名与参数名匹配。 [Parameter(ValueFromPipelineByPropertyName = $true)]
ValueFromRemainingArguments 声明参数接受所有未分配给位置或命名参数的剩余输入,一个命令中只能有一个参数具有此属性。 [Parameter(ValueFromRemainingArguments = $true)]
9.3 参数验证属性
属性名 作用 示例
[Alias()] 为参数定义别名,方便使用长且描述性强的参数名。 [Alias("AliasName")]
[AllowNull()] 允许参数接受 $null 值,仅对必需参数有意义。 [AllowNull()]
[AllowEmptyString()] 允许字符串参数接受空字符串值,仅对必需参数有意义。 [AllowEmptyString()]
[AllowEmptyCollection()] 允许集合参数接受空集合值,仅对必需参数有意义。 [AllowEmptyCollection()]
[ValidateCount()] 限制集合参数中元素的数量范围。 [ValidateCount(1, 10)]
[ValidateLength()] 限制字符串参数的长度范围。 [ValidateLength(1, 20)]
[ValidatePattern()] 强制字符串参数的输入必须匹配指定的正则表达式。 [ValidatePattern("^[a-zA-Z]+$")]
[ValidateRange()] 限制数值参数的取值范围。 [ValidateRange(1, 100)]
[ValidateScript()] 确保参数输入满足脚本块中指定的条件。 [ValidateScript({ $_ -gt 0 })]
[ValidateSet()] 确保参数输入等于集合中的某个选项。 [ValidateSet("Option1", "Option2")]
[ValidateNotNull()] 确保参数输入不为 null ,对可选参数有用。 [ValidateNotNull()]
[ValidateNotNullOrEmpty()] 确保参数输入不为 null 或空,对可选参数有用。 [ValidateNotNullOrEmpty()]
10. 综合示例:编写一个带参数验证的函数

下面是一个综合示例,展示如何编写一个带参数验证的函数:

function Get-ProcessByName {
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "Medium")]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string]$ProcessName,

        [Parameter()]
        [ValidateRange(1, 10)]
        [int]$MaxResults = 5
    )

    if ($psCmdlet.ShouldProcess("Getting processes named $ProcessName")) {
        Get-Process -Name $ProcessName | Select-Object -First $MaxResults
    }
}

# 调用函数
Get-ProcessByName -ProcessName "powershell" -MaxResults 3

在这个示例中, Get-ProcessByName 函数接受两个参数: ProcessName MaxResults ProcessName 是必需参数,且使用 [ValidateNotNullOrEmpty()] 属性确保输入不为空。 MaxResults 是可选参数,使用 [ValidateRange(1, 10)] 属性限制其取值范围在 1 到 10 之间。函数还使用了 [CmdletBinding()] 属性,支持 -WhatIf -Confirm 参数,在执行操作前会提示用户确认。

11. 总结与最佳实践

通过以上内容的学习,我们了解了 PowerShell 脚本编写、命令执行、参数使用、输入输出处理等方面的知识。以下是一些总结和最佳实践:

  • 脚本结构 :对于大型脚本,采用 Main 函数和辅助函数的结构,使代码更易于理解和维护。
  • 参数使用 :合理使用参数属性和验证属性,确保输入的合法性和有效性。使用展开运算符简化参数传递。
  • 命令执行 :根据需求选择调用或点源命令,注意变量和函数的作用域。
  • 输入输出处理 :利用管道输入和输出,提高代码的灵活性和可复用性。

以下是一个 mermaid 流程图,展示编写一个完整 PowerShell 脚本的主要步骤:

graph LR
    A[定义需求] --> B[规划脚本结构]
    B --> C[编写函数和脚本块]
    C --> D[设置参数和验证]
    D --> E[处理输入和输出]
    E --> F[测试和调试]
    F --> G[优化和完善]

表格总结编写 PowerShell 脚本的最佳实践:
| 方面 | 最佳实践 |
| ---- | ---- |
| 脚本结构 | 使用 Main 函数和辅助函数,提高代码可读性。 |
| 参数使用 | 合理使用参数属性和验证属性,简化参数传递。 |
| 命令执行 | 根据需求选择调用或点源,注意作用域。 |
| 输入输出处理 | 利用管道输入输出,提高灵活性和可复用性。 |

希望这些内容能帮助你更好地掌握 PowerShell 脚本编写和命令执行的技巧,提升工作效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值