PowerShell 不僅僅是一種腳本語言,還是一個可執行命令列的 Shell 工具,而他也和其他 Shell 一樣可以透過撰寫腳本來執行很複雜的功能,但有時候我們已經有大量用 C# 撰寫好的類別庫,或者想要用熟悉的 C# 程式碼來擴充 PowerShell 的使用情境時,這裡提供兩種讓你在 PowerShell 中執行 C# 程式的方法。

直接寫程式碼進行載入

PowerShell 提供了 Add-Tpye 命令,這個命令的用途在於將 Microsoft .NET Framework 或 .NET Core 的類別載入 PowerShell 執行階段中。

Add-Type 有可以參數可以直接接收 C# 程式碼,使用方式基本如下:

$Assemblies = ( <# 要加入參考組件 #> )
$CSharpCode = @"要執行的 C# 程式碼"@
Add-Type -ReferencedAssemblies $Assemblies -TypeDefinition $CSharpCode -Language CSharp

# 上述執行後,執行命令的方法
[Namespace.ClassName]::MethodName()

上面建立了兩個變數,分別是要程式碼需要的參考,以及要執行的程式碼本身,接著透過 Add-Type -ReferencedAssemblies $Assemblies -TypeDefinition $CSharpCode -Language CSharp 將該程式載入到 PowerShell 執行階段使用。

實際使用範例

如同上述所提的做法,我們將程式碼加上去。

PowerShell 預設會參考 System.dllSystem.Management.Automation.dll 這兩個組件,若你會用到其他組件時,才需要額外指定下述 $Assemblies 變數的內容值。

$Assemblies = (
    "System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL",
    "System.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"
)
$CSharpCode = @"
using System;
using System.Linq;

namespace PSLib
{
    public static class HelloWorld
    {
        public static void Say(string name)
        {
            Console.Write("Hello World, " + name);
        }

        public static void LinqRange(int max)
        {
            var squares = Enumerable.Range(1, max).Select(i => i * i);
            foreach (var num in squares)
            {
                Console.Write(num + "\t");
            }
        }
    }
}
"@
Add-Type -ReferencedAssemblies $Assemblies -TypeDefinition $CSharpCode -Language CSharp

一個常見的問題是,該如何取得組件的描述,可以參考這篇,主要透過 Visual Studio 的開發人員命令提示字元,使用 gacutil -l 命令就可以取得本機全域組件快取的組件清單。

執行後我們就可以有下面兩個命令可以執行:

# 可執行命令
[PSLib.HelloWorld]::Say()
[PSLib.HelloWorld]::LinqRange(600)

透過載入 DLL 執行

上面的方式看似方便,其實有點不好維護,畢竟要寫純文字的程式碼,有點難寫。

另一種方式相對方便,做法是在 Visual Studio 或 Visual Studio Code 中寫好程式,編譯成 DLL 後,在 PowerShell 中載入該 DLL 並呼叫其方法,看看以下作法。

建立 DLL

開啟你習慣使用的 C# 程式碼編輯器,這裡我用 Visual Studio 建立類別庫專案,並新增 HelloWorld.cs 檔,然後同樣使用上面的程式碼。

Hello World 程式瑪

建置後會得到一顆 PSLib.dll DLL 組件檔案。

得到一顆 DLL 組件檔案

載入 DLL 至 PowerShell

這裡我們建立一個資料夾,並將剛剛建置得到的 DLL 檔複製進來,接著建立 ExecuteCSharpDLL.ps1 檔,程式碼如下:

你可以透過 Write-Host 命令來查看變數值,例如:Write-Host $PSLib

$CurrentLocation = Get-Location
$PSLib = "$CurrentLocation\PSLib.dll"
$Dlls = (
    $PSLib
)
Add-Type -LiteralPath $Dlls

這是透過 Add-Type 命令並指定要載入的 DLL 絕對路定,這裡的 $Dlls 會是個字串陣列,讓我們可以一次載入多個 DLL 組件,透過這樣的方式來將程式碼載入到 PowerShell 執行階段中。

如此一來我們就可以透過以下方式來執行該 DLL 中所提供的方法,用法如下:

# 可執行命令
[PSLib.HelloWorld]::Say("Poy Chang")
[PSLib.HelloWorld]::LinqRange(10)

這樣是不是大大提高了使用的彈性,並且可以讓我們使用熟悉的 C# 來擴充功能。

替代作法

上面有提到 PowerShell 預設會載入 System.dll,因此我們也可以使用 Reflection 反射的方式來載入 DLL 組件,用法如下:

$CurrentLocation = Get-Location
$PSLib = "$CurrentLocation\PSLib.dll"

# 載入指定檔案路徑的 DLL 檔
[Reflection.Assembly]::LoadFile($PSLib)

重點在最後一行,透過 [Reflection.Assembly]::LoadFile() 方法,將絕對路徑的 DLL 檔載入到 PowerShell 執行階段中。

後記

嚴格來說,這裡提供了 3 種載入組件或程式碼的方法,個人是比較偏好使用 Add-Type -LiteralPath <String[]> 這種方法,一來簡潔,二來所提供的彈性更大,以上在 Powershell 中執行 C# 程式碼或 DLL 檔的方法,分享給大家。


參考資料:


Poy Chang

Trial and Error