PowerShellステップカウンタ

# <i class="fa fa-cubes" style="font-size:1em;"></i> 概要
PowerShellスクリプトの行数（コメント行、空白行、その他）をカウントする。

# <i class="fa fa-file-text-o" style="font-size:1em;"></i> コード

```PowerShell
<#
.SYNOPSIS
    PowerShellスクリプトのステップ数をカウント

.DESCRIPTION
    スクリプトの実行行、コメント行、空行を出力する

.PARAMETER ScriptFile
    スクリプトファイル

.PARAMETER NoFolder
    ファイル名にパスを付与しない

.NOTES
    ヒアドキュメント中のコメント記号は考慮しない

.EXAMPLE
    CountPsLine .\Test1.ps1
    Test1.ps1をカウントする

.EXAMPLE
    ls D:\Scripts\*.ps1 | CountPsLine > LineCount.csv
    D:\Scriptsにあるps1ファイルを全てカウントして結果をLineCount.csvに出力する

.EXAMPLE
    CountPsLine .\Test3.ps1 -NoFolder
    Test3.ps1をカウントする
    （結果の「ファイル名」列にパスを付けない。NoFolder指定無しだとフルパス表示）
#>
function CountPsLine(
            [Parameter(Mandatory, ValueFromPipeline=$true)]$ScriptFile, 
            [switch]$NoFolder)
{
    begin
    {
        if ($ScriptFile -ne $null -and $ScriptFile.Contains("*"))
        {
            throw "パラメータにワイルドカードは使えません。複数ファイルはパイプライン経由で渡してください。"
        }
        # ヘッダー出力
        Write-Output "ファイル名`t実行行`tコメント行`t空行`t合計"
    }
    
    process
    {
        function MainProc($ScriptFile)
        {
            [string]$filePath = ""
            
            if($ScriptFile.GetType().FullName -eq "System.String")
            {
                $filePath = Convert-Path $ScriptFile
            }
            elseif ($ScriptFile.GetType().FullName -eq "System.IO.FileInfo")
            {
                $filePath = $ScriptFile.FullName
            }
            else
            {
                Write-Error "この型は処理できません。"
                return
            }
            
            # 拡張子チェック
            if ($filePath.ToLower().EndsWith(".ps1") -or $filePath.ToLower().EndsWith(".psm1"))
            {
                # OK
            }
            else
            {
                Write-Error "ps1またはpsm1ファイルを指定してください。"
                return
            }

            [long]$exec = 0
            [long]$comment = 0
            [long]$blank = 0
            [bool]$multiCommentMode = $false

            Get-Content -LiteralPath $filePath | ForEach-Object {
                # 左右の空白除去
                [string]$lineTrim = $_.Trim()
                # 複数行コメントの開始・終了記号っぽい文字列を除去
                [string]$lineRemove = $lineTrim -replace "`".*<#.*?`"",""
                $lineRemove = $lineRemove -replace "`".*#>.*?`"",""
                
                if ($multiCommentMode)
                {
                    # 複数行コメントの途中
                    
                    if ($lineRemove.EndsWith("#>"))
                    {
                        # 複数行コメントの終わり
                        $comment++
                        $multiCommentMode = $false
                    }
                    elseif ($lineRemove.Contains("#>"))
                    {
                        # 複数行コメントの終わり、その右にコード有り
                        $exec++
                        $multiCommentMode = $false
                    }
                    else
                    {
                        # 複数行コメントの途中
                        $comment++
                    }
                }
                else
                {
                    if ($lineTrim -eq "")
                    {
                        # 空白行
                        $blank++
                    }
                    elseif ($lineRemove.ToLower().StartsWith("#requires"))
                    {
                        # 実行行
                        $exec++
                    }
                    elseif ($lineRemove.StartsWith("#"))
                    {
                        # コメント行
                        $comment++
                    }
                    
                    elseif ($lineRemove.StartsWith("<#") -and $lineRemove.EndsWith("#>"))
                    {
                        # 一行で複数行コメント形式
                        $comment++
                    }
                    elseif ($lineRemove -match "<#.*#>")
                    {
                        # 一行で複数行コメント、コメントの外（左右）にコード有り
                        $exec++
                    }
                    elseif ($lineRemove.StartsWith("<#"))
                    {
                        # 複数行コメントの開始
                        $comment++
                        $multiCommentMode = $true
                    }
                    elseif ($lineRemove.Contains("<#"))
                    {
                        # 複数行コメントの開始、その左にコード有り
                        $exec++
                        $multiCommentMode = $true
                    }
                    else
                    {
                        # 実行行
                        $exec++
                    }
                }
            }

            if ($NoFolder)
            {
                $filePath = Split-Path $filePath -Leaf
            }
            
            # 結果表示
            [long]$total = $blank + $comment + $exec
            Write-Output "${filePath}`t${exec}`t${comment}`t${blank}`t${total}"
        }

        $ScriptFile | ForEach-Object { MainProc $_ }
    }
}
```

# <i class="fa fa-cubes" style="font-size:1em;"></i> 実行例
結果をファイルにリダイレクト
![ps22.PNG](https://qiita-image-store.s3.amazonaws.com/0/142879/9304c560-f890-d1e9-fe23-b95538347002.png)

ファイルをExcelで開いたところ
![ps23.png](https://qiita-image-store.s3.amazonaws.com/0/142879/9f6f44ce-f17c-7817-a06c-e5bedafcf582.png)


# <i class="fa fa-cubes" style="font-size:1em;"></i> 動作確認した環境
* PowerShell V4 (Windows 8.1)
* PowerShell V5 (Windows 10)