適合初學者閱讀的 Windows PowerShell 指令工具教學文件,介紹基本概念與操作方法。
PowerShell 是由微軟所發展的任務自動化與組態管理框架,它的角色類似於 UNIX/Linux 系統上的殼層(shell),透過腳本語言以及各種輔助工具,讓系統管理者可以將各種工作自動化。
起初 PowerShell 僅支援 Windows 系統,後來於 2016 年以開放原始碼(MIT 授權)的方式釋出後,發展成為跨平台的管理工具,目前它已經支援各種主流的 Linux 發行版以及 Mac OS 系統,其安裝檔案與原始碼都可以從 GibHub 網站上直接下載,而 Windows 7 以後的系統都已經有內建 PowerShell 了,所以不需要安裝即可使用。
若要開啟 Windows 的 PowerShell,可從開始選單中尋找「Windows PowerShell」,點選後就可以打開 PowerShell 的執行環境。
PowerShell 的操作介面跟傳統的命令提示字元(cmd.exe
)類似,打開之後就會看到類似這樣的 PowerShell 命列列視窗,在這個視窗之中,我們可以輸入各種 PowerShell 的指令,以互動式的方式處理各種工作。
在 PowerShell 指令視窗中,除了可以執行傳統的 Windows 指令、執行檔之外,PowerShell 本身也提供了一種特殊的 cmdlet(發音為 command-let)結構式指令,所有的 cmdlet 指令名稱都是以 動詞-名詞
的方式來命名,例如 Get-Process
、Get-Content
與 Stop-Process
等。
以下這個例子是使用 Get-Process
這個 cmdlet 指令,查詢 Greenshot
這個應用程式的行程資訊:
Get-Process -Name Greenshot
大部分的 cmdlet 指令名稱都很長,打字麻煩、也容易不小心打錯字,遇到這種情況時,可以先打指令的前幾個字母,接著按下 Tab 鍵,這樣 PowerShell 就會自動想辦法補其後續的字母。
以輸入 Get-Process
這行指令來說,我們就可以在輸入 Get-Pr
這幾個字之後,按下 Tab 來補齊後續的指令名稱,如果以這幾個字母開頭的指令有很多個的話,則會依序顯示每一個可能的組合,我們可以多按幾下 Tab 鍵來尋找自己想要輸入的指令。
雖然 Tab 鍵可以讓我們少打一些字,但是管理者在平常互動式的操作中,使用太長的指令還是不方便,為了讓互動式操作更順手,PowerShell 為所有的 cmdlet 都定義了簡短的別名(aliases),並且允許使用者再輸入參數名稱時,只要輸入足夠辨識的字母即可,另外 PowerShell 的指令是不分大小寫的,所以可以全部都以小寫輸入。
Get-Process
這行指令預設的別名是 gps
,所以我們也可以這樣執行上面的指令:
gps -n Greenshot
cmdlet 指令的參數除了以參數名稱來指定之外,也可以透過參數的位置順序來判斷(這樣就不需要打參數名稱)。以 Get-Process
來說,若不加參數名稱,其第一個參數就是行程名稱(也就是 -Name
),而這個行程名稱參數也允許使用萬用字元,所以我們可以使用這樣的指令找出名稱是 G
開頭、t
結尾的行程:
gps G*t
PowerShell 將許多的資料都當成物件來處理,這樣可以讓許多的工作指令搞撰寫起來更方便。
舉例來說,以下這行指令會產生一個簡單的字串:
"Hello, World."
Hello, World.
事實上這行字串就是一個 .NET 框架的物件,我們可以透過它的 Length
屬性,得知此字串的長度:
"Hello, World.".Length
13
所有會產生輸出的 cmdlet 指令,也可以傳回資料物件,例如 Get-Process
就會傳回 System.Diagnostics.Process
這種物件,我們可以把指令傳回的物件儲存在變數中,方便後續的指令稿使用。
在 PowerShell 中,變數名稱都是以錢字號($
)開頭,若要把 Get-Process
指令的查詢結果儲存下來,可以這樣寫:
$process = Get-Process -Name Greenshot
由於這個儲存下來的 $process
是一個完整的 Process
物件,所以我們可以使用其中的各種屬性或函數來控制該行程。例如取得行程的 CPU 使用率:
$green.CPU
或強制關閉這個應用程式:
$green.Kill()
Stop-Process
這個 cmdlet 指令。PowerShell 本身在設計時就考慮了許多系統管理者的需求,除了可使用 .NET 框架的物件之外,還有其他很多方便的小功能,像在電腦上常用的 KB、MB、GB 等單位,也可以在 PowerShell 中直接使用,例如計算 2TB 的資料以每秒 3.5 MB 的速度備份,需要幾個小時:
2TB / 3.5MB / 3600
166.440634920635
另一項常用的功能就是日期與時間的處理,以下這一行指令是計算從現在的時間到晚上 21:45
還有多久:
[DateTime] "21:45" - [DateTime]::Now
Days : 0 Hours : 11 Minutes : 22 Seconds : 44 Milliseconds : 956 Ticks : 409649565189 TotalDays : 0.474131441190972 TotalHours : 11.3791545885833 TotalMinutes : 682.749275315 TotalSeconds : 40964.9565189 TotalMilliseconds : 40964956.5189
這裡用中括號包起來的 [DateTime]
代表 System.DateTime
這個類別,[DateTime] "21:45"
的作用是將 "21:45"
這個字串轉換為 System.DateTime
的物件。
後面的 [DateTime]::Now
代表呼叫 System.DateTime
類別內的 Now
靜態函數,傳回現在的時間,與前面的時間相減之後,就得到間隔的時間了。
如果只想取出結果中的 Hours
欄位,可以這樣寫:
$result = [DateTime] "21:45" - [DateTime]::Now $result.Hours
11
若要計算兩個日期之間的天數,可以這樣寫:
$result = [DateTime] "2018/10/23" - [DateTime]::Now $result.TotalDays
160.565214351617
有時候我們在處理資料時,會經過好幾道程序,前一個指令的輸出會作為下一個指令的輸入,在這種狀況下就可以使用 PowerShell 的管線(pipe)功能,串接多個指令。
PowerShell 的管線符號為 |
,只要上一個指令所產生的物件類型可以被下一個指令接受,就可以用管線符號直接串接起來使用。例如我們可以使用 Get-Process
指令找出 Edge 瀏覽器的行程,然後將結果直接導給 Stop-Process
,關閉 Edge 瀏覽器:
Get-Process -Name MicrosoftEdge | Stop-Process
另一個例子是使用 Get-Item
將 FolderA
目錄下的的所有檔案列出來,導給 Move-Item
指令,將這些檔案搬移至 FolderB
目錄中:
Get-Item FolderA* | Move-Item -Destination FolderB
我們也可以使用 PowerShell 的管線串接多個指令,以下是將 Get-Process
的輸出用 Where-Object
篩選出 Handles
大於或等於 1000
的項目,經過 Sort-Object
排序後,再由 Format-Table
輸出指定的欄位:
Get-Process | Where-Object { $_.Handles -ge 1000 } | Sort-Object Handles | Format-Table Handles,Name,Description -Auto
PowerShell 提供系統管理者非常強大又方便的功能,不過有了快速方便的管理工具,在使用上也必須謹慎小心,避免下錯指令搞壞系統。
為了讓管理者可以方便確認要執行的實際動作,PowerShell 在許多的指令中都提供了 -WhatIf
與 -Confirm
的功能,可讓管理者在執行重要指令之前,可以先確認一下。
例如我們如果想要把所有系統上 Edge
相關的程式都關閉,可以搜尋所有名稱含有 Edge
的行程,再交給 Stop-Process
關閉程式,如果不確定自己的指令是否會把不該關閉的程式都關閉,可以在 Stop-Process
指令後方加上 -WhatIf
,這樣 Stop-Process
就只會列出要執行的動作有哪些,但是不會真正去執行:
Get-Process -Name *Edge* | Stop-Process -WhatIf
在我們確認所有的動作都沒問題之後,再將 -WhatIf
參數拿掉,實際這些動作,就可以確保不會下錯指令。
另外一種方式是使用 -Confirm
參數,它會讓指令在執行每一項動作時,都讓使用者確認一次,這樣也可以確保執行的動作是正確的:
Get-Process -Name *Edge* | Stop-Process -Confirm
PowerShell 中可用的 cmdlet 指令數量相當多,我們不太可能記得所有的指令名稱與用法,實務上的做法通常都是遇到問題時,再去查詢有哪些相關的指令可以使用。
若想要以關鍵字來尋找相關的 cmdlet 指令,可以使用 Get-Command
配合萬用字元(*
)來搜尋,例如搜尋含有 process
關鍵字的指令就可以執行:
Get-Command *process*
Get-Command
會列出所有符合搜尋條件的指令,若想要查詢某個指令的詳細用法,可以使用 Get-Help
加上指令名稱來查詢,例如查詢 Stop-Process
的用法就可以執行:
Get-Help Stop-Process
在 PowerShell 中除了 cmdlet 指令很多讓人記不起來之外,.NET 框架的物件種類也不少,再加上每一種物件的用法都不同,所以在使用物件時,我們也時常會需要查詢各種物件可用的屬性與方法。
若要查詢一個物件有哪些屬性與方法,可以將物件透過管線的方式導給 Get-Member
,例如查詢 "Hello"
這個字串物件的屬性與方法:
"Hello" | Get-Member
我們也可以將其他 cmdlet 指令輸出的物件直接導給 Get-Member
,查看該指令輸出的物件有哪些屬性與方法可用,這樣查詢方式是最常用的:
Get-Process | Get-Member
或是將儲存在變數中的物件導給 Get-Member
也可以:
$result = Get-Process $result | Get-Member
PowerShell 跟一般的指令稿語言(script language)一樣,可以直接在互動式的視窗中執行,若程式碼比較長的話,也可以把指令寫在指令稿檔案(script file)當中,一次執行檔案內所有的指令。
假設我們有以下的幾行下載網頁的 PowerShell 指令稿:
$webClient = New-Object System.Net.WebClient $url = "https://www.google.com/" $content = $webClient.DownloadString($url) $content > google.html
若要以指令稿檔案的方式執行這段程式碼,首先要把它們儲存成 PowerShell 的指令搞檔案(副檔名為 .ps1
),假設我們將其儲存在 C:OfficeGuidescript.ps1
,則可在 PowerShell 命令列中直接執行這個檔案,即可讓 PowerShell 執行其中的內容:
C:OfficeGuidescript.ps1
若執行時出現了「系統上已停用指令碼執行」的錯誤訊息,請參考 PowerShell 更改執行原則的教學來解決。
若要查詢過去執行過的指令,可以使用 Get-History
指令:
Get-History
在執行一系列的指令之後,若想要將執行過的動作儲存成整份的指令稿,可以使用 Get-History
取得歷史指令之後,用 Foreach-Object
抽出實際的指令內容,然後再導入檔案中儲存起來:
Get-History | Foreach-Object { $_.CommandLine } > C:OfficeGuidescript2.ps1
接著可以啟動記事本編輯一下這些執行過的指令:
notepad C:OfficeGuidescript2.ps1
編輯好之後,就可以直接執行這份指令稿了:
C:OfficeGuidescript2.ps1
在撰寫指令稿的時候,為了讓程式碼更容易閱讀,建議可以加上適當的註解,在 PowerShell 中單行的註解可用井字號(#
)開頭:
# 列出 chrome 的行程 Get-Process -Name chrome
在程式碼中,所有 #
之後的內容都會被視為註解,我們也可以將單行註解放在指令的結尾:
Get-Process -Name chrome # 列出 chrome 的行程
如果要放置多行的註解,可以將註解使用 <#
與 #>
包起來,放在其中的任何文字也都會被視為註解:
<# 這是多行的註解,適合用於詳細的敘述, 或是把整段暫時沒用的程式碼註解起來。 #>
這種多行的註解適合用於詳細的敘述,或是把整段暫時沒用的程式碼註解起來。
PowerShell 的 provider 架構允許程式開發者以類似檔案系統的操作方式,瀏覽與操作各類型的資料庫。
以最普通的檔案系統來說,我們可以使用以下指令列出 C:
槽底下的檔案:
Set-Location C: Get-ChildItem
而在熟悉這套操作模式之後,就可以用相同的方法瀏覽 Windows 的登錄機碼:
Set-Location HKCU:SoftwareMicrosoftWindows Get-ChildItem
相同的操作方法也可以套用在憑證的瀏覽上:
Set-Location cert:CurrentUserRoot Get-ChildItem