共用方式為


第 10 章 - 腳本模組

如果您發現自己經常使用相同的PowerShell單行程式或腳本,將它們轉換成可重複使用的工具就更加重要。 將您的函式封裝於腳本模組中,不僅可提供更專業的外觀,還讓它們更容易支援和與他人分享。

點來源函式

上一章未涵蓋的其中一件事是點來源函式。 當您在腳本中定義函式,但不是模組的一部分時,將函式載入記憶體的唯一方法是點載其 .ps1 檔案。

例如,將下列函式儲存在名為 Get-MrPSVersion.ps1的檔案中。

function Get-MrPSVersion {
    $PSVersionTable
}

當您執行腳本時,它看起來沒有任何反應。

.\Get-MrPSVersion.ps1

嘗試呼叫函式會導致錯誤,因為它未載入記憶體中。

Get-MrPSVersion
Get-MrPSVersion : The term 'Get-MrPSVersion' is not recognized as the name
of a cmdlet, function, script file, or operable program. Check the spelling
of the name, or if a path was included, verify that the path is correct and
try again.
At line:1 char:1
+ Get-MrPSVersion
+ ~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (Get-MrPSVersion:String) [],
   CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

您可以確認函式是否存在於 函式: PSDrive,以確認函式是否已載入記憶體中。

Get-ChildItem -Path Function:\Get-MrPSVersion
Get-ChildItem : Cannot find path 'Get-MrPSVersion' because it does not
exist.
At line:1 char:1
+ Get-ChildItem -Path Function:\Get-MrPSVersion
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (Get-MrPSVersion:String) [Get
   -ChildItem], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.Ge
   tChildItemCommand

執行定義函式的腳本問題在於它會將它載入 腳本 範圍。 腳本完成執行之後,PowerShell 會捨棄該範圍以及 函式。

若要讓函式在腳本執行之後保持可用,它必須載入 至全域 範圍。 您可以透過點來源腳本檔來完成此作業。 您可以為此使用相對路徑。

. .\Get-MrPSVersion.ps1

您也可以在執行腳本時使用其完整路徑。

. C:\Demo\Get-MrPSVersion.ps1

如果路徑的一部分儲存在變數中,您可以將它與路徑的其餘部分結合。 不需要使用字串串連來執行此動作。

$Path = 'C:\'
. $Path\Get-MrPSVersion.ps1

現在,如果您檢查 函式 PSDrive,您會顯示 Get-MrPSVersion 函式可用。

Get-ChildItem -Path Function:\Get-MrPSVersion
CommandType     Name                                               Version
-----------     ----                                               -------
Function        Get-MrPSVersion

腳本模組

在 PowerShell 中,腳本模組只是包含一或多個函式的檔案,就像一 .psm1 般腳本,但擴展名不同。

如何建立腳本模組? 您可能會假設有一個命令名為類似 New-Module。 該假設是合理的猜測,但該命令實際上會建立動態模組,而不是腳本模組。

此情境是一個很好的提醒,即使命令名稱看起來與您所需的完全相同,也要一如既往地閱讀幫助文件。

help New-Module
NAME
    New-Module

SYNOPSIS
    Creates a new dynamic module that exists only in memory.


SYNTAX
    New-Module [-Name] <System.String> [-ScriptBlock]
    <System.Management.Automation.ScriptBlock> [-ArgumentList
    <System.Object[]>] [-AsCustomObject] [-Cmdlet <System.String[]>]
    [-Function <System.String[]>] [-ReturnResult] [<CommonParameters>]


DESCRIPTION
    The `New-Module` cmdlet creates a dynamic module from a script block.
    The members of the dynamic module, such as functions and variables, are
    immediately available in the session and remain available until you
    close the session.

    Like static modules, by default, the cmdlets and functions in a dynamic
    module are exported and the variables and aliases are not. However, you
    can use the Export-ModuleMember cmdlet and the parameters of
    `New-Module` to override the defaults.

    You can also use the **AsCustomObject** parameter of `New-Module` to return
    the dynamic module as a custom object. The members of the modules, such
    as functions, are implemented as script methods of the custom object
    instead of being imported into the session.

    Dynamic modules exist only in memory, not on disk. Like all modules,
    the members of dynamic modules run in a private module scope that is a
    child of the global scope. Get-Module cannot get a dynamic module, but
    Get-Command can get the exported members.

    To make a dynamic module available to `Get-Module`, pipe a `New-Module`
    command to Import-Module, or pipe the module object that `New-Module`
    returns to `Import-Module`. This action adds the dynamic module to the
    `Get-Module` list, but it does not save the module to disk or make it
    persistent.


RELATED LINKS
    Online Version: https://learn.microsoft.com/powershell/module/microsoft.
    powershell.core/new-module?view=powershell-5.1&WT.mc_id=ps-gethelp
    Export-ModuleMember
    Get-Module
    Import-Module
    Remove-Module
    about_Modules

REMARKS
    To see the examples, type: "Get-Help New-Module -Examples".
    For more information, type: "Get-Help New-Module -Detailed".
    For technical information, type: "Get-Help New-Module -Full".
    For online help, type: "Get-Help New-Module -Online"

上一章提到函式應該使用已核准的動詞。 否則,PowerShell 會在匯入模組時產生警告。

下列範例會用 New-Module cmdlet 在記憶體中建立動態模組,特別用來示範當您不使用已核准動詞時會有什麼結果。

New-Module -Name MyModule -ScriptBlock {

    function Return-MrOsVersion {
        Get-CimInstance -ClassName Win32_OperatingSystem |
        Select-Object -Property @{Label='OperatingSystem';Expression={$_.Caption}}
    }

    Export-ModuleMember -Function Return-MrOsVersion

} | Import-Module
WARNING: The names of some imported commands from the module 'MyModule' include
unapproved verbs that might make them less discoverable. To find the commands with
unapproved verbs, run the Import-Module command again with the Verbose parameter. For a
list of approved verbs, type Get-Verb.

雖然您在 New-Module 上一個範例中使用 Cmdlet,如先前所述,但它不是在 PowerShell 中建立腳本模組的命令。

若要建立腳本模組,請將函式儲存於.psm1檔案中。 例如,將下列兩個函式儲存在名為 MyScriptModule.psm1的檔案中。

function Get-MrPSVersion {
    $PSVersionTable
}

function Get-MrComputerName {
    $env:COMPUTERNAME
}

嘗試執行其中一個函式。

Get-MrComputerName

當您呼叫函式時,您會收到錯誤,指出 PowerShell 找不到它。 就像之前一樣,檢查 函式: PSDrive 會確認它未載入記憶體中。

Get-MrComputerName : The term 'Get-MrComputerName' is not recognized as the
name of a cmdlet, function, script file, or operable program. Check the
spelling of the name, or if a path was included, verify that the path is
correct and try again.
At line:1 char:1
+ Get-MrComputerName
+ ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (Get-MrComputerName:String) [
   ], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

若要讓此功能可供使用,您可以使用 MyScriptModule.psm1 cmdlet 手動匯入 Import-Module 檔案。

Import-Module C:\MyScriptModule.psm1

PowerShell 引進了第 3 版的模組自動載入。 若要利用這項功能,腳本模組必須儲存在與檔案相同的基底名稱 .psm1 資料夾中。 該資料夾必須位於環境變數中指定的 $env:PSModulePath 其中一個目錄。

$env:PSModulePath

$env:PSModulePath 輸出難以讀取。

C:\Users\mike-ladm\Documents\WindowsPowerShell\Modules;C:\Program Files\Wind
owsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules;C:\
Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\

若要讓結果更容易閱讀,請分割分號路徑分隔符上的路徑,讓每個路徑都出現在自己的行上。

$env:PSModulePath -split ';'

清單中的前三個路徑是預設模組位置。 SQL Server Management Studio 會在您安裝時新增最後一個路徑。

C:\Users\mike-ladm\Documents\WindowsPowerShell\Modules
C:\Program Files\WindowsPowerShell\Modules
C:\Windows\system32\WindowsPowerShell\v1.0\Modules
C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\

若要讓模組自動載入能夠運作,您必須將 MyScriptModule.psm1 檔案放在名為 MyScriptModule的資料夾中,而且該資料夾必須直接位於所列其中一個路徑內
$env:PSModulePath

並非所有路徑都同樣有用。 例如,我系統上目前的用戶路徑不是清單中的第一個路徑。 這是因為我以不同於我用來執行PowerShell的帳戶登入 Windows。 因此,它不會指向使用者的文件資料夾。

第二個路徑是 AllUsers 路徑,也就是我儲存所有模組的位置。

第三個路徑指向 C:\Windows\System32,這是受保護的系統位置。 只有Microsoft應該將模組放在該處,因為它落在作系統的目錄結構之下。

一旦您將檔案放在 .psm1 其中一個路徑內的適當資料夾中,PowerShell 會在您第一次呼叫其中一個命令時自動載入模組。

模組指令清單

每個模組都應該包含模組指令清單,這是 .psd1 包含模組相關元數據的檔案。 .psd1擴充功能用於清單檔,但並非所有.psd1檔案都是模組清單檔。 您也可以將其用於其他用途,例如在 DSC 中定義環境數據
配置。

您可以使用 Cmdlet 建立模組指令清單 New-ModuleManifest 。 唯一必要的參數是 Path,但讓模組正常運作,您也必須指定 RootModule 參數。

最好包含 如 AuthorDescription 之類的值,特別是當您打算使用 PowerShellGet 將模組發佈至 NuGet 存放庫時。 該案例中需要這些欄位。

判斷模組是否缺少指令清單的一個快速方式是檢查其版本。

Get-Module -Name MyScriptModule

的版本號碼 0.0 是模組缺少指令清單的明確標誌。

ModuleType Version    Name                                ExportedCommands
---------- -------    ----                                ----------------
Script     0.0        MyScriptModule                      {Get-MrComputer...

建立模組指令清單時,您應該包含所有建議的詳細數據,以確保您的模組已做好記錄,並準備好進行共用或發佈。

$moduleManifestParams = @{
    Path = "$env:ProgramFiles\WindowsPowerShell\Modules\MyScriptModule\MyScriptModule.psd1"
    RootModule = 'MyScriptModule'
    Author = 'Mike F. Robbins'
    Description = 'MyScriptModule'
    CompanyName = 'mikefrobbins.com'
}

New-ModuleManifest @moduleManifestParams

如果您在一開始建立模組指令清單時省略任何值,則可以稍後使用 Update-ModuleManifest Cmdlet 來新增或更新它。 避免在建立後使用 New-ModuleManifest 重新建立指令清單,因為這樣做會產生新的 GUID。

定義公用和私人函式

有時候,您的模組可能包含您不想向使用者公開的協助程式函式。 模組中的其他函式會在內部使用這些私用函式,但不會公開給使用者。 有幾種方式可以處理此案例。

如果您未遵循最佳做法,而且只有一個沒有適當模組結構的 .psm1 檔案,則唯一的選項是使用 Export-ModuleMember cmdlet 來控制可見度。 此選項可讓您明確定義應該直接從腳本模組檔案中 .psm1 公開哪些函式,並預設保留其他所有函式。

在下列範例中,只有函 Get-MrPSVersion 式會公開給模組的使用者,而函 Get-MrComputerName 式在內部仍可供模組內的其他函式存取。

function Get-MrPSVersion {
    $PSVersionTable
}

function Get-MrComputerName {
    $env:COMPUTERNAME
}

Export-ModuleMember -Function Get-MrPSVersion

判斷 MyScriptModule 模組中公開可用的命令。

Get-Command -Module MyScriptModule
CommandType     Name                                               Version
-----------     ----                                               -------
Function        Get-MrPSVersion                                    1.0

如果您將模組指令清單新增至模組,最佳做法是明確列出您想要在 FunctionsToExport 區段中導出的函式。 此選項可讓您控制從 .psd1 模組指令清單檔公開給使用者的內容。

FunctionsToExport = 'Get-MrPSVersion'

您不需要在Export-ModuleMember檔案與.psm1模組指令清單的區段中同時使用 FunctionsToExport 。 任一種方法本身就足夠了。

總結

在本章中,您已瞭解如何在PowerShell中將函式轉換成腳本模組。 您也探索了建立腳本模組的最佳做法,包括新增模組指令清單來定義元數據和管理匯出命令的重要性。

回顧

  1. 如何在PowerShell中建立腳本模組?
  2. 為什麼對函式名稱使用已核准的動詞很重要?
  3. 如何在 PowerShell 中建立模組指令清單?
  4. 僅從模組匯出特定函式的兩種方式為何?
  5. 當您執行其中一個命令時,模組必須符合哪些條件才能自動載入?

參考資料