functions/Invoke-PSQuickQuiz.ps1

Function Invoke-PSQuickQuiz {

    [CmdletBinding(DefaultParameterSetName = 'all')]
    Param(
        [Parameter(ParameterSetName = 'single')]
        [ValidateScript( { Get-Module $_ -list })]
        #You can specify a single module for testing. The default is all modules.
        [String]$ModuleName,
        [Parameter(ParameterSetName = 'all')]
        #Enter a comma separated list of module names to ignore. You can use wildcards.
        [string[]]$Exclude,
        #This is used to indicate a continuing test. You should never need to use this parameter.
        [Switch]$NextQuestion,
        [Parameter(HelpMessage = 'Enter a path and filename for a quiz transcript.')]
        [ValidateScript( {
                $parent = Split-Path $_
                if (Test-Path $parent) {
                    return $True
                }
                else {
                    Throw "Failed to find $parent for your transcript."
                    return $false
                }
            })]
        [alias('transcript')]
        [String]$Path
    )

    If ($Path -AND (-not $NextQuestion)) {
        #first time through so initialize a new transcript file.
        $header = @"
PowerShell Quiz - $(Get-Date)
-------------------------------------
PowerShell version: $($PSVersionTable.PSVersion)
User : $($env:USERDOMAIN)\$($env:username)
Quiz parameters :
 
$(($PSBoundParameters | Out-String).trim())
-------------------------------------
"@

        #create a new transcript file
        Set-Content -Value $header -Path $Path

    }
    Clear-Host
    Write-Verbose "Starting $($MyInvocation.MyCommand)"
    Write-Verbose 'PSBoundParameters:'
    Write-Verbose $($PSBoundParameters | Out-String).Trim()

    if (-Not $NextQuestion) {
        Write-Verbose 'First question setup'
        #initialize some variables to keep track of correct answers
        $global:QuestionCount = 0
        $global:CorrectCount = 0
        $global:CommandCache = @()

        if ($ModuleName) {
            $Status = "Getting commands from module $modulename."
            Write-Verbose $Status
            Write-Progress -Activity $MyInvocation.MyCommand -Status $status -CurrentOperation 'Please wait...'
            $global:CommandCache = Get-Command -CommandType Cmdlet, Function -Module $ModuleName

        }
        else {
            $status = 'Getting commands from all available modules.'

            if ($exclude) {
                $status += " Excluding these modules: $($exclude -join ',') "
                #define a separate filter because this causes a problem in PowerShell v6 if $exclude is not specified
                $filter = { $_.Source -AND $_.source -notmatch $($exclude -join '|') }
            }
            else {
                $filter = { $_.Source }
            }
            Write-Verbose $status
            Write-Progress -Activity $MyInvocation.MyCommand -Status $status -CurrentOperation 'Please wait...'
            #get cmdlets and function that have a defined source which should be a module or snapin
            $global:CommandCache = (Get-Command -CommandType Cmdlet, Function).Where($filter)
        }
        Write-Progress -Activity $MyInvocation.MyCommand -Completed
        Write-Verbose "Added $(($global:commandCache).count) commands to the command cache"
        if ((($global:commandCache).count) -eq 0) {
            Write-Warning 'Failed to find commands in any matching modules.'
            #bail out
            Return
        }
    }
    else {
        Write-Verbose 'Continuing the quiz'
        Write-Verbose "Current question count: $($global:QuestionCount)"
        Write-Verbose "Current correct count: $($global:CorrectCount)"
        Write-Verbose "Current command cache: $(($global:commandCache).count)"
    }

    #select a random command with a legitimate synopsis
    Write-Verbose 'Selecting a random command'
    do {
        $cmd = $global:CommandCache | Get-Random
        $synopsis = ($cmd | Get-Help).synopsis

    } until ($synopsis -notmatch '(This cmdlet is not supported)|\[|(Fill in the Synopsis)' -AND $synopsis -match '\w{4,}')

    #get other noun related commands
    [object[]]$commands = @($cmd)
    $commands += Get-Command -Noun $cmd.noun | Where-Object { $_.name -ne $cmd.name } | Select-Object -First 4

    #get additional random commands if there are not enough noun-related
    if ($commands.count -lt 5) {
        Write-Verbose 'Getting supplemental commands for answers'
        While ($commands.count -lt 5) {
            $add = Get-Command -CommandType Cmdlet | Get-Random |
            Where-Object { $commands.name -NotContains $_.name }
            $commands += $add
        }
    }
    #randomize
    $commands = $commands | Get-Random -Count $commands.count

    $Title = 'PowerShell Pop Quiz'
    $Cue = @"
 
Given this short cmdlet description:
 
$synopsis
 
What command would you use?
 
 
"@


    for ($i = 1; $i -lt $commands.count + 1; $i++) {

        $Cue += " [$i]`t$($commands[$i-1].Name)`n"
    }

    Write-Host $Title -ForegroundColor Cyan
    Write-Host $Cue -ForegroundColor Yellow
    If ($Path) {
        Add-Content -Value $cue -Path $Path
    }

    Do {
        try {
            [ValidateRange(1, 5)][int32]$r = Read-Host -Prompt 'Select a menu choice [1-5]' -ErrorAction stop
            Write-Verbose "You entered $r"
        }
        Catch {
            #ignore the error
            Write-Warning $_.exception.message
            $r = 0
        }
    } Until ($r -ge 1 -AND $r -le 5)

    #increment the question counter
    $global:QuestionCount++
    if ($commands[$r - 1].name -eq $cmd.Name) {
        $global:CorrectCount++
        Write-Host "`nYou are Correct!!" -ForegroundColor green
        If ($Path) {
            Add-Content -Value "$($cmd.name) is correct!" -Path $Path
        }
    }
    else {
        $msg = "`nThe correct answer is $($cmd.name)"
        Write-Host $msg -ForegroundColor Red
        if ($Path) {
            Add-Content -Value $msg -Path $Path
        }
    }

    [String]$s = Read-Host "`nDo you want another question? Press any key to continue or Q to quit"
    if ($s -match '^q') {

        $score = Get-GPA -Correct $global:CorrectCount -Possible $global:QuestionCount
        $result = "`nYou scored {0} correct out of {1} for a GPA of {2}. Your grade is {3}." -f $global:CorrectCount, $global:QuestionCount, $score.gpa, $score.Grade
        #colorize output based on gpa
        if ($score.Grade -match 'A') {
            $fg = 'green'
        }
        elseif ($score.grade -match 'B|C') {
            $fg = 'yellow'
        }
        else {
            $fg = 'red'
        }
        Write-Host $result -ForegroundColor $fg
        if ($path) {
            Add-Content -Value $result -Path $Path
            Add-Content -Value "`nEnding PowerShell Quiz $(Get-Date)" -Path $Path
            Write-Host "`nSee $path for a transcript of this quiz." -ForegroundColor Green
        }
        Remove-Variable CorrectCount, QuestionCount, CommandCache -Scope global
    }
    else {
        if (-Not ($PSBoundParameters.containsKey('NextQuestion'))) {
            Write-Verbose 'Flagging for next question'
            $PSBoundParameters.add('NextQuestion', $True)
        }
        Write-Verbose 'Invoking quiz'
        #Write-Verbose ($MyInvocation.MyCommand | Out-String)
        &$($MyInvocation.MyCommand) @PSBoundParameters
    }
}