PowerShell 命令基础 ── 应先掌握的操作与安全使用方法

· · PowerShell, Windows, 命令行, 自动化, 存量资产利用

1. 首先应掌握的内容

PowerShell 是一个命令环境,可以用来检查 Windows 设置、整理文件、排查日志、加工 CSV、操作服务,以及自动化各种常规工作。

不过,一开始并不需要编写复杂的脚本。只要先记住下面这个流程,在实际工作中就已经相当够用了。

查找  查看  筛选  排序  输出  必要时再更改

PowerShell 的基础并不是「死记冗长的命令」。真正重要的是以下三点:

  1. 能用 Get-CommandGet-Help 查询命令
  2. 能用 | 管道把结果传递给下一个命令
  3. 在删除、停止、更改之前,先用 -WhatIf-Confirm 确认影响

PowerShell 可以在同一个窗口中执行「调查」与「更改」。方便的同时,删除或停止也能立即执行,因此养成从只读确认开始的习惯非常重要。

另外,本文中出现的代码,已按章节主题整理成一套示例(可以在临时文件夹中创建练习用工作区,安全地进行尝试),并附带 Pester 测试,整套发布在 GitHub 上。

powershell-command-basics - komurasoft-blog-samples (GitHub)

2. PowerShell 与命令提示符的区别

PowerShell 看起来和传统的命令提示符(cmd.exe)很相似,但底层的思路不同。

角度 命令提示符 PowerShell
主要输出 字符串 对象
命令名称 dircopy Get-ChildItemCopy-Item
结果加工 以字符串处理为主 用属性进行筛选、排序
自动化 批处理文件 .ps1 脚本
优势领域 与旧有命令资产兼容 Windows 管理、CSV、JSON、API、常规工作

举例来说,获取文件列表时,在 cmd.exe 中通常只能把 dir 的显示结果当作一堵字符串来看。而在 PowerShell 中,文件会被当作拥有 NameLengthLastWriteTime 等属性的对象来处理。

Get-ChildItem

理解这个区别之后,像「只显示 7 天内更新过的文件,并按更新时间从新到旧排序」这类处理,就能自然而然地写出来。

Get-ChildItem -File |
  Where-Object { $_.LastWriteTime -gt (Get-Date).AddDays(-7) } |
  Sort-Object LastWriteTime -Descending |
  Select-Object Name, Length, LastWriteTime

3. Windows PowerShell 5.1 与 PowerShell 7.x

Windows 中,有很早就内置的 Windows PowerShell 5.1,也有新系列的 PowerShell 7.x

在实务中,可以用下面的方式来理解。

种类 可执行文件 主要用途
Windows PowerShell 5.1 powershell.exe 旧有的 Windows 专用模块、既有脚本、公司内部资产的维护
PowerShell 7.x pwsh.exe 新脚本、跨平台、持续的功能改进

如果是重新学习,以 PowerShell 7.x 为基准没有问题。不过,公司内部旧有的管理脚本或 Windows 专用模块,有时是以 Windows PowerShell 5.1 为前提编写的。

因此在实务中,第一步要先确认版本。

$PSVersionTable

如果脚本需要在特定版本以上才能运行,可以在开头写上条件,这样能减少事故。

#Requires -Version 7.0

4. Cmdlet 的命名遵循「动词-名词」

PowerShell 的标准命令,基本上都是「动词-名词」的形式。

Get-Process
Get-Service
Get-ChildItem
Copy-Item
Remove-Item
Export-Csv

只要记住这个形式,即使是不熟悉的命令也容易查找。

动词 含义 示例
Get 获取 Get-Process
Set 设置 Set-Location
New 创建 New-Item
Copy 复制 Copy-Item
Move 移动 Move-Item
Remove 删除 Remove-Item
Start 启动 Start-Service
Stop 停止 Stop-Process
Import 导入 Import-Csv
Export 导出 Export-Csv

虽然像 dirlscatcd 这类简短名称也能使用,但大多是别名。

Get-Command dir

如果要保留成脚本,使用正式名称而不是别名会更安全。

# 能看懂,但脚本中应避免
ls *.log

# 意图明确
Get-ChildItem -Filter *.log

5. 首先应学会的三个查询命令

PowerShell 的命令数量非常多,与死记硬背相比,学会「查询的方法」更符合实务需求。

1. Get-Command ── 查找可用的命令

Get-Command 是用来查找已安装的命令、函数、别名以及应用程序的命令。

# 查找名词为 Process 的命令
Get-Command -Noun Process

# 查找与 Service 相关的命令
Get-Command *Service*

# 查找与 CSV 相关的命令
Get-Command *Csv*

如果记不清楚命令名称,可以使用通配符。

Get-Command *Item*
Get-Command *Content*
Get-Command *Json*

2. Get-Help ── 查看用法

Get-Help 是用来确认命令的说明、参数以及示例的命令。

Get-Help Get-ChildItem
Get-Help Get-ChildItem -Examples
Get-Help Get-ChildItem -Full
Get-Help Get-ChildItem -Online

一开始 -Examples 相当实用。

Get-Help Where-Object -Examples

如果帮助内容已经过时或不完整,可以在管理员权限的 PowerShell 中更新帮助文件。

Update-Help

根据环境不同,这可能需要网络连接与相应权限。在公司电脑上,也可能因为代理或管理策略而失败。

3. Get-Member ── 查看对象的内容

PowerShell 的输出,多数情况下并不是「字符串」,而是「对象」。使用 Get-Member,就能确认该对象拥有哪些属性与方法。

Get-Process | Get-Member
Get-Service | Get-Member
Get-ChildItem | Get-Member

举例来说,服务就有 StatusName 等属性。

Get-Service | Select-Object Name, Status

文件则有 NameLengthLastWriteTime 等属性。

Get-ChildItem -File | Select-Object Name, Length, LastWriteTime

当你「不知道该用什么条件筛选」时,先用 Get-Member 确认属性名称。

6. 管道的基础

PowerShell 的 | 会把左边命令的结果传递给右边的命令。

Get-Process | Sort-Object CPU -Descending | Select-Object -First 10

这个示例会从左到右依次执行下面的处理。

获取进程列表
    CPU 使用量从高到低排序
   只显示前 10 

常用的组合如下。

命令 作用 示例
Where-Object 按条件筛选 只获取已停止的服务
Sort-Object 排序 按更新时间从新到旧排序
Select-Object 选择列或数量 只显示名称与大小
ForEach-Object 对每个元素进行处理 逐个处理每个文件
Export-Csv 输出为 CSV 保存排查结果

Where-Object ── 按条件筛选

# 只显示已停止的服务
Get-Service | Where-Object { $_.Status -eq "Stopped" }

# 只显示超过 100MB 的文件
Get-ChildItem -File |
  Where-Object { $_.Length -gt 100MB }

# 只显示名称中包含 backup 的文件
Get-ChildItem -File |
  Where-Object { $_.Name -like "*backup*" }

$_ 代表管道中当前流过来的对象。

Where-Object { $_.Length -gt 100MB }

这是在判断「当前这个文件的 Length 是否大于 100MB」。

PowerShell 3.0 之后,也可以使用简化语法。

Get-Service | Where-Object Status -EQ "Stopped"

不过,刚开始学习时,scriptblock 语法更容易追踪含义。

Get-Service | Where-Object { $_.Status -eq "Stopped" }

Sort-Object ── 排序

# 内存使用量从大到小
Get-Process |
  Sort-Object WorkingSet -Descending |
  Select-Object -First 10 Name, Id, WorkingSet

# 更新时间从新到旧
Get-ChildItem -File |
  Sort-Object LastWriteTime -Descending |
  Select-Object -First 20 Name, LastWriteTime

Select-Object ── 只选择需要的列

Get-Process |
  Select-Object Name, Id, CPU, WorkingSet

也可以用来限制数量。

Get-Process | Select-Object -First 5
Get-Process | Select-Object -Last 5

输出为 CSV 之前,先用 Select-Object 整理成只保留必要的列,之后处理起来会更方便。

Get-Service |
  Select-Object Name, DisplayName, Status, StartType |
  Export-Csv .\services.csv -NoTypeInformation -Encoding UTF8

7. Format 系列命令要放在最后使用

PowerShell 有一些用来整理显示外观的命令。

Format-Table
Format-List
Format-Wide

这些命令是给「屏幕显示用」的。如果在输出到 CSV 之前,或是传递给后续处理之前使用,就会变成显示用的对象,而不是原本的属性。

# 应避免:CSV 中混入了显示用信息
Get-Process |
  Format-Table Name, CPU |
  Export-Csv .\process.csv -NoTypeInformation

# 正确做法:先选择属性,再输出 CSV
Get-Process |
  Select-Object Name, CPU |
  Export-Csv .\process.csv -NoTypeInformation -Encoding UTF8

记住 Format-* 是「最后才用来显示给屏幕看的」,就能减少事故。

8. 文件・文件夹操作的基础

PowerShell 中最常用到的,就是文件与文件夹的操作。

想做的事 命令
查看当前位置 Get-Location
移动位置 Set-Location
查看列表 Get-ChildItem
创建文件・文件夹 New-Item
复制 Copy-Item
移动 Move-Item
重命名 Rename-Item
删除 Remove-Item
确认是否存在 Test-Path

确认当前位置

Get-Location

移动位置。

Set-Location C:\Work

如果路径中包含空格,要用引号括起来。

Set-Location "C:\Work Files\Reports"

查看文件列表

Get-ChildItem
Get-ChildItem -File
Get-ChildItem -Directory
Get-ChildItem -Recurse

按扩展名筛选时,-Filter 很方便。

Get-ChildItem -Filter *.log

如果要连同子文件夹一起搜索,使用 -Recurse

Get-ChildItem C:\Logs -Filter *.log -Recurse

在文件数量庞大的位置,不要一开始就直接用 -Recurse,应先限定目标文件夹之后再执行。

创建

# 创建文件夹
New-Item -ItemType Directory -Path .\archive

# 创建空文件
New-Item -ItemType File -Path .\memo.txt

如果可能已经存在,先进行确认。

if (-not (Test-Path .\archive)) {
  New-Item -ItemType Directory -Path .\archive
}

复制

Copy-Item .\report.xlsx .\backup\report.xlsx

如果要连同文件夹一起复制,使用 -Recurse

Copy-Item .\data .\backup\data -Recurse

若可能涉及覆盖,要事先确认影响。

Copy-Item .\data .\backup\data -Recurse -WhatIf

移动

Move-Item .\old.log .\archive\old.log

如果要按条件移动多个文件,第一次先加上 -WhatIf

Get-ChildItem .\logs -Filter *.log |
  Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-30) } |
  Move-Item -Destination .\archive -WhatIf

没问题的话,再去掉 -WhatIf

Get-ChildItem .\logs -Filter *.log |
  Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-30) } |
  Move-Item -Destination .\archive

删除

删除操作要特别谨慎地处理。

Remove-Item .\old.log -WhatIf

确认目标正确无误之后才执行。

Remove-Item .\old.log

通配符与 -Recurse 的组合威力强大。不要一开始就在生产环境的文件夹中执行,应先用列表显示来确认。

# 先确认目标
Get-ChildItem C:\Logs -Filter *.tmp -Recurse |
  Select-Object FullName, Length, LastWriteTime

# 接着确认将要删除的内容
Get-ChildItem C:\Logs -Filter *.tmp -Recurse |
  Remove-Item -WhatIf

# 最后执行
Get-ChildItem C:\Logs -Filter *.tmp -Recurse |
  Remove-Item

9. 读取、搜索、写入文本文件

在日志排查中,经常会用到文本文件操作。

读取文件

Get-Content .\app.log

只想看结尾部分时,使用 -Tail

Get-Content .\app.log -Tail 50

想持续查看不断追加内容的日志时,使用 -Wait

Get-Content .\app.log -Tail 20 -Wait

搜索字符串

Select-String -Path .\app.log -Pattern "ERROR"

也可以同时搜索多个文件。

Select-String -Path .\logs\*.log -Pattern "ERROR", "WARN"

也能从搜索结果中提取文件名、行号、内容,输出为 CSV。

Select-String -Path .\logs\*.log -Pattern "ERROR" |
  Select-Object Path, LineNumber, Line |
  Export-Csv .\error-lines.csv -NoTypeInformation -Encoding UTF8

写入文件

"hello" | Set-Content .\memo.txt -Encoding UTF8
"next line" | Add-Content .\memo.txt -Encoding UTF8

要保存命令结果时,比起 Out-FileExport-CsvConvertTo-Json 有时之后处理起来更方便。

Get-Process |
  Select-Object Name, Id, CPU |
  Export-Csv .\process.csv -NoTypeInformation -Encoding UTF8

10. 处理 CSV 的基础

在业务自动化中,CSV 出现的频率非常高。在 PowerShell 中,CSV 不会被当作字符串,而是当作「带有列的对象」来处理。

读取 CSV

假设有下面这个 users.csv

Name,Department,Enabled
Suzuki,Sales,true
Tanaka,Accounting,false
Sato,Sales,true

读取它。

$users = Import-Csv .\users.csv
$users

列名会变成属性。

$users | Select-Object Name, Department

按条件筛选

$users |
  Where-Object { $_.Department -eq "Sales" }

CSV 中的值多半以字符串形式存入,因此在处理 true / false 或数值时要特别注意。

$users |
  Where-Object { $_.Enabled -eq "true" }

输出为 CSV

$users |
  Where-Object { $_.Department -eq "Sales" } |
  Export-Csv .\sales-users.csv -NoTypeInformation -Encoding UTF8

Export-Csv 之前不要加入 Format-Table,这是铁律。

# 应避免
$users | Format-Table | Export-Csv .\out.csv -NoTypeInformation

# 正确做法
$users | Select-Object Name, Department, Enabled | Export-Csv .\out.csv -NoTypeInformation -Encoding UTF8

11. 处理 JSON 的基础

配置文件或 Web API 中,也经常会用到 JSON。

$data = Get-Content .\settings.json -Raw | ConvertFrom-Json
$data

把对象转换为 JSON。

[pscustomobject]@{
  Name = "BatchJob"
  Enabled = $true
  Retry = 3
} | ConvertTo-Json

如果包含较深的层级,要指定 -Depth

$config | ConvertTo-Json -Depth 10 | Set-Content .\settings.json -Encoding UTF8

12. 进程与服务的基础

查看进程

Get-Process

确认内存使用量较大的进程。

Get-Process |
  Sort-Object WorkingSet -Descending |
  Select-Object -First 10 Name, Id, WorkingSet

按名称筛选。

Get-Process -Name notepad

停止时要特别谨慎。

Stop-Process -Name notepad -WhatIf

没问题的话再执行。

Stop-Process -Name notepad

查看服务

Get-Service

只想查看已停止的服务时。

Get-Service |
  Where-Object { $_.Status -eq "Stopped" }

按名称筛选。

Get-Service -Name "Spooler"

要重新启动时,也要先确认目标。

Get-Service -Name "Spooler"
Restart-Service -Name "Spooler" -WhatIf

服务操作容易影响业务,因此在生产环境中,应先确认操作手册、维护时间窗口,以及恢复方式。

13. 事件日志的基础

在 Windows 排查中,事件日志是不可或缺的。

确认最近的错误。

Get-WinEvent -LogName System -MaxEvents 100 |
  Where-Object { $_.LevelDisplayName -eq "Error" } |
  Select-Object TimeCreated, ProviderName, Id, Message

查看应用程序日志中最近 50 条。

Get-WinEvent -LogName Application -MaxEvents 50 |
  Select-Object TimeCreated, ProviderName, Id, LevelDisplayName, Message

按时间段筛选时。

$start = (Get-Date).AddHours(-24)

Get-WinEvent -FilterHashtable @{
  LogName = "System"
  StartTime = $start
} |
  Select-Object TimeCreated, ProviderName, Id, LevelDisplayName, Message

把排查结果留存为 CSV,之后分享起来会更方便。

Get-WinEvent -LogName System -MaxEvents 500 |
  Where-Object { $_.LevelDisplayName -in @("Error", "Warning") } |
  Select-Object TimeCreated, ProviderName, Id, LevelDisplayName, Message |
  Export-Csv .\system-events.csv -NoTypeInformation -Encoding UTF8

14. 变量・数组・哈希表

即使只用简短命令也能完成工作,但只要稍微复杂一点,变量就会变得很方便。

变量

$path = "C:\Logs"
Get-ChildItem $path

PowerShell 的变量以 $ 开头。

$today = Get-Date
$limit = (Get-Date).AddDays(-30)

数组

$extensions = @("*.log", "*.txt", "*.csv")
foreach ($ext in $extensions) {
  Get-ChildItem C:\Work -Filter $ext
}

哈希表

哈希表是键与值的组合。

$params = @{
  Path = "C:\Logs"
  Filter = "*.log"
  Recurse = $true
}

Get-ChildItem @params

这种 @params 的写法称为「参数展开(splatting)」。当参数变多时,能让代码更容易阅读。

15. 用 PSCustomObject 整理结果

想把排查结果整理成表格形式时,[pscustomobject] 相当方便。

[pscustomobject]@{
  ComputerName = $env:COMPUTERNAME
  UserName     = $env:USERNAME
  CheckedAt    = Get-Date
}

也可以生成多条结果并输出为 CSV。

Get-ChildItem C:\Logs -Filter *.log |
  ForEach-Object {
    [pscustomobject]@{
      Name          = $_.Name
      FullName      = $_.FullName
      SizeMB        = [math]::Round($_.Length / 1MB, 2)
      LastWriteTime = $_.LastWriteTime
    }
  } |
  Export-Csv .\log-files.csv -NoTypeInformation -Encoding UTF8

16. 脚本文件 .ps1 的基础

经常使用的处理,可以整理成 .ps1 文件。

作为示例,我们来创建一个把旧日志列成清单的脚本。

# Find-OldLogs.ps1
param(
  [string]$Path = "C:\Logs",
  [int]$Days = 30,
  [string]$OutputPath = ".\old-logs.csv"
)

$limit = (Get-Date).AddDays(-$Days)

Get-ChildItem -Path $Path -Filter *.log -File -Recurse |
  Where-Object { $_.LastWriteTime -lt $limit } |
  Select-Object FullName, Length, LastWriteTime |
  Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8

Write-Host "Exported: $OutputPath"

执行示例。

.\Find-OldLogs.ps1 -Path C:\Logs -Days 60 -OutputPath .\old-logs.csv

param() 接收参数,之后就能更容易更改条件。

17. 执行策略的基础

尝试运行 .ps1 时,有时会出现下面这样的错误。

无法加载文件,因为在此系统上禁止运行脚本...

这时,先确认当前的执行策略。

Get-ExecutionPolicy
Get-ExecutionPolicy -List

如果只是在个人开发机器上,想运行本地创建的脚本,通常会在当前用户范围内设置为 RemoteSigned

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

不过,公司电脑有时会由组策略统一管控。不要强行绕过限制,应先向管理员或按照运维规则确认。

执行策略是用来控制 PowerShell 脚本执行条件的安全机制。但它并不是完整的安全边界。在组织中,需要与签名、AppLocker、Microsoft Defender、权限管理、日志审计一起考虑。

18. 错误处理的基础

PowerShell 有时即使发生错误,仍会继续执行。

在重要的处理中,要使用 -ErrorAction Stoptry/catch

try {
  Copy-Item .\source.txt .\backup\source.txt -ErrorAction Stop
  Write-Host "Copy succeeded"
}
catch {
  Write-Error "Copy failed: $($_.Exception.Message)"
}

处理多个文件时,记录失败的项目,之后就能追溯。

$results = foreach ($file in Get-ChildItem .\input -File) {
  try {
    Copy-Item $file.FullName .\backup -ErrorAction Stop

    [pscustomobject]@{
      FileName = $file.Name
      Status   = "OK"
      Message  = ""
    }
  }
  catch {
    [pscustomobject]@{
      FileName = $file.Name
      Status   = "NG"
      Message  = $_.Exception.Message
    }
  }
}

$results | Export-Csv .\copy-result.csv -NoTypeInformation -Encoding UTF8

19. 安全执行变更类命令的顺序

在 PowerShell 中,安全地处理删除、移动、停止等变更类命令的顺序非常重要。

基本顺序如下。

1.  Get 系列查看目标
2.  Where-Object 筛选
3.  Select-Object 确认目标列表
4.  Export-Csv 记录
5.  -WhatIf 确认变更计划
6. 正式执行

以删除 30 天以前的 .tmp 文件为例。

第 1 步:查看目标

Get-ChildItem C:\Temp -Filter *.tmp -File -Recurse

第 2 步:按条件筛选

$limit = (Get-Date).AddDays(-30)

Get-ChildItem C:\Temp -Filter *.tmp -File -Recurse |
  Where-Object { $_.LastWriteTime -lt $limit }

第 3 步:只显示必要的列

$targets = Get-ChildItem C:\Temp -Filter *.tmp -File -Recurse |
  Where-Object { $_.LastWriteTime -lt $limit }

$targets |
  Select-Object FullName, Length, LastWriteTime

第 4 步:留下记录

$targets |
  Select-Object FullName, Length, LastWriteTime |
  Export-Csv .\delete-targets.csv -NoTypeInformation -Encoding UTF8

第 5 步:用 WhatIf 确认

$targets | Remove-Item -WhatIf

第 6 步:执行

$targets | Remove-Item

按照这个顺序,就能减少「不知道删掉了什么」这类事故。

20. 常用基本命令一览

位置・文件

目的 命令示例
显示当前文件夹 Get-Location
切换文件夹 Set-Location C:\Work
显示列表 Get-ChildItem
只显示文件 Get-ChildItem -File
只显示文件夹 Get-ChildItem -Directory
同时搜索子文件夹 Get-ChildItem -Recurse
确认是否存在 Test-Path .\file.txt
创建文件夹 New-Item -ItemType Directory .\backup
复制 Copy-Item .\a.txt .\backup\a.txt
移动 Move-Item .\a.txt .\archive\a.txt
确认将要删除的内容 Remove-Item .\a.txt -WhatIf

对象加工

目的 命令示例
按条件筛选 Where-Object { $_.Status -eq "Running" }
排序 Sort-Object LastWriteTime -Descending
选择列 Select-Object Name, LastWriteTime
只显示前几条 Select-Object -First 10
处理每个元素 ForEach-Object { $_.Name }
查看内容 Get-Member

输入输出

目的 命令示例
读取文本内容 Get-Content .\app.log
查看结尾 Get-Content .\app.log -Tail 50
搜索字符串 Select-String -Path .\app.log -Pattern "ERROR"
读取 CSV Import-Csv .\users.csv
输出 CSV Export-Csv .\out.csv -NoTypeInformation -Encoding UTF8
读取 JSON Get-Content .\a.json -Raw | ConvertFrom-Json
输出 JSON $obj | ConvertTo-Json -Depth 10

Windows 排查

目的 命令示例
进程列表 Get-Process
服务列表 Get-Service
事件日志 Get-WinEvent -LogName System -MaxEvents 100
环境变量 Get-ChildItem Env:
PowerShell 版本 $PSVersionTable
执行策略 Get-ExecutionPolicy -List

21. 实务示例:排查日志并生成报表

考虑下面这样的需求。

想从 C:\App\Logs 下的 .log 文件中,找出最近 7 天内更新且包含 ERROR 的行,整理成 CSV。

不要一开始就写出完成版,逐步构建。

第 1 步:查找日志文件

Get-ChildItem C:\App\Logs -Filter *.log -File -Recurse

第 2 步:筛选最近 7 天内的文件

$since = (Get-Date).AddDays(-7)

Get-ChildItem C:\App\Logs -Filter *.log -File -Recurse |
  Where-Object { $_.LastWriteTime -ge $since }

第 3 步:搜索 ERROR

$since = (Get-Date).AddDays(-7)

Get-ChildItem C:\App\Logs -Filter *.log -File -Recurse |
  Where-Object { $_.LastWriteTime -ge $since } |
  Select-String -Pattern "ERROR"

第 4 步:输出为 CSV

$since = (Get-Date).AddDays(-7)

Get-ChildItem C:\App\Logs -Filter *.log -File -Recurse |
  Where-Object { $_.LastWriteTime -ge $since } |
  Select-String -Pattern "ERROR" |
  Select-Object Path, LineNumber, Line |
  Export-Csv .\error-report.csv -NoTypeInformation -Encoding UTF8

第 5 步:整理成脚本

# Export-ErrorReport.ps1
param(
  [string]$LogPath = "C:\App\Logs",
  [int]$Days = 7,
  [string]$Pattern = "ERROR",
  [string]$OutputPath = ".\error-report.csv"
)

$since = (Get-Date).AddDays(-$Days)

Get-ChildItem $LogPath -Filter *.log -File -Recurse |
  Where-Object { $_.LastWriteTime -ge $since } |
  Select-String -Pattern $Pattern |
  Select-Object Path, LineNumber, Line |
  Export-Csv $OutputPath -NoTypeInformation -Encoding UTF8

Write-Host "Exported: $OutputPath"

执行示例。

.\Export-ErrorReport.ps1 -LogPath C:\App\Logs -Days 14 -Pattern "ERROR|FATAL" -OutputPath .\errors.csv

22. 实务示例:归档旧文件

接下来是不做删除,而是改为移动的示例。

把 C:\Work\Reports 中 90 天以上未更新的 .xlsx 文件,移动到 C:\Work\Archive。

确认目标

$source = "C:\Work\Reports"
$dest = "C:\Work\Archive"
$limit = (Get-Date).AddDays(-90)

$targets = Get-ChildItem $source -Filter *.xlsx -File |
  Where-Object { $_.LastWriteTime -lt $limit }

$targets | Select-Object FullName, Length, LastWriteTime

创建归档目标位置

if (-not (Test-Path $dest)) {
  New-Item -ItemType Directory -Path $dest
}

输出记录

$targets |
  Select-Object FullName, Length, LastWriteTime |
  Export-Csv .\archive-targets.csv -NoTypeInformation -Encoding UTF8

WhatIf 确认

$targets | Move-Item -Destination $dest -WhatIf

执行

$targets | Move-Item -Destination $dest

如果可能发生文件名冲突,这样写会失败。实务中,应事先制定规则,例如按年月拆分文件夹、在目标文件名中加上时间戳,或是已存在同名文件时就跳过。

23. 常见的卡点

现象 原因 应对方法
命令太长记不住 想靠死记硬背 Get-CommandGet-Help -Examples 查询
看不懂 $_ 对管道当前值理解不足 Where-Object { $_.Name -like "*log*" } 这样的形式来记忆
CSV 内容变得很奇怪 Format-Table 之后执行了 Export-Csv Select-Object 之后再执行 Export-Csv
路径含有空格会失败 没有加引号 "C:\Work Files\a.txt" 这样括起来
脚本无法运行 执行策略 Get-ExecutionPolicy -List 确认
删除目标过多 条件范围太宽 先用 Select-Object FullName-WhatIf 确认
不知道属性名称 没有查看对象结构 使用 Get-Member
出现乱码 编码假设不一致 确认输入输出的 -Encoding 以及所用的应用程序

24. 学习命令的顺序

一开始并不需要记住所有命令。

推荐按下面的顺序学习。

第 1 阶段:查看・查找

Get-Command
Get-Help
Get-Member
Get-Location
Set-Location
Get-ChildItem
Get-Content
Select-String

第 2 阶段:筛选・整理

Where-Object
Sort-Object
Select-Object
ForEach-Object
Format-Table
Format-List

第 3 阶段:输入输出

Import-Csv
Export-Csv
ConvertFrom-Json
ConvertTo-Json
Set-Content
Add-Content
Out-File

第 4 阶段:更改

New-Item
Copy-Item
Move-Item
Rename-Item
Remove-Item
Start-Service
Stop-Service
Restart-Service
Stop-Process

变更类命令,一定要搭配确认类命令一起学习。

# 查看
Get-ChildItem .\logs -Filter *.tmp

# 查看将要删除的内容
Get-ChildItem .\logs -Filter *.tmp | Remove-Item -WhatIf

# 执行
Get-ChildItem .\logs -Filter *.tmp | Remove-Item

25. 现场运维检查清单

在业务中使用 PowerShell 时,先确认下面这些要点会比较安全。

  • 已确认运行的是 powershell.exe 还是 pwsh.exe
  • 已用 $PSVersionTable 确认版本
  • 变更前已用 Get-* 系列命令显示目标
  • 已在屏幕上确认过 Where-Object 的条件
  • 删除、移动、停止之前已使用 -WhatIf
  • 已把执行前的目标列表保存为 CSV
  • 在生产环境中已确认备份或恢复流程
  • 已确认脚本的执行策略与公司内部规则
  • 已准备好错误时的日志输出
  • 在共享的脚本中使用了正式命令名称而不是别名

26. 总结

PowerShell 的基础并不是大量死记命令。在实务中真正有效的,是养成下面这套模式:

 Get-Command 查找
 Get-Help 查看用法
 Get-Member 确认属性
 Get-* 查看目标
 Where-Object 筛选
 Select-Object 整理列
 Export-Csv 留下记录
 -WhatIf 确认变更计划
最后才执行

尤其 PowerShell 是一个能删除文件、停止服务、终止进程,甚至操作注册表的强大环境。正因为如此,最先应学会的并不是「危险的命令」,而是「安全确认目标的步骤」。

查看  筛选  记录  预演  执行

只要遵守这个顺序,PowerShell 就不再只是一个黑色窗口,而会成为整理、排查、自动化 Windows 工作的实用工具。

参考链接

相关文章

与本主题相关的服务

Windows 应用程序开发

支持业务应用程序、设备集成、通信工具等 Windows 软件开发。

查看服务 / 联系我们

存量资产利用・迁移支持

支持盘点、改造、迁移存量资产,包括旧有的批处理文件、VBScript、VBA、PowerShell、COM / ActiveX。

联系我们

共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。

常见问题

汇总了咨询这一主题时常见的问题。

学习 PowerShell 该从什么开始?
重点不是死记命令,而是学会「查询的方法」。关键有三点:用 Get-Command 与 Get-Help 查询命令、用管道(|)把结果传给下一个命令,以及在删除、停止、更改之前用 -WhatIf 或 -Confirm 确认影响。不知道对象有哪些属性名称时,就用 Get-Member 查看内容。基本流程是「查找 → 查看 → 筛选 → 排序 → 输出 → 必要时再更改」。
PowerShell 和命令提示符有什么区别?
最大的区别在于,命令提示符的输出以字符串为主,而 PowerShell 的输出是对象。例如文件会被当作拥有 Name、Length、LastWriteTime 等属性的对象来处理,因此用 Where-Object 筛选、用 Sort-Object 排序都能写得很自然。命令名称也统一采用「动词-名词」的形式,例如 Get-ChildItem、Copy-Item,即使是不熟悉的命令也容易查找。
该用 Windows PowerShell 5.1 还是 PowerShell 7.x?
如果是重新学习,以 PowerShell 7.x(pwsh.exe)为基准没有问题。不过,公司内部旧有的管理脚本或 Windows 专用模块,有时是以 Windows PowerShell 5.1(powershell.exe)为前提编写的。实务中建议先用 $PSVersionTable 确认版本,并在需要特定版本以上才能运行的脚本开头加上像 #Requires -Version 7.0 这样的条件,可以减少事故。
在 PowerShell 中该如何安全地执行删除或更改?
重点是遵守「查看 → 筛选 → 记录 → 预演 → 执行」的顺序。具体来说,先用 Get 系列命令显示目标、用 Where-Object 筛选、用 Select-Object 确认目标列表、用 Export-Csv 把结果输出为 CSV 留存记录,再用 -WhatIf 确认变更计划后才正式执行。由于 PowerShell 可以在同一个窗口中同时进行调查与更改,养成先从只读确认开始的习惯非常重要。

作者简介

本文作者的个人简介页面。

Go Komura

小村软件有限公司 代表

以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。

返回博客列表