PSDevSummary.psm1

function Start-TimeLog { 
    [CmdletBinding()]
    param()
    [System.Diagnostics.Stopwatch]::StartNew()
}
function Stop-TimeLog { 
    [CmdletBinding()]
    param ([Parameter(ValueFromPipeline = $true)][System.Diagnostics.Stopwatch] $Time,
        [ValidateSet('OneLiner', 'Array')][string] $Option = 'OneLiner',
        [switch] $Continue)
    Begin { }
    Process { if ($Option -eq 'Array') { $TimeToExecute = "$($Time.Elapsed.Days) days", "$($Time.Elapsed.Hours) hours", "$($Time.Elapsed.Minutes) minutes", "$($Time.Elapsed.Seconds) seconds", "$($Time.Elapsed.Milliseconds) milliseconds" } else { $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds" } }
    End {
        if (-not $Continue) { $Time.Stop() }
        return $TimeToExecute
    }
}
function Write-Color { 
    <#
    .SYNOPSIS
        Write-Color is a wrapper around Write-Host.
 
        It provides:
        - Easy manipulation of colors,
        - Logging output to file (log)
        - Nice formatting options out of the box.
 
    .DESCRIPTION
        Author: przemyslaw.klys at evotec.pl
        Project website: https://evotec.xyz/hub/scripts/write-color-ps1/
        Project support: https://github.com/EvotecIT/PSWriteColor
 
        Original idea: Josh (https://stackoverflow.com/users/81769/josh)
 
    .EXAMPLE
    Write-Color -Text "Red ", "Green ", "Yellow " -Color Red,Green,Yellow
 
    .EXAMPLE
    Write-Color -Text "This is text in Green ",
                    "followed by red ",
                    "and then we have Magenta... ",
                    "isn't it fun? ",
                    "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan
 
    .EXAMPLE
    Write-Color -Text "This is text in Green ",
                    "followed by red ",
                    "and then we have Magenta... ",
                    "isn't it fun? ",
                    "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan -StartTab 3 -LinesBefore 1 -LinesAfter 1
 
    .EXAMPLE
    Write-Color "1. ", "Option 1" -Color Yellow, Green
    Write-Color "2. ", "Option 2" -Color Yellow, Green
    Write-Color "3. ", "Option 3" -Color Yellow, Green
    Write-Color "4. ", "Option 4" -Color Yellow, Green
    Write-Color "9. ", "Press 9 to exit" -Color Yellow, Gray -LinesBefore 1
 
    .EXAMPLE
    Write-Color -LinesBefore 2 -Text "This little ","message is ", "written to log ", "file as well." `
                -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt" -TimeFormat "yyyy-MM-dd HH:mm:ss"
    Write-Color -Text "This can get ","handy if ", "want to display things, and log actions to file ", "at the same time." `
                -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt"
 
    .EXAMPLE
    # Added in 0.5
    Write-Color -T "My text", " is ", "all colorful" -C Yellow, Red, Green -B Green, Green, Yellow
    wc -t "my text" -c yellow -b green
    wc -text "my text" -c red
 
    .NOTES
        CHANGELOG
 
        Version 0.5 (25th April 2018)
        -----------
        - Added backgroundcolor
        - Added aliases T/B/C to shorter code
        - Added alias to function (can be used with "WC")
        - Fixes to module publishing
 
        Version 0.4.0-0.4.9 (25th April 2018)
        -------------------
        - Published as module
        - Fixed small issues
 
        Version 0.31 (20th April 2018)
        ------------
        - Added Try/Catch for Write-Output (might need some additional work)
        - Small change to parameters
 
        Version 0.3 (9th April 2018)
        -----------
        - Added -ShowTime
        - Added -NoNewLine
        - Added function description
        - Changed some formatting
 
        Version 0.2
        -----------
        - Added logging to file
 
        Version 0.1
        -----------
        - First draft
 
        Additional Notes:
        - TimeFormat https://msdn.microsoft.com/en-us/library/8kb3ddd4.aspx
    #>

    [alias('Write-Colour')]
    [CmdletBinding()]
    param ([alias ('T')] [String[]]$Text,
        [alias ('C', 'ForegroundColor', 'FGC')] [ConsoleColor[]]$Color = [ConsoleColor]::White,
        [alias ('B', 'BGC')] [ConsoleColor[]]$BackGroundColor = $null,
        [alias ('Indent')][int] $StartTab = 0,
        [int] $LinesBefore = 0,
        [int] $LinesAfter = 0,
        [int] $StartSpaces = 0,
        [alias ('L')] [string] $LogFile = '',
        [Alias('DateFormat', 'TimeFormat')][string] $DateTimeFormat = 'yyyy-MM-dd HH:mm:ss',
        [alias ('LogTimeStamp')][bool] $LogTime = $true,
        [ValidateSet('unknown', 'string', 'unicode', 'bigendianunicode', 'utf8', 'utf7', 'utf32', 'ascii', 'default', 'oem')][string]$Encoding = 'Unicode',
        [switch] $ShowTime,
        [switch] $NoNewLine)
    $DefaultColor = $Color[0]
    if ($null -ne $BackGroundColor -and $BackGroundColor.Count -ne $Color.Count) { Write-Error "Colors, BackGroundColors parameters count doesn't match. Terminated."; return }
    if ($LinesBefore -ne 0) { for ($i = 0; $i -lt $LinesBefore; $i++) { Write-Host -Object "`n" -NoNewline } }
    if ($StartTab -ne 0) { for ($i = 0; $i -lt $StartTab; $i++) { Write-Host -Object "`t" -NoNewLine } }
    if ($StartSpaces -ne 0) { for ($i = 0; $i -lt $StartSpaces; $i++) { Write-Host -Object ' ' -NoNewLine } }
    if ($ShowTime) { Write-Host -Object "[$([datetime]::Now.ToString($DateTimeFormat))]" -NoNewline }
    if ($Text.Count -ne 0) {
        if ($Color.Count -ge $Text.Count) { if ($null -eq $BackGroundColor) { for ($i = 0; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -NoNewLine } } else { for ($i = 0; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -BackgroundColor $BackGroundColor[$i] -NoNewLine } } } else {
            if ($null -eq $BackGroundColor) {
                for ($i = 0; $i -lt $Color.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -NoNewLine }
                for ($i = $Color.Length; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $DefaultColor -NoNewLine }
            } else {
                for ($i = 0; $i -lt $Color.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -BackgroundColor $BackGroundColor[$i] -NoNewLine }
                for ($i = $Color.Length; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $DefaultColor -BackgroundColor $BackGroundColor[0] -NoNewLine }
            }
        }
    }
    if ($NoNewLine -eq $true) { Write-Host -NoNewline } else { Write-Host }
    if ($LinesAfter -ne 0) { for ($i = 0; $i -lt $LinesAfter; $i++) { Write-Host -Object "`n" -NoNewline } }
    if ($Text.Count -ne 0 -and $LogFile -ne "") {
        $TextToFile = ""
        for ($i = 0; $i -lt $Text.Length; $i++) { $TextToFile += $Text[$i] }
        try { if ($LogTime) { Write-Output -InputObject "[$([datetime]::Now.ToString($DateTimeFormat))]$TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append } else { Write-Output -InputObject "$TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append } } catch { $_.Exception }
    }
}
Function ConvertTo-Markdown {
    <#
    .Synopsis
    Converts a PowerShell object to a Markdown table.
 
    .Description
    Converts a PowerShell object to a Markdown table.
 
    .Parameter InputObject
    PowerShell object to be converted
 
    .Example
    ConvertTo-Markdown -InputObject (Get-Service)
 
    Converts a list of running services on the local machine to a Markdown table
 
    .Example
    ConvertTo-Markdown -InputObject (Import-CSV "C:\Scratch\lwsmachines.csv") | Out-File "C:\Scratch\file.markdown" -Encoding "ASCII"
 
    Converts a CSV file to a Markdown table
 
    .Example
    Import-CSV "C:\Scratch\lwsmachines.csv" | ConvertTo-Markdown | Out-File "C:\Scratch\file2.markdown" -Encoding "ASCII"
 
    Converts a CSV file to a markdown table via the pipeline.
 
    .Notes
    Ben Neise 10/09/14
 
    #>

    [CmdletBinding()]
    [OutputType([string])]
    Param (
        [Parameter(Mandatory, Position = 0, ValueFromPipeline)][PSObject[]]$collection
    )
    Begin {
        $Items = [System.Collections.Generic.List[Object]]::new()
        $columns = [ordered] @{ }
    }
    Process {
        ForEach ($item in $collection) {
            $items.Add($item)

            $item.PSObject.Properties | ForEach-Object {
                if ($null -eq $_.Value) {
                    $_.Value = ""
                }
                if (-not $columns.Contains($_.Name) -or $columns[$_.Name] -lt $_.Value.ToString().Length) {
                    $columns[$_.Name] = $_.Value.ToString().Length
                }
            }
        }
    }
    End {
        ForEach ($key in $($columns.Keys)) {
            $columns[$key] = [Math]::Max($columns[$key], $key.Length)
        }

        $header = ForEach ($key in $columns.Keys) {
            ('{0,-' + $columns[$key] + '}') -f $key
        }
        -join ('| ', $($header -join ' | '), ' |')

        $separator = ForEach ($key in $columns.Keys) {
            '-' * $columns[$key]
        }
        -join ('| ', $($separator -join ' | '), ' |')


        ForEach ($item in $items) {
            $values = ForEach ($key in $columns.Keys) {
                ('{0,-' + $columns[$key] + '}') -f $item.($key)
            }
            -join ('| ', $($values -join ' | '), ' |')
        }
    }
}
function Get-DevSummary {
    [CmdletBinding()]
    param(
        [string] $Author,
        [string] $PathModuleDetails = "$Env:USERPROFILE\Desktop\ModulesDetails.xml",
        [string] $PathModules = "$Env:USERPROFILE\Desktop\Modules.xml",
        [string] $PathGitHub = "$Env:USERPROFILE\Desktop\GitHubModules.xml",
        [string] $PathModulesHTML = "$Env:USERPROFILE\Desktop\MyModules.HTML",
        [switch] $UseCache,
        [switch] $UseHTMLLinks,
        [switch] $UseMarkdown,
        [switch] $ReturnDetails
    )
    if ($UseCache -and (Test-Path -LiteralPath $PathModules)) {
        $AllModules = Import-Clixml -LiteralPath $PathModules
    } else {
        $AllModules = Find-Module -Verbose
        if ($UseCache) {
            $AllModules | Export-Clixml -LiteralPath $PathModules -Depth 5
        }
    }
    if ($UseCache -and (Test-Path -LiteralPath $PathModuleDetails)) {
        $AllModulesDetails = Import-Clixml -LiteralPath $PathModuleDetails
    } else {
        $AllModulesDetails = [ordered] @{ }
    }
    if ($UseCache -and (Test-Path -LiteralPath $PathGitHub)) {
        $GitHubModules = Import-Clixml -LiteralPath $PathGitHub
    } else {
        $GitHubModules = [ordered] @{ }
    }
    if ($Author) {
        [Array] $MyModules = $AllModules | Where-Object { $_.Author -eq $Author } | Sort-Object Name
    } else {
        [Array] $MyModules = $AllModules | Sort-Object Name
    }
    $Objects = foreach ($_ in $MyModules) {
        $Time = Start-TimeLog
        Write-Color -Text "[i] ", "Generating stats for module ", $($_.Name) -NoNewLine -Color Yellow, DarkGray, Yellow
        if ($null -eq $AllModulesDetails[$_.Name]) {
            $AllModulesDetails[$_.Name] = Find-Module -Name $_.Name -AllVersions -AllowPrerelease
        }
        [Array] $Module = $AllModulesDetails[$_.Name]

        Write-Color -Text ' [Total Release Count: ', $Module.Count, '] ' -NoNewLine -Color Cyan, DarkGray, Cyan
        [Array] $InternalModules = foreach ($M in $Module) {
            $PublishedDate = try {
                [DateTime] $M.PublishedDate
            } catch {
                $M.PublishedDate;
                Write-Warning "Conversion failed for $($M.PublishedDate)"
            }

            [PSCustomObject] @{
                Date      = $PublishedDate
                Downloads = $M.AdditionalMetadata.versionDownloadCount
            }
        }
        [DateTime] $CurrentYear = (Get-Date)
        [int] $Year = $CurrentYear.Year
        [int] $LastYear = ($CurrentYear).AddYears(-1).Year
        [int] $PreviousYear = ($CurrentYear).AddYears(-2).Year
        [Array] $ModuleCurrentYear = $InternalModules | Where-Object { $_.Date.Year -eq $Year }
        [Array] $ModuleLastYear = $InternalModules | Where-Object { $_.Date.Year -eq $LastYear }
        [Array] $ModulePreviousYear = $InternalModules | Where-Object { $_.Date.Year -eq $PreviousYear }

        if ($null -eq $GitHubModules[$_.ProjectUri]) {
            try {
                $GitHubModules[$_.ProjectUri] = Get-GitHubRepository -Uri $_.ProjectUri
            } catch {
                $GitHubProject = $Null
            }
        }
        $GitHubProject = $GitHubModules[$_.ProjectUri] | Select-Object full_name, Name, Stargazers_Count, forks_count, Open_issues, license, Language, HTML_URL, Fork, Created_At, Updated_At, Pushed_At, Archived
        if ($UseHTMLLinks) {
            $Name = "<a href='$($_.ProjectUri)' target='_blank'>$($_.Name)</a>"
            $PSGalleryURL = "<a href='https://www.powershellgallery.com/packages/$($_.Name)' target='_blank'>https://www.powershellgallery.com/packages/$($_.Name)</a>"
            $GitHubURL = "<a href='$($_.ProjectUri)' target='_blank'>$($_.ProjectUri)</a>"
        } elseif ($UseMarkdown) {
            $Name = "[$($_.Name)]($($_.ProjectUri))"
        } else {
            $Name = $_.Name
            $PSGalleryURL = "https://www.powershellgallery.com/packages/$($_.Name)"
            $GitHubURL = $_.ProjectUri
        }
        $Object = [ordered] @{
            'Name'                                  = $Name
            'Type'                                  = $_.Type
            'GitHub Stars'                          = $GitHubProject.Stargazers_Count
            'GitHub Forks'                          = $GitHubProject.forks_count
            'GitHub Open Issues'                    = $GitHubProject.Open_issues
            'GitHub Archived'                       = $GitHubProject.Archived
            'GitHub Created'                        = $GitHubProject.Created_At
            'GitHub Updated'                        = $GitHubProject.Updated_At

            # This is a bug in Find-Module where without parameters is the same
            # https://github.com/PowerShell/PowerShellGet/issues/563
            'Download CountTotal'                   = $Module[0].AdditionalMetadata.downloadCount #$_.AdditionalMetadata.downloadCount
            'Download CountLast'                    = $Module[0].AdditionalMetadata.versionDownloadCount #$_.AdditionalMetadata.versionDownloadCount
            'Releases Total'                        = $Module.Count
            "Releases CurrentYear ($Year)"          = $ModuleCurrentYear.Count
            "Releases LastYear ($LastYear)"         = $ModuleLastYear.Count
            "Releases PreviousYear ($PreviousYear)" = $ModulePreviousYear.Count
            'Last Updated'                          = $_.AdditionalMetadata.published
            'PS Gallery Url'                        = $PSGalleryURL
            #'Project Url' = $GitHubURL
            'Description'                           = $_.Description
        }
        if ($ReturnDetails) {
            $Object.'Modules' = $Module
        }

        $EndTime = Stop-TimeLog -Time $Time -Option OneLiner
        Write-Color -Text ' [Time to gather data: ', $EndTime, ']' -Color Cyan, DarkGray, Cyan
        [PSCustomObject] $Object
    }
    if ($UseCache) {
        $AllModulesDetails | Export-Clixml -LiteralPath $PathModuleDetails -Depth 5
        $GitHubModules | Export-Clixml -LiteralPath $PathGitHub -Depth 5
    }
    if ($UseMarkdown) {
        $Objects | Select-Object 'Name', 'Github Stars', 'Download CountTotal', 'Download CountLast', 'Releases Total', "Releases CurrentYear ($Year)", "Releases LastYear ($LastYear)" | ConvertTo-Markdown
    } else {
        $Objects
    }
}
function Get-PowerShellGitHubModules {
    [CmdletBinding()]
    param(
        [string] $Owner = 'EvotecIT'
    )
    $GitHubModules = Get-GitHubRepository -OwnerName $Owner
    #$GitHubModules | Sort-Object Stargazers_Count -Descending | Format-Table full_name, Name, Stargazers_Count, forks_count, Open_issues, license, Language, HTML_URL, Fork, Created_At, Updated_At, Pushed_At, Archived -AutoSize
    foreach ($_ in $GitHubModules) {
        [PSCustomObject] @{
            'Name'      = $_.Name
            'Full Name' = $_.full_name
            'Stars'     = $_.Stargazers_Count
            'Forks'     = $_.forks_count
            'Issues'    = $_.Open_issues
            'License'   = $_.license.Name
            'Language'  = $_.Language
            'Uri'       = $_.HTML_URL
            'Is Fork'   = $_.Fork
            'Created'   = $_.Created_At
            'Updated'   = $_.Updated_At
            'Archived'  = $_.Archived
        }
    }
}



Export-ModuleMember -Function @('Get-DevSummary', 'Get-PowerShellGitHubModules') -Alias @()