\ Amazonのセール 完全に理解した /

自作したPowerShell関数をまとめて掲載

いままで技術ブログ(Zenn.dev)のこちらの記事に自作した関数(Function)をまとめて掲載してましたが、
掲載する関数が多くなりプラットフォームの文字数制限(80,000文字)に引っかかってしまいました。

そのため、こちらの記事にコンテンツを外だしして一挙、掲載することに。前置きは飛ばして早速紹介していきます。

目次

Zennの記事で紹介したPowerShellの自作関数

並び順は投稿日付です。古い記事(昇順)から掲載しています。

  1. 挿入するタブ数を制御しインデントを合わせ配列を一括出力する方法
  2. 複数コマンド格納した文字列配列を一つひとつ実行する方法
  3. 複数のサーバーに対し指定したポートで疎通確認する方法
  4. 文字コードと管理者権限を判定しウィンドウタイトルを変更
  5. wingetの結果を変数に代入し出力すると文字化けが発生
  6. 標準出力を無視するコマンドに変換するFunction
  7. “ping -t”のようにPowerShellでも連続してping疎通する方法
  8. 文字コード および BOM付き を判定するFunction
  9. PowerShell 6.0以降(Core)でOS環境を確認できる自動変数
  10. PowerShellからPythonのchardetを使って文字コードを判定
  11. 変数のデータ型を簡単に確認できるFunction
  12. ファイル内の改行コードを一括変換するFunction
  13. 複数IDを検索キーにwinget showの結果を抽出し表示するFunction
  14. 文字列のバイト数を取得する方法(文字列抽出するFunctionも紹介)
  15. 配列の種類(ジャグ配列 or 多次元配列)を判定するFunction”
  16. PowerShellのわかりにくい比較演算子を記号で判定可能にするFunction
  17. 指定したモジュールの導入有無を確認するFunction
  18. ファイルのロック状態を確認するFunction
  19. “Markdownテーブル”と“Excelファイル”を相互変換する方法
  20. 2つの配列同士の要素数が同じかチェックするFunction
  21. 定義したFunctionの中身を確認する方法
  22. 定数が定義済みかチェックするFunction
  23. 指定したファイルの中身が存在するかチェックするFunction
  24. フォルダーをツリー形式で表示するFunction(CLIとGUIの2通り)
  25. Markdownのテーブル形式で各列の開始位置を合わせ整形するFunction
  26. PSCustomObjectで同じ要素(項目数・項目名)か比較するFunction
  27. UNIXのwhichコマンドのように実行ファイルの格納先を確認する方法
  28. コマンド出力結果のインデントを上げて少し見やすくするFunction
  29. ログインユーザーがAdministratorsに所属しているか判定するFunction
  30. 指定ドライブ内でファイルサイズが大きい順に上位ファイルを取得する方法
  31. wingetコマンドでChromeなどのソフト・アプリをインストールする方法
  32. 2次元のジャグ配列と多次元配列それぞれに変換できるFunction

挿入するタブ数を制御しインデントを合わせ配列を一括出力する方法

<#
.SYNOPSIS
    文字列配列の各要素について、Shift-JISでのバイト数を計算し、インデントを揃えてコンソールに出力します。

.DESCRIPTION
    この関数は、StringArray パラメータで指定された文字列配列を処理します。
    配列内の各文字列について、Shift-JIS エンコーディングでのバイト数を計算します。
    次に、配列内で最もバイト数が大きい文字列のバイト長を基準として、各行の出力のインデントをタブ文字で調整します。
    これにより、「、バイト数(Shift-JIS)[...]」の部分が整列され、視覚的に比較しやすくなります。

.PARAMETER StringArray
    バイト数を計算し、インデントを揃えて表示したい文字列の配列を指定します。
    このパラメータは必須です。

.EXAMPLE
    PS C:\> $stringLists = @("1234567890", "あ", "ア", "123456789012345678901234567890", "abcdefg")
    PS C:\> Show-StringsWithByteCount $stringLists

    ============ 出力結果 ============

    文字列 [1234567890]                     、バイト数(Shift-JIS)[10] byte
    文字列 [あ]                             、バイト数(Shift-JIS)[2] byte
    文字列 [ア]                              、バイト数(Shift-JIS)[1] byte
    文字列 [123456789012345678901234567890] 、バイト数(Shift-JIS)[30] byte
    文字列 [abcdefg]                        、バイト数(Shift-JIS)[7] byte

    ================================

    この例では、複数の文字列を含む配列を関数に渡します。
    関数は各文字列のShift-JISでのバイト数を計算し、最も長いバイト数(この場合は30)に合わせてインデントを調整して結果を出力します。

.NOTES
    - インデントの計算に使用するタブ幅は、スクリプト内で8文字としてハードコーディングされています。
      お使いのコンソールやターミナルのタブ設定によっては、表示が期待通りに揃わない場合があります。
    - バイト数の計算に使用するエンコーディングは "shift_jis" に固定されています。
    - 出力には `Write-Host` を使用しているため、結果をパイプラインで他のコマンドに渡すことはできません。
#>
Function Show-StringsWithByteCount {
    param (
        [Parameter(Mandatory=$true)]
        [System.Object[]]$StringArray
    )

    [System.Object[]]$byteCounts = $StringArray.Clone()
    [System.Int32]$i = 0
    [System.Int32]$maxLength = 0
    for ($i = 0; $i -lt $byteCounts.Count; $i++) {
        $byteCounts[$i] = [System.Text.Encoding]::GetEncoding("shift_jis").GetByteCount($byteCounts[$i])
        if ($maxLength -lt $byteCounts[$i]) {
            $maxLength = $byteCounts[$i]
        }
    }

    # タブ数の計算 と コンソール出力
    [System.Int32]$tabCount = 0
    [System.Int32]$tabWidth = 8
    Write-Host ''
    Write-Host ' ============ 出力結果 ============ '
    Write-Host ''
    for ($i = 0; $i -lt $byteCounts.Count; $i++) {
        # タブの数を計算します
        # 最長の文字列との差分をタブの幅で割り、切り上げます
        $tabCount = [Math]::Ceiling(($maxLength - [System.Int32]$byteCounts[$i]) / $tabWidth)
        # 最低1つはタブを挿入するよう設定
        if ($tabCount -eq 0) {
            $tabCount = 1
        }

        # 最終桁に改行なしで先頭部の文字列を出力
        Write-Host "文字列 [$($StringArray[$i])]" -NoNewline
        Write-Host ("`t" * $tabCount) -NoNewline
        # 最終桁に改行ありで続く文字列を出力
        Write-Host "、バイト数(Shift-JIS)[$($byteCounts[$i])] byte"
    }
    Write-Host ''
    Write-Host ' ================================ '
    Write-Host ''
    Write-Host ''
}

複数コマンド格納した文字列配列を一つひとつ実行する方法

<#
.SYNOPSIS
    文字列として格納された複数のコマンドを順次実行します。

.DESCRIPTION
    この関数は、commands パラメータで指定された文字列の配列を受け取ります。
    配列内の各文字列を個別のコマンドとして解釈し、[ScriptBlock]::Create() メソッドでスクリプトブロックに変換した後、Invoke-Command を用いて現在のセッションで実行します。
    これにより、動的に生成されたり、設定ファイルから読み込んだりしたコマンドのリストを簡単に実行することが可能になります。

.PARAMETER commands
    実行したいコマンドレットやスクリプトを格納した文字列の配列を指定します。
    このパラメータは必須です。

.EXAMPLE
    PS C:\> Invoke-MultipleCommands -commands @('Get-Date', 'Get-Item .', 'Get-PSDrive C')

    # 実行結果(例)

    2023年10月27日金曜日 10:30:00

        ディレクトリ: C:\Users\YourUser\Documents

    Mode                 LastWriteTime         Length Name
    ----                 -------------         ------ ----
    d-----        2023/10/26     11:00                ProjectA
    -a----        2023/10/27     09:45           1234 somefile.txt

    Name           Used (GB)     Free (GB) Provider      Root
    ----           ---------     --------- --------      ----
    C                 120.50        135.50 FileSystem    C:\

    この例では、'Get-Date'、'Get-Item .'、'Get-PSDrive C' の3つのコマンドを順番に実行します。
    各コマンドの出力が、実行されるたびにコンソールに表示されます。

.INPUTS
    なし
    この関数はパイプライン入力を受け付けません。

.OUTPUTS
    System.Object
    実行された各コマンドレットが出力するオブジェクトが、そのまま出力されます。出力されるオブジェクトの型は、実行されるコマンドに依存します。

.NOTES
    - この関数は文字列をコマンドとして実行するため、信頼できないソースからの入力を渡すと、意図しない、あるいは悪意のあるコードが実行される可能性があります。使用する際は、入力されるコマンド文字列が安全であることを十分に確認してください。
    - コマンドは現在のPowerShellセッションのコンテキストで実行されます。
    - コマンドの実行中にエラーが発生した場合、そのエラーはコンソールに表示されますが、スクリプトは停止せず後続のコマンドの実行を試みます。
#>
# 関数として定義
Function Invoke-MultipleCommands {
    Param (
        # 必須項目:実行するコマンドレットがある文字列配列用のパラメーター
        [Parameter(Mandatory=$true)]
        [System.String[]]$commands
    )
    # 配列内のコマンドを繰り返し処理で実行
    foreach ($command in $commands) {
        # 文字列のコマンドをスクリプトブロックに変換
        [System.Management.Automation.ScriptBlock]$scriptblock = [ScriptBlock]::Create($command)
        # スクリプトブロックを実行
        Invoke-Command -ScriptBlock $scriptblock
    }
}

複数のサーバーに対し指定したポートで疎通確認する方法

<#
.SYNOPSIS
    複数のサーバーとポートのペアに対してポート疎通確認(Test-NetConnection)を実行します。

.DESCRIPTION
    この関数は、サーバー名(またはIPアドレス)とポート番号のペアを格納したジャグ配列を引数として受け取ります。
    配列内の各ペアに対して Test-NetConnection コマンドレットを順に実行し、ポートの疎通性を確認します。
    Test-NetConnection が正常に実行されれば、その結果がコンソールに直接表示されます。
    コマンドレットの実行中に例外が発生した場合(例: 名前解決の失敗など)は、catch ブロックでエラーを捕捉し、どのサーバーとポートの組み合わせで問題が発生したかを示すカスタムエラーメッセージを Write-Error を用いて出力します。

.PARAMETER ServerPortPairs
    ポート疎通を確認したいサーバーとポートのペアを格納したジャグ配列(配列の配列)を指定します。
    各内部配列は、要素0にサーバー名またはIPアドレス、要素1にポート番号を文字列として格納する必要があります。
    このパラメータは必須です。

.EXAMPLE
    PS C:\> [System.String[][]]$ServerPortPairs = @(
    >>     @('localhost','8080'),
    >>     @('localhost','8000'),
    >>     @('google.com','443'),
    >>     @('smtp.google.com','25')
    >> )
    PS C:\> Check-PortMultipleServers $ServerPortPairs

    # 実行結果(一部抜粋)
    警告: TCP connect to (::1 : 8080) failed
    警告: TCP connect to (127.0.0.1 : 8080) failed

    ComputerName           : localhost
    RemoteAddress          : ::1
    RemotePort             : 8080
    TcpTestSucceeded       : False

    ComputerName     : localhost
    RemoteAddress    : ::1
    RemotePort       : 8000
    TcpTestSucceeded : True

    ComputerName     : google.com
    RemoteAddress    : 142.251.42.142
    RemotePort       : 443
    TcpTestSucceeded : True
    ...

    この例では、localhostの8080ポートと8000ポート、google.comの443ポート、smtp.google.comの25ポートへの接続を試みます。
    Test-NetConnection の結果がそれぞれのペアに対して順に出力されます。この例では、localhost:8080への接続が失敗し、他は成功している様子を示しています。

.NOTES
    - この関数は内部で `Test-NetConnection` コマンドレットを使用します。このコマンドレットは Windows PowerShell 4.0 以降、または PowerShell Core 6.1 以降で利用可能です。
    - ポートが閉じている場合、`Test-NetConnection` は例外を発生させず、`TcpTestSucceeded` プロパティが `False` のオブジェクトを返します。`try-catch` ブロックは、ホスト名の名前解決の失敗など、コマンドレット自体の実行に失敗した場合の例外を捕捉するために使用されます。
    - 成功した接続の結果は成功ストリームに、`catch` ブロックで捕捉されたエラーはエラー ストリームに出力されます。
#>
# Function 定義
Function Check-PortMultipleServers {
    param (
        [Parameter(Mandatory=$true)]
        [System.String[][]]$ServerPortPairs
    )
    $tempStrings=New-Object System.Text.StringBuilder

    for($i = 0; $i -lt $ServerPortPairs.Length; $i++) {
        $currentPair = $ServerPortPairs[$i]

        try {
            Test-NetConnection $currentPair[0] -Port $currentPair[1]
        }
        catch {
            $tempStrings=New-Object System.Text.StringBuilder
            @("Test-NetConnectionコマンドレットでエラーが発生しました。`r`n",`
              "エラーとなったコマンド:[Test-NetConnection $($currentPair[0]) -Port $($currentPair[1])]")|
            ForEach-Object{[void]$tempStrings.Append($_)}
            [System.String]$messageError = $tempStrings.ToString()
            Write-Error $messageError
        }
    }
}

文字コードと管理者権限を判定しウィンドウタイトルを変更

<#
.SYNOPSIS
    現在のPowerShellセッションが管理者権限で実行されているかどうかを判定します。

.DESCRIPTION
    この関数は、[System.Security.Principal.WindowsPrincipal] クラスを利用して、現在のユーザーが組み込みの "Administrator" ロールに属しているかを確認します。これにより、スクリプトが昇格された権限で実行されているかを簡単にチェックできます。

.EXAMPLE
    PS C:\> if (Test-IsAdmin) {
    >>     Write-Host "このセッションは管理者権限で実行されています。"
    >> } else {
    >>     Write-Host "このセッションは管理者権限で実行されていません。"
    >> }
    このセッションは管理者権限で実行されています。

    この例では、関数の戻り値($trueまたは$false)を使って、現在の権限状態に応じたメッセージを表示します。

.OUTPUTS
    System.Boolean
    管理者権限で実行されている場合は $true を、そうでない場合は $false を返します。
#>
# 管理者として実行しているか確認(Trueの場合、“管理者として実行”していると判断)
Function Test-IsAdmin {
    $winId = [System.Security.Principal.WindowsIdentity]::GetCurrent()
    $winPrincipal = new-object System.Security.Principal.WindowsPrincipal($winId)
    $adminPermission = [System.Security.Principal.WindowsBuiltInRole]::Administrator
    return $winPrincipal.IsInRole($adminPermission)
}

<#
.SYNOPSIS
    PowerShellセッションの主要な3つの文字コード設定を取得します。

.DESCRIPTION
    この関数は、PowerShellの動作に影響を与える3つの主要なエンコーディング設定を調査し、それらのWebName(例: "utf-8", "shift_jis")を文字列配列として返します。
    取得する設定は以下の通りです。
    1. $PSDefaultParameterValues['*:Encoding']: コマンドレットの-Encodingパラメータの既定値。
    2. $global:OutputEncoding: PowerShellから外部プログラムへパイプで渡す際のエンコーディング。
    3. [console]::OutputEncoding: PowerShellコンソール自体の出力エンコーディング。

.EXAMPLE
    PS C:\> $charcodes = Get-PsCharcode
    PS C:\> "DefaultParameter: $($charcodes[0])"
    PS C:\> "GlobalEncoding: $($charcodes[1])"
    PS C:\> "ConsoleEncoding: $($charcodes[2])"
    DefaultParameter:
    GlobalEncoding: us-ascii
    ConsoleEncoding: shift_jis

    この例では、関数を実行して返された配列を変数に格納し、各エンコーディング設定を表示しています。

.OUTPUTS
    System.String[]
    3つの要素を持つ文字列配列を返します。
    - Index 0: $PSDefaultParameterValues['*:Encoding'] の値
    - Index 1: $global:OutputEncoding のWebName
    - Index 2: [console]::OutputEncoding のWebName

.NOTES
    $PSDefaultParameterValues['*:Encoding'] が設定されていない場合、対応する配列要素は空の文字列になります。
#>
# 現在、設定している文字コードを取得
Function Get-PsCharcode {
    [System.String[]]$psCharcode = @(
        # コンソールに出力する文字コードの規定値
        ($PSDefaultParameterValues['*:Encoding']),
        # PowerShellから外部プログラムに渡す文字エンコードの設定
        ($global:OutputEncoding).WebName,
        # PowerShellのコンソールに出力する文字エンコードの設定
        ([console]::OutputEncoding).WebName
    )

    return $psCharcode
}

<#
.SYNOPSIS
    現在のPowerShellウィンドウのタイトルに、文字コード設定と管理者権限の有無を追加します。

.DESCRIPTION
    この関数は、現在のPowerShellウィンドウのタイトルを読み込み、その末尾に情報を追加してタイトルを更新します。
    内部で `Get-PsCharcode` 関数を呼び出して主要な3つの文字コード設定を取得し、`Test-IsAdmin` 関数を呼び出して管理者権限の有無を確認します。
    これらの情報を整形し、「|」を区切り文字として現在のタイトルに追加表示します。
    関数を複数回実行しても、元のタイトル部分が重複しないように設計されています。

.EXAMPLE
    PS C:\> # 実行前のタイトル: Windows PowerShell
    PS C:\> Change-WindowTitle
    # 実行後、ウィンドウのタイトルが以下のように変更される:
    # Windows PowerShell | DefaultParameter=[] ; GlobalEncoding=[us-ascii] ; ConsoleEncoding=[shift_jis] ; #Administrator

    この例では、関数を実行することで、ウィンドウのタイトルに現在の文字コード設定と管理者状態が追加されます。

.OUTPUTS
    なし
    この関数は値を返さず、ホストUIのウィンドウタイトルを直接変更します。

.NOTES
    - この関数は、同じスクリプト内に `Test-IsAdmin` および `Get-PsCharcode` 関数が定義されていることを前提としています。
    - if-elseブロック内のロジックが同一のため、現在の実装では管理者・非管理者に関わらず末尾に「#Administrator」が付きます。
    - PowerShell ISEやVisual Studio Codeの統合ターミナルなど、一部のホスト環境ではウィンドウタイトルの変更が反映されない場合があります。
#>
# PowerShellウィンドウのタイトル変更
Function Change-WindowTitle {
    # 区切り文字の設定
    [System.String]$separator1 = '|'
    [System.String]$separator2 = ';'

    # 現在のタイトルを取得

    [System.String]$title = $Host.UI.RawUI.WindowTitle
    [System.String]$baseTitle = $title

    # すでにこのFunctionでタイトル変更している場合、一番左にある元のタイトル名のみ抽出
    [System.String[]]$titleArray = $title.Split($separator1)
    if ($titleArray.Length -ne 0) {
        $baseTitle = ($titleArray[0]).TrimEnd()
    }

    # 現在の文字コードを取得
    [System.String[]]$psCharcode = Get-PsCharcode

    # 管理者として実行しているかにより設定するタイトル名を分岐
    [System.String]$changeTitle = $baseTitle
    if (Test-IsAdmin) {
        # PowerShellを管理者として実行している場合
        $changeTitle = "$baseTitle $separator1 " +
                       "DefaultParameter=[$($psCharcode[0])] $separator2 " +
                       "GlobalEncoding=[$($psCharcode[1])] $separator2 " +
                       "ConsoleEncoding=[$($psCharcode[2])] $separator2 " +
                       "#Administrator"
    }
    else {
        # していない場合
        $changeTitle = "$baseTitle $separator1 " +
                       "DefaultParameter=[$($psCharcode[0])] $separator2 " +
                       "GlobalEncoding=[$($psCharcode[1])] $separator2 " +
                       "ConsoleEncoding=[$($psCharcode[2])] $separator2 " +
                       "#Administrator"
    }
    $Host.UI.RawUI.WindowTitle = $changeTitle
}

wingetの結果を変数に代入し出力すると文字化けが発生

<#
.SYNOPSIS
    PowerShellセッションの主要な文字コード設定をまとめて変更します。

.DESCRIPTION
    この関数は、指定されたモードに応じて、PowerShellの3つの主要な文字コード設定を変更します。
    変更対象となるのは以下の3つです。
    - $PSDefaultParameterValues['*:Encoding']: コマンドレットの-Encodingパラメータの既定値。
    - $global:OutputEncoding: PowerShellから外部プログラムへパイプ/リダイレクトする際のエンコーディング。
    - [console]::OutputEncoding: PowerShellコンソール自体の出力エンコーディング。

    'utf8', 'sjis', 'ascii' などのプリセットから選択して設定できます。また、設定を初期状態に戻すことも可能です。

.PARAMETER CharCode
    設定する文字コードのモードを指定します。指定可能な値は以下の通りです。
    - 'utf8': 各種エンコーディングをUTF-8に設定します。
    - 'sjis': 各種エンコーディングをShift-JISに設定します。
    - 'ascii': 各種エンコーディングをASCIIに設定します。
    - 'rm_encoding': コマンドレットの既定のエンコーディング設定 ($PSDefaultParameterValues) のみ解除します。
    - 'reset_encoding': エンコーディング設定をPowerShellの初期状態に戻します。

    このパラメータを省略した場合、'reset_encoding' がデフォルト値として使用されます。

.EXAMPLE
    # 例 1: 文字コードをUTF-8に設定する
    PS C:\> Set-PsOutputEncoding -CharCode 'utf8'

    # このコマンドを実行すると、現在のセッションの文字コード関連設定がUTF-8に統一されます。
    # 実行結果は、前回作成した Change-WindowTitle 関数などで確認できます。

.EXAMPLE
    # 例 2: 文字コードをShift-JISに設定する
    PS C:\> Set-PsOutputEncoding 'sjis'

    # 文字コード関連設定がShift-JISに設定されます。
    # ただし、PowerShellのバージョンによって一部の挙動が異なる点に注意が必要です(NOTES参照)。

.EXAMPLE
    # 例 3: パラメータを指定せずに実行し、設定を初期状態に戻す
    PS C:\> Set-PsOutputEncoding

    # デフォルトの 'reset_encoding' モードが実行され、エンコーディング設定が
    # 使用しているPowerShellのエディションに応じた初期状態に戻ります。

.OUTPUTS
    なし
    この関数は値を返さず、現在のPowerShellセッションの設定を直接変更する副作用を持ちます。

.NOTES
    - この関数による設定変更は、現在のPowerShellセッションでのみ有効です。新しいウィンドウを開くと設定は元に戻ります。
    - 'sjis' モードについて: PowerShell 7 (Core) 以降では、`$PSDefaultParameterValues['*:Encoding']` に 'shift_jis' を直接設定できないため、'default' を使用しています。これはWindows PowerShell (5.1以前) ではShift-JISとして機能しますが、CoreではUTF-8として解釈されます。
    - 'reset_encoding' モードは、PowerShellのエディション ($PSVersionTable.PSEdition が 'Core' か 'Desktop' か) によって復元される初期値が異なります。
#>
Function Set-PsOutputEncoding {
    param (
        [System.String]$CharCode = 'reset_encoding'
    )

    switch ($CharCode) {
        # 文字エンコードをUTF8に設定する
        'utf8' {
            $PSDefaultParameterValues['*:Encoding'] = 'utf8'
            $global:OutputEncoding = [System.Text.Encoding]::UTF8
            [console]::OutputEncoding = [System.Text.Encoding]::UTF8
        }
        # 文字エンコードをShift JIS(SJIS)に設定する
        'sjis' {
            # $PSDefaultParameterValues['*:Encoding'] = 'default'について
            #   この設定はCore以外(5.1以前)の環境でのみShift JISで設定される。
            #   Core環境のデフォルト値は、UTF-8でありUTF-8で設定されてしまう。
            #   また、Shift JISのパラメーターも存在しない為、Core環境でShift JISの設定は不可となる。
            $PSDefaultParameterValues['*:Encoding'] = 'default'
            $global:OutputEncoding = [System.Text.Encoding]::GetEncoding('shift_jis')
            [console]::OutputEncoding = [System.Text.Encoding]::GetEncoding('shift_jis')
        }
        # 文字エンコードをASCIIに設定する
        'ascii' {
            $PSDefaultParameterValues.Remove('*:Encoding')
            $global:OutputEncoding = [System.Text.Encoding]::ASCII
            [console]::OutputEncoding = [System.Text.Encoding]::ASCII
        }
        # デフォルトパラメータの文字エンコード指定を解除する
        'rm_encoding' {
            $PSDefaultParameterValues.Remove('*:Encoding')
        }
        # 文字エンコード設定を初期状態に戻す
        'reset_encoding' {
            $PSDefaultParameterValues.Remove('*:Encoding')

            if ($PSVersionTable.PSEdition -eq 'Core') {
                # Core の場合
                $global:OutputEncoding = [System.Text.Encoding]::UTF8
                [console]::OutputEncoding = [System.Text.Encoding]::GetEncoding('shift_jis')
            }
            else {
                # Core 以外の場合(PowerShell 5.1 以前)
                $global:OutputEncoding = [System.Text.Encoding]::ASCII
                [console]::OutputEncoding = [System.Text.Encoding]::GetEncoding('shift_jis')
            }
        }
    }
}

標準出力を無視するコマンドに変換するFunction

<#
.SYNOPSIS
    文字列配列の各要素の先頭と末尾に、指定された文字列を追加します。

.DESCRIPTION
    この関数は、commands パラメータで指定された文字列の配列を処理します。
    配列内の各文字列に対して、first パラメータで指定された文字列を先頭に、last パラメータで指定された文字列を末尾に結合します。
    この処理をすべての要素に適用し、加工後の新しい文字列配列を返します。
    デフォルトでは、コマンドレットの出力を抑制するための `[void](...)` キャスト形式の文字列を生成します。

.PARAMETER commands
    加工対象となるコマンドや文字列を格納した配列を指定します。
    このパラメータは必須です。

.PARAMETER first
    各文字列の先頭に追加する文字列を指定します。
    このパラメータを省略した場合、デフォルト値として '[void](' が使用されます。

.PARAMETER last
    各文字列の末尾に追加する文字列を指定します。
    このパラメータを省略した場合、デフォルト値として ')' が使用されます。

.EXAMPLE
    # 例 1: デフォルトの動作でvoidキャスト文字列を生成する
    PS C:\> $commandList = @('Get-Date', 'Get-Process')
    PS C:\> Add-FixedString -commands $commandList
    [void](Get-Date)
    [void](Get-Process)

    この例では、各コマンド文字列がデフォルトの first ('[void](') と last (')') で囲まれ、
    voidキャスト形式の新しい文字列配列が生成されて返されます。

.EXAMPLE
    # 例 2: パラメータを指定して、出力を$nullにリダイレクトする文字列を生成する
    PS C:\> $commandList = @('Get-Date', 'Get-Process')
    PS C:\> Add-FixedString -commands $commandList -first '' -last ' > $null'
    Get-Date > $null
    Get-Process > $null

    この例では、first パラメータに空文字列、last パラメータにリダイレクト用の文字列 ' > $null' を指定しています。
    これにより、各コマンドの末尾にリダイレクト処理が追加された新しい文字列配列が生成されます。

.OUTPUTS
    System.String[]
    加工後の文字列を格納した新しい配列を返します。

.NOTES
    この関数は文字列を加工するだけで、コマンド自体を実行する機能はありません。
    生成された文字列を実行するには、`Invoke-Expression` などの別のコマンドレットを使用する必要があります。
#>
Function Add-FixedString {
    Param (
        # 必須パラメーター:コマンドが記載された文字列配列
        [Parameter(Mandatory=$true)]
        [System.String[]]$commands,

        # オプションパラメーター:先頭に追加する文字
        [System.String]$first = '[void](',

        # オプションパラメーター:末尾に追加する文字
        [System.String]$last = ')'
    )

    # 結果を格納する配列を作成
    [System.String[]]$result = @()

    # 配列内のコマンドを繰り返し処理で変更
    [System.String]$modified = ''
    foreach ($command in $commands) {
        # 先頭と末尾に固定値の文字を追加
        $modified = "$($first)$($command)$($last)"

        # 結果の配列に追加
        $result += $modified
    }

    # 結果の配列を返す
    return $result
}

“ping -t”のようにPowerShellでも連続してping疎通する方法

<#
.SYNOPSIS
    指定したホストに対して、指定した間隔で連続的にPingを実行します。

.DESCRIPTION
    この関数は、コマンドプロンプトの `ping -t` コマンドと同様の機能を提供します。
    TargetHost パラメータで指定されたホストに対し、Test-Connection コマンドレットを1回ずつ実行する処理を無限に繰り返します。
    Pingの実行間隔は PingInterval パラメータでミリ秒単位で指定できます。
    Pingが成功すると、実行日時、ターゲットホスト、応答時間をカンマ区切りの文字列として出力します。
    Pingが失敗した場合(例: 名前解決ができないなど)、エラーメッセージを赤色で表示し、処理は続行されます。
    この関数を停止するには、Ctrl+C を押してください。

.PARAMETER TargetHost
    Pingの宛先となるホスト名またはIPアドレスを指定します。
    このパラメータを省略した場合、デフォルト値として 'localhost' が使用されます。

.PARAMETER PingInterval
    各Pingの実行間隔をミリ秒単位で指定します。
    このパラメータを省略した場合、デフォルト値として 1000 (1秒) が使用されます。

.EXAMPLE
    # 例 1: www.bing.com に対してデフォルト間隔でPingを実行する
    PS C:\> Invoke-ContinuousPing www.bing.com

    2024/05/17 12:21:53.557, www.bing.com, 28 ms
    2024/05/17 12:21:54.643, www.bing.com, 21 ms
    2024/05/17 12:21:55.703, www.bing.com, 21 ms
    ... (Ctrl+C を押すまで処理が続く)

    この例では、ホスト 'www.bing.com' に対して、デフォルトの間隔(1秒)で連続的にPingを実行します。

.EXAMPLE
    # 例 2: 特定のIPアドレスに対して500ms間隔でPingを実行する
    PS C:\> Invoke-ContinuousPing -TargetHost 192.168.1.1 -PingInterval 500

    この例では、IPアドレス '192.168.1.1' に対して、500ミリ秒(0.5秒)間隔で連続的にPingを実行します。

.OUTPUTS
    System.String
    Pingが成功した場合、"yyyy/MM/dd HH:mm:ss.fff, [TargetHost], [ResponseTime] ms" の形式の文字列を出力ストリームに書き込みます。

.NOTES
    - この関数は無限ループで動作します。処理を停止するには、コンソールで Ctrl+C を押してください。
    - Pingの失敗(Test-Connectionコマンドレット自体のエラー)は try-catch ブロックで捕捉され、エラーメッセージが表示された後も処理は続行されます。
    - 応答時間が 0 ミリ秒の場合 (例: localhostへのping)、出力は "0 ms" と表示されます。
#>
# Functionの定義
Function Invoke-ContinuousPing{
    param(
        [System.String]$TargetHost = 'localhost',
        [System.Int32]$PingInterval = 1000
    )
    # ブレーク送信されるまで繰り返し
    while ($true) {
        try {
            # ping実行
            # [Microsoft.PowerShell.Commands.TestConnectionCommand+PingStatus]
            $pingResult = Test-Connection $TargetHost -Count 1 -ErrorAction Stop
            # 画面に結果表示
            [System.String]$currentDatetime = (Get-Date -Format "yyyy/MM/dd HH:mm:ss.fff")
            # ResponseTimeが0の場合、nullになるため判定する
            if ($null -eq $pingResult.ResponseTime) {
                # ResponseTImeがnullの場合は、固定値"0 ms"
                Write-Output "$currentDatetime, $TargetHost, 0 ms"
            }
            else {
                # nullではない場合、取得した値を設定
                Write-Output "$currentDatetime, $TargetHost, $($pingResult.ResponseTime) ms"
            }
        }
        catch {
            Write-Host "エラー:$($_.Exception.Message)" -ForegroundColor Red
            # 「ping -t」の挙動に合わせ、ping疎通が取れなくても処理を続行する。
            # もし、NGとなった場合に処理を中断したい場合は、ここで「break」を実行。
            # break
        }
        # 間隔をあける
        Start-Sleep -Milliseconds $PingInterval
    }
}

文字コード および BOM付き を判定するFunction

PowerShellで現在の文字コードを確認

<#
.SYNOPSIS
    指定されたテキストファイルの文字コード(エンコーディング)を判定します。

.DESCRIPTION
    この関数は、TargetFile パラメータで指定されたファイルを `System.IO.StreamReader` を使って開きます。
    `StreamReader` は、ファイルの先頭にあるバイトオーダーマーク(BOM)を自動的に検出し、それに基づいて文字コードを判定します。
    判定されたエンコーディングの表示名(例: "Unicode (UTF-8)")がコンソールに出力されます。

.PARAMETER TargetFile
    文字コードを判定したいテキストファイルのパスを指定します。このパラメータは必須です。

.EXAMPLE
    # utf8.txt という名前のBOM付きUTF-8ファイルの文字コードを確認する
    PS C:\> Get-PsEncoding .\utf8.txt

    EncodingName: [Unicode (UTF-8)]

    この例では、`utf8.txt` ファイルのエンコーディングが "Unicode (UTF-8)" であることを確認しています。

.OUTPUTS
    なし
    この関数は `Write-Host` を使用してコンソールに直接結果を出力するため、成功ストリームにはオブジェクトを返しません。

.NOTES
    - この関数の文字コード判定は、主にファイルのバイトオーダーマーク(BOM)に依存します。
    - BOMがないテキストファイルの場合、`StreamReader` はデフォルトでUTF-8としてファイルを読み込もうとするため、実際のエンコーディングと異なる結果が表示される可能性があります。
    - この関数は `Write-Host` を使用しているため、判定結果をパイプラインで他のコマンドに渡すことはできません。
#>
# 文字コードの判定
Function Get-PsEncoding {
    param (
        [Parameter(Mandatory=$true)][System.String]$TargetFile
    )

    $streamReader = New-Object System.IO.StreamReader (Convert-Path $TargetFile)
    $profileEncoding = $streamReader.CurrentEncoding
    $streamReader.Close()

    Write-Host "EncodingName: [$($profileEncoding.EncodingName)]"
}

UTFの文字コードがBOM付きか判定

<#
.SYNOPSIS
    指定されたファイルが各種UTFエンコーディングのバイトオーダーマーク(BOM)を持つかどうかを判定します。

.DESCRIPTION
    この関数は、TargetFile パラメータで指定されたファイルの先頭バイトを読み込みます。
    読み込んだバイトシーケンスを、あらかじめ定義されたUTF-7, UTF-8, UTF-16 (Big Endian/Little Endian), UTF-32 (Big Endian/Little Endian) のBOMと比較します。
    一致するBOMが見つかった場合はその種類を、見つからなかった場合はBOMがない旨のメッセージをコンソールに出力します。

.PARAMETER TargetFile
    BOMの有無を判定したいファイルのパスを指定します。このパラメータは必須です。

.EXAMPLE
    PS C:\> # BOM付きのUTF-8ファイルに対して実行
    PS C:\> Check-BOMStatus .\utf8_bom.txt

    [.\utf8_bom.txt] is UTF-8 BOM.

    この例では、`utf8_bom.txt` ファイルにUTF-8のBOMが含まれていることが確認されます。

.OUTPUTS
    なし
    この関数は `Write-Host` を使用してコンソールに直接結果を出力するため、成功ストリームにはオブジェクトを返しません。

.NOTES
    - この関数はUTF系のBOMのみをチェック対象としています。他のエンコーディング(Shift-JISなど)は判定できません。
    - ファイルが存在しない場合は、警告メッセージを表示して処理を終了します。
    - ファイルの読み込みには `Get-Content -Encoding Byte` を使用しており、ファイルの先頭から最大4バイトを読み込んで判定します。
#>
Function Check-BOMStatus {
    param (
        [Parameter(Mandatory=$true)][System.String]$TargetFile
    )

    # 無効なファイルだった場合
    if (-Not (Test-Path $TargetFile)) {
        Write-Warning 'The target file does not exist.' -ForegroundColor Red
        return
    }

    # BOMのバイトシーケンス
    $UTF7_BOM1 = [System.Byte[]](0x2B,0x2F,0x76,0x38)
    $UTF7_BOM2 = [System.Byte[]](0x2B,0x2F,0x76,0x39)
    $UTF7_BOM3 = [System.Byte[]](0x2B,0x2F,0x76,0x2B)
    $UTF7_BOM4 = [System.Byte[]](0x2B,0x2F,0x76,0x2F)
    $UTF8_BOM = [System.Byte[]](0xEF,0xBB,0xBF)
    $UTF16BE_BOM = [System.Byte[]](0xFE,0xFF)
    $UTF16LE_BOM = [System.Byte[]](0xFF,0xFE)
    $UTF32BE_BOM = [System.Byte[]](0x00,0x00,0xFE,0xFF)
    $UTF32LE_BOM = [System.Byte[]](0xFF,0xFE,0x00,0x00)

    # 先頭行をバイトで読み込み先頭から3バイト分のデータを取得
    [System.Byte[]]$first4Bytes = (Get-Content -Path $TargetFile -Encoding Byte -TotalCount 4)
    [System.Byte[]]$first3Bytes = $first4Bytes[0..2]
    [System.Byte[]]$first2Bytes = $first4Bytes[0..1]

    # 先頭バイトでBOM付きか判定
    # UTF-7
    if (($null -eq (Compare-Object $first4Bytes $UTF7_BOM1 -SyncWindow 0)) -Or
        ($null -eq (Compare-Object $first4Bytes $UTF7_BOM2 -SyncWindow 0)) -Or
        ($null -eq (Compare-Object $first4Bytes $UTF7_BOM3 -SyncWindow 0)) -Or
        ($null -eq (Compare-Object $first4Bytes $UTF7_BOM4 -SyncWindow 0))) {
        Write-Host "[$($TargetFile)] is UTF-7 BOM."
    }
    # UTF-8
    elseif ($null -eq (Compare-Object $first3Bytes $UTF8_BOM -SyncWindow 0)) {
        Write-Host "[$($TargetFile)] is UTF-8 BOM."
    }
    # UTF-16 BE
    elseif ($null -eq (Compare-Object $first2Bytes $UTF16BE_BOM -SyncWindow 0)) {
        Write-Host "[$($TargetFile)] is UTF-16 BE BOM."
    }
    # UTF-16 LE
    elseif ($null -eq (Compare-Object $first2Bytes $UTF16LE_BOM -SyncWindow 0)) {
        Write-Host "[$($TargetFile)] is UTF-16 LE BOM."
    }
    # UTF-32 BE
    elseif ($null -eq (Compare-Object $first4Bytes $UTF32BE_BOM -SyncWindow 0)) {
        Write-Host "[$($TargetFile)] is UTF-32 BE BOM."
    }
    # UTF-32 LE
    elseif ($null -eq (Compare-Object $first4Bytes $UTF32LE_BOM -SyncWindow 0)) {
        Write-Host "[$($TargetFile)] is UTF-32 LE BOM."
    }
    else {
        Write-Host "[$($TargetFile)] is not BOM." -ForegroundColor Red
    }
}

PowerShell 6.0以降(Core)でOS環境を確認できる自動変数

# エラーコード enum設定
Add-Type -TypeDefinition @"
    public enum MESSAGECODE {
        Successful = 0,
        Error_NotCore,
        Error_NotSupportedVersion,
        Error_NotWindows
    }
"@

<#
.SYNOPSIS
    現在のPowerShell実行環境が、特定の要件(Windows上のPowerShell 7 Core以上)を満たしているかチェックします。

.DESCRIPTION
    この関数は、引数を取らずに現在のPowerShellセッションの環境を検証します。
    具体的には、以下の3つの条件を順番にチェックします。
    1. PowerShellのエディションが 'Core' (6.0以降) であること。
    2. PowerShellのメジャーバージョンが 7 以上であること。
    3. 実行OSが Windows であること。

    各チェックで要件を満たさない場合、コンソールに赤色で理由を表示し、対応するエラーステータスを返して処理を終了します。
    すべてのチェックを通過した場合は、成功メッセージを表示し、成功ステータスを返します。
    戻り値には、スクリプトの冒頭で定義された `MESSAGECODE` というカスタムenum型が使用されます。

.EXAMPLE
    # 例 1: 要件を満たす環境(例: Windows上のPowerShell 7.3)で実行した場合
    PS C:\> $status = Check-PsEnv
    Core(6.0以降)の環境で、かつ 7以上 の環境、Windows OS の環境である
    PS C:\> $status
    Successful
    PS C:\> [int]$status
    0

    # この例では、すべての条件を満たしているため、成功メッセージが表示され、
    # 戻り値として [MESSAGECODE]::Successful が返されます。

.EXAMPLE
    # 例 2: 要件を満たさない環境(例: Windows PowerShell 5.1)で実行した場合
    PS C:\> $status = Check-PsEnv
    Core(6.0以降)の環境ではない
    PS C:\> $status
    Error_NotCore
    PS C:\> [int]$status
    1

    # この例では、最初のチェック(Coreエディションかどうか)で失敗するため、
    # 関連するエラーメッセージが表示され、戻り値として [MESSAGECODE]::Error_NotCore が返されます。

.OUTPUTS
    MESSAGECODE
    チェック結果を示す `MESSAGECODE` enum の値を返します。
    - Successful (0)
    - Error_NotCore (1)
    - Error_NotSupportedVersion (2)
    - Error_NotWindows (3)

.NOTES
    - この関数を実行する前に、`MESSAGECODE` enum が `Add-Type` を使って定義されている必要があります。
    - この関数は環境をチェックするだけで、何かを変更することはありません。
    - `Write-Host` を使用してコンソールにメッセージを出力します。
#>
Function Check-PsEnv {
    [MESSAGECODE]$returncode = [MESSAGECODE]::Successful

    # 環境情報を取得
    [System.Collections.Hashtable]$psVersion = $PSVersionTable

    # 環境の判定:Coreではない場合(5.1だと'Desktop'となる)
    if ($psVersion.PSEdition -ne 'Core') {
        $returncode = [MESSAGECODE]::Error_NotCore
        Write-Host 'Core(6.0以降)の環境ではない' -ForegroundColor Red
    }
    # 環境の判定:メジャーバージョンが7より小さい場合
    elseif ($psVersion.PSVersion.Major -lt 7) {
        $returncode = [MESSAGECODE]::Error_NotSupportedVersion
        Write-Host 'Core(6.0以降)の環境だが、7以上 の環境ではない' -ForegroundColor Red
    }
    # 環境の判定:Windows OSではない場合(PowerShell Coreのみ使用できる自動変数)
    elseif (-Not($IsWindows)) {
        $returncode = [MESSAGECODE]::Error_NotWindows
        Write-Host 'Core(6.0以降)の環境で、かつ 7以上 の環境だが、Windows OS の環境ではない' -ForegroundColor Red
    }
    else {
        Write-Host 'Core(6.0以降)の環境で、かつ 7以上 の環境、Windows OS の環境である'
    }

    return $returncode
}

PowerShellからPythonのchardetを使って文字コードを判定

<#
.SYNOPSIS
    Pythonの `chardet` ライブラリを利用して、指定されたファイルの文字コードを推定します。

.DESCRIPTION
    この関数は、指定されたファイルの文字コードを判定するために、内部でPythonスクリプトを呼び出します。
    まず、Pythonが実行環境にインストールされているかを確認します。
    次に、`chardet` ライブラリをインポートし、対象ファイルの文字コードを判定するPythonスクリプト (`chardet_runner.py`) をユーザープロファイル配下の 'user-defined-py' フォルダに自動的に作成します(初回実行時など)。
    もし `chardet` ライブラリがインストールされていない場合は、Pythonのpipを通じて自動的にインストールを試みます。
    最終的に、生成されたPythonスクリプトを実行し、その結果(推定されたエンコーディングと信頼度)をコンソールに出力します。
    この方法により、BOM(バイトオーダーマーク)がないファイルでも高い精度で文字コードを判定できます。

.PARAMETER TargetFile
    文字コードを判定したいファイルのパスを指定します。このパラメータは必須です。

.EXAMPLE
    # BOMなしUTF-16ファイルの文字コードを判定する
    PS C:\> Get-PyEncoding .\utf16.txt

    # 実行結果(chardetが判定した結果が出力される)
    Detected encoding for [D:\path\to\your\files\utf16.txt] is ascii with 100.0% confidence.

    この例では、`utf16.txt` を対象に文字コード判定を実行し、その結果がコンソールに表示されます。
    (注:`chardet`の判定結果はファイルの内容に依存します。この例ではASCII互換の文字のみが含まれているため'ascii'と判定されています。)

.OUTPUTS
    System.String
    Pythonスクリプトの標準出力が、そのままPowerShellの成功ストリームに出力されます。
    ただし、Pythonや対象ファイルが存在しない場合は、Write-Hostによるメッセージがコンソールに表示されるだけで、成功ストリームへの出力はありません。

.NOTES
    - この関数を実行するには、事前にPythonがインストールされ、'python' コマンドがパスに通っている必要があります。
    - 初回実行時など `chardet` ライブラリが存在しない場合、インターネット経由で `pip install chardet` が実行されます。プロキシ環境下などでは失敗する可能性があります。
    - 関数は `$PROFILE\..\user-defined-py\chardet_runner.py` というパスにPythonスクリプトを自動生成します。
    - `chardet` による判定はあくまで「推定」であり、100%の正確性を保証するものではありません。結果と共に表示される信頼度(confidence)も参考にしてください。
#>
Function Get-PyEncoding {
    param (
        [Parameter(Mandatory=$true)][System.String]$TargetFile
    )

    # python インストール確認
    if (-Not(Get-Command 'python' -ErrorAction SilentlyContinue)) {
        Write-Host 'Python is not install.' -ForegroundColor Red
        return
    }

    # 存在チェック
    if (-Not(Test-Path $TargetFile)) {
        Write-Host "[$TargetFile] does not exist." -ForegroundColor Red
        return
    }

    # 絶対パスに変換
    [System.String]$fullPath = (Convert-Path $TargetFile)

    # データ種類のチェック
    if (-Not(Test-Path $fullPath -PathType Leaf)) {
        Write-Host "[$fullPath] is not a file." -ForegroundColor Red
        return
    }

    # Pythonスクリプトのコード
    [System.String[]]$pySource = 
@"
import subprocess
import sys

# chardet がインストールされていない場合はインストールしてからインポート
try:
    import chardet
except ImportError:
    subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'chardet', '--user'])
    import chardet

# 文字コードを判定するFunction
def determine_encoding(file_path):
    with open(file_path, 'rb') as file:
        raw_data = file.read()
        result = chardet.detect(raw_data)
        print(f"Detected encoding for [{file_path}] is {result['encoding']} with {result['confidence']*100}% confidence.")

# コマンドラインの引数を取得
file_path = sys.argv[1]

# 対象ファイルの文字列を判定
determine_encoding(file_path)

"@ -split "`r`n"

    # Pythonスクリプトの準備
    #   Pythonスクリプトを格納するフォルダーがない場合は、新規作成
    [System.String]$pyFolderPath = "$PROFILE\..\user-defined-py"
    if (-Not(Test-Path $pyFolderPath)) {
        New-Item -Path $pyFolderPath -ItemType 'directory' -Force > $null
    }
    $pyFolderPath = (Convert-Path $pyFolderPath)
    #   今回実行するPythonスクリプトがない場合は、新規作成
    [System.String]$pyScriptPath = "$pyFolderPath\chardet_runner.py"
    if (-Not(Test-Path $pyScriptPath)) {
        $utf8Encoding = New-Object System.Text.UTF8Encoding
        [System.IO.File]::WriteAllText($pyScriptPath, $pySource, $utf8Encoding)
    }

    # Pythonスクリプトの実行
    try {
        & python $pyScriptPath $fullPath
    }
    catch {
        Write-Warning 'Python script execution error.'
        return
    }
}

変数のデータ型を簡単に確認できるFunction

<#
.SYNOPSIS
    指定された変数のデータ型とベースタイプ(基底クラス)を取得して表示します。

.DESCRIPTION
    この関数は、Variable パラメータで指定された変数のデータ型情報を取得します。
    具体的には、.NETの GetType() メソッドを利用して、変数が持つ実際のデータ型(FullName)と、そのデータ型が継承している基底クラスの型(BaseType)を調べます。
    取得した情報は PSCustomObject に格納され、最終的に Format-Table を通じて整形されたテーブル形式でコンソールに表示されます。

.PARAMETER Variable
    データ型を調査したい変数を指定します。このパラメータは必須です。

.EXAMPLE
    PS C:\> $lists = @(
    >>     @('AAAA',1),
    >>     @('BBBB',2),
    >>     @('CCCC',3)
    >> )
    PS C:\> Get-Datatype $lists

    BaseType     DataType
    --------     --------
    System.Array System.Object[]

    この例では、ジャグ配列(配列の配列)を変数に格納し、そのデータ型を調べています。
    結果として、ベースタイプが `System.Array`、データ型が `System.Object[]` であることがわかります。

.OUTPUTS
    System.Management.Automation.PSCustomObject
    この関数は内部で PSCustomObject を生成しますが、Format-Table を通じてコンソールに出力するため、パイプラインにオブジェクトを直接は返しません。

.NOTES
    - この関数は `Format-Table` を使用して結果を表示します。そのため、出力は視覚的な確認を目的としており、関数の結果を別の変数に代入したり、パイプラインで他のコマンドに渡して処理したりすることには適していません。
    - 単純な値(例: `[int]10` や `"hello"`)から複雑なオブジェクトまで、あらゆる変数を引数として渡すことができます。
#>
Function Get-Datatype {
    param (
        [Parameter(Mandatory=$true)]$Variable
    )

    # 文字列配列を宣言
    [System.String[]]$rowData = @(
        $Variable.GetType().BaseType.FullName,      # 変数のベースタイプ
        $Variable.GetType().FullName                # 変数のデータ型
    )

    # PSCustomObjectで項目名を設定
    $typesTable = [PSCustomObject]@{
        BaseType = $rowData[0]
        DataType = $rowData[1]
    }

    # コンソールで表示
    $typesTable | Format-Table -Property BaseType, DataType -AutoSize -Wrap
}

ファイル内の改行コードを一括変換するFunction

ファイル内の改行コードを一括変換

<#
.SYNOPSIS
    ファイル内の改行コード(CRLF, LF, CR)を視覚的なマーカーに変換してコンソールに表示します。

.DESCRIPTION
    この関数は、TargetFile パラメータで指定されたファイルを読み込み、その中に含まれる改行コードを視覚的にわかりやすいマーカー(<CRLF>, <LF>, <CR>)に置き換えて表示します。
    ファイルの内容を直接変更することなく、どのような改行コードがどこに使われているかを確認するのに役立ちます。
    また、Returncode パラメータで、この関数自体の出力がコンソールに表示される際の改行コード(CRLFまたはLF)を指定できます。

.PARAMETER TargetFile
    改行コードを視覚化したいファイルのパスを指定します。このパラメータは必須です。

.PARAMETER Returncode
    マーカー付きのテキストをコンソールに出力する際に使用する改行コードを指定します。
    'CRLF' または 'LF' を指定できます。デフォルト値は 'CRLF' です。

.EXAMPLE
    # utf8.txt ファイル内の改行コードを確認する
    PS D:\> Show-Returncode .\utf8.txt

     *-- Result: Show-Returncode ---------------------------------------------*
    test<CRLF>

     *----------------------------------------------------------------------------*

    この例では、`utf8.txt` ファイル内のCRLF改行コードが `<CRLF>` というマーカーに置き換えられて表示されます。

.OUTPUTS
    なし
    この関数は `Write-Host` を使用してコンソールに直接結果を出力するため、成功ストリームにはオブジェクトを返しません。
#>
# 改行コードを可視化するFunction
Function Show-Returncode {
    param (
        [Parameter(Mandatory=$true)][System.String]$TargetFile,
        [ValidateSet('CRLF', 'LF')][System.String]$Returncode = 'CRLF'
    )

    [System.Collections.Hashtable]$ReturncodeRegex = @{
        'CR'   = "`r"
        'LF'   = "`n"
        'CRLF' = "`r`n"
    }

    [System.Collections.Hashtable]$ReturncodeMark = @{
        'CR'   = '<CR>'
        'LF'   = '<LF>'
        'CRLF' = '<CRLF>'
    }

    # コンソールに表示する際の改行コードを追加。改行コードは引数で指定した CRLF か LF が入る。
    [System.Collections.Hashtable]$ReturncodeVisualize = @{
        'CR'   = "<CR>$($ReturncodeRegex[$Returncode])"
        'LF'   = "<LF>$($ReturncodeRegex[$Returncode])"
        'CRLF' = "<CRLF>$($ReturncodeRegex[$Returncode])"
    }

    # 改行コードをマークに変換
    [System.String]$targetData = (Get-Content -Path $TargetFile -Raw)
    $targetData = $targetData -Replace $ReturncodeRegex['CRLF'], $ReturncodeMark['CRLF']
    $targetData = $targetData -Replace $ReturncodeRegex['LF'], $ReturncodeMark['LF']
    $targetData = $targetData -Replace $ReturncodeRegex['CR'], $ReturncodeMark['CR']

    # マークからマーク+改行コード(コンソール表示用)に変換
    $targetData = $targetData -Replace $ReturncodeMark['CRLF'], $ReturncodeVisualize['CRLF']
    $targetData = $targetData -Replace $ReturncodeMark['LF'], $ReturncodeVisualize['LF']
    $targetData = $targetData -Replace $ReturncodeMark['CR'], $ReturncodeVisualize['CR']

    Write-Host ''
    Write-Host ' *-- Result: Show-Returncode ---------------------------------------------* '
    Write-Host $targetData
    Write-Host ' *----------------------------------------------------------------------------* '
    Write-Host ''
    Write-Host ''
}

改行コードを変換するFunction

<#
.SYNOPSIS
    ファイル内の指定された改行コードを、別の改行コードに一括で変換します。

.DESCRIPTION
    この関数は、指定されたファイル内の改行コードを変換します。
    BeforeReturncode パラメータで変換元の改行コードを、AfterReturncode パラメータで変換先の改行コードを指定します。
    変換結果は、Destination パラメータで指定された場所に保存されます。Destination を省略すると、元のファイルが上書きされます。
    また、Show パラメータに $true を指定すると、変換処理後に `Show-Returncode` 関数を呼び出し、変換後のファイルの改行コードをコンソールに表示して確認できます。

.PARAMETER TargetFile
    改行コードを変換したい対象のファイルのパスを指定します。このパラメータは必須です。

.PARAMETER BeforeReturncode
    変換元の改行コードを指定します。'CR', 'LF', 'CRLF' のいずれかを選択します。このパラメータは必須です。

.PARAMETER AfterReturncode
    変換先の改行コードを指定します。'CR', 'LF', 'CRLF' のいずれかを選択します。'NONE' を指定すると、対象の改行コードは削除されます。このパラメータは必須です。

.PARAMETER Destination
    変換後のファイルの保存先パスを任意で指定します。
    省略した場合は、元のファイル (TargetFile) に上書き保存されます。

.PARAMETER Show
    $true を指定すると、変換完了後に `Show-Returncode` 関数を呼び出し、変換後のファイルの状態をコンソールに表示します。

.EXAMPLE
    # utf8.txt の改行コードをCRLFからLFに変換し、'utf8_lf.txt' として保存後、結果を確認する
    PS D:\> Replace-Returncode .\utf8.txt CRLF LF .\utf8_lf.txt $true

    名前を付けて保存します。
     保存先: [D:\Downloads\utf8_lf.txt]

     *-- Result: Show-Returncode ---------------------------------------------*
    test<LF>

     *----------------------------------------------------------------------------*

    この例では、`utf8.txt` ファイル内のCRLFをLFに変換し、結果を `utf8_lf.txt` に保存します。
    -Show $true が指定されているため、最後に `Show-Returncode` が実行され、変換が正しく行われたこと(`<LF>` マーカー)が確認できます。

.OUTPUTS
    なし
    この関数は `Write-Host` や `Write-Warning` を使用してコンソールに直接メッセージを出力し、ファイルへの書き込みを行うため、成功ストリームにはオブジェクトを返しません。

.NOTES
    - Show パラメータを使用するには、`Show-Returncode` 関数が同じスコープ内に定義されている必要があります。
    - この関数はファイルの改行コードのみを変更し、文字エンコーディングは変更しません。
    - 安全のため、上書き保存する前にはファイルのバックアップを取ることを推奨します。
#>
# 改行コードを変換するFunction
Function Replace-Returncode {
    param (
        # 必須:変換対象のファイルを指定
        [Parameter(Mandatory=$true)][System.String]$TargetFile,
        # 必須:変換する前後の改行コードを指定
        [Parameter(Mandatory=$true)][ValidateSet('CR', 'LF', 'CRLF')][System.String]$BeforeReturncode,
        [Parameter(Mandatory=$true)][ValidateSet('CR', 'LF', 'CRLF', 'NONE')][System.String]$AfterReturncode,
        # 任意:変換後のファイルを別ファイルで保存したい場合に保存先を指定
        [System.String]$Destination='',
        # 任意:変換後のファイルを可視化しコンソール表示したい場合に指定
        [System.Boolean]$Show=$false
    )

    # Before・Afterが異なる改行コードを指定しているかチェック
    if ($BeforeReturncode -eq $AfterReturncode) {
        Write-Warning '引数で指定された 変換前 と 変換後 の改行コードが同一です。引数を見直してください。'
        return
    }

    # ファイルが存在しない場合
    if (-Not(Test-Path $TargetFile)) {
        Write-Warning '変換対象のファイルが存在しません。処理を中断します。'
        return
    }

    # ファイルの中身がない場合
    [System.String]$beforeData = (Get-Content -Path $TargetFile -Raw)
    if ($null -eq $beforeData) {
        Write-Warning '変換対象のファイル内容が空です。処理を中断します。'
        return
    }

    # 改行コードのハッシュテーブル作成
    [System.Collections.Hashtable]$ReturncodeRegex = @{
        'CR'   = "`r"
        'LF'   = "`n"
        'CRLF' = "`r`n"
        'NONE' = ''
    }

    # 指定した変換前後の改行コードを正規表現の表記に変更
    [System.String]$beforeReturncodeRegex = $ReturncodeRegex[$BeforeReturncode]
    [System.String]$afterReturncodeRegex = $ReturncodeRegex[$AfterReturncode]

    # 変換処理
    [System.String]$afterData = ($beforeData -Replace $beforeReturncodeRegex, $afterReturncodeRegex)

    # 変換されなかった場合
    if ($null -eq (Compare-Object $beforeData $afterData -SyncWindow 0)) {
        Write-Warning '処理を実行しましたが、対象の改行コードがなく変換されませんでした。処理を終了します。'
        return
    }
    # 保存先の指定がない場合、上書き保存
    if ($Destination -eq '') {
        $Destination = $TargetFile
        Write-Host ''
        Write-Host '上書き保存します。'
    }
    # 保存先が指定されている場合、別ファイルで保存(名前を付けて保存)
    else {
        if (Test-Path $Destination -PathType Leaf) {
            Write-Warning '指定の保存場所には、すでにファイルが存在します。処理を中断します。'
            return
        }
        if (-Not(Test-Path "$Destination\.." -PathType Container)) {
            Write-Warning '保存場所のフォルダーが存在しません。処理を中断します。'
            return
        }
        Write-Host ''
        Write-Host '名前を付けて保存します。'
    }
    # 保存
    Try {
        Set-Content -Path $Destination -Value $afterData -NoNewline
    }
    catch {
        Write-Error 'Replace-Returncodeの保存処理でエラーが発生しました。処理を中断します。'
        return
    }
    [System.String]$savepath_full = Convert-Path $Destination
    Write-Host " 保存先: [$savepath_full]"
    Write-Host ''
    Write-Host ''

    # 表示
    if ($Show) {
        Show-Returncode($Destination)
    }
}

複数IDを検索キーにwinget showの結果を抽出し表示するFunction

<#
.SYNOPSIS
    指定された複数のWingetパッケージIDに対して `winget show` を実行し、結果からアプリ名、ID、インストーラーの種類を抽出して表示します。

.DESCRIPTION
    この関数は、TargetIDs パラメータで指定されたWingetパッケージIDの配列を受け取り、それぞれのIDに対して `winget show` コマンドを順に実行します。
    コマンドの出力から、アプリが見つかったかどうかを判定し、見つかった場合はアプリ名、ID、インストーラーの種類を抜き出して、一行にまとめてコンソールに表示します。
    IDが見つからなかった場合は、警告メッセージを表示して次のIDの処理を続行します。
    この関数は、`winget` の詳細な出力から必要な情報だけを簡潔にリストアップしたい場合に便利です。

.PARAMETER TargetIDs
    情報を取得したいWingetパッケージIDを格納した文字列の配列を指定します。

.EXAMPLE
    PS C:\> $searchIDs = @(
    >>     'Microsoft.Edge',
    >>     '1234567890AB', # 存在しないIDの例
    >>     'XPFFZHVGQWWLHB',
    >>     'Microsoft.VCLibs.Desktop.14'
    >> )
    PS C:\> Write-WingetShow $searchIDs

    Microsoft Edge [Microsoft.Edge] [msi]
    警告: 見つかりませんでした。ID: [1234567890AB]
    OneNote [XPFFZHVGQWWLHB] [exe]
    Microsoft Visual C++ 2015 UWP Desktop Runtime Package [Microsoft.VCLibs.Desktop.14] [msix]

    この例では、4つのIDを含む配列を関数に渡します。
    関数は各IDに対して `winget show` を実行し、見つかったものについては整形された情報を、見つからなかったものについては警告メッセージを出力します。

.OUTPUTS
    なし
    この関数は `Write-Host` および `Write-Warning` を使用してコンソールに直接結果を出力するため、成功ストリームにはオブジェクトを返しません。

.NOTES
    - この関数を実行するには、`winget` コマンドがインストールされ、実行可能である必要があります。
    - 関数は、`winget` の出力が日本語("見つかりました "など)であることを前提としています。OSの表示言語が異なる場合、正しく情報を抽出できない可能性があります。
    - 関数の実行時にコンソールの文字コードをUTF-8に変更します。この設定は現在のPowerShellセッションで維持されます。
#>
# Function
Function Write-WingetShow {
    param (
        [System.String[]]$TargetIDs
    )
    # 抽出対象の文字列を指定
    [System.String]$hitMessage = '見つかりました '
    [System.String]$installerType = '  インストーラーの種類: '

    # 文字コードをUTF-8に変更
    [console]::OutputEncoding = [System.Text.Encoding]::UTF8

    # コマンド結果から抽出しアプリ名・ID・ソースのみを表示
    foreach ($id in $TargetIDs) {
        [System.String[]]$appInfo = (winget show --id $id)
        # 検索結果あり
        if ($appInfo -match $hitMessage) {
            $appNameAndId = $appInfo | Select-String "$hitMessage.*" | ForEach-Object {
                $_.Matches[0].Value -replace "$hitMessage", ''
            }
            $appSource = $appInfo | Select-String "$installerType.*" | ForEach-Object {
                $_.Matches[0].Value -replace "$installerType", ''
            }
            Write-Host "$appNameAndId [$appSource]"
        }
        # 検索結果なし
        else {
            Write-Warning "見つかりませんでした。ID: [$id]"
            # 中断する場合はコメントアウト解除
            #return
        }
    }
}

文字列のバイト数を取得する方法(文字列抽出するFunctionも紹介)

指定された文字列のShift-JISエンコーディングでのバイト数を計算

<#
.SYNOPSIS
    指定された文字列のShift-JISエンコーディングでのバイト数を計算します。

.DESCRIPTION
    この関数は、TargetString パラメータで受け取った文字列について、[System.Text.Encoding] クラスを使用してShift-JISエンコーディングでのバイト数を計算し、その値を返します。
    Shift-JISでは、一般的に全角文字は2バイト、半角文字は1バイトとして扱われます。

.PARAMETER TargetString
    バイト数を計算したい文字列を指定します。このパラメータは必須です。

.EXAMPLE
    PS C:\> Get-SjisByteCount '項目0001  '
    10

    この例では、文字列 '項目0001  ' のShift-JISでのバイト数を計算します。
    「項目」(全角2文字 x 2バイト)と「0001  」(半角6文字 x 1バイト)で、合計10バイトが返されます。

.OUTPUTS
    System.Int32
    計算された文字列のバイト数を返します。

.NOTES
    - バイト数の計算に使用するエンコーディングは "Shift_JIS" に固定されています。
#>
Function Get-SjisByteCount {
    param (
        [Parameter(Mandatory=$true)]
        [System.String]$TargetString
    )

    # 文字コードをSJISで設定
    $sjisEncoding = [System.Text.Encoding]::GetEncoding("Shift_JIS")

    # 文字列のバイト数を返す
    return $sjisEncoding.GetByteCount($TargetString)
}

Shift-JISエンコーディングを基準に、指定されたバイト位置から指定されたバイト数分の文字列を抽出

<#
.SYNOPSIS
    Shift-JISエンコーディングを基準に、指定されたバイト位置から指定されたバイト数分の文字列を抽出します。

.DESCRIPTION
    この関数は、まず対象の文字列をShift-JISのバイト配列に変換します。
    次に、ByteStart パラメータで指定された開始位置から、ByteLength パラメータで指定されたバイト数分を切り出し、新しいバイト配列を作成します。
    最後に、この新しいバイト配列をShift-JISの文字列に再変換して返します。
    固定長のバイトデータを扱う際に便利です。

.PARAMETER TargetString
    抽出元の文字列を指定します。このパラメータは必須です。

.PARAMETER ByteStart
    抽出を開始するバイト位置を、0から始まるインデックスで指定します。このパラメータは必須です。

.PARAMETER ByteLength
    抽出するバイト数を指定します。このパラメータは必須です。

.EXAMPLE
    PS C:\> Get-SubstringByByte '1234あか' 4 4
    あか

    この例では、文字列 '1234あか' からバイト単位で部分文字列を抽出します。
    Shift-JISでは、「1234」は4バイト、「あ」は2バイト、「か」は2バイトです。
    ByteStart 4 は5バイト目(「あ」の先頭)を指し、そこから ByteLength 4 バイト分(「あ」と「か」)を抽出するため、結果として「あか」が返されます。

.OUTPUTS
    System.String
    指定されたバイト範囲から抽出された文字列を返します。

.NOTES
    - エンコーディングは "Shift_JIS" に固定されています。
    - バイトの開始位置や長さが2バイト文字の途中で区切られる場合、文字化けが発生する可能性があります。例えば、'あ' (2バイト) の1バイト目だけを抽出しようとすると、正しく文字として復元できません。
    - 指定したバイト範囲 (ByteStart + ByteLength) が元の文字列の合計バイト数を超える場合、エラーが発生します。
#>
Function Get-SubstringByByte {
    param (
        [Parameter(Mandatory=$true)][System.String]$TargetString,
        [Parameter(Mandatory=$true)][System.Int32]$ByteStart,
        [Parameter(Mandatory=$true)][System.Int32]$ByteLength
    )

    $sjisEncoding = [System.Text.Encoding]::GetEncoding("Shift_JIS")

    # 文字列をバイト配列に変換
    [System.Byte[]]$stringBytes = $sjisEncoding.GetBytes($TargetString)

    # 抽出するバイト配列を初期化
    $substringBytes = New-Object Byte[] $ByteLength

    # 指定されたバイト位置からバイト配列を抽出
    [System.Array]::Copy($stringBytes, $ByteStart, $substringBytes, 0, $ByteLength)

    # 抽出したバイトデータを文字列として返す
    return $sjisEncoding.GetString($substringBytes)
}

配列の種類(ジャグ配列 or 多次元配列)を判定するFunction”

2024.6.13 追記

<#
.SYNOPSIS
    引数で渡された変数が、単一配列、多段階配列(ジャグ配列)、多次元配列のいずれであるかを判定します。

.DESCRIPTION
    この関数は、InputObject パラメータで指定された変数がどのような種類の配列かを判定し、結果を整数コードで返します。
    - 単一配列 (例: @(1, 2, 3))
    - 多段階配列(ジャグ配列) (例: @(@(1), @(2, 3)))
    - 多次元配列 (例: New-Object 'int[,]' 2,2)

    入力が配列ではない、または$nullの場合は、配列ではないことを示すコードを返します。
    この関数は、配列の構造に応じて異なる処理を行いたい場合に便利です。

.PARAMETER InputObject
    判定対象の変数を指定します。

.EXAMPLE
    # 様々な種類の配列を準備
    $singleArray = @('あ', 'い', 'う')
    $multiLevel = @(@(1, 2), @(3, 4))
    $multiDim = New-Object 'System.String[,]' 2,2

    # それぞれの配列に対して関数を実行
    PS C:\> Get-ArrayType $singleArray
    0
    PS C:\> Get-ArrayType $multiLevel
    1
    PS C:\> Get-ArrayType $multiDim
    2
    PS C:\> Get-ArrayType "This is not an array"
    -1

    この例では、3つの異なるタイプの配列と非配列データを準備し、それぞれに対して関数を実行して返される整数コードを確認しています。
    0は単一配列、1は多段階配列、2は多次元配列、-1は非配列を示します。

.OUTPUTS
    System.Int32
    判定結果として、以下のいずれかの整数値を返します。
    - -1: 配列ではない、または$null (OtherTypes)
    -  0: 単一配列 (SingleArray)
    -  1: 多段階配列(ジャグ配列) (MultiLevel)
    -  2: 多次元配列 (MultiDimensional)

.NOTES
    - PowerShellで一般的に `@(...)` を入れ子にして作成される「ジャグ配列(多段階配列)」と、`New-Object 'Type[,]'` などで作成される真の「多次元配列」を区別します。
    - 多段階配列の判定は、多次元配列の判定(Rankプロパティの確認)よりも優先して行われます。
#>
Function Get-ArrayType {
    param(
        $InputObject
    )

    [System.Collections.Hashtable]$arrayTypes = @{
        "OtherTypes" = -1
        "SingleArray" = 0
        "MultiLevel" = 1
        "MultiDimensional" = 2
    }

    # データがない場合
    if ($null -eq $InputObject) {
        return $arrayTypes["OtherTypes"]
    }

    # 一番外枠が配列ではない場合
    if ($InputObject -isnot [System.Array]) {
        return $arrayTypes["OtherTypes"]
    }

    # ジャグ配列(多段階配列)か判定
    $isMultiLevel = $false
    foreach ($element in $InputObject) {
        if ($element -is [System.Array]) {
            # 配列の中も配列で多段配列
            $isMultiLevel = $true
            break
        }
    }
    if ($isMultiLevel) {
        return $arrayTypes["MultiLevel"]
    }    

    # 多次元配列か判定
    if ($InputObject.Rank -ge 2) {
        # 2次元以上の場合
        return $arrayTypes["MultiDimensional"]
    }
    else {
        # 1次元の場合
        return $arrayTypes["SingleArray"]
    }
}
# テストデータの準備

#   単一の配列
$singleArray = @('あ', 'い', 'う')

#   多段階配列
$multiLevel = @(@(1, 2, 3), @(4, 5), @(6, 7, 8, 9))

#   String型の1x2 多次元配列
$stringMultiDim1x2 = New-Object 'System.String[,]' 1,2
$stringMultiDim1x2[0,0] = 'Hello'
$stringMultiDim1x2[0,1] = 'World'

#   Int32型の3x2 多次元配列
$intMultiDim3x2 = New-Object 'System.Int32[,]' 3,2
$intMultiDim3x2[0,0] = 1
$intMultiDim3x2[0,1] = 2
$intMultiDim3x2[1,0] = 3
$intMultiDim3x2[1,1] = 4
$intMultiDim3x2[2,0] = 5
$intMultiDim3x2[2,1] = 6

#   Object型の3x1 多次元配列
$objectMultiDim3x1 = New-Object 'System.Object[,]' 3,1
$objectMultiDim3x1[0,0] = 'I am String.'
$objectMultiDim3x1[1,0] = 1
$objectMultiDim3x1[2,0] = 10.5

#   テストデータを集約
$testData = @(
    @{ "Description" = "単一配列"; "InputObject" = $singleArray },
    @{ "Description" = "多段階配列"; "InputObject" = $multiLevel },
    @{ "Description" = "String型1x2多次元配列"; "InputObject" = $stringMultiDim1x2 },
    @{ "Description" = "Int32型3x2多次元配列"; "InputObject" = $intMultiDim3x2 }
    @{ "Description" = "Object型3x1多次元配列"; "InputObject" = $objectMultiDim3x1 }
)

# Functionを実行
foreach ($data in $testData) {
    $result = Get-ArrayType -InputObject $data["InputObject"]
    Write-Host "$($data["Description"])の結果: $result"
}

# 実行結果
単一配列の結果: 0
多段階配列の結果: 1
String型1x2多次元配列の結果: 2
Int32型3x2多次元配列の結果: 2
Object型3x1多次元配列の結果: 2

PowerShellのわかりにくい比較演算子を記号で判定可能にするFunction

2024.6.14 追記

<#
.SYNOPSIS
    2つの値を、記号形式の文字列で指定された演算子を使って比較します。

.DESCRIPTION
    この関数は、PowerShellの標準的な比較演算子(-eq, -ne, -lt など)の代わりに、より一般的な記号(==, !=, < など)を文字列として受け取って比較処理を実行します。
    Value1 と Value2 の2つの値を、Operator パラメータで指定された演算子で比較し、その結果としてブール値($true または $false)を返します。
    動的に比較条件を変更する必要がある場合に便利です。

.PARAMETER Value1
    比較の左辺となる値を指定します。このパラメータは必須です。

.PARAMETER Operator
    比較に使用する演算子を文字列で指定します。
    利用可能な値: '==', '!=', '<', '<=', '>', '>='
    このパラメータは必須です。

.PARAMETER Value2
    比較の右辺となる値を指定します。このパラメータは必須です。

.EXAMPLE
    # 10 が 20 より小さいかどうかを判定する
    PS C:\> if (Test-Comparison -Value1 10 -Operator '<' -Value2 20) {
    >>     Write-Host "10は20より小さいです。"
    >> }
    10は20より小さいです。

    この例では、`Test-Comparison` 関数をif文の条件として使用しています。
    `10 < 20` の比較結果が `$true` であるため、ifブロック内のコードが実行されます。

.OUTPUTS
    System.Boolean
    比較の結果として、$true または $false を返します。

.NOTES
    - 指定された演算子がサポートされていない場合、この関数は例外をスローします。
    - この関数は、数値だけでなく、文字列や日付など、PowerShellの比較演算子が処理できる任意のオブジェクト型を比較できます。
#>
Function Test-Comparison {
    param(
        [Parameter(Mandatory=$true)][System.Object]$Value1,

        [Parameter(Mandatory=$true)]
        [System.String]$Operator,

        [Parameter(Mandatory=$true)]
        [System.Object]$Value2
    )

    switch ($Operator) {
        # 通常の比較演算子
        '=='            { return $Value1 -eq $Value2 }
        '!='            { return $Value1 -ne $Value2 }
        '<'             { return $Value1 -lt $Value2 }
        '<='            { return $Value1 -le $Value2 }
        '>'             { return $Value1 -gt $Value2 }
        '>='            { return $Value1 -ge $Value2 }
        default         { throw "Invalid operator: $Operator" }
    }
}
# 例: 10 と 20 を比較して、10 が 20 より小さいかどうかを判定
$standardValue = 10
$comparedValue = 20

# そのまま実行
Write-Host '--- そのままFunctionを実行した結果 ---'
Test-Comparison -Value1 $standardValue -Operator '<' -Value2 $comparedValue
Write-Host ''

# if文で実行
Write-Host '--- if文でFunctionを実行した結果 ---'
if (Test-Comparison $standardValue '<' $comparedValue) {
    Write-Host "True判定:$standardValue は $comparedValue より小さい"
    Write-Host ''
}
else {
    Write-Host "False判定:$comparedValue は $comparedValue より小さくない"
    Write-Host ''
}

# 実行結果
--- そのままFunctionを実行した結果 ---
True

--- if文でFunctionを実行した結果 ---
True判定:10 は 20 より小さい

[PowerShell]指定したモジュールの導入有無を確認するFunction

2024.6.17 追記

<#
.SYNOPSIS
    指定されたPowerShellモジュールがインストールされているかどうかを確認します。

.DESCRIPTION
    この関数は、ModuleName パラメータで指定された名前のモジュールが、現在のシステムで利用可能かどうかを判定します。
    内部では `Get-Module -ListAvailable` コマンドレットを使用しており、モジュールが見つかれば $true を、見つからなければ $false を返します。
    スクリプトの実行前に特定のモジュールの存在を前提条件としてチェックするのに役立ちます。

.PARAMETER ModuleName
    確認したいモジュールの名前を文字列で指定します。

.EXAMPLE
    # 'ImportExcel'モジュールが存在しない場合にインストールする
    PS C:\> $moduleName = 'ImportExcel'
    PS C:\> if (-Not(Test-ModuleInstalled $moduleName)) {
    >>     [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    >>     # 管理者権限が必要
    >>     Install-Module -Name $moduleName -Scope CurrentUser -Force
    >>     Write-Host "$($moduleName) モジュールをインストールしました。"
    >> } else {
    >>     Write-Host "$($moduleName) モジュールは既にインストールされています。"
    >> }

    この例では、`Test-ModuleInstalled` を使って 'ImportExcel' モジュールがインストールされているかを確認します。
    関数が `$false` を返した場合(未導入の場合)、`Install-Module` を実行してモジュールをインストールします。
    既に導入済みの場合は、その旨のメッセージが表示されます。

.OUTPUTS
    System.Boolean
    モジュールがインストールされている場合は `$true` を、されていない場合は `$false` を返します。

.NOTES
    この関数は `Get-Module -ListAvailable` を使用しているため、`$env:PSModulePath` 環境変数に定義されているパス内のモジュールを検索対象とします。
#>
Function Test-ModuleInstalled {
    param (
        [System.String]$ModuleName
    )

    $moduleInstalled = $false

    # モジュール情報を取得
    $module = (Get-Module -ListAvailable -Name $ModuleName)
    # モジュールが導入済みの場合
    if ($null -ne $module) {
        $moduleInstalled = $true
    }

    return $moduleInstalled
}

[PowerShell]ファイルのロック状態を確認するFunction

2024.6.18 追記

<#
.SYNOPSIS
    指定されたファイルが他のプロセスによってロックされているかどうかを確認します。

.DESCRIPTION
    この関数は、Path パラメータで指定されたファイルが現在ロックされているかどうかをテストします。
    内部では `[System.IO.File]::Open` メソッドを使用し、対象ファイルを読み書きアクセスモードで開くことを試みます。
    ファイルが正常に開けた場合は、ロックされていないと判断し、すぐにファイルを閉じて `$false` を返します。
    ファイルが他のプロセスによって使用中で開くことができなかった場合(IOException が発生した場合)、ファイルはロックされていると判断し、`$true` を返します。
    ファイル処理を行う前に、対象ファイルが操作可能かどうかを事前にチェックするのに役立ちます。

.PARAMETER Path
    ロック状態を確認したいファイルのパスを文字列で指定します。このパラメータは必須です。

.EXAMPLE
    # Excelファイル 'D:\Downloads\TestBook.xlsx' のロック状態を確認する
    PS C:\> $targetPath = 'D:\Downloads\TestBook.xlsx'

    # シナリオ1: Excelでファイルを開いていない状態で実行
    PS C:\> Test-FileLocked $targetPath
    False

    # シナリオ2: Excelでファイルを開いている状態で実行
    PS C:\> Test-FileLocked $targetPath
    True

    この例では、同じファイルに対して、他のアプリケーション(Excel)で開いている場合と開いていない場合で関数を実行しています。
    ファイルがロックされている(開かれている)場合に `$true` が返されることがわかります。

.OUTPUTS
    System.Boolean
    ファイルがロックされている場合は `$true` を、ロックされていない場合は `$false` を返します。

.NOTES
    - このチェックは関数が実行された瞬間の状態を反映するものです。チェック直後にファイルの状態が変わる可能性もあります。
    - 対象のファイルが存在しない場合、この関数は `Write-Error` を使ってエラーをスローし、実行を停止します。
    - ファイルを開くための十分なアクセス権がない場合、ロックされていなくてもロックされていると誤って判断される可能性があります。
#>
Function Test-FileLocked {
    param (
        [Parameter(Mandatory=$true)][System.String]$Path
    )

    if (-Not(Test-Path $Path)) {
        Write-Error '対象ファイルが存在しません。' -ErrorAction Stop
    }

    # 相対パスだとOpenメソッドが正常動作しない為、絶対パスに変換
    $fullPath = (Resolve-Path -Path $Path).Path

    $fileLocked = $false
    try {
        # 読み取り専用でファイルを開く処理を実行
        $fileStream = [System.IO.File]::Open($fullPath, 'Open', 'ReadWrite', 'None')
    }
    catch {
        # ファイルが開けない場合、ロック状態と判断
        $fileLocked = $true
    }
    finally {
        if ($null -ne $fileStream) {
            $fileStream.Close()
        }
    }

    return $fileLocked
}

“Markdownテーブル”と“Excelファイル”を相互変換する方法

2025.7.15 追記

ここのFunctionでは、先に紹介した自作関数「Test-FileLocked」を呼び出しています。

補足情報:引数の配列の種類を判定するFunction
<#
.SYNOPSIS
    指定されたファイルが他のプロセスによってロックされているかどうかを確認します。

.DESCRIPTION
    この関数は、Path パラメータで指定されたファイルが現在ロックされているかどうかをテストします。
    内部では `[System.IO.File]::Open` メソッドを使用し、対象ファイルを読み書きアクセスモードで開くことを試みます。
    ファイルが正常に開けた場合は、ロックされていないと判断し、すぐにファイルを閉じて `$false` を返します。
    ファイルが他のプロセスによって使用中で開くことができなかった場合(IOException が発生した場合)、ファイルはロックされていると判断し、`$true` を返します。
    ファイル処理を行う前に、対象ファイルが操作可能かどうかを事前にチェックするのに役立ちます。

.PARAMETER Path
    ロック状態を確認したいファイルのパスを文字列で指定します。このパラメータは必須です。

.EXAMPLE
    # Excelファイル 'D:\Downloads\TestBook.xlsx' のロック状態を確認する
    PS C:\> $targetPath = 'D:\Downloads\TestBook.xlsx'

    # シナリオ1: Excelでファイルを開いていない状態で実行
    PS C:\> Test-FileLocked $targetPath
    False

    # シナリオ2: Excelでファイルを開いている状態で実行
    PS C:\> Test-FileLocked $targetPath
    True

    この例では、同じファイルに対して、他のアプリケーション(Excel)で開いている場合と開いていない場合で関数を実行しています。
    ファイルがロックされている(開かれている)場合に `$true` が返されることがわかります。

.OUTPUTS
    System.Boolean
    ファイルがロックされている場合は `$true` を、ロックされていない場合は `$false` を返します。

.NOTES
    - このチェックは関数が実行された瞬間の状態を反映するものです。チェック直後にファイルの状態が変わる可能性もあります。
    - 対象のファイルが存在しない場合、この関数は `Write-Error` を使ってエラーをスローし、実行を停止します。
    - ファイルを開くための十分なアクセス権がない場合、ロックされていなくてもロックされていると誤って判断される可能性があります。
#>
Function Test-FileLocked {
    param (
        [Parameter(Mandatory=$true)][System.String]$Path
    )

    if (-Not(Test-Path $Path)) {
        Write-Error '対象ファイルが存在しません。' -ErrorAction Stop
    }

    # 相対パスだとOpenメソッドが正常動作しない為、絶対パスに変換
    $fullPath = (Resolve-Path -Path $Path).Path

    $fileLocked = $false
    try {
        # 読み取り専用でファイルを開く処理を実行
        $fileStream = [System.IO.File]::Open($fullPath, 'Open', 'ReadWrite', 'None')
    }
    catch {
        # ファイルが開けない場合、ロック状態と判断
        $fileLocked = $true
    }
    finally {
        if ($null -ne $fileStream) {
            $fileStream.Close()
        }
    }

    return $fileLocked
}

Markdown-Table から Excelファイル に変換できるPowerShellのコード

<#
.SYNOPSIS
    Markdownファイルからテーブル形式のテキストを抽出し、PowerShellオブジェクトの配列に変換します。

.DESCRIPTION
    この関数は、FromMarkdownPath パラメータで指定されたMarkdownファイルを読み込みます。
    正規表現を使用してファイル内のMarkdownテーブル('|'で区切られたテキスト)を検出し、その内容を解析します。
    テーブルの1行目をヘッダー(プロパティ名)、3行目以降をデータ行(プロパティ値)として扱い、各行を1つのPSCustomObjectに変換します。
    この関数は、`Convert-MarkdownTableToExcel` から内部的に呼び出されるヘルパー関数として機能します。

.PARAMETER FromMarkdownPath
    テーブルを抽出したいMarkdownファイルのパスを指定します。このパラメータは必須です。

.OUTPUTS
    System.Management.Automation.PSCustomObject[]
    Markdownテーブルの各データ行を表すPSCustomObjectの配列を返します。テーブルが見つからない場合は空の配列を返します。

.NOTES
    - この関数はMarkdownテーブルの解析のみを行い、Excelファイルの操作は行いません。
    - テーブルの2行目(ヘッダーとデータの区切り行 `|---|---|`)は処理でスキップされます。
    - データ行の列数がヘッダーの列数と一致しない場合、その行はスキップされ、コンソールにメッセージが表示されます。
#>
Function Get-ExcelTable {
    param (
        [Parameter(Mandatory=$true)]
        [string]$FromMarkdownPath
    )

    # 内容を読み取る
    $inputContents = (Get-Content $FromMarkdownPath -Raw)

    # 表の開始と終了を検出する正規表現
    $tableRegex = '(?smi)^\|.*(?<!\\)\|.*$'
    $headerSplitRegex = '^\|?(.*?)(?<!\\)\|$'
    $rowSplitRegex = '^\|(.+?)(?<!\\)\|$'

    # 表の内容を抽出します
    $excelTables = @()
    [regex]::Matches($inputContents, $tableRegex) | ForEach-Object {
        $tableText = $_.Value

        # ヘッダーを解析します
        $rowCount = 0
        $rows = @()

        $tableText -split "`n" | ForEach-Object {
            if ($rowCount -eq 0) {
                $headers = [regex]::Split($_, '(?<!\\)\|')
                $headers = $headers[1..($headers.Length-2)] # 最初と最後の空の要素を削除
                $rowCount += 1
            }
            elseif ($rowCount -eq 1) {
                # ヘッダーとコンテンツの区切り行をスキップ
                $rowCount += 1
            }
            else {
                $row = [regex]::Split($_, '(?<!\\)\|')
                $row = $row[1..($row.Length-2)] # 最初と最後の空の要素を削除

                # ヘッダー項目数 と データ項目数を比較
                if ($row.Count -eq $headers.Count) {
                    $rows += ,$row
                }
                else {
                    # 空行以外
                    if (-Not([System.String]::IsNullOrEmpty($_.Trim()))) {
                        Write-Host "ヘッダーの項目数と合わなかった為、下記のデータをスキップしました。[ヘッダー項目数: $($headers.Count), データ項目数: $($row.Count)]"
                        Write-Host '```:markdown'
                        Write-Host "$_"
                        Write-Host '```'
                    }
                }
            }
        }

        # 各行をオブジェクトに変換します
        $rows | ForEach-Object {
            $rowObject = New-Object psobject
            for ($i = 0; $i -lt $headers.Count; $i++) {
                $rowObject | Add-Member -MemberType NoteProperty -Name $headers[$i].Trim() -Value $_[$i].Trim()
            }
            $excelTables += ,$rowObject
        }
    }

    return $excelTables
}
<#
.SYNOPSIS
    Markdownファイル内のテーブルを読み込み、Excelファイルとしてエクスポートします。

.DESCRIPTION
    この関数は、Markdownファイルからテーブルデータを抽出し、それをExcelファイルに変換して保存する一連の処理を自動化します。
    まず内部的に `Get-ExcelTable` 関数を呼び出してMarkdownテーブルをPowerShellオブジェクトに変換します。
    その後、`ImportExcel` モジュールの `Export-Excel` コマンドレットを使用して、オブジェクトをExcelファイルに出力します。
    出力先のファイルが既に存在する場合は、ファイルがロックされていないかを確認し、ユーザーに上書き保存の許可を求めます。

.PARAMETER FromMarkdownPath
    入力元となるMarkdownファイルのパスを指定します。このパラメータは必須です。

.PARAMETER ToExcelPath
    出力先となるExcelファイルのパスを指定します。このパラメータは必須です。

.EXAMPLE
    PS C:\> $markdownPath = 'D:\Downloads\inputMarkdownTable.md'
    PS C:\> $excelPath = 'D:\Downloads\outputExcel.xlsx'
    PS C:\> Convert-MarkdownTableToExcel $markdownPath $excelPath

    # 実行すると、inputMarkdownTable.md 内のテーブルが outputExcel.xlsx として保存されます。
    # outputExcel.xlsx が既に存在する場合は、"ファイルを上書きしますか? (Yes/No)" というプロンプトが表示されます。

.OUTPUTS
    なし
    この関数はファイルへの書き込みと、コンソールへのメッセージ表示を主な動作とするため、成功ストリームにはオブジェクトを返しません。

.NOTES
    - この関数は `ImportExcel` モジュールがインストールされていることを前提としています。`Export-Excel` コマンドレットが利用できないとエラーになります。
    - この関数は、同じスクリプト内に `Get-ExcelTable` 関数と `Test-FileLocked` 関数が定義されている必要があります。
    - 上書き確認の際にはユーザー入力 (`Read-Host`) が必要になるため、完全自動化されたスクリプトでの使用には注意が必要です。
    - `-Show` パラメータにより、処理完了後にExcelが自動的に起動します。
#>
# Markdown形式のテーブル から Excelファイル に変換するFunciton
Function Convert-MarkdownTableToExcel {
    param (
        [Parameter(Mandatory=$true)]
        [System.String]$FromMarkdownPath,
        [Parameter(Mandatory=$true)]
        [System.String]$ToExcelPath

    )

    # Markdown形式のテーブル を Excel
    $excelTables = (Get-ExcelTable -FromMarkdownPath $FromMarkdownPath)

    if ($null -eq $excelTables) {
        Write-Error 'ファイルを読み取りましたが、データを読み取れませんでした。'
        return
    }

    if (Test-Path -Path $ToExcelPath) {
        # ファイルがロックされているかテストします
        if (Test-FileLocked -Path $ToExcelPath) {
            Write-Warning 'Excelファイルが開かれています。ファイルを閉じてから再試行してください。'
            return
        }

        # 入力メッセージ
        $userInput = Read-Host 'ファイルを上書きしますか? (Yes/No)'
        $yesPatterns = '[Yy][Ee][Ss]|[Yy]'
        $noPatterns = '[Nn][Oo]|[Nn]'
        $isYes = $false
        # 試行回数: 3回
        $maxCount = 2
        for ($i = 0; $i -lt $maxCount; $i++) {
            if ($userInput -match $yesPatterns) {
                $isYes = $true
                break
            }
            else {
                Write-Host '上書き保存をキャンセルしました。'
                return
            }
        }
        if (-Not ($isYes)) {
            Write-Warning '試行回数を超過しました。処理を中断します。'
            return
        }

        # ユーザーが「はい」を選択した場合、ファイルを上書き保存します
        try {
            $excelTables | Export-Excel -Path $toExcelPath -Show
            Write-Host '上書き保存しました。'
        }
        catch {
            Write-Error '上書き保存でエラーが発生しました。'
        }
    }
    else {
        # 新規保存
        try {
            $excelTables | Export-Excel -Path $toExcelPath -Show
            Write-Host "新規保存しました。"
        }
        catch {
            Write-Error '新規保存でエラーが発生しました。'
        }
    }
}
# Function「Convert-MarkdownTableToExcel」を実行
# 実行
$markdownPath = 'D:\Downloads\inputMarkdownTable.md'
$excelPath = 'D:\Downloads\outputExcel.xlsx'
Convert-MarkdownTableToExcel $markdownPath $excelPath

Excelファイル から Markdown-Table に変換できるPowerShellのコード

<#
.SYNOPSIS
    Markdownファイルからテーブル形式のテキストを抽出し、PowerShellオブジェクトの配列に変換します。

.DESCRIPTION
    この関数は、FromMarkdownPath パラメータで指定されたMarkdownファイルを読み込みます。
    正規表現を使用してファイル内のMarkdownテーブル('|'で区切られたテキスト)を検出し、その内容を解析します。
    テーブルの1行目をヘッダー(プロパティ名)、3行目以降をデータ行(プロパティ値)として扱い、各行を1つのPSCustomObjectに変換します。
    この関数は、`Convert-MarkdownTableToExcel` から内部的に呼び出されるヘルパー関数として機能します。

.PARAMETER FromMarkdownPath
    テーブルを抽出したいMarkdownファイルのパスを指定します。このパラメータは必須です。

.OUTPUTS
    System.Management.Automation.PSCustomObject[]
    Markdownテーブルの各データ行を表すPSCustomObjectの配列を返します。テーブルが見つからない場合は空の配列を返します。

.NOTES
    - この関数はMarkdownテーブルの解析のみを行い、Excelファイルの操作は行いません。
    - テーブルの2行目(ヘッダーとデータの区切り行 `|---|---|`)は処理でスキップされます。
    - データ行の列数がヘッダーの列数と一致しない場合、その行はスキップされ、コンソールにメッセージが表示されます。
#>
# コンテンツを変換する処理
Function Get-ExcelTable {
    param (
        [Parameter(Mandatory=$true)]
        [string]$FromMarkdownPath
    )

    # 内容を読み取る
    $inputContents = (Get-Content $FromMarkdownPath -Raw)

    # 表の開始と終了を検出する正規表現
    $tableRegex = '(?smi)^\|.*(?<!\\)\|.*$'
    $headerSplitRegex = '^\|?(.*?)(?<!\\)\|$'
    $rowSplitRegex = '^\|(.+?)(?<!\\)\|$'

    # 表の内容を抽出します
    $excelTables = @()
    [regex]::Matches($inputContents, $tableRegex) | ForEach-Object {
        $tableText = $_.Value

        # ヘッダーを解析します
        $rowCount = 0
        $rows = @()

        $tableText -split "`n" | ForEach-Object {
            if ($rowCount -eq 0) {
                $headers = [regex]::Split($_, '(?<!\\)\|')
                $headers = $headers[1..($headers.Length-2)] # 最初と最後の空の要素を削除
                $rowCount += 1
            }
            elseif ($rowCount -eq 1) {
                # ヘッダーとコンテンツの区切り行をスキップ
                $rowCount += 1
            }
            else {
                $row = [regex]::Split($_, '(?<!\\)\|')
                $row = $row[1..($row.Length-2)] # 最初と最後の空の要素を削除

                # ヘッダー項目数 と データ項目数を比較
                if ($row.Count -eq $headers.Count) {
                    $rows += ,$row
                }
                else {
                    # 空行以外
                    if (-Not([System.String]::IsNullOrEmpty($_.Trim()))) {
                        Write-Host "ヘッダーの項目数と合わなかった為、下記のデータをスキップしました。[ヘッダー項目数: $($headers.Count), データ項目数: $($row.Count)]"
                        Write-Host '```:markdown'
                        Write-Host "$_"
                        Write-Host '```'
                    }
                }
            }
        }

        # 各行をオブジェクトに変換します
        $rows | ForEach-Object {
            $rowObject = New-Object psobject
            for ($i = 0; $i -lt $headers.Count; $i++) {
                $rowObject | Add-Member -MemberType NoteProperty -Name $headers[$i].Trim() -Value $_[$i].Trim()
            }
            $excelTables += ,$rowObject
        }
    }

    return $excelTables
}
<#
.SYNOPSIS
    Markdownファイル内のテーブルを読み込み、Excelファイルとしてエクスポートします。

.DESCRIPTION
    この関数は、Markdownファイルからテーブルデータを抽出し、それをExcelファイルに変換して保存する一連の処理を自動化します。
    まず内部的に `Get-ExcelTable` 関数を呼び出してMarkdownテーブルをPowerShellオブジェクトに変換します。
    その後、`ImportExcel` モジュールの `Export-Excel` コマンドレットを使用して、オブジェクトをExcelファイルに出力します。
    出力先のファイルが既に存在する場合は、ファイルがロックされていないかを確認し、ユーザーに上書き保存の許可を求めます。

.PARAMETER FromMarkdownPath
    入力元となるMarkdownファイルのパスを指定します。このパラメータは必須です。

.PARAMETER ToExcelPath
    出力先となるExcelファイルのパスを指定します。このパラメータは必須です。

.EXAMPLE
    PS C:\> $markdownPath = 'D:\Downloads\inputMarkdownTable.md'
    PS C:\> $excelPath = 'D:\Downloads\outputExcel.xlsx'
    PS C:\> Convert-MarkdownTableToExcel $markdownPath $excelPath

    # 実行すると、inputMarkdownTable.md 内のテーブルが outputExcel.xlsx として保存されます。
    # outputExcel.xlsx が既に存在する場合は、"ファイルを上書きしますか? (Yes/No)" というプロンプトが表示されます。

.OUTPUTS
    なし
    この関数はファイルへの書き込みと、コンソールへのメッセージ表示を主な動作とするため、成功ストリームにはオブジェクトを返しません。

.NOTES
    - この関数は `ImportExcel` モジュールがインストールされていることを前提としています。`Export-Excel` コマンドレットが利用できないとエラーになります。
    - この関数は、同じスクリプト内に `Get-ExcelTable` 関数と `Test-FileLocked` 関数が定義されている必要があります。
    - 上書き確認の際にはユーザー入力 (`Read-Host`) が必要になるため、完全自動化されたスクリプトでの使用には注意が必要です。
    - `-Show` パラメータにより、処理完了後にExcelが自動的に起動します。
#>
# Markdown形式のテーブル から Excelファイル に変換するFunciton
Function Convert-MarkdownTableToExcel {
    param (
        [Parameter(Mandatory=$true)]
        [System.String]$FromMarkdownPath,
        [Parameter(Mandatory=$true)]
        [System.String]$ToExcelPath

    )

    # Markdown形式のテーブル を Excel
    $excelTables = (Get-ExcelTable -FromMarkdownPath $FromMarkdownPath)

    if ($null -eq $excelTables) {
        Write-Error 'ファイルを読み取りましたが、データを読み取れませんでした。'
        return
    }

    if (Test-Path -Path $ToExcelPath) {
        # ファイルがロックされているかテストします
        if (Test-FileLocked -Path $ToExcelPath) {
            Write-Warning 'Excelファイルが開かれています。ファイルを閉じてから再試行してください。'
            return
        }

        # 入力メッセージ
        $userInput = Read-Host 'ファイルを上書きしますか? (Yes/No)'
        $yesPatterns = '[Yy][Ee][Ss]|[Yy]'
        $noPatterns = '[Nn][Oo]|[Nn]'
        $isYes = $false
        # 試行回数: 3回
        $maxCount = 2
        for ($i = 0; $i -lt $maxCount; $i++) {
            if ($userInput -match $yesPatterns) {
                $isYes = $true
                break
            }
            else {
                Write-Host '上書き保存をキャンセルしました。'
                return
            }
        }
        if (-Not ($isYes)) {
            Write-Warning '試行回数を超過しました。処理を中断します。'
            return
        }

        # ユーザーが「はい」を選択した場合、ファイルを上書き保存します
        try {
            $excelTables | Export-Excel -Path $toExcelPath -Show
            Write-Host '上書き保存しました。'
        }
        catch {
            Write-Error '上書き保存でエラーが発生しました。'
        }
    }
    else {
        # 新規保存
        try {
            $excelTables | Export-Excel -Path $toExcelPath -Show
            Write-Host "新規保存しました。"
        }
        catch {
            Write-Error '新規保存でエラーが発生しました。'
        }
    }
}
# Function「Convert-ExcelToMarkdownTable」を実行
# 実行
$excelPath = 'D:\Downloads\inputExcel.xlsx'
$markdownPath = 'D:\Downloads\outputMarkdownTable.md'
Convert-ExcelToMarkdownTable $excelPath $markdownPath

2つの配列同士の要素数が同じかチェックするFunction

2025.7.15 追記

ここのFunctionでは、先に紹介した自作関数「Get-ArrayType」を呼び出しています。

補足情報:引数の配列の種類を判定するFunction
<#
.SYNOPSIS
    引数で渡された変数が、単一配列、多段階配列(ジャグ配列)、多次元配列のいずれであるかを判定します。

.DESCRIPTION
    この関数は、InputObject パラメータで指定された変数がどのような種類の配列かを判定し、結果を整数コードで返します。
    - 単一配列 (例: @(1, 2, 3))
    - 多段階配列(ジャグ配列) (例: @(@(1), @(2, 3)))
    - 多次元配列 (例: New-Object 'int[,]' 2,2)

    入力が配列ではない、または$nullの場合は、配列ではないことを示すコードを返します。
    この関数は、配列の構造に応じて異なる処理を行いたい場合に便利です。

.PARAMETER InputObject
    判定対象の変数を指定します。

.EXAMPLE
    # 様々な種類の配列を準備
    $singleArray = @('あ', 'い', 'う')
    $multiLevel = @(@(1, 2), @(3, 4))
    $multiDim = New-Object 'System.String[,]' 2,2

    # それぞれの配列に対して関数を実行
    PS C:\> Get-ArrayType $singleArray
    0
    PS C:\> Get-ArrayType $multiLevel
    1
    PS C:\> Get-ArrayType $multiDim
    2
    PS C:\> Get-ArrayType "This is not an array"
    -1

    この例では、3つの異なるタイプの配列と非配列データを準備し、それぞれに対して関数を実行して返される整数コードを確認しています。
    0は単一配列、1は多段階配列、2は多次元配列、-1は非配列を示します。

.OUTPUTS
    System.Int32
    判定結果として、以下のいずれかの整数値を返します。
    - -1: 配列ではない、または$null (OtherTypes)
    -  0: 単一配列 (SingleArray)
    -  1: 多段階配列(ジャグ配列) (MultiLevel)
    -  2: 多次元配列 (MultiDimensional)

.NOTES
    - PowerShellで一般的に `@(...)` を入れ子にして作成される「ジャグ配列(多段階配列)」と、`New-Object 'Type[,]'` などで作成される真の「多次元配列」を区別します。
    - 多段階配列の判定は、多次元配列の判定(Rankプロパティの確認)よりも優先して行われます。
#>
Function Get-ArrayType {
    param(
        $InputObject
    )

    [System.Collections.Hashtable]$arrayTypes = @{
        "OtherTypes" = -1
        "SingleArray" = 0
        "MultiLevel" = 1
        "MultiDimensional" = 2
    }

    # データがない場合
    if ($null -eq $InputObject) {
        return $arrayTypes["OtherTypes"]
    }

    # 一番外枠が配列ではない場合
    if ($InputObject -isnot [System.Array]) {
        return $arrayTypes["OtherTypes"]
    }

    # ジャグ配列(多段階配列)か判定
    $isMultiLevel = $false
    foreach ($element in $InputObject) {
        if ($element -is [System.Array]) {
            # 配列の中も配列で多段配列
            $isMultiLevel = $true
            break
        }
    }
    if ($isMultiLevel) {
        return $arrayTypes["MultiLevel"]
    }    

    # 多次元配列か判定
    if ($InputObject.Rank -ge 2) {
        # 2次元以上の場合
        return $arrayTypes["MultiDimensional"]
    }
    else {
        # 1次元の場合
        return $arrayTypes["SingleArray"]
    }
}

2つの多次元配列の要素数をチェックするコード

<#
.SYNOPSIS
    2つの多次元配列の構造(次元数と各次元の要素数)が同一であるかを確認します。

.DESCRIPTION
    この関数は、Array1 と Array2 で指定された2つの変数が、両方とも多次元配列であり、かつその構造が完全に一致するかどうかを判定します。
    判定は以下のステップで行われます。
    1. 内部で `Get-ArrayType` 関数を呼び出し、両方の引数が多次元配列であることを確認します。
    2. `.Rank` プロパティを比較し、両方の配列の次元数が同じであることを確認します。
    3. `.GetLength(dimensionIndex)` メソッドを使用し、各次元の要素数がすべて同じであることを確認します。

    すべての条件を満たす場合にのみ `$true` を返します。配列に格納されている要素の値自体は比較しません。

.PARAMETER Array1
    比較元の多次元配列を指定します。このパラメータは必須です。

.PARAMETER Array2
    比較先の多次元配列を指定します。このパラメータは必須です。

.EXAMPLE
    # 比較用の多次元配列を準備
    $arrayA = New-Object 'int[,]' 2, 3  # 2x3 の配列
    $arrayB = New-Object 'int[,]' 2, 3  # 同じく 2x3 の配列
    $arrayC = New-Object 'int[,]' 3, 2  # 構造が異なる 3x2 の配列

    # 例 1: 構造が完全に一致する場合
    PS C:\> Test-MultiDimensionalArrayEquality -Array1 $arrayA -Array2 $arrayB
    True

    # 例 2: 構造が異なる場合(次元ごとの要素数が違う)
    PS C:\> Test-MultiDimensionalArrayEquality -Array1 $arrayA -Array2 $arrayC
    False

    # 例 3: 片方が多次元配列ではない場合
    PS C:\> $singleArray = @(1, 2, 3)
    PS C:\> Test-MultiDimensionalArrayEquality -Array1 $arrayA -Array2 $singleArray
    警告: 引数の「Array2」が多次元配列ではありません。[配列の判定結果: 0]
    False

.OUTPUTS
    System.Boolean
    2つの多次元配列の構造が完全に一致する場合は `$true` を、そうでない場合は `$false` を返します。

.NOTES
    - この関数は、`Get-ArrayType` という別のカスタム関数が同じスコープ内に定義されていることを前提としています。
    - この関数は配列の「構造」のみを比較し、配列に含まれる要素の値は無視します。
    - 引数に多次元配列以外が渡された場合、警告メッセージが表示され、`$false` が返されます。
#>
# 多次元配列の要素数をチェック
Function Test-MultiDimensionalArrayEquality {
    param (
        [Parameter(Mandatory=$true)]$Array1,
        [Parameter(Mandatory=$true)]$Array2
    )

    # 多次元配列か判定
    $resultArrayType = (Get-ArrayType $Array1)
    if ($resultArrayType -ne 2) {
        Write-Warning "引数の「Array1」が多次元配列ではありません。[配列の判定結果: $($resultArrayType)]"
        return $false
    }
    $resultArrayType = (Get-ArrayType $Array2)
    if ($resultArrayType -ne 2) {
        Write-Warning "引数の「Array2」が多次元配列ではありません。[配列の判定結果: $($resultArrayType)]"
        return $false
    }

    # 配列の次元数を比較
    $dimensionArray1 = $Array1.Rank
    $dimensionArray2 = $Array2.Rank

    if ($dimensionArray1 -ne $dimensionArray2) {
        return $false
    }

    # 各次元毎の要素数をチェック
    for ($i = 0; $i -lt $dimensionArray1; $i++) {
        if ($Array1.GetLength($i) -ne $Array2.GetLength($i)) {
            return $false
        }
    }

    # 要素数が一致
    return $true
}

2つのジャグ配列(多段配列)が同じ要素数かチェックするコード

<#
.SYNOPSIS
    2つのジャグ配列(多段階配列)の構造と、含まれるすべての要素の値が完全に一致するかどうかを確認します。

.DESCRIPTION
    この関数は、Array1 と Array2 で指定された2つの変数が、ジャグ配列として完全に等しいかどうかを判定します。
    判定は以下のステップで行われます。
    1. 内部で `Get-ArrayType` 関数を呼び出し、両方の引数がジャグ配列であることを確認します。
    2. 最上位の配列の要素数を比較します。
    3. 各内部配列の要素数を比較します。
    4. 対応する位置にあるすべての要素の値そのものを比較します。

    これらすべての条件を満たす場合にのみ `$true` を返します。

.PARAMETER Array1
    比較元のジャグ配列を指定します。このパラメータは必須です。

.PARAMETER Array2
    比較先のジャグ配列を指定します。このパラメータは必須です。

.EXAMPLE
    # 比較用のジャグ配列を準備
    $arrayA = @(@(1, 2), @(3))
    $arrayB = @(@(1, 2), @(3)) # $arrayAと完全に一致
    $arrayC = @(@(1, 2), @(9)) # 値が異なる
    $arrayD = @(@(1), @(2, 3)) # 構造が異なる

    # 例 1: 構造も値も完全に一致する場合
    PS C:\> Test-MultiLevelArrayEquality -Array1 $arrayA -Array2 $arrayB
    True

    # 例 2: 構造は同じだが、要素の値が異なる場合
    PS C:\> Test-MultiLevelArrayEquality -Array1 $arrayA -Array2 $arrayC
    False

    # 例 3: 構造が異なる場合
    PS C:\> Test-MultiLevelArrayEquality -Array1 $arrayA -Array2 $arrayD
    False

.OUTPUTS
    System.Boolean
    2つのジャグ配列の構造と要素の値が完全に一致する場合は `$true` を、そうでない場合は `$false` を返します。

.NOTES
    - この関数は、`Get-ArrayType` という別のカスタム関数が同じスコープ内に定義されていることを前提としています。
    - この関数は配列の「構造」だけでなく、含まれる「要素の値」も厳密に比較します。
    - `-Debug` スイッチを付けて実行すると、比較処理の詳細なログが表示されます。
#>
# ジャグ配列の要素数をチェック
Function Test-MultiLevelArrayEquality {
    param (
        [Parameter(Mandatory=$true)]$Array1,
        [Parameter(Mandatory=$true)]$Array2
    )

    # ャグ配列か判定
    $resultArrayType = (Get-ArrayType $Array1)
    if ($resultArrayType -ne 1) {
        Write-Warning "引数の「Array1」がジャグ配列(多次元配列)ではありません。[配列の判定結果: $($resultArrayType)]"
        return $false
    }
    $resultArrayType = (Get-ArrayType $Array2)
    if ($resultArrayType -ne 1) {
        Write-Warning "引数の「Array2」がジャグ配列(多次元配列)ではありません。[配列の判定結果: $($resultArrayType)]"
        return $false
    }

    # 配列の次元数を比較
    $levelArray1 = $Array1.Length
    $levelArray2 = $Array2.Length

    Write-Debug "`$levelArray1: [$($levelArray1)], `$levelArray2: [$($levelArray2)]"
    if ($levelArray1 -ne $levelArray2) {
        return $false
    }

    # 各次元毎の要素数をチェック
    for ($i = 0; $i -lt $Array1.Length; $i++) {
        Write-Debug "`$Array1[$($i)].Length: [$($Array1[$i].Length)], `$Array2[$($i)].Length: [$($Array2[$i].Length)]"
        if ($Array1[$i].Length -ne $Array2[$i].Length) {
            return $false
        }
        for ($j = 0; $j -lt $Array1[$i].Length; $j++) {
            Write-Debug "`$Array1[$($i)][$($j)]: [$($Array1[$i][$j])], `$Array2[$($i)][$($j)]: [$($Array2[$i][$j])]"
            if ($Array1[$i][$j] -ne $Array2[$i][$j]) {
                return $false
            }
        }
    }

    # 要素数が一致
    return $true
}

値も含めて配列をまるまる比較

<#
.SYNOPSIS
    2つの配列が、要素の内容と順序を含めて完全に同一であるかを確認します。

.DESCRIPTION
    この関数は、`Compare-Object` コマンドレットを使用して、2つの配列 (`Array1` と `Array2`) を比較します。
    `-SyncWindow 0` パラメータを指定することで、要素の順序も厳密に比較の対象とします。
    両方の配列に含まれる要素とその順序が完全に一致する場合にのみ、この関数は `$true` を返します。

.PARAMETER Array1
    比較の基準となる配列を指定します。このパラメータは必須です。

.PARAMETER Array2
    `Array1` と比較する対象の配列を指定します。このパラメータは必須です。

.EXAMPLE
    # 比較用の配列を準備
    $a = 1, 2, 3
    $b = 1, 2, 3  # $a と完全に同一
    $c = 3, 2, 1  # 要素は同じだが順序が違う
    $d = 1, 2, 4  # 要素が違う

    # 例 1: 完全に同一の配列を比較
    PS C:\> Test-ArrayEquality -Array1 $a -Array2 $b
    True

    # 例 2: 順序が異なる配列を比較
    PS C:\> Test-ArrayEquality -Array1 $a -Array2 $c
    False

    # 例 3: 要素が異なる配列を比較
    PS C:\> Test-ArrayEquality -Array1 $a -Array2 $d
    False

.OUTPUTS
    System.Boolean
    配列が完全に同一であれば `$true` を、そうでなければ `$false` を返します。

.NOTES
    - この関数は `Compare-Object` の `-SyncWindow 0` オプションに依存しており、要素の順序が重要となります。順序を問わない比較をしたい場合は、この関数は適していません。
    - この関数は1次元配列の比較に最適です。多次元配列やジャグ配列のようなネストされた配列の場合、内部の配列は参照として比較されるため、期待通りの深い比較は行われません。
#>
Function Test-ArrayEquality {
    param (
        [Parameter(Mandatory=$true)][System.Array]$Array1,
        [Parameter(Mandatory=$true)][System.Array]$Array2
    )

    # 比較(差異が無い場合、$null か 空の配列 が返る)
    $diffResult = (Compare-Object -ReferenceObject $Array1 -DifferenceObject $Array2 -SyncWindow 0)

    # 比較結果を評価
    return (($null -eq $diffResult) -or ($diffResult.Count -eq 0))
}

定義したFunctionの中身を確認する方法

2025.7.15 追記

<#
.SYNOPSIS
    現在読み込まれている関数の定義(ソースコード)を取得し、文字列として返します。

.DESCRIPTION
    この関数は、FunctionName パラメータで指定された、現在のPowerShellセッションにロードされている関数の定義を取得します。
    内部で `Get-Command` を使用して関数のスクリプトブロックを取得し、それを整形された文字列に変換します。
    オプションとして、出力の改行コードを指定したり、結果をMarkdownのコードブロック形式でラップしたりすることができます。
    動的にドキュメントを生成したり、他の関数の実装を確認したりするのに便利です。

.PARAMETER FunctionName
    定義を取得したい関数の名前を文字列で指定します。

.PARAMETER Newline
    出力される文字列内で使用する改行コードを指定します。
    デフォルトは `"`r`n"` (CRLF) です。

.PARAMETER MarkdownMode
    このスイッチを `$true` に設定すると、返される関数定義がPowerShell用のMarkdownコードブロック (``````powershell ... ``````) で囲まれます。
    デフォルトは `$false` です。

.EXAMPLE
    # --- 準備:テスト用の関数を定義 ---
    Function My-DemoFunction {
        [CmdletBinding()]
        param()
        Write-Host "This is a demo."
    }

    # 例 1: 基本的な使用方法。関数の定義をプレーンテキストで取得する。
    PS C:\> Get-FunctionDefinition -FunctionName 'My-DemoFunction'

    # 実行結果(文字列として返される):
    # Function My-DemoFunction {
    #     [CmdletBinding()]
    #     param()
    #     Write-Host "This is a demo."
    # }

.EXAMPLE
    # 例 2: Markdownモードを有効にして、結果をクリップボードにコピーする
    PS C:\> Get-FunctionDefinition -FunctionName 'My-DemoFunction' -MarkdownMode $true | Set-Clipboard

    # クリップボードには以下のようなMarkdown形式のテキストがコピーされる。
    # `````powershell
    # Function My-DemoFunction {
    #     [CmdletBinding()]
    #     param()
    #     Write-Host "This is a demo."
    # }
    # `````

.EXAMPLE
    # 例 3: 存在しない関数を指定した場合
    PS C:\> Get-FunctionDefinition -FunctionName 'NonExistent-Function'
    WARNING: 該当のFunctionが見つかりませんでした。[検索したFunction名: NonExistent-Function]

.OUTPUTS
    System.String
    関数の定義を含む単一の文字列を返します。関数が見つからない場合は、何も返しません。

.NOTES
    - この関数は、対象の関数が現在のPowerShellセッションに読み込まれている(定義またはインポートされている)必要があります。
    - 返される定義は、`Get-Command` が保持するスクリプトブロックから再構築されたものであり、元のソースファイルと完全に同一のフォーマット(例: `Function`キーワード前のコメントなど)であるとは限りません。
#>
Function Get-FunctionDefinition {
    param (
        [System.String]$FunctionName,
        [System.String]$Newline="`r`n",
        [System.Boolean]$MarkdownMode=$False
    )

    # Functionが存在しない場合
    if ($null -eq (Get-Command -Name $FunctionName -ErrorAction SilentlyContinue)) {
        Write-Warning "該当のFunctionが見つかりませんでした。[検索したFunction名: $($FunctionName)]"
        return
    }

    # 先頭の空行を飛ばしてFunctionの定義内容を取得
    #   スクリプトブロック内の改行コードを「LF」で設定(※ PowerShellバージョンによって変わるかも)
    $scriptblockNewline = "`n"
    $plainDefinition = (((Get-Command $FunctionName).ScriptBlock.ToString() -Split $scriptblockNewline | Select-Object -Skip 1) -Join $Newline)

    $functionDefinition = ''
    if ($MarkdownMode) {
        $functionDefinition += "``````powershell$($Newline)"
        $functionDefinition += "Function $FunctionName {$($Newline)"
        $functionDefinition += $plainDefinition
        $functionDefinition += "}$($Newline)"
        $functionDefinition += "``````$($Newline)"
    }
    else {
        $functionDefinition += "Function $FunctionName {$($Newline)"
        $functionDefinition += $plainDefinition
        $functionDefinition += "}$($Newline)"
    }

    return $functionDefinition
}

定数が定義済みかチェックするFunction

2025.7.15 追記

<#
.SYNOPSIS
    指定された名前の変数が、すべて現在のスコープに定義されているかを確認します。

.DESCRIPTION
    この関数は、Constants パラメータで受け取った文字列配列に含まれるすべての変数名について、その存在をチェックします。
    内部では `Get-Variable` を使用し、配列内の変数名が一つでも未定義($null)である場合、直ちに `$false` を返します。
    すべての変数が定義済みであれば `$true` を返します。
    この関数は、スクリプトが依存する定数や構成変数が正しく設定されているかを事前に検証するのに役立ちます。

.PARAMETER Constants
    存在を確認したい変数名のリストを、文字列の配列として指定します。

.EXAMPLE
    # 例 1: 必要な変数がすべて定義されている場合
    PS C:\> $AppPath = 'C:\apps\myapp.exe'
    PS C:\> $LogFile = 'C:\logs\app.log'
    PS C:\> if (Test-ConstantDefined -Constants 'AppPath', 'LogFile') {
    >>     Write-Host "設定は正常です。"
    >> }
    設定は正常です。

.EXAMPLE
    # 例 2: 一部の変数が定義されていない場合
    PS C:\> $AppPath = 'C:\apps\myapp.exe'
    PS C:\> # $LogFile は未定義
    PS C:\> if (-Not (Test-ConstantDefined -Constants 'AppPath', 'LogFile')) {
    >>     Write-Warning "必要な変数が定義されていません!"
    >> }
    WARNING: 必要な変数が定義されていません!

.OUTPUTS
    System.Boolean
    指定されたすべての変数が定義されている場合は `$true` を、一つでも未定義の変数があれば `$false` を返します。

.NOTES
    - この関数は変数が存在するかどうか(`$null`でないか)のみをチェックします。変数の値が空文字列 (`''`) や `$false`、`0` であっても「定義済み」と判断します。
    - `Get-Variable` は現在のスコープから親スコープへと変数を検索します。
#>
Function Test-ConstantDefined {
    param (
        [System.String[]]$Constants
    )
    foreach ($const in $Constants) {
        [System.String]$constValue = (Get-Variable -Name $const -ErrorAction SilentlyContinue)
        # Nullの場合は未定義として判断
        # (空文字や空白のみは定数として定義される可能性があるためチェックしない)
        if ($null -eq $constValue) {
            return $false
        }
    }
    return $true
}

指定したファイルの中身が存在するかチェックするFunction

2025.7.15 追記

<#
.SYNOPSIS
    指定されたファイルに、空白文字以外の実質的な内容が含まれているかを確認します。

.DESCRIPTION
    この関数は、Path パラメータで指定されたファイルを読み込み、その内容が空または空白文字のみで構成されていないかを判定します。
    内部では `Get-Content` を使用してファイルの内容をすべて読み込み、その結果を `[System.String]::IsNullOrWhiteSpace()` メソッドで評価します。
    これにより、ファイルが完全に空である場合や、スペース、タブ、改行のみが含まれる場合を「内容なし」とみなし、`$false` を返します。
    何らかの印刷可能な文字が1つでも含まれていれば「内容あり」と判断し、`$true` を返します。
    ログファイルやデータファイルの処理前に、空でないことを確認するのに役立ちます。

.PARAMETER Path
    内容を確認したいファイルのパスを文字列で指定します。

.EXAMPLE
    # 例 1: 内容があるファイル
    PS C:\> "Hello World" | Out-File .\test.txt
    PS C:\> Test-FileHasContent -Path .\test.txt
    True

    # 例 2: 空のファイル
    PS C:\> "" | Out-File .\empty.txt
    PS C:\> Test-FileHasContent -Path .\empty.txt
    False

    # 例 3: スペースと改行しか含まないファイル
    PS C:\> "   `r`n `t " | Out-File .\whitespace.txt
    PS C:\> Test-FileHasContent -Path .\whitespace.txt
    False

.OUTPUTS
    System.Boolean
    ファイルに実質的な内容が含まれている場合は `$true` を、そうでない(空、空白のみ、または読み取りエラー)場合は `$false` を返します。

.NOTES
    - ファイルの読み込み中にエラー(例: ファイルが存在しない、アクセス権がない)が発生した場合、この関数はエラーメッセージを表示し、`$false` を返します。
    - この関数はファイル全体をメモリに読み込むため、非常に巨大なファイルに対して使用する際はパフォーマンスに注意が必要です。
#>
Function Test-FileHasContent {
    param (
        [System.String]$Path
    )

    try {
        $fileContents = (Get-Content $Path)
    }
    catch {
        Write-Error 'ファイル内容を読み込む際にエラーが発生しました。'
        return $false
    }

    # Null または 空文字、空白のみだった場合はコンテンツなしの判定
    return (-not ([System.String]::IsNullOrWhiteSpace($fileContents)))
}

フォルダーをツリー形式で表示するFunction(CLIとGUIの2通り)

2025.7.15 追記

CLIで出力可能なFunction

<#
.SYNOPSIS
    指定されたパスのフォルダとファイルの構造を、視覚的なツリー形式で表示します。

.DESCRIPTION
    この関数は、コマンドプロンプトの `tree` コマンドのように、指定されたフォルダの構造を再帰的にスキャンし、階層的なツリー形式のテキストを生成します。
    ファイルとフォルダは、各階層内でファイルが先に、フォルダが後にソートされて表示されます。
    オプションとして、罫線のスタイルをASCII文字に変更したり、結果を直接クリップボードにコピーしたり、使用する改行コードを選択したりすることができます。

.PARAMETER Path
    ツリー表示の起点となるフォルダのパスを指定します。このパラメータは必須です。

.PARAMETER AsciiMode
    このスイッチを `$true` に設定すると、罫線文字(例: │, └)の代わりに `|`, `+`, `\` などのASCII文字を使用してツリーを描画します。
    テキストエディタや環境による文字化けを防ぐのに役立ちます。デフォルトは `$false` です。

.PARAMETER CopyToClipboard
    このスイッチを `$true` に設定すると、生成されたツリー表示のテキストをコンソールに出力する代わりに、クリップボードにコピーします。
    ドキュメントへの貼り付けなどに便利です。デフォルトは `$false` です。

.PARAMETER NewlineCode
    出力テキストに使用する改行コードを指定します。
    - 1: CRLF (`"`r`n"`) (デフォルト)
    - 2: LF (`"`n"`)

.EXAMPLE
    # 例 1: カレントディレクトリのツリーを表示する
    PS C:\> Write-TreeView -Path .

    # 実行結果(例):
    # .
    # ├── file1.txt
    # ├── file2.log
    # │
    # └── < subfolder >
    #     ├── subfile.txt
    #     │
    #     └── < anotherfolder >
    #            
    #

.EXAMPLE
    # 例 2: ASCIIモードを有効にして表示する
    PS C:\> Write-TreeView -Path 'C:\Program Files\PowerShell' -AsciiMode $true

    # 実行結果(例):
    # C:\Program Files\PowerShell
    # +---- pwsh.exe
    # +---- pwsh.pdb
    # |
    # \---- < 7 >
    #      +---- pwsh.dll
    #      |
    #      \---- < Ref >
    #           \---- Microsoft.PowerShell.Commands.Management.dll
    #

.EXAMPLE
    # 例 3: 結果をクリップボードにコピーする
    PS C:\> Write-TreeView -Path . -CopyToClipboard $true
    出力結果をクリップボードにコピーしました!

    # この後、Ctrl+Vなどで貼り付けるとツリー表示のテキストがペーストされる。

.OUTPUTS
    System.String
    生成されたツリー表示のテキストを文字列として出力します。
    `-CopyToClipboard`が指定された場合は、コンソールに完了メッセージを出力するだけで、成功ストリームへの出力はありません。

.NOTES
    - この関数は内部で再帰処理を行うため、非常に深い階層構造や膨大な数のファイルを持つフォルダに対して実行すると、時間がかかったり、多くのメモリを消費したりする可能性があります。
    - 日本語環境では、ASCIIモードでない場合の `\` が円記号(¥)として表示されることがあります。
#>
Function Write-TreeView {
    param (
        [Parameter(Mandatory=$true)][System.String]$Path,
        [System.Boolean]$AsciiMode = $False,
        [System.Boolean]$CopyToClipboard = $False,
        [ValidateSet(1, 2)][System.Int32]$NewlineCode = 1
    )

    # フォルダーがない場合は処理中断
    if (-not (Test-Path -Path $Path)) {
        Write-Warning "指定されたパスは存在しません: $Path"
        return
    }

    # 改行コードを設定
    $newlineTable = @{
        1 = "`r`n"  # CRLF
        2 = "`n"    # LF
    }

    # 出力形式を設定
    # * 日本語OSではバックスラッシュ(\)が円マーク(¥)となる。
    $indentTable = @{}
    if ($AsciiMode) {
        $indentTable["Pattern1"] = '     '
        $indentTable["Pattern2"] = '|     '
        $indentTable["Pattern3"] = '\---- '
        $indentTable["Pattern4"] = '+---- '
    }
    else {
        $indentTable["Pattern1"] = '   '
        $indentTable["Pattern2"] = '│  '
        $indentTable["Pattern3"] = '└── '
        $indentTable["Pattern4"] = '├── '
    }

    # 内部関数:フォルダーのツリー構造を取得する関数
    Function Write-FolderTree {
        param (
            [System.String]$CurrentPath,
            [System.Collections.Hashtable]$Levels
        )

        # ファイルが先頭に表示されるよう並び替え
        $items = (Get-ChildItem -Path $CurrentPath | Sort-Object { -not $_.PSIsContainer }, Name)
        $count = $items.Count
        $index = 0

        $ouputFoldertree = ""

        foreach ($item in $items) {
            $index++
            $isLastItem = ($index -eq $count)
            $level = $Levels.Count
            $Levels[$level] = $isLastItem
            $indent = ""

            for ($i = 0; $i -lt $level; $i++) {
                $indent += if ($Levels[$i]) { "$($indentTable["Pattern1"])" } else { "$($indentTable["Pattern2"])" }
            }

            $line = if ($isLastItem) { "$($indentTable["Pattern3"])" } else { "$($indentTable["Pattern4"])" }

            if ($item.PSIsContainer) {
                # フォルダーの場合
                $ouputFoldertree += "$($indent)$($line)< $($item.Name) >$($newlineTable[$NewlineCode])"
                $ouputFoldertree += (Write-FolderTree -CurrentPath $item.FullName -Levels $Levels)
            } else {
                # ファイルの場合
                $ouputFoldertree += "$($indent)$($line)$($item.Name)$($newlineTable[$NewlineCode])"
            }

            # フォルダー内で最終アイテムの場合に次のアイテムと間隔を空ける
            if ($isLastItem) {
                $ouputFoldertree += "$($indent)$($newlineTable[$NewlineCode])"
            }

            $Levels.Remove($level)
        }

        return $ouputFoldertree
    }

    $Levels = @{}
    $outputTreeview =  "$($Path)$($newlineTable[$NewlineCode])"
    $outputTreeview += (Write-FolderTree -CurrentPath $Path -Levels $Levels)

    if ($CopyToClipboard) {
        $outputTreeview | Set-Clipboard
        Write-Output "出力結果をクリップボードにコピーしました!$($newlineTable[$NewlineCode])"
    }
    else {
        Write-Output $outputTreeview
    }
}

GUIで表示可能なFunction

"自作Function「Out-TreeView」の画面"
実際の画面:自作Function「Out-TreeView」

<#
.SYNOPSIS
    指定されたパスのフォルダ構造を、対話的なGUIツリービューウィンドウで表示します。

.DESCRIPTION
    この関数は、Windows Formsを利用して、指定されたフォルダの構造を視覚的なツリービューとして表示するGUIアプリケーションを起動します。
    ユーザーはツリーを展開・縮小してファイルシステムをナビゲートできます。
    - フォルダは青い背景色でハイライトされます。
    - ノードを選択すると、ステータスバーにそのアイテムの最終更新日時が表示されます。
    - ファイルノードをダブルクリックすると、関連付けられたデフォルトのプログラムでファイルを開きます。
    - ショートカットキーによる操作も可能です:
        - Enterキー: 選択したファイルを開く、またはフォルダをエクスプローラーで開きます。
        - Ctrl+C: 選択したアイテムの名前をクリップボードにコピーします。
        - Ctrl+Shift+C: 選択したアイテムのフルパスをクリップボードにコピーします。

.PARAMETER Path
    ツリー表示の起点となるフォルダのパスを指定します。このパラメータは必須です。

.PARAMETER WindowSize
    表示されるウィンドウのサイズを [幅, 高さ] の形式でピクセル単位で指定します。
    デフォルトは @(600, 450) です。

.PARAMETER FontSize
    ツリービューに表示されるテキストのフォントサイズを指定します。
    デフォルトは 9.5 です。

.EXAMPLE
    # 例 1: カレントディレクトリのツリービューを表示する
    PS C:\> Out-TreeView -Path .

    # カレントディレクトリをルートとしたツリービューウィンドウが開きます。

.EXAMPLE
    # 例 2: C:\Windows の内容を、大きなウィンドウとフォントで表示する
    PS C:\> Out-TreeView -Path 'C:\Windows' -WindowSize @(800, 600) -FontSize 11

    # カスタムサイズのウィンドウで C:\Windows のツリービューが表示されます。

.OUTPUTS
    なし
    この関数はGUIウィンドウを表示し、そのウィンドウが閉じられるまでスクリプトの実行をブロックします。PowerShellの成功ストリームにはオブジェクトを返しません。

.NOTES
    - この関数はWindows Formsライブラリに依存しているため、Windows OS環境でのみ動作します。
    - 非常に深い階層構造や膨大な数のファイルを持つフォルダに対して実行すると、ウィンドウの初期表示に時間がかかる場合があります。
    - この関数は対話的なウィンドウ (`ShowDialog`) を表示するため、完全に自動化されたスクリプト内で使用すると、ウィンドウが閉じられるまで処理が停止します。
#>
Function Out-TreeView {
    param (
        [Parameter(Mandatory=$true)][System.String]$Path,
        [System.Int32[]]$WindowSize = @(600, 450),
        [System.Single]$FontSize = 9.5
    )
    Add-Type -AssemblyName System.Windows.Forms
    Add-Type -AssemblyName System.Drawing

    # フォルダーがない場合は処理中断
    if (-not (Test-Path -Path $Path)) {
        Write-Warning "指定されたパスは存在しません: $Path"
        return
    }

    # 内部関数:フォルダーのツリー構造を取得する関数
    Function Get-FolderTree {
        param (
            [System.String]$Path,
            [Windows.Forms.TreeNode]$ParentNode
        )
        $items = (Get-ChildItem -Path $Path)
        foreach ($item in $items) {
            $node = New-Object Windows.Forms.TreeNode
            $node.Text = $item.Name
            $node.Tag = $item.FullName
            if ($item.PSIsContainer) {
                $node.BackColor = [System.Drawing.Color]::DarkBlue
                $node.ForeColor = [System.Drawing.Color]::AliceBlue
                Get-FolderTree -Path $item.FullName -ParentNode $node
            } else {
                $node.ForeColor = [System.Drawing.Color]::Black
            }
            $ParentNode.Nodes.Add($node) > $null
        }
    }

    # フォームの作成
    $mainForm = New-Object Windows.Forms.Form
    $mainForm.Text = "$($Path) - $($MyInvocation.MyCommand.Name)"
    $mainForm.Size = New-Object Drawing.Size($WindowSize[0], $WindowSize[1])
    $mainForm.Icon = [System.Drawing.SystemIcons]::Information

    # ステータスバーの作成
    # PowerShell 7だとステータスバーが動作しないため変更
    #$statusBar = New-Object Windows.Forms.StatusBar
    #$mainForm.Controls.Add($statusBar)
    $toolStrip = New-Object System.Windows.Forms.ToolStrip
    $statusLabel = New-Object System.Windows.Forms.ToolStripStatusLabel
    $toolStrip.Items.Add($statusLabel) | Out-Null
    $mainForm.Controls.Add($toolStrip)

    # ツリービューの作成
    $treeView = New-Object Windows.Forms.TreeView
    [System.Int32]$witdhSize = [System.Int32]$WindowSize[0] * 0.957
    [System.Int32]$heightSize = [System.Int32]$WindowSize[1] * 0.845
    $treeView.Size = New-Object Drawing.Size($witdhSize, $heightSize)
    # PowerShell 7だとステータスバーが動作しないため変更
    #$treeView.Location = New-Object Drawing.Point(5, 5)
    $treeView.Location = New-Object Drawing.Point(5, 25)
    $treeView.Anchor = `
    [System.Windows.Forms.AnchorStyles]::Top -bor `
    [System.Windows.Forms.AnchorStyles]::Bottom -bor `
    [System.Windows.Forms.AnchorStyles]::Left -bor `
    [System.Windows.Forms.AnchorStyles]::Right
    $treeView.Font = New-Object System.Drawing.Font("Microsoft Sans Serif", $FontSize)

    # ルートノードの作成
    $root = New-Object Windows.Forms.TreeNode
    $root.Text = $Path
    $root.Tag = $Path
    $root.BackColor = [System.Drawing.Color]::DarkBlue
    $root.ForeColor = [System.Drawing.Color]::AliceBlue
    $treeView.Nodes.Add($root) > $null

    # ツリー構造の取得
    Get-FolderTree -Path $root.Tag -ParentNode $root

    # ノード選択イベントの設定
    $treeView.add_AfterSelect({
        param ($sender, $e)
        $item = Get-Item -Path $e.Node.Tag
        # PowerShell 7だとステータスバーが動作しないため変更
        #$statusBar.Text = "$($item.Name) - Last Modified: $($item.LastWriteTime)"
        $statusLabel.Text = "$($item.Name) - Last Modified: $($item.LastWriteTime)"
    })

    # ノードダブルクリックイベントの設定
    $treeView.add_NodeMouseDoubleClick({
        param ($sender, $e)
        $item = Get-Item -Path $e.Node.Tag
        if (-not ($item.PSIsContainer)) {
            Invoke-Item -Path $item.FullName
        }
    })

    # ショートカットキー
    $treeView.add_KeyDown({
        param ($sender, $e)
        # Enterキーが押された場合
        if ($e.KeyCode -eq [System.Windows.Forms.Keys]::Enter) {
            $selectedNode = $treeView.SelectedNode
            if ($selectedNode -ne $null) {
                $item = Get-Item -Path $selectedNode.Tag
                if ($item.PSIsContainer) {
                    Start-Process explorer.exe -ArgumentList $item.FullName
                } else {
                    Invoke-Item -Path $item.FullName
                }
            }
        }
        # Ctrl + Shift + C:ノード名の絶対パスをクリップボードにコピー
        elseif ($e.Control -and $e.Shift -and $e.KeyCode -eq [System.Windows.Forms.Keys]::C) {
            $selectedNode = $treeView.SelectedNode
            if ($selectedNode -ne $null) {
                [System.Windows.Forms.Clipboard]::SetText($selectedNode.Tag)
            }
        }
        # Ctrl + C:ノード名をクリップボードにコピー
        elseif ($e.Control -and $e.KeyCode -eq [System.Windows.Forms.Keys]::C) {
            $selectedNode = $treeView.SelectedNode
            if ($selectedNode -ne $null) {
                [System.Windows.Forms.Clipboard]::SetText($selectedNode.Text)
            }
        }
    })

    # フォームにツリービューを追加
    $mainForm.Controls.Add($treeView)

    # フォームの表示
    $mainForm.Add_Shown({$mainForm.Activate()})
    $mainForm.ShowDialog() > $null
}

Markdownのテーブル形式で各列の開始位置を合わせ整形するFunction

2025.7.15 追記

ここのFunctionでは、先に紹介した「Check-BOMStatus」を参考に作成した「Test-UTF8Bom」を呼び出しています。

<#
.SYNOPSIS
    指定されたファイルがUTF-8 BOM(バイトオーダーマーク)を持つかどうかを判定します。

.DESCRIPTION
    この関数は、FilePath パラメータで指定されたファイルの先頭3バイトを読み込み、それがUTF-8のBOM(バイトシーケンス 0xEF, 0xBB, 0xBF)と一致するかどうかを確認します。
    ファイルがUTF-8 BOMで始まっている場合にのみ $true を返します。

.PARAMETER FilePath
    判定対象のファイルのパスを指定します。このパラメータは必須です。

.EXAMPLE
    # --- 準備:BOM付きとBOMなしのUTF-8ファイルを作成 ---
    # PS C:\> Set-Content -Path .\bom.txt -Value "BOMあり" -Encoding UTF8
    # PS C:\> Set-Content -Path .\nobom.txt -Value "BOMなし" -Encoding UTF8NoBOM

    # BOM付きファイルで実行した場合
    PS C:\> Test-UTF8Bom -FilePath .\bom.txt
    True

    # BOMなしファイルで実行した場合
    PS C:\> Test-UTF8Bom -FilePath .\nobom.txt
    False

.OUTPUTS
    System.Boolean
    ファイルがUTF-8 BOMを持つ場合は `$true` を、そうでない場合は `$false` を返します。

.NOTES
    - この関数はファイルの先頭3バイトのみをチェックします。ファイルの内容やUTF-8以外のエンコーディングについては考慮しません。
    - ファイルが存在しない、または読み取りアクセス権がない場合、この関数はエラーをスローします。呼び出し側で `Test-Path` や `try-catch` を使用して対処することが推奨されます。
    - ファイルサイズが3バイト未満の場合でも、この関数はエラーにならずに正しく `$false` を返します。

.LINKS
    https://zenn.dev/haretokidoki/articles/962a7fc6c51b47
#>
Function Test-UTF8Bom {
    param (
        [Parameter(Mandatory=$true)][System.String]$FilePath
    )

    # BOMのバイトシーケンス
    $UTF8_BOM = [System.Byte[]](0xEF, 0xBB, 0xBF)

    # バイトデータで読み込み
    $fileBytes = [System.IO.File]::ReadAllBytes($FilePath)
    # 先頭から3バイト分のデータでBOM付きか判定
    if ($fileBytes[0..2] -join "," -eq $UTF8_BOM -join ",") {
        return $true
    } else {
        return $false
    }
}

下記がメインのFunctionです。

<#
.SYNOPSIS
    Markdownファイル内のテーブルの列幅を、内容に応じて自動的に整形します。

.DESCRIPTION
    この関数は、指定されたMarkdownファイル内のテーブルを読み込み、各列の表示幅を揃えて見た目を整えます。
    内部でマルチバイト文字(例: 日本語)を2文字分、シングルバイト文字(例: 英数字)を1文字分として各セルの表示幅を計算します。
    そして、各列で最も幅の広いセルに合わせて、他のセルに半角スペースを追加(パディング)し、整形されたテーブル全体の文字列を返します。
    この処理により、ターミナルやエディタでテーブルが綺麗に整列して表示されるようになります。

.PARAMETER FilePath
    整形したいMarkdownテーブルが含まれるファイルのパスを指定します。

.EXAMPLE
    # --- 準備:整形前のMarkdownファイル (table.md) ---
    # | Name | Language | Type |
    # |---|---|---|
    # | PowerShell | C# | Script |
    # | Python | C | Script |
    # | 日本語 | - | 全角文字 |

    PS C:\> $formattedTable = Format-MarkdownTableColumnWidth -FilePath .\table.md
    PS C:\> $formattedTable | Out-File -FilePath .\formatted_table.md -Encoding UTF8

    # formatted_table.md には以下のように整形されたテーブルが保存される
    #
    # --- formatted_table.md の内容 ---
    # | Name       | Language | Type      |
    # |------------|----------|-----------|
    # | PowerShell | C#       | Script    |
    # | Python     | C        | Script    |
    # | 日本語     | -        | 全角文字  |

.OUTPUTS
    System.String
    整形されたMarkdownテーブル全体を含む単一の文字列を返します。
    入力ファイルがUTF-8 BOMでない場合や、エラーが発生した場合は $null を返します。

.NOTES
    - この関数は、`Test-UTF8Bom` という別のカスタム関数が同じスコープ内に定義されていることを前提としています。
    - 処理対象はUTF-8 BOM付きのファイルに限定されます。BOMがないファイルの場合、警告が表示され処理は実行されません。
    - ファイル全体をメモリに読み込んで処理するため、非常に巨大なファイルに対して使用する際はパフォーマンスに注意が必要です。
#>
Function Format-MarkdownTableColumnWidth {
    param (
        [System.String]$FilePath
    )

    # 文字コードの判定
    if (-not (Test-UTF8Bom $FilePath)) {
        Write-Warning 'Not UTF8 BOM'
        return $null
    }

    # マルチバイト文字を考慮して文字列のバイト数を計算する関数
    Function Get-DisplayWidth {
        param (
            [string]$InputString
        )
        $width = 0

        foreach ($char in $InputString.ToCharArray()) {
            if ([System.Text.Encoding]::UTF8.GetByteCount($char) -gt 1) {
                # マルチバイト文字の場合は2文字分としてカウント
                $width += 2
            } else {
                # シングルバイト文字の場合は1文字分としてカウント
                $width += 1
            }
        }

        return $width
    }


    # ファイルから入力を読み込む
    $MarkdownTable = (Get-Content -Path $FilePath -Raw)

    # 入力を行ごとに分割
    $lines = $MarkdownTable -split "`n"

    # 各列の最大幅を計算
    $maxWidths = @{}
    foreach ($line in $lines) {
        if (-not ([System.String]::IsNullOrWhiteSpace($line))) {
            $columns = $line -split '\|'
            for ($i = 1; $i -lt $columns.Length - 1; $i++) {
                $column = $columns[$i].Trim()
                $byteLength = (Get-DisplayWidth $column)
                if (-not $maxWidths.ContainsKey($i)) {
                    $maxWidths[$i] = $byteLength
                } else {
                    if ($byteLength -gt $maxWidths[$i]) {
                        $maxWidths[$i] = $byteLength
                    }
                }
            }
        }
    }

    # 列の幅を揃えて再構築
    $alignedTable = @()
    foreach ($line in $lines) {
        if (-not ([System.String]::IsNullOrWhiteSpace($line))) {
            $columns = $line -split '\|'
            $newLine = '|'
            for ($i = 1; $i -lt $columns.Length - 1; $i++) {
                $column = $columns[$i].Trim()
                $padding = " " * ($maxWidths[$i] - (Get-DisplayWidth $column))
                $newLine += " " + $column + $padding + ' |'
            }
            $alignedTable += $newLine
        }
    }

    return $alignedTable -join "`r`n"
}

PSCustomObjectで同じ要素(項目数・項目名)か比較するFunction

2025.7.15 追記

下記はサブのFunctionです。

<#
.SYNOPSIS
    指定された配列のすべての要素が [PSCustomObject] 型であるかどうかを判定します。

.DESCRIPTION
    この関数は、Argument パラメータで受け取った配列内の各要素を順番にチェックします。
    すべての要素が [System.Management.Automation.PSCustomObject] 型であれば $true を返します。
    一つでも異なる型の要素が含まれている場合、その時点でチェックを中断し、$false を返します。
    これは、`Test-PSCustomObjectEquality` のような、特定の型を前提とする関数のためのヘルパー関数として機能します。

.PARAMETER Argument
    判定対象となるオブジェクトの配列を指定します。このパラメータは必須です。

.EXAMPLE
    # 例 1: すべての要素が PSCustomObject の場合
    PS C:\> $array1 = @([pscustomobject]@{A=1}, [pscustomobject]@{B=2})
    PS C:\> Test-IsPSCustomObject -Argument $array1
    True

    # 例 2: 他の型が混在している場合
    PS C:\> $array2 = @([pscustomobject]@{A=1}, "string", 123)
    PS C:\> Test-IsPSCustomObject -Argument $array2
    False

.OUTPUTS
    System.Boolean
    すべての要素が [PSCustomObject] であれば `$true` を、一つでもそうでなければ `$false` を返します。
#>
Function Test-IsPSCustomObject {
    param(
        [Parameter(Mandatory=$true)]
        [System.Object[]]$Argument
    )

    foreach ($arg in $Argument) {
        if (-not ($arg -is [System.Management.Automation.PSCustomObject])) {
            return $false
        }
    }
    return $true
}

下記がメインのFunctionとなります。

<#
.SYNOPSIS
    2つの PSCustomObject 配列のプロパティ構造(項目名と順序)が同一であるかを確認します。

.DESCRIPTION
    この関数は、2つの PSCustomObject 配列の構造的な等価性を検証します。
    具体的には、各配列の最初のオブジェクトが持つプロパティの名前と順序を比較します。
    両方の配列が空でなく、すべての要素が PSCustomObject 型であり、かつプロパティ構造が完全に一致する場合にのみ $true を返します。
    この関数は、オブジェクトに含まれる「値」や、配列の「要素数」は比較しません。

.PARAMETER Object1
    比較元となる PSCustomObject の配列を指定します。このパラメータは必須です。

.PARAMETER Object2
    比較先となる PSCustomObject の配列を指定します。このパラメータは必須です。

.EXAMPLE
    # --- 準備 ---
    $objA = @(
        [pscustomobject]@{ Name = "Apple";  Color = "Red" },
        [pscustomobject]@{ Name = "Banana"; Color = "Yellow" }
    )
    $objB = @(
        [pscustomobject]@{ Name = "Grape"; Color = "Purple" }
    ) # 構造は同じ
    $objC = @(
        [pscustomobject]@{ Color = "Red"; Name = "Apple" }
    ) # プロパティの順序が違う

    # 例 1: プロパティ構造が一致する場合
    PS C:\> Test-PSCustomObjectEquality -Object1 $objA -Object2 $objB
    True

    # 例 2: プロパティの順序が異なる場合
    PS C:\> Test-PSCustomObjectEquality -Object1 $objA -Object2 $objC
    WARNING: オブジェクト同士の項目が一致していません。
    False

.OUTPUTS
    System.Boolean
    プロパティ構造が一致する場合は `$true` を、そうでない場合は `$false` を返します。

.NOTES
    - この関数は `Test-IsPSCustomObject` という別のカスタム関数が同じスコープ内に定義されていることを前提としています。
    - この関数は、各配列の「最初の要素」(`[0]`) のみを基準にプロパティ構造を比較します。
    - デフォルトでは、配列のデータ件数(要素数)は比較の対象外です(コード内でコメントアウトされています)。
#>
Function Test-PSCustomObjectEquality {
    param (
        [Parameter(Mandatory=$true)][System.Object[]]$Object1,
        [Parameter(Mandatory=$true)][System.Object[]]$Object2
    )

    # データ存在チェック
    if (($Object1.Count -eq 0) -or ($Object2.Count -eq 0)) {
        Write-Warning "いずれか引数のデータがありません。[引数1の件数: $($Object1.Count), 引数2の件数: $($Object2.Count)]"
        return $false
    }

    # オブジェクト内がPSCustomObjectであるか判定
    if (-not (Test-IsPSCustomObject $Object1)) {
        Write-Warning '引数の「Object1」がPSCustomObjectではありません。'
        return $false
    }
    elseif (-not (Test-IsPSCustomObject $Object2)) {
        Write-Warning '引数の「Object2」がPSCustomObjectではありません。'
        return $false
    }

    # 項目名と項目数を比較
    $object1ColumnData = $Object1[0].psobject.properties | ForEach-Object { $_.Name }
    $object2ColumnData = $Object2[0].psobject.properties | ForEach-Object { $_.Name }
    $compareResult = (Compare-Object $object1ColumnData $object2ColumnData -SyncWindow 0)
    if (($null -ne $compareResult) -and ($compareResult.Count -ne 0)) {
        Write-Warning "オブジェクト同士の項目が一致していません。"
        return $false
    }

    # # データ件数を比較(チェックする場合は、コメントアウト解除)
    # if ($Object1.Count -ne $Object2.Count) {
    #     Write-Warning "オブジェクト内のデータ件数が一致しません。[引数1の件数: $($Object1.Count), 引数2の件数: $($Object2.Count)]"
    #     return $false
    # }

    # 比較した結果2つのオブジェクトが一致
    return $true
}

UNIXのwhichコマンドのように実行ファイルの格納先を確認する方法

2025.7.15 追記

<#
.SYNOPSIS
    指定されたコマンドのソース(パスやモジュール名)を取得します。

.DESCRIPTION
    この関数は、Linux/Unixの `which` コマンドのように、指定されたコマンドのソース(通常は実行可能ファイルのフルパスや、コマンドレットが属するモジュール名)を特定します。
    内部で `Get-Command` を使用し、見つかったコマンドオブジェクトの `Source` プロパティを返します。
    コマンドが見つからない場合は、警告メッセージが表示されます。

.PARAMETER CommandName
    ソースを検索したいコマンドの名前を指定します。

.EXAMPLE
    # 例 1: 実行可能ファイルのパスを検索する
    PS C:\> which -CommandName 'notepad.exe'
    C:\Windows\System32\notepad.exe

    # 例 2: PowerShellコマンドレットが属するモジュールを検索する
    PS C:\> which -CommandName 'Get-ChildItem'
    Microsoft.PowerShell.Management

    # 例 3: 存在しないコマンドを指定した場合
    PS C:\> which -CommandName 'nonexistent-command'
    WARNING: 該当のコマンドが見つかりませんでした。[指定したコマンド名: nonexistent-command]

.OUTPUTS
    System.String
    コマンドのソースパスまたはモジュール名を含む文字列を返します。コマンドが見つからない場合は何も返しません。

.NOTES
    - この関数は `which` という名前ですが、PowerShellの `Get-Command` に基づいて動作するため、エイリアスや関数なども検索対象となり、Unixの `which` とは挙動が異なる場合があります。
#>
Function which {
    param (
        [System.String]$CommandName
    )
    $commandData = (Get-Command -Name $CommandName -ErrorAction SilentlyContinue)

    if ($null -eq $commandData) {
        Write-Warning "該当のコマンドが見つかりませんでした。[指定したコマンド名: $($CommandName)]"
        return
    }

    return $commandData.Source
}

コマンド出力結果のインデントを上げて少し見やすくするFunction

2025.7.15 追記

<#
.SYNOPSIS
    指定されたオブジェクトや文字列を、インデントを付けてコンソールに出力します。

.DESCRIPTION
    この関数は、パイプラインから受け取ったオブジェクトや直接指定されたオブジェクトを、整形されたインデント付きの文字列として出力します。
    オブジェクトは `Out-String` を使って文字列に変換された後、各行の先頭に指定されたレベルのインデント(1レベルあたり半角スペース4つ)が付加されます。
    文字列が直接渡された場合も同様にインデントが適用されます。
    空行や空白のみの行は出力から除外されます。
    この関数は、スクリプトの出力結果を階層的に見やすく表示したい場合に便利です。

.PARAMETER InputObject
    インデントを付けて出力したいオブジェクトを指定します。
    このパラメータはパイプライン入力を受け付けます。必須パラメータです。

.PARAMETER IndentCount
    インデントのレベルを整数で指定します。1を指定するとスペース4つ、2を指定するとスペース8つのように、インデントが深くなります。
    デフォルト値は 1 です。

.EXAMPLE
    # 例 1: 文字列配列をパイプラインで渡してインデントを付ける
    PS C:\> "Line 1", "Line 2" | Write-IndentedOutput

    # 実行結果:
    #     Line 1
    #     Line 2

.EXAMPLE
    # 例 2: Get-Process コマンドレットの結果をインデントレベル2で表示する
    PS C:\> Get-Process pwsh | Write-IndentedOutput -IndentCount 2

    # 実行結果 (pwshプロセスの情報がインデントされて表示される):
    #         Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName
    #         -------  ------    -----      -----     ------     --  -- -----------
    #             980      73   132180     154036      13.27  12345   1 pwsh

.EXAMPLE
    # 例 3: Get-Date の結果をデフォルトのインデントで表示する
    PS C:\> Get-Date | Write-IndentedOutput

    # 実行結果:
    #     2024年5月17日金曜日 15:30:00

.INPUTS
    System.Object
    パイプライン経由で任意のオブジェクトを受け取ることができます。

.OUTPUTS
    System.String
    インデントが付加された文字列を出力します。

.NOTES
    - オブジェクトを文字列に変換する際、内部で `Out-String` コマンドレットが使用されます。そのため、出力形式は `Out-String` の挙動に依存します。
    - 出力から空行や空白文字のみの行は取り除かれます。
#>
function Write-IndentedOutput {
    [CmdletBinding()]
    param (
        # パイプラインまたは直接渡されたオブジェクト
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [System.Object]$InputObject,

        # インデントに使用するインデックス数 ※デフォルトは1インデント(1インデント = 半角スペース4つ分)
        [Parameter(Mandatory = $false)]
        [System.Int32]$IndentCount = 1
    )

    begin {
        # インデント文字列を作成
        $indentString = New-Object System.String(" " , ($IndentCount * 4))
        # パイプラインで受け取ったオブジェクトを格納するためのコレクションを初期化
        $collectedObjects = @()
    }

    process {
        # 各入力オブジェクトを順に配列に格納
        $collectedObjects += $InputObject
    }

    end {
        # 収集したオブジェクトが存在しない場合は何もしない
        if ($collectedObjects.Count -eq 0) {
            Write-Warning "入力オブジェクトが指定されていません。"
            return
        }

        try {
            $outputText = if ($collectedObjects -is [System.String[]]) {
                # すでに文字列(または多数の文字列)の場合は連結する
                $collectedObjects -join [Environment]::NewLine
            } else {
                # オブジェクト全体を文字列化して出力
                $collectedObjects | Out-String
            }

            # 改行ごとに文字列を分割し、それぞれの行にインデント文字列を付加して出力
            $lines = $outputText -split [Environment]::NewLine
            foreach ($line in $lines) {
                if ($line.Trim() -ne "") {
                    Write-Output "$indentString$line"
                }
            }
        } catch {
            Write-Error "出力処理中にエラーが発生しました: $($_.Exception.Message)"
        }
    }
}

ログインユーザーがAdministratorsに所属しているか判定するFunction

2025.7.15 追記

<#
.SYNOPSIS
    指定されたユーザーが、指定されたローカルグループのメンバーであるかどうかを確認します。

.DESCRIPTION
    この関数は、targetUser パラメータで指定されたユーザーが、targetGroup パラメータで指定されたローカルグループに所属しているかどうかを判定します。
    パラメータを省略した場合、デフォルトで現在のユーザーがローカルの 'Administrators' グループのメンバーであるかをチェックします。
    内部では `Get-LocalGroupMember` コマンドレットを使用し、結果をブール値($true または $false)で返します。
    スクリプトの実行権限を確認する際などに便利です。

.PARAMETER targetUser
    メンバーシップを確認したいユーザーの名前を文字列で指定します。
    デフォルト値は、現在のコンピューター名とユーザー名(例: 'MY-PC\MyUser')です。

.PARAMETER targetGroup
    対象となるローカルグループの名前を文字列で指定します。
    デフォルト値は 'Administrators' です。

.EXAMPLE
    # 例 1: 現在のユーザーがAdministratorsグループのメンバーか確認する
    PS C:\> if (Test-GroupMembership) {
    >>     Write-Host "管理者として実行中です。"
    >> } else {
    >>     Write-Host "一般ユーザーとして実行中です。"
    >> }
    管理者として実行中です。

.EXAMPLE
    # 例 2: 特定のユーザーが 'Remote Desktop Users' グループに所属しているか確認する
    PS C:\> Test-GroupMembership -targetUser "MY-PC\testuser" -targetGroup "Remote Desktop Users"
    False

.OUTPUTS
    System.Boolean
    ユーザーがグループのメンバーである場合は `$true` を、そうでない場合は `$false` を返します。

.NOTES
    - この関数は `Get-LocalGroupMember` コマンドレットに依存しているため、Windows環境で、かつローカルグループ情報を読み取る権限がある場合にのみ正しく動作します。
    - Active Directoryのグループではなく、ローカルコンピューター上のグループを対象とします。
#>
function Test-GroupMembership {
    param (
        # 初期値は、現在のコンピューター名とユーザー名を設定
        [System.String]$targetUser = "$($env:COMPUTERNAME)\$($env:USERNAME)",
        # 初期値は、Administrators
        [System.String]$targetGroup = 'Administrators'
    )
    # 指定のグループに所属するユーザーの一覧を取得
    $groupMembers = (Get-LocalGroupMember -Name $targetGroup)

    # 対象ユーザーが指定のグループに含まれているか判定
    $isMember = ($groupMembers.Name -contains $targetUser)

    return $isMember
}

指定ドライブ内でファイルサイズが大きい順に上位ファイルを取得する方法

2025.7.15 追記

<#
.SYNOPSIS
    指定されたドライブ内で、ファイルサイズが大きい順に上位のファイルを取得します。

.DESCRIPTION
    この関数は、指定されたドライブレターのルートから再帰的にすべてのファイルをスキャンし、ファイルサイズ(Lengthプロパティ)に基づいて降順に並べ替えます。
    そして、TopCount パラメータで指定された件数だけ上位のファイルを抽出し、ランキング番号、フルパス、サイズ(バイト)、最終更新日時を含むカスタムオブジェクトのリストとして返します。
    アクセス権限がないためにスキャンできないフォルダは、エラーを表示せずにスキップされます。

.PARAMETER DriveLetter
    検索対象とするドライブのドライブレター(例: 'C' や 'D')を1文字で指定します。このパラメータは必須です。

.PARAMETER TopCount
    取得するファイルの上位件数を指定します。
    デフォルト値は 10 です。

.EXAMPLE
    # 例 1: Cドライブで最も大きいファイルの上位10件を取得する
    PS C:\> Get-LargeFiles -DriveLetter 'C'

    # 実行結果(例):
    # Rank FullName                                Size            LastWriteTime
    # ---- --------                                ----            -------------
    #    1 C:\Windows\System32\some_large_file.dll 123456789       2023/10/27 10:00:00
    #    2 C:\Users\user\Downloads\big_archive.zip 100000000       2024/05/17 15:00:00
    #  ...

.EXAMPLE
    # 例 2: Dドライブで最も大きいファイルの上位20件を取得し、テーブル形式で見やすく表示する
    PS C:\> Get-LargeFiles -DriveLetter 'D' -TopCount 20 | Format-Table -AutoSize

.OUTPUTS
    System.Management.Automation.PSCustomObject[]
    以下のプロパティを持つカスタムオブジェクトの配列を返します。
    - Rank:          サイズ順のランキング (Int32)
    - FullName:      ファイルのフルパス (String)
    - Size:          ファイルサイズ(バイト単位) (Int64)
    - LastWriteTime: ファイルの最終更新日時 (DateTime)

.NOTES
    - ドライブ全体をスキャンするため、特に大容量のドライブでは実行に時間がかかる場合があります。
    - この関数はファイルの検索のみを行い、ファイルの変更や削除は行いません。
    - 管理者権限で実行しない場合、アクセスできないシステムフォルダなどが検索対象から除外され、結果が不正確になる可能性があります。
#>
function Get-LargeFiles {
    [CmdletBinding()]
    param(
        # 検索対象のドライブレターを指定(例:C, D など一文字)
        [Parameter(Mandatory = $true)]
        [ValidatePattern('^[A-Za-z]$')]
        [string]$DriveLetter,

        # 上位何位まで抽出するか指定(例:50)
        [ValidateRange(1, [int]::MaxValue)]
        [int]$TopCount = 10
    )

    $path = "${DriveLetter}:\"

    try {
        # アクセス可能なファイルのリストを取得
        $files = Get-ChildItem -Path $path -Recurse -File -ErrorAction SilentlyContinue

        if (-not $files) {
            Write-Warning "指定したドライブ ($($path)) 内にファイルが見つかりませんでした。"
            return
        }

        # ファイルサイズが大きい順に並び替え、指定順位分のランキングを生成
        $result = $files |
            Sort-Object -Property Length -Descending |
            Select-Object -First $TopCount |
            ForEach-Object -Begin { $i = 1 } -Process {
                [pscustomobject]@{
                    Rank          = $i++
                    FullName      = $_.FullName
                    Size          = $_.Length
                    LastWriteTime = $_.LastWriteTime
                }
            }

        return $result
        # 順位が不要であればコッチ
        #$result = $files |
        #    Sort-Object -Property Length -Descending |
        #    Select-Object -First $TopCount -Property FullName, Length, LastWriteTime
        #
        #return $result
    }
    catch {
         Write-Error "エラー発生: $($_.Exception.GetType().Name) - $($_.Exception.Message)"
    }
}

wingetコマンドでChromeなどのソフト・アプリをインストールする方法

2025.7.15 追記

<#
.SYNOPSIS
    Winget を使用して、対話的にパッケージを検索し、特定のバージョンをインストールします。

.DESCRIPTION
    この関数は、Windows Package Manager (Winget) のラッパーとして機能し、パッケージのインストールプロセスを簡略化します。
    - パッケージIDを指定しない場合: ユーザーに検索クエリの入力を促し、`winget search` の結果を表示します。ユーザーはリストからインストールしたいパッケージのIDを選択します。
    - パッケージIDを指定した場合: `winget show --versions` を実行し、利用可能なバージョンのリストを表示します。
    - バージョンを指定しない場合: ユーザーにインストールしたいバージョンの入力を促します。未入力の場合は、利用可能な最新バージョンが自動的に選択されます。

    最終的に、選択されたパッケージとバージョンの組み合わせで `winget install` をサイレントモードで実行します。

.PARAMETER PackageId
    インストールしたいパッケージのIDを文字列で指定します。
    このパラメータを省略すると、関数は対話的な検索モードで動作します。

.PARAMETER Version
    インストールしたいパッケージの特定のバージョンを文字列で指定します。
    このパラメータを省略すると、利用可能なバージョンのリストからユーザーが選択するか、最新バージョンが自動的に選択されます。

.PARAMETER Source
    パッケージの取得元となるソースを指定します。
    デフォルトは 'winget' です。

.EXAMPLE
    # 例 1: 対話モードで実行する
    PS C:\> Install-WingetPackage

    # 実行すると、まず検索キーワードの入力を求められます。
    # > Wingetパッケージで検索する名前もしくはIDを入力しEnterキーを押してください。(例: chrome)
    # chrome [Enter]
    #
    # 検索結果が表示された後、インストールするIDの入力を求められます。
    # > インストールするパッケージの「ID」を入力しEnterキーを押してください。(例: google.chrome)
    # Google.Chrome [Enter]
    #
    # 利用可能なバージョンが表示された後、インストールするバージョンの入力を求められます。
    # > インストールするバージョンを入力しEnterキーを押してください。(未入力の場合は最新バージョンを選択)
    # [Enter] (最新版を選択)
    #
    # これにより、Google Chromeの最新版がサイレントインストールされます。

.EXAMPLE
    # 例 2: パッケージIDとバージョンを直接指定してインストールする
    PS C:\> Install-WingetPackage -PackageId 'Microsoft.PowerShell' -Version '7.4.2.0'

    # このコマンドは、対話的なプロンプトなしで、PowerShell 7.4.2.0 を直接インストールします。

.OUTPUTS
    System.Int32
    処理が正常に完了した場合は 0 を返します。
    途中でエラーが発生した場合は、-11, -12, -13, -21, -22, -23, -24 のいずれかの負のステータスコードを返します。

.NOTES
    - この関数を実行するには、`winget` コマンドがインストールされ、実行可能である必要があります。
    - パッケージのインストールは `--silent` オプション付きで実行されるため、通常はGUIのインストーラーは表示されません。
    - この関数は対話的な入力 (`Read-Host`) を求める部分があるため、完全自動化スクリプトでの使用には注意が必要です。
#>
Function Install-WingetPackage {
    param (
        [Parameter(Mandatory = $false)]
        [string]$PackageId, # インストール対象のパッケージ ID(任意)

        [Parameter(Mandatory = $false)]
        [string]$Version, # 指定があればそのバージョンをインストール

        [Parameter(Mandatory = $false)]
        [string]$Source = "winget" # 指定があればソースも選択
    )

    $statusCode = 0

    # コンソールのエンコーディングをUTF-8に設定
    [Console]::OutputEncoding = [System.Text.Encoding]::UTF8

    # パッケージ ID が指定されていない場合、パッケージリストを取得
    if (-Not $PackageId) {
        Write-Host ""
        Write-Host "> 利用可能なパッケージのリストを取得中..." -ForegroundColor Yellow
        Write-Host ""
        Write-Host "> Wingetパッケージで検索する名前もしくはIDを入力しEnterキーを押してください。(例: chrome)"
        $query = Read-Host
        $wingetListOutput = (winget search -q $query | Out-String)

        if (-Not $wingetListOutput) {
            Write-Error "wingetコマンドが実行できないか、指定された条件でデータを取得できませんでした。"
            $statusCode = -11
            return $statusCode
        }

        # パッケージリストを整形して表示
        $lines = ($wingetListOutput -split "`r?`n")
        # 名前やID、バージョンなどの情報があるデータと区切り線のみを抜粋
        $availablePackages = $lines | Where-Object { $_ -match "^\S+\s+\S+" -or $_ -match "^-{2,}" }

        if (-Not $availablePackages) {
            Write-Error "> 利用可能なデータが見つかりませんでした。条件を見直して再試行してください。"
            $statusCode = -12
            return $statusCode
        }

        Write-Host ""
        Write-Host "> 取得したパッケージ情報の一覧を表示します。:" -ForegroundColor Green
        $availablePackages | ForEach-Object { Write-Host $_ }

        # ユーザーに ID を入力させる
        Write-Host ""
        Write-Host "> インストールするパッケージの「ID」を入力しEnterキーを押してください。(例: google.chrome)"
        $selectedId = Read-Host
        if (-Not $selectedId) {
            Write-Error "未入力です。処理を終了します。"
            $statusCode = -13
            return $statusCode
        }
    }
    else {
        $selectedId = $PackageId
    }

    # 選択されたパッケージのバージョンを確認
    Write-Host ""
    Write-Host "> 利用可能なバージョンを確認中($selectedId)..." -ForegroundColor Yellow
    $wingetOutput = (winget show --id $selectedId --versions | Out-String)

    if (-Not $wingetOutput) {
        Write-Error "パッケージ '$selectedId' が見つかりませんでした。"
        $statusCode = -21
        return $statusCode
    }

    # バージョン情報のみを抽出し一覧を作成
    $lines = ($wingetOutput -split "`r?`n")
    $availableVersions = ($lines -replace "\s+", "" | Where-Object { $_ -ne "" -and $_ -match "^\d" } | Sort-Object)

    if (-Not $availableVersions) {
        Write-Error "パッケージ '$selectedId' のバージョン情報を取得できませんでした。"
        $statusCode = -22
        return $statusCode
    }

    Write-Host ""
    Write-Host "> 取得したバージョン情報の一覧を表示します。:" -ForegroundColor Green
    ($availableVersions | ForEach-Object { Write-Host $_ })

    if (-Not $Version) {
        # ユーザーにバージョンを入力させる(空の場合は最新版を使用)
        Write-Host ""
        Write-Host "> インストールするバージョンを入力しEnterキーを押してください。(未入力の場合は最新バージョンを選択)"
        $selectedVersion = Read-Host

        # バージョンが未指定の場合、最新版を選択
        if (-Not $selectedVersion) {
            $selectedVersion = $availableVersions[-1]
            Write-Host ""
            Write-Host "> 最新版 '$selectedVersion' をインストールします。" -ForegroundColor Yellow
        }
    }
    else {
        $selectedVersion = $Version
    }

    if (-Not ($availableVersions -contains $selectedVersion)) {
        Write-Error "指定されたバージョン '$selectedVersion' は一覧にありません。"
        $statusCode = -23
        return $statusCode
    }

    Write-Host ""
    Write-Host "> $selectedId のバージョン $selectedVersion のダウンロードとインストールを開始します..." -ForegroundColor Green

    # 標準出力を破棄してインストール処理を実行
    (winget install --id $selectedId --version $selectedVersion --source $Source --silent | Out-Null)
    $wingetExitcode = $LASTEXITCODE

    if ($wingetExitcode -eq 0) {
        Write-Host "> $selectedId バージョン $selectedVersion のインストールが完了しました。" -ForegroundColor Green
    } else {
        Write-Error "インストール中にエラーが発生しました。winget 終了コード: $wingetExitcode"
        $statusCode = -24
        return $statusCode
    }

    return $statusCode
}

2次元のジャグ配列と多次元配列それぞれに変換できるFunction

2025.7.15 追記

ジャグ配列 → 多次元配列 に変換するFunction

<#
.SYNOPSIS
    ジャグ配列(配列の配列)を2次元の多次元配列に変換。
.DESCRIPTION
    入力されたジャグ配列を分析し、すべての要素を格納できる最小の多次元配列を作成。
    各行の要素数が異なり不足している要素は $null 埋めされる。
.PARAMETER JaggedArray
    変換対象のジャグ配列(配列が内包された配列)。
.EXAMPLE
    # テスト用のジャグ配列を作成
    PS> $jagged = @(@(1, 2), @(3, 4, 5), @(6))

    # 関数を実行して多次元配列に変換
    PS> Convert-JaggedToMulti -JaggedArray $jagged
.OUTPUTS
    [System.Object[,]]
#>
Function Convert-JaggedToMulti {
    [CmdletBinding()]
    param(
        # 多次元配列 → ジャグ配列 で発生した奇妙な挙動にあわせて引数必須オプションをコメントアウト(有効にしても正常動作可能)
        #[Parameter(Mandatory = $true)] 
        [array]$JaggedArray
    )

    # 入力が空の配列の場合は、0x0の多次元配列を返す
    if ($JaggedArray.Length -eq 0) {
        $multiArray = New-Object 'object[,]' 0, 0
        return ,$multiArray
    }

    # 各行の配列の中で、最大の要素数を取得
    $maxColumnCount = ($JaggedArray | Where-Object { $_ -is [System.Array] } | Measure-Object -Property Length -Maximum).Maximum

    # 入力に配列が1つも含まれない場合、$maxColumnCount が $null になるため、0に設定
    if ($null -eq $maxColumnCount) {
        $maxColumnCount = 0
    }

    # 多次元配列を初期化 (ここでの出力は抑制しない。最後に明示的に返すため)
    $multiArray = New-Object 'object[,]' $JaggedArray.Length, $maxColumnCount

    # 要素をコピー
    for ($i = 0; $i -lt $JaggedArray.Length; $i++) {
        if ($JaggedArray[$i] -is [System.Array]) {
            for ($j = 0; $j -lt $JaggedArray[$i].Length; $j++) {
                $multiArray[$i, $j] = $JaggedArray[$i][$j]
                #Write-Debug "`$multiArray[$i, $j]: [$($multiArray[$i, $j])]"
            }
        }
    }

    return ,$multiArray
}

多次元配列 → ジャグ配列 に変換するFunction

<#
.SYNOPSIS
    2次元の多次元配列をジャグ配列(配列の配列)に変換します。
.DESCRIPTION
    入力された2次元の多次元配列を分析し、ジャグ配列を生成します。
    多次元配列の各行が、ジャグ配列の内部配列に対応します。
    元の配列に含まれる $null 値は、変換後のジャグ配列には含まれません。
.PARAMETER MultiArray
    変換対象の2次元多次元配列。
.EXAMPLE
    # テスト用の多次元配列を作成
    PS> $multi = New-Object 'object[,]' 3, 3
    PS> $multi[0,0] = 1; $multi[0,1] = 2
    PS> $multi[1,0] = 3; $multi[1,1] = 4; $multi[1,2] = 5
    PS> $multi[2,0] = 6

    # 関数を実行してジャグ配列に変換
    PS> $jagged = Convert-MultiToJagged -MultiArray $multi
.OUTPUTS
    [System.Object[]]
    各要素が配列であるジャグ配列。
#>
Function Convert-MultiToJagged {
    [CmdletBinding()]
    param(
        #引数必須で指定していると、なぜかnullが渡されてしまう奇妙な挙動が発生した為、コメントアウト
        #[Parameter(Mandatory = $true)]
        [System.Array]$MultiArray
    )

    # 入力が2次元配列であることを検証
    if ($MultiArray.Rank -ne 2) {
        Write-Error "入力は2次元の多次元配列である必要があります。入力された配列の次元数: $($MultiArray.Rank)"
        return
    }

    # ジャグ配列を格納するための可変長リストを準備
    $jaggedArrayList = [System.Collections.ArrayList]::new()

    # 行と列の数を取得
    $rowCount = $MultiArray.GetLength(0)
    $colCount = $MultiArray.GetLength(1)

    # 各行をループ処理
    for ($i = 0; $i -lt $rowCount; $i++) {
        # 各行の要素を格納するための可変長リストを準備
        $rowList = [System.Collections.ArrayList]::new()

        # 各列をループ処理
        for ($j = 0; $j -lt $colCount; $j++) {
            $element = $MultiArray[$i, $j]

            # 要素が $null でない場合のみリストに追加
            if ($null -ne $element) {
                # Add()メソッドの戻り値(追加された要素のインデックス)がパイプラインに出力されるのを防ぐ
                [void]$rowList.Add($element)
                #Write-Debug "`$rowList: [$($rowList)]"
            }
        }

        # 完成した行(配列に変換済み)を全体のジャグ配列リストに追加
        [void]$jaggedArrayList.Add($rowList.ToArray())
        #Write-Debug "`$jaggedArrayList[$i]: [$($jaggedArrayList[$i])]"
    }

    # 最終的な結果(ArrayListを配列に変換したもの)をパイプラインに出力
    # これにより、呼び出し元はジャグ配列として受け取れる
    return ,$jaggedArrayList.ToArray()
}

Zennのメイン記事

akiGAMEBOY

個人的なお気に入りとしては、
フォルダーをツリー形式で表示するFunction(CLIとGUIの2通り)
ですね。

意外とCLIでツリー表示する方法を重宝しています。

関連記事

サポート募集中

この記事はお役に立てたでしょうか。現在、サポートを募集しています。
コーヒーを一杯 ☕ ご馳走いただけないでしょうか。

この記事が気に入ったら
フォローしてね!

この記事をシェアする

コメント

コメントする

コメントは日本語で入力してください。(スパム対策)

CAPTCHA

目次