﻿# PowerShell 如何判斷 Cmdlet 或 Function 回傳資料的筆數 (0, 1, >1)

老實說，我在寫 PowerShell 的時候，偶爾會遇到一些非常葩的設計，很多時候不深入探究，根本就無法理解。以我今天要寫的這篇文章為例，當你執行一個 Cmdlet 並回傳資料時，當你可能拿到 0 筆、1 筆、超過 1 筆的情況時，正常人應該會覺得我們應該會得到一個「陣列」，但是你知道嗎，在 PowerShell 竟然可能會用到三種不同的處理方式，超怪的。因為這個問題實在遇到太多次了，這次我終於有空寫成文章，希望可以給遇到相同問題的人一些指引。

首先，你必須理解，這樣的邏輯是內建在 PowerShell Core 的語言特性中的，所以預設所有的 Cmdlet 都是這樣回傳的！

再來，你必須理解 PowerShell 的陣列表示法如下：

1.  空陣列
    
    ```powershell
    @()
    ```
    
2.  只有一個元素的陣列
    
    ```powershell
    @('Hello')
    ```
    
3.  包含兩個元素的陣列
    
    ```powershell
    @('Hello', 'World')
    ```
    

![PowerShell Array](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/72a29d98a8dcadaac421-177993622-9d92563c-9a70-4aa1-b0c5-17c0667d5ff5.png)

### 奇葩特性一：System.ValueType

接著，我先設計三個不同的 function 讓你瞭解這個問題：

```powershell
# 回傳 0 筆資料
function Get0 { return @() }
# 回傳 1 筆資料
function Get1 { return @(1) }
# 回傳 2 筆資料
function Get2 { return @(1,2) }
```

1.  判斷一個 Cmdlet 或 Function 回傳為 0 筆的方法
    
    ```powershell
    (Get0)
    (Get0).Count         # 0
    (Get0).Count -eq 0   # True
    (Get0).Length        # 0
    (Get0).Length -eq 0  # True
    (Get0)[0]            # InvalidOperation: Cannot index into a null array.
    (Get0).GetType()     # InvalidOperation: You cannot call a method on a null-valued expression.
    (Get0) -eq $null     # True
    ```
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/aa07c4d734445fa49263-178008727-0216a06a-14fa-4b99-8254-d95106d1cefa.png)
    
    各位觀眾，你說奇葩不奇葩，一個**空值**(null-valued expression)無法執行 `GetType()` 但卻可以取得 `Count` 或 `Length` 屬性值為 `0`！ 😅
    
2.  判斷一個 Cmdlet 或 Function 回傳為 1 筆的方法
    
    ```powershell
    (Get1)
    (Get1).Count         # 1
    (Get1).Count -eq 1   # True
    (Get1).Length        # 1
    (Get1).Length -eq 1  # True
    (Get1)[0]            # 1
    (Get1).GetType()     # System.ValueType (這不是陣列類型)
    (Get1) -eq $null     # False
    ```
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/76271e75da2fae9da099-178008740-27a45479-6c5c-4af9-b383-3768c4a4e6fb.png)
    
    各位觀眾，你說奇葩不奇葩，一個感覺回傳 `1` 筆的陣列，其實不是陣列，但卻可以取得 `Count` 或 `Length` 屬性值為 `1`！ 😅
    
3.  判斷一個 Cmdlet 或 Function 回傳為 0 筆的方法
    
    ```powershell
    (Get2)
    (Get2).Count         # 2
    (Get2).Count -eq 2   # True
    (Get2).Length        # 2
    (Get2).Length -eq 2  # True
    (Get2)[0]            # 2
    (Get2).GetType()     # System.Array (陣列類型)
    (Get2) -eq $null     # (這裡回傳一個空陣列喔，超怪的)
    ```
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/f766c08f0ed2103c0c04-178008753-bbdbc7e6-a5d4-4124-ae50-ca9fcc33d413.png)
    
    只有超過 1 筆的情況，才是個還算正常一點的「陣列」，不過陣列跟 `$null` 比較的結果，竟然是個陣列，還有沒有個正常一點的語言特性啊！
    

如果你覺得這樣已經很奇葩，那你就錯了，請繼續看下去！ 🔥

### 奇葩特性二：System.Object

我再設計三個不同的 function 讓你瞭解另一種奇葩情況，這次我用**字串**來當成陣列元素，字串在 PowerShell 裡面使用 `System.Object` 型別：

```powershell
# 回傳 0 筆資料
function Get0 { return @() }
# 回傳 1 筆資料
function Get1 { return @('Hello') }
# 回傳 2 筆資料
function Get2 { return @('Hello', 'World') }
```

1.  判斷一個 Cmdlet 或 Function 回傳為 0 筆的方法
    
    ```powershell
    (Get0)
    (Get0).Count         # 0
    (Get0).Count -eq 0   # True
    (Get0).Length        # 0
    (Get0).Length -eq 0  # True
    (Get0)[0]            # InvalidOperation: Cannot index into a null array.
    (Get0).GetType()     # InvalidOperation: You cannot call a method on a null-valued expression.
    (Get0) -eq $null     # True
    ```
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/61f0e71a921b0030e0ca-178008770-5765ff8e-89a8-4643-9565-231fd33573d9.png)
    
    這個例子跟上一個例子一樣，沒有**更**奇葩！
    
2.  判斷一個 Cmdlet 或 Function 回傳為 1 筆的方法
    
    ```powershell
    (Get1)
    (Get1).Count         # 1
    (Get1).Count -eq 1   # True
    (Get1).Length        # 5 (喔喔喔，是 5 耶，因為 'Hello' 是 5 個字元)
    (Get1).Length -eq 1  # False (事實證明 Get1 回傳的結果不是個陣列，而是個字串物件)
    (Get1)[0]            # H (回傳字串的第 1 個字元)
    (Get1).GetType()     # System.Object (這不是陣列類型)
    (Get1) -eq $null     # False
    ```
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/65207248033b61f5f1e4-178008781-9af1290c-639a-4948-9e08-49f1be6a6deb.png)
    
    各位觀眾，請看上述註解部分，你說有沒有**更**奇葩了！ 😅
    
3.  判斷一個 Cmdlet 或 Function 回傳為 0 筆的方法
    
    ```powershell
    (Get2)
    (Get2).Count         # 2
    (Get2).Count -eq 2   # True
    (Get2).Length        # 2
    (Get2).Length -eq 2  # True
    (Get2)[0]            # Hello
    (Get2).GetType()     # System.Array (陣列類型)
    (Get2) -eq $null     # (這裡回傳一個空陣列喔，超怪的)
    ```
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/e8e9c72843624d8d8fa6-178008790-452cbd29-e799-4c30-bb3d-5c640a3873f5.png)
    
    這個例子跟上一個例子一樣，沒有**更**奇葩！但我還是覺得 `(Get2) -eq $null` 出現一個空陣列真的超怪的！
    

### 實戰演練比對資料筆數的方法

經由上述的各種情境分析，你應該可以想像的到，網路上可能找到的解決方案，能有多奇葩，就有多奇葩，各種各式各樣的判斷式寫法都找的到，有些會複雜到讓你覺得懷疑人生。

最後，我想要總結一下在 PowerShell 之中判斷回傳筆數的**標準寫法**，那就是：

> 一律使用 (Cmdlet).Count 來取得筆數，超簡單！

不過，當你知道筆數之後，還要適度的修改你存取物件的方式，才能讓你的 PowerShell 程式如預期的執行。

1.  取回 0 筆的情況
    
    這個 [Get-Job](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/get-job?view=powershell-7.2&WT.mc_id=DT-MVP-4015686) 命令在我的回傳為 0 筆，所以我們其實可以預期他會回傳一個 `$null` 空值。
    
    ```powershell
    $items = Get-Job
    ```
    
    判斷方法：
    
    ```powershell
    if ($items.Count -eq 0) { echo 'No Data' }
    ```
    
    > 注意: 不需要判斷結果是否為 `$null`，但此時 `$items` 的值為 `$null` 喔！
    
    有趣的地方就是，其實你可以用 [ForEach-Object](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/foreach-object?view=powershell-7.2&WT.mc_id=DT-MVP-4015686) 在一個 `$null` 上，並不會出錯！
    
    ```powershell
    $items | foreach { $_.Id }
    ```
    
2.  取回 1 筆的情況
    
    這個 [Get-Disk](https://learn.microsoft.com/en-us/powershell/module/storage/get-disk?view=windowsserver2022-ps&WT.mc_id=DT-MVP-4015686) 命令在我的回傳為 1 筆，所以我們其實可以預期他會回傳一個 `Object` 物件 (非陣列)。
    
    ```powershell
    $items = Get-Disk
    ```
    
    判斷方法：
    
    ```powershell
    if ($items.Count -eq 1) { $items | select Number,FriendlyName,HealthStatus }
    ```
    
    > 注意: 不需要判斷結果是否為 `$null`，但此時 `$jobs` 的值為一個 `CimInstance` 物件，不是一個陣列！
    
    有趣的地方就是，其實你可以用 [ForEach-Object](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/foreach-object?view=powershell-7.2&WT.mc_id=DT-MVP-4015686) 在一個不是陣列的物件上，並不會出錯！
    
    ```powershell
    $items | foreach { $_.FriendlyName }
    ```
    
3.  取回超過 1 筆的情況
    
    這個 [Get-Process](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-process?view=powershell-7.2&WT.mc_id=DT-MVP-4015686) 命令在我的回傳為 411 筆，所以我們其實可以預期他會回傳一個 `Array` 陣列。
    
    ```powershell
    $items = Get-Process
    ```
    
    判斷方法：
    
    ```powershell
    if ($items.Count -gt 1) { $items | foreach { $_.ProcessName } }
    ```
    
    > 注意: 不需要判斷結果是否為 `$null`，但此時 `$items` 的值為一個 `Process` 陣列！
    
    你可以直接用 [ForEach-Object](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/foreach-object?view=powershell-7.2&WT.mc_id=DT-MVP-4015686) 跑迴圈！
    
    ```powershell
    $items | foreach { $_.Name }
    ```
    

### 再補充幾個冷知識

[網路上](https://stackoverflow.com/a/11107477/910074)常會有人建議透過封裝在一個陣列的方式，解決回傳值可能不是陣列的問題，但是你有可能會遇到以下狀況。

1.  取回 0 筆的情況
    
    ```bash
    $item = $null
    @($item).Length -eq 1
    ```
    
    > 這種把 `$null` 包在「陣列」的寫法，陣列數量不會為 0，而是 1，所以放到迴圈跑，還是會跑一次！
    
2.  取回 1 筆的情況
    
    ```bash
    $item = 'Hello'
    @($item).Length -eq 1
    ```
    
3.  取回超過 1 筆的情況
    
    ```bash
    $item = @('Hello', 'World')
    @($item).Length -eq 2
    ```
    
    > 這種「陣列」包「陣列」的寫法，在 PowerShell 並不會變成「二維陣列」喔，要注意！
    

所以[網路上看到的](https://stackoverflow.com/a/11107477/910074)這種建議，會在 0 筆的時候，額外跑一遍迴圈，所以不是一個真正完美的解法。

以下則是我的解法：

```powershell
# 在取得物件之後，透過以下方法轉換成絕對的陣列
if ($item -eq $null) { $item = @() } else { $item = @($item) }
```

1.  取回 0 筆的情況
    
    ```bash
    $item = $null
    
    # 在取得物件之後，透過以下方法轉換成絕對的陣列
    if ($item -eq $null) { $item = @() } else { $item = @($item) }
    
    @($item).Length -eq 0
    ```
    
    > 這種把 `$null` 包在「陣列」的寫法，陣列數量不會為 0，而是 1，所以放到迴圈跑，還是會跑一次！
    
2.  取回 1 筆的情況
    
    ```bash
    $item = 'Hello'
    
    # 在取得物件之後，透過以下方法轉換成絕對的陣列
    if ($item -eq $null) { $item = @() } else { $item = @($item) }
    
    @($item).Length -eq 1
    ```
    
3.  取回超過 1 筆的情況
    
    ```bash
    $item = @('Hello', 'World')
    
    # 在取得物件之後，透過以下方法轉換成絕對的陣列
    if ($item -eq $null) { $item = @() } else { $item = @($item) }
    
    @($item).Length -eq 2
    ```
    
    > 這種「陣列」包「陣列」的寫法，在 PowerShell 並不會變成「二維陣列」喔，要注意！
    

我後來又再次研究了一下，我發現只有 PowerShell Core 執行環境下，所有物件才有 `.Count` 屬性，而 Windows PowerShell 則只能說是「大部分」的物件都有 `.Count` 屬性，少部分物件類型還是沒有的！ 🔥

### 總結

好吧，我要來總結了，綜合上述的的各種情境，我終於可以很自信的驗證一個觀念，那就是 PowerShell 雖然骨子裡以 .NET 為基礎，但他在語言特性上卻一點都不像 .NET 的強型別特性。我們在 .NET 經常需要判斷各種空值的情境，但是在 PowerShell 根本不用，畢竟這是給 IT 人員的腳本語言，因此開發人員絕對不能把 PowerShell 跟 .NET 混為一談。

> 我終於瞭解，原來開發人員寫不好 PowerShell 是有原因的！ 😆

所以我最終的標準寫法，可以歸納出兩個結論：

1.  判斷筆數用 `.Count`，千萬不要用 `.Length`！
    
    ```powershell
    $item.Count
    ```
    
    > 此情境適用於 0, 1, >1 筆資料，但僅限於 PowerShell Core 環境下。
    
2.  讀取資料不要直接存取，而是強制轉成陣列來處理，然後直接用 foreach 跑迴圈就對了，萬無一失！
    
    ```powershell
    # 在取得物件之後，透過以下方法轉換成絕對的陣列
    if ($item -eq $null) { $item = @() } else { $item = @($item) }
    
    $item | foreach { $_ }
    ```
    

網路上各種奇葩的寫法，還有各位之前寫過的程式碼，翻出來看可能都會覺得莫名其妙，原來這麼簡單就可以寫好了！ 😍

### 相關連結

-   [How to use the PowerShell documentation](https://learn.microsoft.com/en-us/powershell/scripting/how-to-use-docs?view=powershell-7.2&WT.mc_id=DT-MVP-4015686)
-   [Everything you wanted to know about $null](https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-null?view=powershell-7.2&WT.mc_id=DT-MVP-4015686)
-   [您想知道有關於 $null 的一切](https://learn.microsoft.com/zh-tw/powershell/scripting/learn/deep-dives/everything-about-null?view=powershell-7.2&WT.mc_id=DT-MVP-4015686)
-   [How can I force Powershell to return an array when a call only returns one object? - Stack Overflow](https://stackoverflow.com/a/72914316/910074)
-   [How does the Count property work in Powershell? - Stack Overflow](https://stackoverflow.com/a/26513523/910074)
