public/Test-BuildEnvironment.ps1

function Test-BuildEnvironment {
    <#
    .SYNOPSIS
    Tests whether the .NET framework required by a build is available on the
    current machine.

    .DESCRIPTION
    Resolves the MSBuild and .NET runtime directories for the specified
    framework version and checks that every required directory exists.
    Returns $true if the environment is ready, $false otherwise.

    The framework to check is determined in order of precedence:
      1. The -Framework parameter, if supplied.
      2. The framework declared in -BuildFile, if supplied.
      3. The active psake build context (if one is on the stack).
      4. The psake default framework (4.7.2).

    This is useful in Pester specs to skip tests that require a framework
    toolchain that is not installed:

        It 'compiles with MSBuild 4.8' {
            if (-not (Test-BuildEnvironment -Framework '4.8')) {
                Set-ItResult -Skipped -Because 'Framework 4.8 not available'
            }
            # ... rest of test
        }

        It 'uses the project framework' {
            if (-not (Test-BuildEnvironment -BuildFile './psakefile.ps1')) {
                Set-ItResult -Skipped -Because 'Required framework not available'
            }
            # ... rest of test
        }

    .PARAMETER Framework
    The .NET framework version string to test (e.g. '4.8', '3.5', '4.7.2x64').
    Takes precedence over -BuildFile and the active context.

    .PARAMETER BuildFile
    Path to a psake build script. The framework declared in that file is read
    and tested. Ignored when -Framework is also supplied.

    .OUTPUTS
    [bool]

    .EXAMPLE
    Test-BuildEnvironment -Framework '4.8'

    Returns $true when MSBuild 17.0 or 16.0 and the v4.0.30319 runtime
    directory are both present.

    .EXAMPLE
    Test-BuildEnvironment -BuildFile './psakefile.ps1'

    Loads the build file, reads its Framework setting, and returns $true if
    that framework is installed.

    .EXAMPLE
    if (-not (Test-BuildEnvironment)) {
        Write-Warning "Build environment not ready for current framework"
    }

    Tests the framework configured in the active psake context.
    #>

    [CmdletBinding(DefaultParameterSetName = 'Framework')]
    [OutputType([bool])]
    param(
        [Parameter(Position = 0, ParameterSetName = 'Framework')]
        [string]$Framework,

        [Parameter(Mandatory = $true, ParameterSetName = 'BuildFile')]
        [ValidateNotNullOrEmpty()]
        [string]$BuildFile
    )

    # Not in ValidateScript because we want to allow the parameter to be
    # supplied but the file to be missing, and handle that gracefully with a
    # $false return value.
    if ( -not (Test-Path $BuildFile -PathType Leaf)) {
        Write-BuildOutput "Build file not found." "Error"
        return $false
    }

    # Resolve framework string from the most specific source available.
    if (-not $Framework) {
        if ($PSCmdlet.ParameterSetName -eq 'BuildFile') {
            Write-Verbose "Test-BuildEnvironment: reading framework from '$BuildFile'"
            try {
                $invokeInBuildFileScopeSplat = @{
                    BuildFile          = $BuildFile
                    Module             = $MyInvocation.MyCommand.Module
                    SkipSetEnvironment = $true
                    ScriptBlock        = {
                        param($CurrentContext)
                        return $CurrentContext.config.framework
                    }
                }
                $Framework = Invoke-InBuildFileScope @invokeInBuildFileScopeSplat
            } catch {
                Write-Verbose ("Could not load build file '{0}': {1}" -f $BuildFile, $_)
                return $false
            } finally {
                Restore-Environment
            }
        } elseif ($psake.Context.Count -gt 0) {
            $Framework = $psake.Context.Peek().config.framework
        } else {
            $Framework = $psake.ConfigDefault.Framework
        }
    }

    Write-Verbose "Test-BuildEnvironment: testing framework '$Framework'"

    # On non-Windows there is no .NET Framework toolchain to validate.
    if ((Test-Path Variable:\IsWindows) -and -not $IsWindows) {
        Write-Verbose "Non-Windows platform - framework check skipped"
        return $true
    }

    try {
        $dirs = Resolve-FrameworkDirectories -Framework $Framework
    } catch {
        Write-Verbose ("Framework '{0}' could not be resolved: {1}" -f $Framework, $_)
        return $false
    }

    $allExist = $true
    foreach ($directory in $dirs) {
        if (-not (Test-Path $directory -PathType Container)) {
            Write-Verbose ("Required directory not found: {0}" -f $directory)
            $allExist = $false
        }
    }

    return $allExist
}