TM-PSGitHubGistManagement.psm1

using namespace System
using namespace System.IO
using namespace System.Management.Automation


function Get-GistScript {
<#
    .SYNOPSIS
    Downloads the contents of a specified GitHub Gist file.
 
    .PARAMETER GistUri
    The GitHub Gist URL.
 
    .PARAMETER FileName
    The file to select from within the Gist.
#>

    [CmdletBinding()]
    [OutputType([Void])]
    param (
        [Parameter(Mandatory)]
        [Validation.ValidateGistUriFormatAttribute()]
        [string]$GistUri,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$FileName
    )

    if ($GistUri -match '/(?<guid>[a-zA-Z0-9]{32})$') {
        $GistUri = "https://api.github.com/gists/$($matches.guid)"
    }
    $gist = Invoke-RestMethod $gistUri -ErrorAction Stop

    return $gist.Files.$FileName.Content
}


function Update-GistScript {
<#
    .SYNOPSIS
    Updates a local script file with the content of a GitHub Gist.
 
    .DESCRIPTION
    Compares the local script version number to the version in the remote Gist, and updates the local script
    if the Gist version is newer.
 
    .PARAMETER Path
    Local script file path
 
    .PARAMETER ScriptPath
    Full path of the local script file (Alias: FullName)
 
    .OUTPUTS
    Returns a boolean value indicating whether the local Gist script has been updated.
#>

    [CmdletBinding()]
    [OutputType([Boolean])]
    param (
        [Parameter(
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            ParameterSetName = 'FileInfo',
            Position = 0
        )]
        [Validation.ValidatePathExists('File')]
        [FileInfo]$Path,

        [Alias('FullName')]
        [Parameter(
            Mandatory,
            ValueFromPipeline,
            ParameterSetName = 'String',
            ValueFromPipelineByPropertyName,
            Position = 0
        )]
        [Validation.ValidatePathExists('File')]
        [string]$ScriptPath
    )

    begin {
        $projectUriRegEx = '\.PROJECTURI\r?\n\s*(?<ProjectUri>https?:.*)'
        $versionRegEx = '\.VERSION\r?\n\s*(?<Version>\d+\.\d+\.\d+)'
        $FilePath = switch ($PSCmdlet.ParameterSetName) {
            'String' { $ScriptPath }
            'FileInfo' { $Path.FullName }
        }
        $FileName = [Path]::GetFileName($FilePath)
    }

    process {
        [string]$BadVerNum = '0.0.0'
        [string]$ScriptContent = [IO.File]::ReadAllText($FilePath)
        [version]$currentVersion = if ($ScriptContent -match $versionRegEx) { $matches.Version } else { $BadVerNum }
        [string]$projectUri = [string]::Empty
        if ($ScriptContent -match $projectUriRegEx) { $projectUri = $matches.ProjectUri.Trim() }

        if ($projectUri -ne [string]::Empty) {
            try {
                $gistScript = Get-GistScript -GistUri $projectUri -FileName $FileName
                [version]$gistVersion = if ($gistScript -match $versionRegEx) { $matches.Version } else { $BadVerNum }

                if ($gistVersion -gt $currentVersion) {
                    Microsoft.PowerShell.Utility\Write-Host (
                        "Updating '$FileName' from v$currentVersion to v$gistVersion."
                    )
                    Set-Content -Path $FilePath -Value $gistScript
                    return $true
                } elseif ($gistVersion -eq $BadVerNum) {
                    Microsoft.PowerShell.Utility\Write-Warning (
                        "Unsuccessful gist version parse. v$gistVersion in '$FilePath' is invalid."
                    )
                } elseif ($gistVersion -lt $currentVersion) {
                    Microsoft.PowerShell.Utility\Write-Host (
                        "Local copy of '$FileName' is v$currentVersion which is newer than the gist (v$gistVersion). " +
                        "Don't forget to update the gist when you're done editing!"
                    )
                }
            } catch {
                Microsoft.PowerShell.Utility\Write-Warning "Failed to update gist. Error: $($_.Exception.Message)"
            }
        } else {
            Microsoft.PowerShell.Utility\Write-Warning (
                "'$FilePath' does not contain the required ProjectUri, or it is in an invalid format."
            )
        }
        return $false
    }
}


function Start-BackgroundGistScriptUpdate {
<#
    .SYNOPSIS
    Starts a background job to update the specified Gist script.
 
    .DESCRIPTION
    Initiates a background job to update a PowerShell script from the GitHub Gist URL mentioned in the PROJECTURI field
    of the ScriptFileInfo header.
 
    .PARAMETER LocalScriptPath
    Specifies the path to the local Gist script that needs to be updated.
 
    .NOTES
    This function requires internet access to reach the gist URL.
#>

    [CmdletBinding()]
    [OutputType([System.Management.Automation.Job])]
    param (
        [Parameter(Mandatory)]
        [Validation.ValidatePathExists('File')]
        [string]$LocalScriptPath
    )

    # Get the file item from the local script path
    $ScriptPath = Get-Item -Path $LocalScriptPath -ErrorAction Stop

    # Create parameters for the job or thread that will be started
    $UpdateGistJobParams = [hashtable]@{
        Name         = "Update Gist Script - $($ScriptPath.Name)"
        ArgumentList = @($ScriptPath.FullName)
        ScriptBlock  = {
            param ($LocalScriptPath)
            . ([ScriptBlock]::Create('using module TM-ValidationUtility'))
            Import-Module -Name TM-PSGitHubGistManagement
            # Call the Update-GistScript function if the local script path is valid
            Update-GistScript -ScriptPath $LocalScriptPath | Out-Null # Ignore the boolean return
        }
    }

    # Create the job or thread depending on the PowerShell edition
    $Job = if ($PSVersionTable.PSEdition -eq 'Desktop') {
        Start-Job @UpdateGistJobParams
    } else {
        Start-ThreadJob @UpdateGistJobParams
    }

    return $Job
}