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

依環境不同,這可能需要網路連線與相應權限。在公司電腦上,也可能因為 Proxy 或管理原則而失敗。

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 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。

回到部落格一覽