Public/Invoke-XKCD.ps1

using namespace System.IO
using namespace System.Net.Http
using namespace System.Net.Http.Headers

function Invoke-XKCD {
    <#
        .SYNOPSIS
        Retrieves XKCD comics based on the specified parameters.

        .DESCRIPTION
        XKCD is a serial web comic created in 2005 by American author Randall Munroe.
        The comic's tagline describes it as "a web comic of romance, sarcasm, math,
        and language". Munroe states on the comic's website that the name of the
        comic is not an initialism but "just a word with no phonetic pronunciation".

        .PARAMETER All
        Retrieves all available XKCD comics.

        .PARAMETER Number
        Retrieves specific XKCD comic based on its identifier (ID).

        .PARAMETER Random
        Retrieves a random XKCD comic.

        .PARAMETER From
        Specifies the starting number of a range of XKCD comics to retrieve.
        Must be used with the -To parameter.

        .PARAMETER To
        Specifies the ending number of a range of XKCD comics to retrieve.
        Must be greater than the value of -From.

        .PARAMETER Last
        Retrieves the last N comics, where N is specified by this parameter.

        .PARAMETER Path
        Specifies the directory where downloaded XKCD comics will be saved.
        The default path is the current working directory.

        .PARAMETER Download
        Indicates that the retrieved XKCD comics should be downloaded to the
        specified -Path.

        .PARAMETER Force
        Forces overwriting of existing files during download operations, if they
        already exist in the specified path.

        .INPUTS
        None. You can't pipe objects to Invoke-XKCD.

        .OUTPUTS
        XKCD. The function returns a XKCD object with properties describing the comic.

        .EXAMPLE
        PS> Invoke-XKCD -Number 42

        Retrieves information about the XKCD comic #42.

        .EXAMPLE
        PS> Invoke-XKCD -Random

        Retrieves a random XKCD comic.

        .EXAMPLE
        PS> Invoke-XKCD -From 10 -To 20

        Retrieves XKCD comics numbered 10 through 20.

        .EXAMPLE
        PS> Invoke-XKCD -Last 1 -Download -Path "C:\Comics"

        Downloads the latest XKCD comic to the "C:\Comics" directory.

        .EXAMPLE
        PS> Invoke-XKCD -All -Download

        Downloads all XKCD comics to the current working directory.

        .LINK
        https://xkcd.com/
        https://en.wikipedia.org/wiki/Xkcd
    #>

    [Alias("xkcd")]
    [OutputType([XKCD])]
    [CmdletBinding(DefaultParameterSetName = "Last", SupportsShouldProcess, ConfirmImpact = "Low")]
    param(
        [Parameter(Mandatory, ParameterSetName = "All")]
        [switch] $All,

        [Parameter(Mandatory, ParameterSetName = "Number")]
        [int[]] $Number,

        [Parameter(Mandatory, ParameterSetName = "Random")]
        [switch] $Random,

        [ValidateRange(1, [int]::MaxValue)]
        [Parameter(Mandatory, ParameterSetName = "Range")]
        [int] $From,

        [ValidateScript({
            if ($_ -le $From) {
                Write-Error "The value of -To must be greater than -From" `
                    -Category InvalidArgument `
                    -ErrorAction Stop
            }

            return $true
        })]
        [Parameter(Mandatory, ParameterSetName = "Range")]
        [int] $To,

        [Parameter(ParameterSetName = "Last")]
        [int] $Last = 1,

        [string] $Path = $PWD.Path,

        [switch] $Download,

        [switch] $Force
    )

    begin {
        # The Info response contains the following data:
        #
        # ======================================================================
        # KEY TYPE NULLABLE REMARKS
        # ======================================================================
        # year int year of publication
        # month int month of publication
        # day int day of publication
        # num int running number of comic
        # title string title of website
        # safe_title string ASCII-friendly title
        # alt string alt text on image
        # link string X ?
        # img string DDL to comic
        # news string X ?
        # transcript string X ?
        # ======================================================================
        $Info = if (!$MyInvocation.BoundParameters.ContainsKey("Number") -or $null -eq $Number) {
            Invoke-RestMethod -Uri "https://xkcd.com/info.0.json"
        }

        [int[]] $Ids = switch ($PSCmdlet.ParameterSetName) {
            "All" {
                @(1..$Info.Num)
            }
            "Number" {
                $Number
            }
            "Random" {
                @([System.Random]::Shared.Next(1, $Info.Num + 1))
            }
            "Range" {
                @($From..$To)
            }
            "Last" {
                @(($Info.Num - $Last + 1)..$Info.Num)
            }
            default {
                1
            }
        }
    }
    process {
        for ([int] $i = 0; $i -lt $Ids.Count; $i++) {
            $Id = $Ids[$i]

            try {
                $Response = Invoke-RestMethod -Uri "https://xkcd.com/$Id/info.0.json"
                $FileExtension = $Response.Img.Split("/")[-1].Split(".")[-1]
                $FilePath = [Path]::Join($Path, "${Id}.${FileExtension}")

                if ($Download.IsPresent -and $PSCmdlet.ShouldProcess($Response.img, "Download $($FilePath)")) {
                    [int] $PercentComplete = [Math]::Round($i / $Ids.Count * 100, 0)

                    Write-Progress -Activity "Download XKCD ${Id}" `
                        -Status "${PercentComplete}%" `
                        -PercentComplete $PercentComplete

                    Write-Verbose "Downloading $Id to $Path"
                    Invoke-WebRequest -Uri $Response.img -OutFile $FilePath
                }

                Write-Output $([XKCD]::new(
                    $Id,
                    $Response.title,
                    $Response.alt,
                    $Response.year,
                    $Response.month,
                    $Response.day,
                    "https://xkcd.com/$Id/"
                ))
            } catch {
                Write-Error "A comic with ID=${Id} does not exist." `
                    -Category InvalidArgument `
                    -ErrorAction Continue
            }
        }
    }
}