1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

シェルスクリプト&PowerShellAdvent Calendar 2024

Day 24

PowerShell で自分で定義したコマンド・エイリアスの一覧を取得する

Last updated at Posted at 2024-12-23

シェルスクリプト&PowerShell Advent Calendar 2024の記事です。
はじめて外部のアドベントカレンダーに参加させていただきました!
至らないところ等あるかと思いますがよろしくお願いします!

はじめに

この記事は PowerShell で自分で定義した(Powershell にもとからあるもの以外) コマンドやエイリアスの一覧を取得するためのものです。

皆さん自分好みにシェルをカスタマイズしているかと思いますが、過去に自分が登録したものって忘れてしまいますよね..? でもいちいち $PROFILE を開くのも面倒だし...
そんな方にぴったりだと思います。

本記事では2通りのやり方で実現します。

  • $PROFILE の実行前と実行後を比較する方法
    • 面倒ですが確実です
  • FunctionInfo.ScriptBlock.FileAliasInfo.HelpUri を用いる方法
    • 少し確率的な方法になってしまいます

基本の方針

基本は

  • Alias を取得するには Get-Alias を、
  • 自作コマンドを取得するには Get-ChildItem Function: を用います。

後者に関しては以下の記事がとても参考になるのでそちらをどうぞ。

簡単に言えば Powershell ではコマンドも変数もドライブ(C:とかと同じヤツ)に入るのでそこを探すのです。ドライブについては以下の公式ドキュメントをどうぞ。

作成するコマンドのインタフェース

以下のようなユーザインタフェースにします。

  1. 引数なしの場合: コマンドとエイリアスを一覧表示
# 引数を与えない場合: コマンドとエイリアスを一覧表示

> Get-UserDefined

function:

Name                             SourceFile                                                                  Definition
----                             ----------                                                                  ----------
Add-PoshGitToProfile             D:\Scoop\modules\posh-git\Utils.ps1                                         ...
ConvertTo-MsysPath               D:\PowershellPrograms\PowershellCmdlets\ConvertTo-msysPath.ps1              ...
ConvertTo-Narrow                 D:\PowershellPrograms\PowershellCmdlets\ConvertCharWidth.ps1                ...
ConvertTo-Slash                  C:\Users\XXX\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1 ...
ConvertTo-Wide                   D:\PowershellPrograms\PowershellCmdlets\ConvertCharWidth.ps1                ...
Copy-Date                        C:\Users\XXX\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1 ...
cpwd                             C:\Users\XXX\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1 ...

alias:

Name          Definition
----          ----------
code          code-insiders
cpdt          Copy-Date
ctmsys        ConvertTo-MsysPath
Rgui          C:\Program Files\R\R-4.3.0\bin\x64\Rgui.exe
  1. 引数ありの場合: 情報を詳細表示
# コマンド
> Get-UserDefined copy-date

function:
Name:
        Copy-Date

SourceFile:
        C:\Users\XXX\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1

Definition:

        param(
                [validateset("today", "yesterday", "tomorrow")]$when = "today",
                [validateset(
                        "yyyyMMdd",
                        "yyyy/MM/dd",
                        "yyyy-MM-dd",
                        "normal",
...

# alias
> Get-UserDefined rgui


alias:
Name:
        Rgui

Definition:
        C:\Program Files\R\R-4.3.0\bin\x64\Rgui.exe

$PROFILE の実行前と実行後を比較する方法

この方法は $PROFILE を実行する前と後でオブジェクトを採取し、それを比較する方法です。

まずは以下のスクリプトを $PROFILE (およびそれに準ずるもの) の 先頭に 置きます

# default aliases and functions
$defaultAliases = (Get-Alias)
$defaultFuncs = (Get-ChildItem Function:)
Write-Output "`nIf you see all user-defined functions or aliases, type:`n`tGet-UserDefined`n"

次に、以下のコマンドを先ほどのファイルの 最後に 置きます。

function Get-UserDefined {
    param (
        [string]$name
    )
    $allAliases = (Get-Alias)
    $allFuncs = (Get-ChildItem Function:)

    # 差分を取得する
    $aliasDiff = (Compare-Object $defaultAliases $allAliases).InputObject
    $funcDiff = (Compare-Object $defaultFuncs $allFuncs).InputObject

    # 表示しやすいように PSCustomObject にする
    $userAliases = $aliasDiff | ForEach-Object {
        [PSCustomObject]@{
            Name       = $_.Name
            Definition = $_.Definition
        }
    }
    $userFuncs = $funcDiff | ForEach-Object {
        [PSCustomObject]@{
            Name       = $_.Name
            SourceFile = $_.ScriptBlock.File
            Definition = $_.Definition
        }
    }

    # もし引数が与えられていた場合の処理
    if ($name) {
        $aliasDiff | Where-Object {
            $_.Name -eq $name 
        } | ForEach-Object {
            Write-Output "`n`nalias:"
            Write-Output "Name:"
            Write-Output "`t$($_.Name)"
            Write-Output "`nDefinition:"
            Write-Output "`t$($_.Definition)"
        }
        $funcDiff | Where-Object {
            $_.Name -eq $name 
        } | ForEach-Object {
            Write-Output "`n`nfunction:"
            Write-Output "Name:"
            Write-Output "`t$($_.Name)"
            Write-Output "`nSourceFile:"
            Write-Output "`t$($_.ScriptBlock.File)"
            Write-Output "`nDefinition:"
            Write-Output $_.Definition
        }
    }
    # 引数なし
    else {
        Write-Output "`nfunction:"
        Write-Output $userFuncs | Format-Table -Property Name, SourceFile, Definition
        
        Write-Output "`nalias:"
        Write-Output $userAliases | Format-Table -Property Name, Definition
    }
}

これで上記2つの間で定義された alias やコマンドを参照できるというわけです。

しかしこれでは場所に依存してしまいますし、あまり便利とは言えません。まあ間にあるものは確実にキャプチャできるので確実と言えば確実ですが。

FunctionInfo.ScriptBlock.FileAliasInfo.HelpUri を用いる方法

そこで次に確実ではないものの、不便でもない方法を紹介します。

先ほどと同じように Get-ChildItem Function:Get-Alias で情報を取得するのですが、それを以下のようにして選別します。

  • (コマンド) FunctionInfo.ScriptBlock.File を使って、コマンドが定義されたファイルを調べる
    • これは確実と言えば確実です。本記事では元からあるものとモジュール以外のすべてをユーザ定義コマンドとして扱いました。これは人によって変えてもいいと思います。
  • (alias) AliasInfo.HelpUri を使ってコマンドのヘルプの参照先として指定されている URL で選別する

前者に関してはもっといい方法が無いかと調べているときに、以下の先駆者様を発見し、参考にさせていただきました。

コマンド:


function Get-UserDefined2 {
    param (
        [string]$name
    )

    # ユーザalias
    $userAliases = (Get-Alias) | Where-Object { 
        $isUserAlias = $true
        if ($_.HelpUri) {
            # 元からある alias は Help Link が以下の2つ(多分)
            # ただしユーザであっても Help ドキュメントに以下のURLを記載することは可能なため万能ではない 
            if ($_.HelpUri -like "https://go.microsoft.com/fwlink*" -or $_.HelpUri -like "https://learn.microsoft.com/powershell/*"){
                $isUserAlias = $false   
            }
        } 
        Write-Output $isUserAlias
    } | ForEach-Object {
        [PSCustomObject]@{
            Name       = $_.Name
            Definition = $_.Definition
        }
    }

    # ユーザコマンド
    $userFuncs = (Get-ChildItem Function:) | Where-Object {
        $isUserFunc = $true
        $filepath = $_.ScriptBlock.File
        if ($filepath) {
            # ScriptBlock.File の値が空でないもの
            foreach ($path in $env:PSModulePath -split ";") {
                # module だったらユーザコマンドではない(と考える)
                if ($filepath -like "${path}*") { $isUserFunc = $false }
            }
        }
        else {
            # ScriptBlock.File の値が空だったら元からあるもの
            $isUserFunc = $false
        }
        Write-Output $isUserFunc

    } | ForEach-Object {
        [PSCustomObject]@{
            Name       = $_.Name
            SourceFile = $_.ScriptBlock.File
            Definition = $_.Definition
        }
    }

    # もし引数が与えられていた場合の処理
    if ($name) {
        $userAliases | Where-Object {
            $_.Name -eq $name 
        } | ForEach-Object {
            Write-Output "`n`nalias:"
            Write-Output "Name:"
            Write-Output "`t$($_.Name)"
            Write-Output "`nDefinition:"
            Write-Output "`t$($_.Definition)"
        }
        $userFuncs | Where-Object {
            $_.Name -eq $name 
        } | ForEach-Object {
            Write-Output "`n`nfunction:"
            Write-Output "Name:"
            Write-Output "`t$($_.Name)"
            Write-Output "`nSourceFile:"
            Write-Output "`t$($_.SourceFile)"
            Write-Output "`nDefinition:"
            Write-Output $_.Definition
        }
    }
    # 引数なし
    else {
        Write-Output "`nfunction:"
        Write-Output $userFuncs | Format-Table -Property Name, SourceFile, Definition
            
        Write-Output "`nalias:"
        Write-Output $userAliases | Format-Table -Property Name, Definition
    }
}

終わりに

結構需要が高そうなことで、先駆者様などいろいろな人が工夫していることがわかりました。

もっといい方法ないかなぁ...

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?