Windows PowerShell 講座 (5)—儲存資料的其他方式及編寫指令碼的前置準備

發佈日期: 2008 年 3 月 26 日

**作者:**賴榮樞
    www.goodman-lai.idv.tw

本文的內容分成兩個部分:一是延續上一篇討論變數的文章,補充說明 Windows PowerShell 的常數和陣列;另一個部分則是為下一篇正式討論指令碼程式編寫的文章預作熱身,先行討論編寫指令碼的前置準備。

本頁內容

常數
唯讀的變數
陣列
關聯陣列
Windows PowerShell 指令碼的副檔名
執行 Windows PowerShell 指令碼程式
結語

簡單來說,程式是演算法和資料的集合;程式是以演算法處理資料,資料可能儲存於程式之外 (例如任何類型的檔案或資料庫),但是當程式欲處理資料時,都會先將資料載入程式的變數、常數、陣列等資料儲存區域,再加以處理。現代程式語言大都設計了變數、常數或陣列來儲存資料,Windows PowerShell 亦然。我們已經在上一篇文章《Windows PowerShell講座(4)—變數》(https://www.microsoft.com/taiwan/technet/columns/profwin/66-Winpowerwshell4.mspx) 說明了 Windows PowerShell 的變數,於此本文將補充 Windows PowerShell 儲存資料的其他方式,包括常數和陣列。

此外,本文的後半部將討論編寫指令碼的前置準備,包括如何開啟 Windows PowerShell 執行指令碼的能力、如何執行Windows PowerShell指令碼程式,以及 Windows PowerShell 主程式 powershell.exe 的啟動選項。

常數

對 Windows PowerShell 來說,變數與常數的差別,是常數不允許移除、不允許更動其值。 Windows PowerShell 常數可以說是一種特殊的變數,其實 Windows PowerShell 變數有一個名為Options的屬性,如果變數的 Options 屬性值為 Constant,那麼這種變數即具備常數的特性 (不允許移除、清除、或更動其值),也因此就將這種變數視為常數。

我們可以利用 New-Variable (別名為 nv) 或 Set-Variable (別名為 set)、並加上特定的 Options 參數來建立常數,例如以下建立了名為 cPi、值為 3.14159 的常數;請注意,必須加上 -Option Constant 才能讓變數變成常數:

nv -Name cPi -Value 3.1415 -Option Constant

Windows PowerShell 常數的使用與變數相同,必須在名稱之前加上 $ 符號。由於 Windows PowerShell 常數不允許移除、清除或更改其值,因此若以 Remove-Variable、Clear-Variable 或 Set-Variable 試圖移除、清除或更改其值,都會出現錯誤 (如下圖)。此外要注意的是,不能將現有的變數轉成常數,例如以 Set-Variable 對現有變數加上 -Option Constant,會導致錯誤。再者,建立常數的同時別忘了指定其值,因為建立之後就無法更改 (即使建立時忘了指定亦然)。

Dd125500.67-winPowerShell-5-01(zh-tw,TechNet.10).jpg

唯讀的變數

如果您覺得 Windows PowerShel l常數 (也就是 Option 屬性值為 Constant 的變數)一經建立就不能更改其值不夠彈性,也可以利用上述的唯讀變數,也就是在建立時,將變數的 Option 屬性值設為 ReadOnly。例如以下的例子建立了一個名為 LastName 的唯讀變數,其初值為 Lai:

nv -Name LastName -Value Lai -Option ReadOnly

Windows PowerShell 的唯讀變數與常數的差別,是利用 -Force 參數就能更改其值 (常數是完全不能更改),但與常數相同的是,唯讀變數也不能移除、清除。例如以下的例子是將上例建立的 LastName 唯讀變數值改成 Lo:

set -Name LastName -Value Lo -Force

或可簡略寫成:

set LastName Lo -Force

如果嘗試要清除或移除唯讀變數,會出現與企圖清除、移除常數相同的錯誤。

陣列

陣列是一組資料的集合,而這些資料都編了索引值 (index),因此我們可以利用索引值來取得陣列裡的資料。若以 「維度」 來看陣列,最常用的是一維陣列,下圖是一維陣列的簡單示意圖。有些程式語言的陣列索引值是 1 起始,而 Windows PowerShell 的陣列值則是 0 起始,因此以下陣列七個元素的索引值是從 0 到 6。

索引值

0

1

2

3

4

5

6

陣列元素

red

orange

yellow

green

blue

indigo

violet

某些情況,我們必須以一組變數來儲存一群相關的資料,例如彩虹的七個顏色、伺服器裡的磁碟機等,在這類的情況,如果是以個別變數來儲存這些相關的資料,程式會很不好處理,例如:

$RainbowColor1 = 
"red"
$RainbowColor2 = 
"orange"
…
$RainbowColor7 = 
"violet"

指定陣列內容

但如果改用陣列,就比較容易處理:

$RainbowColor = "red", "orange", "yellow", "green", "blue", "indigo", "violet"

以上會建立一個名為 RainbowColor 的陣列,而且這個陣列的內容 (稱為陣列元素) 分別是彩虹七種顏色的英文;因為每個陣列元素都是字串,因此必須以雙引號括住各個字串,並且以逗號隔開這七個陣列值 (以上只是 Windows PowerShell 建立一維陣列的作法,後續還會提及進一步存取陣列元素的方式,現在我們繼續說明建立陣列的作法)。

如果陣列元素為數值,就不需括註雙引號,否則數值會被視為字串,例如以下陣列 a 的內容是六個整數值:

$a = 1, 3, 17, 24, 8, 33

如果要建立內容為循序整數值的陣列,可以利用範圍運算子.. (兩個半形英文句點),例如以下陣列 b 的內容是 2 到 11 的整數值:

$b = 2..11

甚至,舉例來說,如果想要將 1、2 和 5 到 9 等七個數值指定到陣列,下列應該是最快的方式。又如果想要將 1 到 2、5 到 7、10 到 15 等 11 個數值指定到陣列,該怎麼做才最快呢?這個題目就留給你舉一反三。

$b = 1, 2 + 5 .. 9

透過上述的例子或許您發現陣列名稱和變數名稱的規則似乎類似。的確,您甚至可以將陣列視為一組變數的集合,而且陣列元素可以混合各種 Windows PowerShell 型別,例如以下陣列 c 混合了整數、字串、小數:

$c = 2, 3, "apple", 3.14, "banana"

宣告陣列型別

如同 Windows PowerShell 變數可以宣告成特定的型別,陣列也可以,而且在作法上,變數與陣列有許多相同之處,因為陣列可以說是源自變數。首先,陣列與變數的型別相同,都是源自 .NET Framework 的型別。 其次,陣列也是以類似的方式宣告型別,例如以下欲宣告整數型別的陣列,並指定內容 (也請留意與變數宣告型別的差異):

# 陣列宣告特定型別,並指定內容
[int[]]$g = 
15, 22, 33, 40

# 變數宣告特定型別,並指定內容
[int]$Age = 18

同樣的,指定過內容 (也就是非 Null 值) 的陣列 (或變數),都可以利用的 GetType 方法,或 Type 物件的 Name 屬性值,傳回變數的 Type 物件,或取得型別名稱;例如:

$g.GetType()
$g.GetType().Name

指定個別的陣列元素值

在陣列建立並設定初值之後,我們仍可以指定個別的陣列元素值,在這個時候,您必須知道欲指定元素值的陣列索引值。例如以下第一個例子是將整數 10 指定成陣列 $a 的第二個元素值,而第二個例子則是將字串 Lai 指定成陣列 $ LastName 的最後一個元素值:

[int[]]$a = 1, 2, 3, 4
$a[1] = 10

[string[]]$LastName = "Lin", "Li", "Lo", "Hu"
LastName[-1] = "Lai"

另一種指定個別的陣列元素值的作法,是利用SetValue方法;以下是上述兩個例子改以SetValue方法的作法:

$a.SetValue(10, 1)

#SetValue方法不能使用負的索引值
$LastName.SetValue("Lai", 3)

增加陣列元素

如果想新增陣列元素,可以利用遞增運算子 +=。繼續以上的兩個例子,以下第一個例子會在陣列 $a 末尾加入整數 101 為其新的元素,而第二個例子則會加入字串 Chen:

$a += 101

$LastName += "Oh"

遞增運算子也可以用在陣列元素 (上述是作用在陣列),其結果是將元素遞增特定值。例如以下的例子:

# 將整數10與第一個元素相加
$a[0] += 10

# 將字串Yang與最後一個元素相加(連接)
$LastName[-1] += "Yang"

合併陣列

Windows PowerShell 的陣列很容易合併,只要利用加號就可以;例如以下的例子:

$i = 1, 2
$j = 3, 4
$k = $i + $j
# $k的內容為 1, 2, 3, 4

$x = "Hello, "
$y = "Windows PowerShell!"
$z = $x + $y # $z 的內容為 Hello,Windows PowerShell!

同理,利用指定運算子 (=) 就能將整個陣列的內容,指定給另一個陣列,例如:

$n = 
0, 1, 2, 3, 4, 5, 
6, 7, 8, 9
$p = $n

此外,我們也可以利用範圍運算子將陣列的部分內容指定給另一個陣列。例如以下的例子會將陣列 $n 索引值的第 0、1 及 3 到 8 等八個元素的值指定給陣列 $m:

$m = 
$n[0, 1 + 3..8]

取得陣列內容

將資料存入變數之後,最重要的就是取出並進一步處理,陣列亦然,但因為陣列裡有一組元素,因此若要取得個別元素,就必須利用元素的索引值。以上述的彩虹陣列為例,如果要取得第一個元素,就要利用索引值 0,如果要取得最後一個元素,其索引值是 6;甚至可以用逗號隔開欲取得的元素索引值,例如:

$RainbowColor[0]

$RainbowColor[6]

$RainbowColor[0, 3]

而索引值也可以是負數:-1 代表陣列的最後一個元素、-2 代表陣列的倒數第二個元素、-3 代表陣列的倒數第三個元素,依此類推。

Windows PowerShell 的世界處處可以見到物件,就連陣列也是,而我們可以利用陣列物件的 Length 屬性,得知陣列的元素數量,例如:

$RainbowColor.Length

$c.Length

上述前者會得到 7,後者則是 4;雖然我們可以利用下列的方式,取得陣列的最後一個元素,但總不比利用索引值 -1 來得更為方便:

$RainbowColor[$RainbowColor.Length - 1]

$c[$c.Length - 1]

範圍運算子 (..) 也可以用在陣列元素的取得,甚至還可以搭配負的索引值,例如以下的例子:

# 取得 $RainbowColor 陣列的前四個元素
$RainbowColor[0..3]

# 取得 $RainbowColor 陣列的第一、第三以及第五到第七等五個元素
$RainbowColor[0, 2 + 4..6]

# 取得 $RainbowColor 陣列的最後及倒數第二個元素
$RainbowColor[-1..-2]

# 取得 $RainbowColor 陣列的第一、最後及倒數第二個元素(共三個)
$RainbowColor[0..-2]

由於陣列的索引值是 0 起始的循序整數,而且陣列元素也都是循序排列,因此我們也經常利用 「迴圈」 來處理陣列。但關於細節,我們將留待後續說明迴圈的文章再討論。

刪除陣列

刪除陣列的方式一如刪除變數,利用 Remove-Item 或 Remove-Variable 皆可,例如:

Remove-Item Variable:a

Remove-Variable LastName

關聯陣列

上述所討論的陣列,是以 「索引值」 作為存取、處理元素的基礎,每個索引值對應到特定的元素。這雖然已經很方便儲存一組資料,但是 Windows PowerShell 還提供了 「關聯陣列」 (associative array),能以另一種方式讓我們儲存一組相關的資料。

雖然概念類似一維陣列,但關聯陣列與陣列最大的差別,是前者採用 「索引鍵」 (key) 作為存取陣列內容的基礎;索引鍵是字串,每個索引鍵對應到特定的資料值 (value),因此索引鍵和資料值是互為。舉例來說,以下的員工分機表即可利用關聯陣列來儲存。

索引鍵(員工姓名)

王小明

李大同

林中成

資料值(分機號碼)

4122

4213

4421

建立並指定關聯陣列內容

以下是建立並指定關聯陣列內容的語法:

$<關聯陣列名稱> = @{<索引鍵1 = 資料值1="">; <索引鍵2 = 資料值2="">;...}

若是上述分機表,其關聯陣列的建立方式如下:

$phoneEx = @{王小明 = 4122; 李大同 = 4213; 林中成 = 4421}

請注意,如果索引鍵或資料值內含空白字元,或者資料值內含符號,就必須以單引號或雙引號括住索引鍵或資料值,例如:

$phoneTable = @{"Mike Wang" = "2845-4122"; "John Li" = "5211-4213"…}

上述的索引鍵是因為內含空白字元,所以必須加註引號,而資料值因為有橫線符號 (-),如果不加註引號,橫線符號會被視為減號,因此存入關聯陣列的值會是兩個整數的差。

關聯陣列的資料值可以混合各種資料型別,甚至可以是另一個關聯陣列,例如以下的例子:

$Mix = @{Key1 = 100; Key2 = Get-ChildItem; Key3 = "Hello!"; Key4 = $phoneEx}

更改、取得關聯陣列的資料值

點符號或陣列符號可以用來取得關聯陣列的資料值,例如:

$phoneTable.'Mike Wang'

$phoneTable['John Li']

如果要更改關聯陣列資料值的方式,可以利用之前提及建立關聯陣列的方式,再重新指定新的資料值即可;或者也可以利用點符號或陣列符號,例如:

$phoneTable.'Mike Wang' = "8541-6258"

$phoneTable['John Li'] = "8889-6841"

我們也可以利用關聯陣列的 Keys 和 Values 屬性,來取得關聯陣列的索引鍵和資料值,例如:

$phoneTable.Keys
$phoneTable.Values

$Mix.Keys
$Mix.Values

關聯陣列也能利用迴圈,但細節也留待後續說明迴圈的文章再討論。

刪除陣列

刪除關聯陣列的方式一如刪除陣列,利用 Remove-Item 或 Remove-Variable 皆可,例如:

Remove-Item Variable:a

Remove-Variable LastName

何以需要編寫 Windows PowerShell 指令碼程式

Windows PowerShell 不只是功能完整的 shell 環境,也提供了指令碼 (script) 程式的直譯能力。有些系統管理者可能認為 「寫程式」 是一件艱深的苦差事,但其實未必,尤其指令碼的編寫又比其他類型程式的編寫更為容易,因此系統管理者除了應該熟悉 Windows PowerShell 的指令操作,更應該熟悉 Windows PowerShell 的指令碼編寫。

或許您會質疑:「Windows PowerShell 已經夠方便、實用了,為什麼還要編寫指令碼程式?」 如果您覺得 Windows PowerShell 方便、實用,那更應該熟悉 Windows PowerShell 指令碼程式的編寫,因為這會讓 Windows PowerShell 更加的方便、實用。

簡單來說,Windows PowerShell 指令碼程式就是 Windows PowerShell 指令的集合,如果您經常依賴某些指令來完成一件工作,那麼只要將這些指令分行依序集中在文字檔,除了給予文字檔主檔名,再給予 ps1 作為副檔名,這些指令就成為指令檔,而只要執行這個指令檔,Windows PowerShell 直譯環境就會依序執行指令檔裡的指令,因此您不需要每次都輸入相同的指令。

再者—這也是之所以應該熟悉指令碼程式編寫的重要原因,指令碼程式可以依照您的指定,自動重複執行某些指令 (例如重複處理 20 個資料夾的內容),或者根據不同的邏輯狀況而給予不同的處理 (例如處理所有副檔名為 .txt 的檔案)。

Windows PowerShell 指令碼的副檔名

為了說明 Windows PowerShell 指令碼程式的編寫與執行,我們先寫個非常簡單的小程式,這個程式只有以下這行指令:

get-host

get-host 會顯示 Windows PowerShell 的版本及地域資訊。因為 Windows PowerShell 指令碼檔案是副檔名為 ps1 的純文字檔 (最後一個字元是數字 1),因此我們可以任何文字編輯器來編寫,但要注意的是,存檔時一定要將副檔名存成 ps1。目前有許多更便於編寫指令碼的編輯器,甚至也有編寫指令碼的整合開發環境,這類工具對指令碼開發很有幫助,若日後經常需要編寫大量的指令碼程式,不妨找一個好用的工具,但若還是初學者,建議先將焦點放在 Windows PowerShell 的學習。

執行 Windows PowerShell 指令碼程式

我們將這個簡單的程式存成 test1.ps1,並且放在 c:\MyMSH 資料夾。Windows PowerShell 指令碼程式可以從文字模式 Windows PowerShell 執行,也可從 Windows GUI 執行,在說明執行方式之前,我們要先開啟 Windows PowerShell 執行指令碼的能力。

開啟 Windows PowerShell 執行指令碼的能力

基於安全的考量,Windows PowerShell 預設關閉了指令檔的執行功能,若要開啟,我們需要 set-executionPolicy,不過您可以先利用 get-executionPolicy 來檢查系統目前的指令檔執行限制:

  • Restricted:預設值,關閉指令檔的執行功能。

  • AllSigned:只允許執行受信任發行者簽署過的指令檔。

  • RemoteSigned:從網際網路下載的指令檔必須經過受信任發行者的簽署才能執行。

  • Unrestricted:任何指令檔皆可執行,但是從網路網路下載的指令檔在執行之前會出現提示交談窗。

為了方便起見,我們將指令檔的執行限制改成RemoteSigned,也就是自己在本機建立的指令檔不受限制,但限制來自網際網路的指令檔 (但要提醒您的是,只要權限足夠的使用者,都有可能自行修改或建立危害系統的設定檔或指令檔);設定的方式如下:

set-executionPolicy RemoteSigned

如果想還原成限制所有指令檔執行的預設狀態,請如下操作:

set-executionPolicy Restricted

上述設定的影響範圍遍及整個 Windows 環境,而不只是設定時的 Windows PowerShell 環境。

指令碼程式的完整路徑

如果要從 Windows PowerShell 文字模式環境執行 ps1 指令碼,除了要輸入完整的檔名 (包含副檔名 ps1),通常還要指定指令碼程式的完整路徑 (因為 Windows PowerShell 執行環境只會搜尋Path環境變數裡的資料夾),例如下圖裡的三種執行狀況,只有指定了指令碼程式路徑的第三種 (也就是.\test1.ps1),才能得到正確的結果;就算工作資料夾已經切換到指令碼程式所在的 c:\MyMSH,也要指定路徑。

Dd125500.67-winPowerShell-5-02(zh-tw,TechNet.10).jpg

指令路徑的方式有很多種,上述之所以使用 .\test1.ps1,是因為工作資料夾已經切換到了指令碼程式所在的 c:\MyMSH,因此能以 「單點」 表示目前的資料夾。除了相對路徑的表示方式,也可以利用絕對路徑表示方式 (不論工作資料夾為何,絕對路徑只要無誤,皆可找到檔案),此例的絕對路徑即為:

c:\MyMSH\test1.ps1

要提醒您的是,如果路徑或檔名裡有空白字元,例如 c:\My MSH 或 test 1.ps1,除了必須以單引號或雙引號括住徑名或檔名,還必須在最前面加上執行運算子&,例如:

# 絕對路徑
PS C:\User> & "c:\My MSH\test 1.ps1"

# 相對路徑(也可以用單引號括住)
PS C:\User> & '..\My MSH\test 1.ps1'

powershell.exe 的啟動選項

預設無法直接從 Windows 執行 Windows PowerShell 指令碼程式,如果雙按檔案總管裡的 ps1 檔,只會以記事本開啟程式檔。雖然我們可以改用 『執行』 交談窗來執行,但必須將欲執行的指令碼程式當作 powershell.exe (Windows PowerShell 環境的主程式)的選項,並且要加上 -NoExit 選項,以免執行之後 Windows PowerShell 視窗馬上消失;例如:

Dd125500.67-winPowerShell-5-03(zh-tw,TechNet.10).jpg

如果徑名或檔名裡有空白字元,類似之前提及必須加上執行運算子&,但是只能以單引號括住完整徑名,例如:

powershell -NoExit & 'c:\My MSH\test 1.ps1

許多程式都可以在執行時加上啟動選項,Windows PowerShell 執行環境的 powershell.exe 也一樣,關於 powershell.exe 啟動選項的說明,只要在 『命令提示字元』 或 Windows PowerShell 環境執行以下指令:

powershell /?

就會列出 Windows PowerShell 的啟動參數,以及參數的使用說明:

powershell[.exe] [-PSConsoleFile <file> | -Version <version>]
                [-NoLogo] [-NoExit] [-NoProfile] [-NonInteractive]
                [-OutputFormat {Text | XML}] [-InputFormat {Text | XML}]
                [-Command { - | <script-block> [-args <arg-array>] | <string> [<CommandParameters>] } ]

以下的這些選項的說明。

  • -Help、/Help、-?、/?:顯示 Windows PowerShell 的啟動選項說明訊息。

  • -PSConsoleFile:載入指定的 Windows PowerShell 控制台檔案;Export-Console cmdlet 可以匯出 Windows PowerShell 環境的控制台檔案。例如:

    PowerShell -PSConsoleFile MyPSConsole.psc1

  • -Version:啟動特定版本的 Windows PowerShell (前提是系統裡已經安裝這些版本的 Windows PowerShell)。例如:

    PowerShell -Version 1

    PowerShell -Version 1.0

  • -NoLogo:啟動時不顯示開頭的版權訊息。

  • -NoExit:執行完啟動所指定的指令後,不結束 Windows PowerShell。

  • -NoProfile:不載入使用者設定檔。

  • -Noninteractive:關閉互動模式;有些 cmdlet 若未指定必要參數,會以互動模式提示使用者輸入必要參數,這個選項會關閉互動模式,因此若未輸入必要參數,會直接顯示錯誤訊息。

  • -OutputFormat:指定 Windows PowerShell 的輸出格式,有效的值是 Text 和 XML (循序的 CLIXML 格式)。

  • -InputFormat:指定輸入到 Windows PowerShell 的格式,有效的值是 Text 和 XML (循序的 CLIXML 格式)。

  • -Command:指定給 Windows PowerShell 的執行命令 (及任何參數)、程式區塊 (用大括號括住程式碼),執行後 Windows PowerShell 隨即結束 (除非另指定 -NoExit 選項)。如果 -Command 的值是要執行的命令字串,要避免其他的 Windows PowerShell 選項被認為是命令字串的參數,例如以下的 -NoExit 選項會被認為是 dir 的參數:

PowerShell -Command dir -NoExit

正確的寫法應該是:

PowerShell -NoExit -Command dir

PowerShell -Command "& {dir}" -NoExit

如果要執行的命令字串之後需要加上參數,可以寫成:

PowerShell -NoExit -Command dir -Name desktop

PowerShell -Command "& {dir -Name desktop}" -NoExit

結語

Windows PowerShell 提供了相當實用的變數、常數、陣列機制,這些機制是程式處理資料的重要基礎,因此我們花了一篇半的文章篇幅來說明這些機制的基本用法。此外,Windows PowerShell 除了是功能優異的文字模式操作介面 (shell),本身也兼具指令碼直譯功能,因此指令碼程式更是 Windows PowerShell 管理系統的利器。

本文的後半部論及編寫指令碼的前置準備,包括如何開啟 Windows PowerShell 執行指令碼的能力、如何執行 Windows PowerShell 指令碼程式,以及 Windows PowerShell 主程式 powershell.exe 的啟動選項。

我們將在下一篇文章繼續討論 Windows PowerShell 指令碼程式的編寫細節,包括運算紫、運算式、迴圈、流程控制等主題,而這也等於正式進入 Windows PowerShell 指令碼程式編寫的範圍。