Resolve-MSBuild.ps1


<#PSScriptInfo
.VERSION 1.0.0
.AUTHOR Roman Kuzmin
.COPYRIGHT (c) Roman Kuzmin
.TAGS Invoke-Build, MSBuild
.GUID 53c01926-4fc5-4cbd-aa46-32e415b2373b
.LICENSEURI http://www.apache.org/licenses/LICENSE-2.0
.PROJECTURI https://github.com/nightroman/Invoke-Build
#>


<#
.Synopsis
    Finds the specified or latest version of MSBuild.
 
.Description
    For MSBuild 15.0+ the command uses VSSetup module if it is installed, see
    PSGallery. If it is not installed then some typical locations are checked.
    Thus, VSSetup module is required for not standard installations.
 
    For MSBuild 14.0 and older the information is taken from the registry.
 
.Parameter Version
        Specifies the required MSBuild version. If it is omitted, empty, or *
        then the command finds and returns the latest installed version path.
 
.Outputs
    The full path to MSBuild.exe
 
.Example
    Resolve-MSBuild 15.0
    Gets location of MSBuild installed with Visual Studio 2017.
 
.Link
    https://www.powershellgallery.com/packages/VSSetup
#>


[CmdletBinding()]
param(
    [string]$Version
)

function Get-MSBuild15VSSetup {
    if (Get-Module VSSetup -ListAvailable) {
        Import-Module VSSetup
        $vs = @(Get-VSSetupInstance | Select-VSSetupInstance -Version 15.0 -Require Microsoft.Component.MSBuild)
        if ($vs) {
            Join-Path ($vs[0].InstallationPath) MSBuild\15.0\Bin\MSBuild.exe
        }
    }
}

function Get-MSBuild15Guess {
    if (!($root = ${env:ProgramFiles(x86)})) {$root = $env:ProgramFiles}
    if (Test-Path -LiteralPath "$root\Microsoft Visual Studio\2017") {
        $rp = @(Resolve-Path "$root\Microsoft Visual Studio\2017\*\MSBuild\15.0\Bin\MSBuild.exe" -ErrorAction 0)
        if ($rp) {
            $rp[-1].ProviderPath
        }
    }
}

function Get-MSBuild15 {
    if ($path = Get-MSBuild15VSSetup) {
        $path
    }
    else {
        Get-MSBuild15Guess
    }
}

function Get-MSBuildOldLatest {
    $rp = @(Get-ChildItem HKLM:\SOFTWARE\Microsoft\MSBuild\ToolsVersions | Sort-Object {[Version]$_.PSChildName})
    if ($rp) {
        Join-Path ($rp[-1].GetValue('MSBuildToolsPath')) MSBuild.exe
    }
}

function Get-MSBuildOldVersion($Version) {
    $rp = [Microsoft.Win32.Registry]::GetValue("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSBuild\ToolsVersions\$Version", 'MSBuildToolsPath', '')
    if ($rp) {
        Join-Path $rp MSBuild.exe
    }
}

$ErrorActionPreference = 1
try {
    $v15 = [Version]'15.0'
    $vMax = [Version]'9999.0'
    if (!$Version) {$Version = '*'}
    $vRequired = if ($Version -eq '*') {$vMax} else {[Version]$Version}

    if ($vRequired -eq $v15) {
        if ($path = Get-MSBuild15) {
            return $path
        }
    }
    elseif ($vRequired -lt $v15) {
        if ($path = Get-MSBuildOldVersion $Version) {
            return $path
        }
    }
    elseif ($vRequired -eq $vMax) {
        if ($path = Get-MSBuild15) {
            return $path
        }
        if ($path = Get-MSBuildOldLatest) {
            return $path
        }
    }

    throw 'The specified version is not found.'
}
catch {
    Write-Error "Cannot resolve MSBuild $Version : $_" -ErrorAction 1
}