PowerShell 是一种基于.NETFramework 的命令行 shell 和脚本语言,它提供了访问 Windows 操作系统的许多强大功能,这些功能在传统的命令提示符(CMD)中难以访问。PowerShell 的核心是基于小型功能单元,称为 Cmdlets(命令-单元)。可以通过 Microsoft Technet 页面获取 Cmdlets 的列表。
PowerShell 脚本使用的脚本语言具有自己的语法。本章将简要概述语法,并解释 PowerShell 脚本中可能隐藏的一些陷阱。
变量以 '$' 开头定义:
$programFilesX86 = (${env:ProgramFiles(x86)}, ${env:ProgramFiles} -ne $null)[0]
上述示例将变量 "programFilesX86" 设置为 x86 程序的程序文件文件夹路径。如果 x86 平台上不存在该文件夹,则在 x86 文件夹不可用时,变量将设置为普通程序文件目录。
常量也以 '$' 开头定义:
$CONSTVAL = "This value can't be changed"
Set-Variable CONSTVAL -option ReadOnly
尽管解释器规范不需要这样做,但建议将常量全部大写,以区分变量。
PowerShell脚本文件是解释执行的,如果在调用之前没有读取到某个方法,PowerShell 将遇到错误。避免这种错误陷阱的最佳方法是在文件顶部定义一个名为 "Main" 的方法,然后在文件末尾定义一个跳转到这个方法的跳转:
function main{
# 主函数,如经典结构化程序中所知。
myFunction
}
function myFunction{
Write-Host "myFunction was called"
}
main # 跳转到执行 main
下面的例子本应做同样的事情,但由于在调用时 "myFunction" 函数尚未被解释,因此无法运行:
myFunction # 错误 - myFunction 尚未读取,但已经被调用
function myFunction{
Write-Host "myFunction was called" # 永远不会到达
}
与命令提示符一样,PowerShell提供了大量的 Cmdlets 来访问文件系统并修改文件和目录。
可以使用 Copy-Item Cmdlet 复制文件,其中第一个参数指定源文件,第二个参数指定目标文件的完整路径:
Copy-Item -Path "C:\Source\Powershell" -Destination "C:\Testumgebung"
通过在命令末尾添加 -Recurse 参数,可以使复制过程递归:
Copy-Item -Path "C:\Source\Powershell" -Destination "C:\Testumgebung" -Recurse
可以使用 Get-Item Cmdlet 获取文件或文件夹信息:
Get-Item -Path "C:\"
要获取路径的子项信息,可以使用带有通配符的 Get-Item 或 Get-ChildItem:
Get-Item -Path "C:\*"
Get-ChildItem -Path "C:\"
两个 Cmdlets 都返回 C:\ 的子项详细信息:
可以使用 Remove-Item Cmdlet 删除文件或目录:
Remove-Item -Path "C:\Source\Test1\*.txt" -Recurse -Force
-Recurse 参数只能应用于文件,而 -Force 也删除锁定的文件。在上述参数化中,任何 *.txt 文件将从 C:\Source\Test1 及其子文件夹中删除。
可以使用 Copy-Item Cmdlet 复制文件或目录:
Copy-Item -Path "C:\Source\*.txt" -Destination "C:\Source\Textfiles" -Recurse
此命令将从 C:\Source 及其子文件夹复制任何 *.txt 文件到 C:\Source\Textfiles,子文件夹结构保持不变。
PowerShell 提供了大量的 Cmdlets 来帮助分析和操作正在运行的进程。
可以使用几乎任何可能的选项启动新进程:
Start-Process powershell.exe
这将启动一个新的 PowerShell 实例。Start-Process 是一个强大的 Cmdlet,提供了许多参数。可以在 Microsoft Technet 主页上找到所有参数的列表。
要获取系统上所有正在运行的进程列表,可以使用 Get-Process:
Get-Process
这将显示所有正在运行的进程列表,包括一些附加信息:
当然,可以通过其名称搜索特定进程:
Get-Process powershell
这种行为可以通过搜索多个进程名称来扩展,例如 PowerShell 和 Windows Explorer:
Get-Process powershell,explorer
明白了 - 可以以这种方式检查任何数量的进程名称。如果正在寻找一个没有运行的进程,PowerShell 将返回一个错误(例如抛出)。可能还不知道的是,进程名称允许使用通配符,意味着:
Get-Process power*
将返回每个以 "power" 开头的正在运行的进程。那些碰巧知道 WMI 类 Win32_Process 的人可能已经意识到,所有演示的内容都可以通过这个类轻松完成。但 PowerShell 可以做更多:WMI 不会公开诸如公司或产品版本之类的属性,但 PowerShell 会。以下示例中,Get-Process 通过 Select-Object Cmdlet 过滤,除了进程名称、产品版本和公司之外的任何信息:
Get-Process powershell | Select-Object name,productversion,company
现在可能会问自己,应该如何知道哪些属性可以通过 get-process 获得,不是吗?可以自己找出来。以下行通过 get-member 过滤 get-process 并显示所有可用成员:
Get-Process | Get-Member
自己试试吧 - 会得到一个非常长的成员列表,可以自由使用它们。
当然,迟早会遇到需要停止正在运行的进程的情况 - 使用 stop-process Cmdlet 来完成它。它接受进程 ID 或进程名称作为参数:
Stop-Process 23498
Stop-Process -processname notepad
以下函数使用.NETSystem.Diagnostics 命名空间启动进程。当然,也可以简单地使用 Start-Process Cmdlet 来实现,但它为提供了在 PowerShell 脚本中使用 .NET 类的正确语法的一个很好的开始。
function StartProcess([string]$filePath, [array]$arguments, [bool] $waitForExit)
{
$pinfo = New-Object System.Diagnostics.ProcessStartInfo # 创建一个新的进程启动信息对象
$pinfo.FileName = $filePath # 将文件名设置为 filePath 参数
$pinfo.RedirectStandardError = $true # 标准错误和标准输出将被重定向
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = $arguments # 将参数设置为指定的 $arguments
$p = New-Object System.Diagnostics.Process # 创建一个进程
$p.StartInfo = $pinfo # 将启动信息分配给进程
$p.Start() | Out-Null # 启动进程
IF($waitForExit){ # 如果指定,则等待进程退出
$p.WaitForExit()
}
}
本章提供了一些编写的PowerShell函数示例。所有这些函数都提供了现有 Cmdlets 没有提供的功能,但在许多情况下仍然很有用。
仅创建目录会在目录已经存在时抛出错误。这个小助手函数通过仅在目录不存在时创建目录来避免这个问题:
function CreateDirectoryIfNotExisting([string]$dirPath){
IF(!(Test-Path -Path $dirPath)){# 检查目录是否存在
New-Item -ItemType directory -Path $dirPath # 如果不存在,则创建它
}
}
如果指定的文件在文件系统中缺失,则此函数将中止脚本执行。
function AbortIfFileMissing ([string]$filePath, [string]$name)
{
IF(!(Test-Path $filePath)){# 如果找不到文件则抛出
Throw ("ERROR: The File $name was not found! Expected path: $filePath")
}
ELSE{
Write-Host ("File " + $name + " found. It's located at " + $filePath)
}
}
function AbortIfDirectoryMissing ([string]$dirPath, [string]$name)
{
IF(!(Test-Path $dirPath)){# 如果找不到目录则抛出
Throw ("ERROR: The directory $name was not found! Expected path: $dirPath")
}
ELSE{
Write-Host ($name + " found. It's located at " + $dirPath)
}
}