Obtaining project size metrics with PowerShell I'm often frustrated by the lack of ability to obtain simple size metrics about code projects I'm working on, like total lines of code, or character count. To fix this, I wrote the following PowerShell utility:

Function GetProjectMetrics
{
    [CmdletBinding()]
    Param(
        [Parameter(mandatory=$True)]
        [string]$projectPath,

        [Parameter(mandatory=$True)]
        [string]$fileFilter,

        [switch]$ignorews
    )

    Set-Location $projectPath
    Switch($ignorews)
    {
        $True 
        { 
            Get-ChildItem -recurse -include $fileFilter                   `
                | Get-Content                                             `
                | Measure-Object -Character -Word -Line -IgnoreWhiteSpace
            Break
        }

        $False
        {
            Get-ChildItem -recurse -include $fileFilter `
                | Get-Content                           `
                | Measure-Object -Character -Word -Line
            Break
        }

        Default
        {
            Write-Host "Error executing 'GetProjectMetrics'."
            Break
        }
    }
}

New-Alias -Name gpm -Value GetProjectMetrics
New-Alias -Name getpm -Value GetProjectMetrics


The above code is placed within my Microsoft.PowerShell_profile.ps1 file (my PowerShell profile), which gives me the ability to use it straight from the PowerShell command line.

Here's some example usage:


PS C:\Users\Ethan&gt; gpm ".\Documents\MyPythonProject" "*.py" -ignorews
PS C:\Users\Ethan&gt; getpm ".\Documents\MyPythonProject" "*.py"
PS C:\Users\Ethan&gt; GetProjectMetrics ".\OneDrive\CS Projects\CS Tests" "*.cs" -ignorews



What can I improve?
My suggestions:


apply PowerShell scripting best practices; for particulars, see Concept: A list of some Best Practices for Windows PowerShell by powershell-guru;
get more knowledge from the utility (see Count and Property output properties; see also -Detailed input parameter);
allow -LiteralPath to take range operator characters in project path literally;
use Set-Alias -Name … -Value … -Force rather than New-Alias;
(not implemented yet) consider involving -Encoding parameter for Get-Content cmdlet.


The script (rewritten almost completely):

&lt;#
 SYNOPSIS: Calculates simple size metrics about code projects.
          Combination of `Get-ChildItem`, `Get-Content` and `Measure-Object`

  Parameter         Meaning (see native cmdlet)
  ---------         ---------------------------
  -Path             obvious (Convert-Path, Get-ChildItem, Get-Item, Push-Location)
  -LiteralPath      obvious (Convert-Path, Get-ChildItem, Get-Item, Push-Location)
  -Filter           obvious (Get-ChildItem)
  -IgnoreWhiteSpace obvious (Measure-Object)
  -Detailed         returns size metrics about individual files severally
  -PushLocation     side effect: `Push-Location`

 RETURNS: Custom combination of GenericMeasureInfo and TextMeasureInfo:

  Name       Type     Meaning
  ----       ----     -------
  Count      [int]    number of files of given pattern in given (literal)path
  Lines      [int]    number of lines
  Words      [int]    number of words
  Characters [int]    number of characters
  Property   [string] tested path
#&gt;

Set-StrictMode -Version latest

Function Measure-Project
{
    [CmdletBinding(DefaultParameterSetName='Items')]
    Param(
        [Alias('projectPath')]
        [Parameter(Mandatory=$false,
                   Position=0,
                   ParameterSetName='Items')]
        [string]$Path='.\',

        [Parameter(Mandatory=$false,
                   Position=0,
                   ParameterSetName='LiteralItems')]
        [string]$LiteralPath='.\',

        [Alias('fileFilter')]
        [Parameter(Mandatory=$false,
                   Position=1)]
        [string]$Filter='*.ps1',

        [Alias('IgnoreWS')]
        [switch]$IgnoreWhiteSpace,

        [Alias('PushD')]
        [switch]$PushLocation,

        [switch]$Detailed
    )
    Function out ($Cnt, $Prm, $Obj) {
        $auxCounter = $Obj | Measure-Object -Character -Word -Line @auxParMO
        $auxCounter | Add-Member -NotePropertyName Count -NotePropertyValue $Cnt
        $auxCounter.Property = $Prm
        return $auxCounter
    }
    if ( $PSCmdlet.ParameterSetName -eq 'LiteralItems') {
        $auxParTP = @{ LiteralPath = $LiteralPath }
    } else {
        $auxParTP = @{ Path = $Path }
    }
    if ( Test-Path @auxParTP ) {
        $auxPath = Convert-Path @auxParTP
        if ( (Get-Item @auxParTP) -is [System.IO.DirectoryInfo]) {
            $auxFiles = Get-ChildItem @auxParTP -Filter $Filter -Recurse -File
            $auxParMO = @{ IgnoreWhiteSpace = $IgnoreWhiteSpace.IsPresent } 
                         # $PSBoundParameters.ContainsKey('IgnoreWhiteSpace')
            if ($Detailed.IsPresent -and $auxFiles) {
                $auxFiles | ForEach-Object {
                    out -Cnt 1 -Prm $_.FullName -Obj ($_ | Get-Content)
                }
            } else {
                $auxPar = Join-Path -Path $auxPath -ChildPath $Filter
                if ( $auxFiles ) {
                    if ( $auxFiles -is [array] ) {
                        $auxCnt = $auxFiles.Count
                    } else {
                        $auxCnt = 1
                    }
                    out -Cnt $auxCnt -Prm $auxPar -Obj ($auxFiles | Get-Content)
                } else {
                    Write-Warning "No '$Filter' inside '$Path'"
                    out -Cnt 0 -Prm $auxPar -Obj ''
                }
            }
            if ( $PushLocation.IsPresent ) {Push-Location @auxParTP}
        } else {
            $auxMsg = "'$($auxParTP.Values.GetEnumerator())' is not a directory"
            Write-Error -Message $auxMsg -Category InvalidArgument
        }
    }
    else
    {
        $auxMsg = "Cannot find path '$($auxParTP.Values.GetEnumerator())'"
        Write-Error -Message $auxMsg -Category ObjectNotFound
    }
}


Sample output demonstrates necessity of -Encoding parameter as both files contain nearly the same text so the utility should show the same count of characters. Of course, I'd expect the same encoding of source files in a particular project so sorry for the following simplified example:


  PS D:\PSh&gt; Measure-Project -LiteralPath 'D:\bat\[source]\XX\' -filter * -Detailed | ft

Count Lines Words Characters Property                           
----- ----- ----- ---------- --------                           
    1     1     5        118 D:\bat\[source]\XX\[sourcefile].log
    1     1     5         59 D:\bat\[source]\XX\[sourcefile].txt

PS D:\PSh&gt; type -LiteralPath 'D:\bat\[source]\XX\[sourcefile].txt'
Toto je obsah souboru `D:\bat\[source]\XX\[sourcefile].txt`

PS D:\PSh&gt; type -LiteralPath 'D:\bat\[source]\XX\[sourcefile].log' -Encoding Unicode
Toto je obsah souboru `D:\bat\[source]\XX\[sourcefile].log`