PowerShell 函数是一段用于执行特定任务的代码块。创建并测试完成后,该函数可以在多个脚本中重复使用,从而减少编码工作量并降低出错风险。使用命名良好的函数还能让脚本更易于阅读和维护。此外,由于函数可以返回可作为其他函数或代码块输入的值,因此它们有助于构建更复杂的操作。
本文将解释你开始使用 PowerShell 函数所需了解的一切内容,包括重要的最佳实践,并提供深入探索更高级选项的指导。
PowerShell 函数入门
如何在 PowerShell 中创建函数
要定义函数,需要使用 function 关键字。基本语法如下:
function <Function-Name> {
<Code-Block>
}
花括号 {} 内的代码将在函数被调用时执行。
如果你愿意,你可以使用 param 关键字来指定函数参数,并可同时指定数据类型、默认值等。下面是添加了参数后的语法:
function <Function-Name> {
[param(
[<ParameterType>]$<ParameterName>,
[<ParameterType>]$<ParameterName2> = <DefaultValue>
)]
<Code-Block>
}
此 PowerShell 函数示例会将两个数字相乘并显示结果:
function Multiply-Numbers {
param (
[int]$Number1,
[int]$Number2
)
$Result = $Number1 * $Number2
Write-Output "The multiplication value of $Number1 and $Number2 is $Result."
}
保存函数
要保存函数,只需将其存储在扩展名为.ps1 的脚本文件中即可。
调用函数
在 PowerShell 中调用函数时,输入函数名称,然后输入任何必要的命名参数。例如,下面介绍如何调用我们在上方定义的函数:
Multiply-Numbers -number1 6 -Number2 8
在当前会话中作为一部分运行函数(Dot-Sourcing)
Dot-sourcing 使您可以像是在当前控制台会话中直接定义一样运行 PowerShell 脚本。要使用 Dot-sourcing,在调用函数时在函数名前加上点和空格(. )即可。
例如,假设我们已经定义了以下名为 Add-Numbers:
function Add-Numbers {
param (
[int]$Number1,
[int]$Number2
)
$Sum = $Number1 + $Number2
Write-Output "The sum of $Number1 and $Number2 is $Sum."
}
如下所示,我们可以将该函数进行 Dot-sourcing 以导入到当前会话中:
为 PowerShell 函数命名
为了从 PowerShell 函数中获得最大价值,请在为函数命名时务必遵循以下准则。
使用“动词-名词”格式。
PowerShell 提供了一组预先构建的 cmdlet,它们的名称遵循特定格式:用于说明要执行的操作的动词,后面跟用于命名要作用的对象的名词。例如,get-user 这个 cmdlet 会检索有关用户的信息,而remove-item 会删除指定的项。
遵循为你创建的函数使用相同的命名规范是一种最佳实践。这样做有助于你更有效地设计函数,并使它们更容易复用。
使用已批准的动词。
PowerShell 提供了一份建议在函数中使用的动词列表。要查看该列表,只需运行以下 cmdlet:
Get-Verb
选择具有描述性的名词。
至关重要的是,函数的名称不要与内置 cmdlet 使用相同的名称。防止这个问题的一个好方法是避免使用诸如 user 和 data. 之类的通用名词。相反,应选择能够清楚表明函数处理的数据类型的名词。例如,理解这些函数如何不同很容易: Get-EmployeeData, Get-UserInfo 和 Get-ServiceInfo。
避免在函数名称中用于版本管理。
在函数名后面追加版本号(例如 Get-UserInfoV2 )不建议这样做。这会让脚本更难理解,并增加使用旧版本函数的机会。相反,请通过文件版本管理系统来处理版本控制。
高级函数功能
接收管道输入
你可以使用 ValueFromPipeline 属性来设计一个函数,使其通过管道接收来自其他函数或命令的输入。
举例来说,让我们创建一个函数:逐个处理字符串列表,将每个字符串转换为大写并输出结果。为便于理解,我们将把字符串处理分成三个独立的模块:
- BEGIN — 该代码仅运行一次,并在处理任何管道输入之前执行。
- PROCESS — 该部分会针对传递到管道的每个项目运行,并逐一处理输入对象。
- END — 在所有输入都已处理完之后运行,用于完成并最终确定输出。
function Convert-ToUppercase {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $true, Mandatory = $true)]
[string]$InputString
)
BEGIN {
Write-Verbose "Starting the uppercase conversion process."
}
PROCESS {
if (-not [string]::IsNullOrWhiteSpace($InputString)) {
Write-Verbose "Processing string: $InputString"
$InputString.ToUpper()
} else {
Write-Verbose "Skipped empty or null string."
}
}
END {
Write-Verbose "Finished processing all strings."
}
}
下面是如何调用我们的函数来转换通过管道提供的字符串列表:
"hello", "world", "this", "is", "powershell" | Convert-ToUppercase -Verbose
请注意,称为 filters 的轻量级函数可以提供一种非常高效的方式来处理通过管道传递的对象。
让函数像 Cmdlet 一样运行
你可以使用 CmdletBinding 属性将一个基础函数转换为高级函数。这样做会让该函数像 cmdlet 一样运行,包括支持已定义的 PowerShell 参数。
下面是我们前面定义的,用于将两个数字相加的基本函数:
function Add-Numbers {
param (
[int]$Number1,
[int]$Number2
)
$Sum = $Number1 + $Number2
Write-Output "The sum of $Number1 and $Number2 is $Sum."
}
我们可以按如下方式将其转换为高级函数:
function Add-Numbers {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[int]$Number1,
[Parameter(Mandatory)]
[int]$Number2
)
process {
# Verbose Output
Write-Verbose "Starting addition of $Number1 and $Number2"
# Debugging Output
Write-Debug "Debugging: Ensuring numbers are valid integers"
# Perform the Addition
try {
$Result = $Number1 + $Number2
Write-Output $Result
} catch {
# Handle errors with error stream
Write-Error "An error occurred while performing the addition."
}
# Verbose Completion Message
Write-Verbose "Addition completed successfully"
}
}
申请 Netwrix Auditor 免费试用
构建函数模块
函数模块是一个带有 .psm1 扩展名的文件,里面包含一组相关函数。将函数组织到模块中便于重复使用和维护。
要创建函数模块,只需创建一个文本文件,将你的函数粘贴进去,然后以 .psm1 扩展名保存到 C:\Program Files\WindowsPowerShell\Modules 目录中。例如,我们可以创建一个包含算术函数的模块,例如前面定义的 Add-Numbers 函数。
要将模块导入到你的 PowerShell 会话中,请使用下面所示的 Import-Module 命令:
错误处理
PowerShell 提供多种错误处理技术,帮助你顺畅地处理那些本可能导致函数执行因错误而停止的问题,例如尝试除以零。
Try/Catch/Finally 块
当你知道某个特定条件可能会引发问题时,可以在函数中使用 Try、Catch 和 Finally 块,以便从容处理该条件:
- Try — 此块包含可能产生错误的代码(例如除法运算)。如果没有发生错误,则 Catch 块将不会被执行。
- Catch — 如果 Try 块中发生错误,Catch 块将通过捕获并处理错误详情来处理该错误。
- 最后 — 该代码块是可选的。若存在,则无论是否发生错误,都会在 Try 和 Catch 块之后执行。
-ErrorAction 参数
-ErrorAction 参数允许你指定特定函数中错误应如何处理。可能的值有:
- 继续 — 显示错误消息并继续。
- 停止 — 发生错误时停止脚本的执行。
- SilentlyContinue — 抑制错误消息并继续执行。
- Inquire — 提示用户如何处理错误,并提供诸如 Yes、 No 和 Retry 的选项。
- Ignore — 完全忽略错误,甚至不显示任何消息。
下面是 -ErrorAction 参数的一些示例:
Remove-Item "C:\path\to\nonexistentfile.txt" -ErrorAction SilentlyContinue
Get-Process -Name "Notepad" -ErrorAction Stop
$ErrorActionPreference 变量
变量 $ErrorActionPreference 控制会话中所有 cmdlet 的错误如何在全局范围内处理。可通过设置此变量来影响所有命令中的错误处理行为,除非由 -ErrorAction 参数覆盖。
详细示例
下面是一个使用多种错误处理方法的示例:
- $ErrorActionPreference 变量将全局错误处理偏好设置为 stop,因此当发生未被其他方式处理的错误时,执行将停止。
- Try/Catch 块会在不停止执行的情况下,妥善处理除以零可能导致的错误。Try 块执行数学运算。如果遇到除以零,处理会转到 Catch 块,该块会打印错误信息。
- 无论是否发生错误,Finally 代码块都会执行,并打印一条消息,表示操作已完成。
# Set global error action preference to 'Stop' to immediately halt on errors
$ErrorActionPreference = 'Stop'
function Perform-MathOperations {
param (
[int]$Num1,
[int]$Num2
)
try {
# Performing addition
Write-Host "Performing addition: $Num1 + $Num2"
$additionResult = $Num1 + $Num2
Write-Host "Addition Result: $additionResult"
# Checking for division by zero before performing division
if ($Num2 -eq 0) {
throw "Division by zero is not allowed."
}
# Performing division
Write-Host "Performing division: $Num1 / $Num2"
$divisionResult = $Num1 / $Num2
Write-Host "Division Result: $divisionResult"
}
catch {
# Catching any error that occurs in the try block
Write-Host "An error occurred: $_"
Write-Host "Error Type: $($_.Exception.GetType().Name)"
}
finally {
# This block always runs regardless of error occurrence
Write-Host "Finished performing math operations."
}
}
下面的截图展示了以两种方式调用此函数的结果。首先,我们使用有效输入调用它:
Perform-MathOperations -Num1 10 -Num2 2
然后,我们使用会导致尝试除以零的输入调用它:
Perform-MathOperations -Num1 10 -Num2 0
排查函数问题
在函数执行过程中,你可以使用 Write-output (或 Write-host), Write-Verbose 和 Write-Debug 来显示提供各种详细程度的消息。借助这些信息,你可以一步步追踪执行路径,从而定位意外行为。
例如,下面的截图展示了我们如何修改基础的 Add-Numbers 函数,以提供故障排查的详细信息。请注意,默认情况下不会显示冗长(verbose)消息流,因此调用该函数时需要使用 -Verbose。
Add-Numbers -Number1 5 -Number2 10 -Verbose
同样,Write-Debug 消息默认不会在控制台中显示,但你可以在执行函数时通过包含 -Debug 来显示:
Add-Numbers -Number1 5 -Number2 10 -Debug
最佳实践
- 为每个函数设计为只执行一项任务。这样可以更容易地进行测试、使用和维护。
- 以模块化方式设计函数,使其能够在不同脚本中重复使用。
- 使用参数时,请指定它们的默认值。
- 在函数中加入详细注释。基于注释的帮助将有助于调试并促进函数复用。
- 让你的函数返回对象,而不是格式化后的输出,以增强可复用性。
补充示例
下面的函数演示了前面讨论过的许多能力,包括错误处理:
# Function: Backup-Files
# Description: Backs up files from a source directory to a backup directory.
function Backup-Files {
param (
[Parameter(Mandatory = $true)]
[string]$SourcePath, # The directory containing the files to back up
[Parameter(Mandatory = $true)]
[string]$BackupPath, # The directory where files will be backed up
[string[]]$Extensions = @("*") # File extensions to back up (default: all files)
)
# Check if the source path exists
if (-not (Test-Path -Path $SourcePath)) {
Write-Error "Source path '$SourcePath' does not exist."
return
}
# Create the backup directory if it does not exist
if (-not (Test-Path -Path $BackupPath)) {
Write-Output "Creating backup directory at '$BackupPath'..."
New-Item -ItemType Directory -Path $BackupPath | Out-Null
}
# Get files matching the specified extensions
foreach ($extension in $Extensions) {
$files = Get-ChildItem -Path $SourcePath -Filter "*.$extension" -File -ErrorAction SilentlyContinue
foreach ($file in $files) {
$destination = Join-Path -Path $BackupPath -ChildPath $file.Name
Write-Output "Backing up file: $($file.FullName) -> $destination"
Copy-Item -Path $file.FullName -Destination $destination -Force
}
}
Write-Output "Backup completed successfully."
}
我们可以调用这个函数来备份所有文件,或者仅选择如这里所示的文件扩展名:
Backup-Files -SourcePath "D:\Office\project" -BackupPath "D:\Backup" -Extensions @("csv", "txt")
结论
PowerShell 中的函数为脚本编写提供了结构化的方法,从而提升模块化、可重用性和可维护性。欢迎你使用本文中提供的所有示例代码进行尝试。当你开始创建自己的函数时,请务必采用关于命名和使用函数的最佳实践。
面向初学者的 Windows PowerShell 脚本教程
下载电子书分享到
了解更多
关于作者
Tyler Reese
产品管理副总裁,CISSP
凭借在软件安全行业超过二十年的经验,Tyler Reese 对当今企业面临的快速演变的身份与安全挑战非常熟悉。目前,他担任 Netwrix Identity and Access Management 组合的产品总监;他的职责包括评估市场趋势、确定 IAM 产品线的发展方向,并最终满足终端用户的需求。他的职业经历从为财富 500 强公司提供 IAM 咨询,到在一家大型直销面向消费者(direct-to-consumer)的公司担任企业架构师,涵盖范围十分广泛。 目前,他持有 CISSP 认证。