Upgraded the Invoke-MSBuild to version 2.5.1

This commit is contained in:
MichaelGrafnetter 2017-04-23 16:56:26 +02:00
parent 2be3f5f1d0
commit c5df8d5875
2 changed files with 345 additions and 185 deletions

View File

@ -12,7 +12,10 @@
RootModule = 'Invoke-MsBuild.psm1'
# Version number of this module.
ModuleVersion = '2.2.0'
ModuleVersion = '2.5.1'
# Supported PSEditions
# CompatiblePSEditions = @()
# ID used to uniquely identify this module
GUID = '8ca20938-b92a-42a1-bf65-f644e16a8d9e'
@ -38,10 +41,10 @@ PowerShellVersion = '2.0'
# Minimum version of the Windows PowerShell host required by this module
# PowerShellHostVersion = ''
# Minimum version of Microsoft .NET Framework required by this module
# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# DotNetFrameworkVersion = ''
# Minimum version of the common language runtime (CLR) required by this module
# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# CLRVersion = ''
# Processor architecture (None, X86, Amd64) required by this module
@ -65,17 +68,17 @@ PowerShellVersion = '2.0'
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
# NestedModules = @()
# Functions to export from this module
FunctionsToExport = '*'
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = @('Invoke-MsBuild')
# Cmdlets to export from this module
CmdletsToExport = '*'
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = @()
# Variables to export from this module
VariablesToExport = '*'
VariablesToExport = @()
# Aliases to export from this module
AliasesToExport = '*'
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = @()
# DSC resources to export from this module
# DscResourcesToExport = @()
@ -104,38 +107,7 @@ PrivateData = @{
# IconUri = ''
# ReleaseNotes of this module
ReleaseNotes = '- Added LogVerbosityLevel parameter to adjust the verbosity MsBuild uses to write to the log file.
- Fixed bug that prevented us from finding msbuild.exe on some machines.
----------
Invoke-MsBuild v2 has the following breaking changes from v1:
- A hash table with several properties is returned instead of a simple $true/$false/$null value.
- The GetLogPath switch is gone and replaced with the WhatIf switch.
New features in v2 include:
- A build log file containing only build errors is created alongside the regular build log file.
- The errors build log file can be auto-launched on build failure.
- New switch has been added to show the build output in the calling scripts console window (does not work with some 3rd party consoles due to Start-Process cmdlet bug).
- A hash table containing the following properties is now returned:
+ BuildSucceeded = $true if the build passed, $false if the build failed, and $null if we are not sure.
+ BuildLogFilePath = The path to the builds log file.
+ BuildErrorsLogFilePath = The path to the builds error log file.
+ ItemToBuildFilePath = The item that MsBuild is ran against.
+ CommandUsedToBuild = The full command that is used to invoke MsBuild. This can be useful for inspecting what parameters are passed to MsBuild.exe.
+ Message = A message describing any problems that were encoutered by Invoke-MsBuild. This is typically an empty string unless something went wrong.
+ MsBuildProcess = The process that was used to execute MsBuild.exe.
Changes to make when updating from v1 to v2:
- To capture/display the build success result, you must change:
Invoke-MsBuild ...
to:
(Invoke-MsBuild ...).BuildSucceeded
- To get the path where the log file will be created, you must change:
Invoke-MsBuild ... -GetLogPath
to:
(Invoke-MsBuild ... -WhatIf).BuildLogFilePath'
ReleaseNotes = '- Fix to find the "Program Files" location correctly on 32 bit windows without throwing an error.'
} # End of PSData hashtable

View File

@ -29,6 +29,9 @@ function Invoke-MsBuild
Use the keyword "PathDirectory" to put the log files in the same directory as the .sln or project file being built.
Two log files are generated: one with the complete build log, and one that contains only errors from the build.
.PARAMETER LogVerbosity
If set, this will set the verbosity of the build log. Possible values are: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic].
.PARAMETER AutoLaunchBuildLogOnFailure
If set, this switch will cause the build log to automatically be launched into the default viewer if the build fails.
This log file contains all of the build output.
@ -65,41 +68,53 @@ function Invoke-MsBuild
NOTE: To avoid the build process from appearing to hang, PromptForInputBeforeClosing only has an effect with ShowBuildOutputInCurrentWindow when running
in the default "ConsoleHost" PowerShell console window, as we know it works properly with that console (it does not in other consoles like ISE, PowerGUI, etc.).
.PARAMETER MsBuildFilePath
By default this script will locate and use the latest version of MsBuild.exe on the machine.
If you have MsBuild.exe in a non-standard location, or want to force the use of an older MsBuild.exe version, you may pass in the file path of the MsBuild.exe to use.
.PARAMETER VisualStudioDeveloperCommandPromptFilePath
By default this script will locate and use the latest version of the Visual Studio Developer Command Prompt to run MsBuild.
If you installed Visual Studio in a non-standard location, or want to force the use of an older Visual Studio Command Prompt version, you may pass in the file path to
the Visual Studio Command Prompt to use. The filename is typically VsDevCmd.bat.
.PARAMETER PassThru
If set, this switch will cause the calling script not to wait until the build (launched in another process) completes before continuing execution.
Instead the build will be started in a new process and that process will immediately be returned, allowing the calling script to continue
execution while the build is performed, and also to inspect the process to see when it completes.
NOTE: This switch cannot be used with the AutoLaunchBuildLogOnFailure, AutoLaunchBuildErrorsLogOnFailure, KeepBuildLogOnSuccessfulBuilds, or PromptForInputBeforeClosing switches.
.PARAMETER LogVerbosity
If set, this will set the verbosity of the build log. Possible values are: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic].
.PARAMETER WhatIf
If set, the build will not actually be performed.
Instead it will just return the result object containing the file paths that would be created if the build is performed with the same parameters.
Instead it will just return the result hash table containing the file paths that would be created if the build is performed with the same parameters.
.OUTPUTS
When the -PassThru switch is provided, the process being used to run MsBuild.exe is returned.
When the -PassThru switch is not provided, a hash table with the following properties is returned:
BuildSucceeded = $true if the build passed, $false if the build failed, and $null if we are not sure.
BuildLogFilePath = The path to the build's log file.
BuildErrorsLogFilePath = The path to the build's error log file.
ItemToBuildFilePath = The item that MsBuild is ran against.
CommandUsedToBuild = The full command that is used to invoke MsBuild. This can be useful for inspecting what parameters are passed to MsBuild.exe.
ItemToBuildFilePath = The item that MsBuild ran against.
CommandUsedToBuild = The full command that was used to invoke MsBuild. This can be useful for inspecting what parameters are passed to MsBuild.exe.
Message = A message describing any problems that were encoutered by Invoke-MsBuild. This is typically an empty string unless something went wrong.
MsBuildProcess = The process that was used to execute MsBuild.exe.
BuildDuration = The amount of time the build took to complete, represented as a TimeSpan.
.EXAMPLE
$buildResult = Invoke-MsBuild -Path "C:\Some Folder\MySolution.sln"
if ($buildResult.BuildSucceeded -eq $true)
{ Write-Host "Build completed successfully." }
else if (!$buildResult.BuildSucceeded -eq $false)
{ Write-Host "Build failed. Check the build log file $($buildResult.BuildLogFilePath) for errors." }
else if ($buildResult.BuildSucceeded -eq $null)
{ Write-Host "Unsure if build passed or failed: $($buildResult.Message)" }
{
Write-Output ("Build completed successfully in {0:N1} seconds." -f $buildResult.BuildDuration.TotalSeconds)
}
elseif ($buildResult.BuildSucceeded -eq $false)
{
Write-Output ("Build failed after {0:N1} seconds. Check the build log file '$($buildResult.BuildLogFilePath)' for errors." -f $buildResult.BuildDuration.TotalSeconds)
}
elseif ($buildResult.BuildSucceeded -eq $null)
{
Write-Output "Unsure if build passed or failed: $($buildResult.Message)"
}
Perform the default MsBuild actions on the Visual Studio solution to build the projects in it, and returns a hash table containing the results.
The PowerShell script will halt execution until MsBuild completes.
@ -120,7 +135,7 @@ function Invoke-MsBuild
.EXAMPLE
if ((Invoke-MsBuild -Path $pathToSolution).BuildSucceeded -eq $true)
{
Write-Host "Build completed successfully."
Write-Output "Build completed successfully."
}
Perfom the build against the file specified at $pathToSolution and checks it for success in a single line.
@ -129,7 +144,12 @@ function Invoke-MsBuild
Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -MsBuildParameters "/target:Clean;Build" -ShowBuildOutputInNewWindow
Cleans then Builds the given C# project.
A window displaying the output from MsBuild will be shown so the user can view the progress of the build.
A window displaying the output from MsBuild will be shown so the user can view the progress of the build without it polluting their current terminal window.
.EXAMPLE
Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -ShowBuildOutputInCurrentWindow
Builds the given C# project and displays the output from MsBuild in the current terminal window.
.EXAMPLE
Invoke-MsBuild -Path "C:\MySolution.sln" -Params "/target:Clean;Build /property:Configuration=Release;Platform=x64;BuildInParallel=true /verbosity:Detailed /maxcpucount"
@ -168,14 +188,14 @@ function Invoke-MsBuild
.EXAMPLE
Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -WhatIf
Returns the result object containing the same property values that would be created if the build was ran with the same parameters.
Returns the result hash table containing the same property values that would be created if the build was ran with the same parameters.
The BuildSucceeded property will be $null since no build will actually be invoked.
This will display all of the returned object's properties and their values.
This will display all of the returned hash table's properties and their values.
.EXAMPLE
Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" > $null
Builds the given C# project, discarding the result object and not displaying its properties.
Builds the given C# project, discarding the result hash table and not displaying its properties.
.LINK
Project home: https://github.com/deadlydog/Invoke-MsBuild
@ -183,7 +203,7 @@ function Invoke-MsBuild
.NOTES
Name: Invoke-MsBuild
Author: Daniel Schroeder (originally based on the module at http://geekswithblogs.net/dwdii/archive/2011/05/27/part-2-automating-a-visual-studio-build-with-powershell.aspx)
Version: 2.2.0
Version: 2.5.1
#>
[CmdletBinding(DefaultParameterSetName="Wait")]
param
@ -204,9 +224,9 @@ function Invoke-MsBuild
[Alias("LogDirectory","L")]
[string] $BuildLogDirectoryPath = $env:Temp,
[parameter(Mandatory=$false)]
[parameter(Mandatory=$false)]
[ValidateSet('q','quiet','m','minimal','n','normal','d','detailed','diag','diagnostic')]
[string] $LogVerbosityLevel = 'normal',
[string] $LogVerbosityLevel = 'normal',
[parameter(Mandatory=$false,ParameterSetName="Wait")]
[ValidateNotNullOrEmpty()]
@ -230,6 +250,14 @@ function Invoke-MsBuild
[parameter(Mandatory=$false,ParameterSetName="Wait")]
[switch] $PromptForInputBeforeClosing,
[parameter(Mandatory=$false)]
[ValidateScript({Test-Path $_})]
[string] $MsBuildFilePath,
[parameter(Mandatory=$false)]
[ValidateScript({Test-Path $_})]
[string] $VisualStudioDeveloperCommandPromptFilePath,
[parameter(Mandatory=$false,ParameterSetName="PassThru")]
[switch] $PassThru,
@ -247,14 +275,14 @@ function Invoke-MsBuild
Set-StrictMode -Version Latest
# Ignore cultural differences. This is so that when reading version numbers it does not change the '.' to ',' when the OS's language/culture is not English.
[CultureInfo]::CurrentCulture = [CultureInfo]::InvariantCulture
[System.Threading.Thread]::CurrentThread.CurrentCulture = [CultureInfo]::InvariantCulture
# Default the ParameterSet variables that may not have been set depending on which parameter set is being used. This is required for PowerShell v2.0 compatibility.
if (!(Test-Path Variable:Private:AutoLaunchBuildLogOnFailure)) { $AutoLaunchBuildLogOnFailure = $false }
# Default the ParameterSet variables that may not have been set depending on which parameter set is being used. This is required for PowerShell v2.0 compatibility.
if (!(Test-Path Variable:Private:AutoLaunchBuildLogOnFailure)) { $AutoLaunchBuildLogOnFailure = $false }
if (!(Test-Path Variable:Private:AutoLaunchBuildLogOnFailure)) { $AutoLaunchBuildErrorsLogOnFailure = $false }
if (!(Test-Path Variable:Private:KeepBuildLogOnSuccessfulBuilds)) { $KeepBuildLogOnSuccessfulBuilds = $false }
if (!(Test-Path Variable:Private:KeepBuildLogOnSuccessfulBuilds)) { $KeepBuildLogOnSuccessfulBuilds = $false }
if (!(Test-Path Variable:Private:PromptForInputBeforeClosing)) { $PromptForInputBeforeClosing = $false }
if (!(Test-Path Variable:Private:PassThru)) { $PassThru = $false }
if (!(Test-Path Variable:Private:PassThru)) { $PassThru = $false }
# If the keyword was supplied, place the log in the same folder as the solution/project being built.
if ($BuildLogDirectoryPath.Equals("PathDirectory", [System.StringComparison]::InvariantCultureIgnoreCase))
@ -271,7 +299,7 @@ function Invoke-MsBuild
$buildErrorsLogFilePath = (Join-Path -Path $BuildLogDirectoryPath -ChildPath $solutionFileName) + ".msbulid.errors.log"
$windowStyleOfNewWindow = if ($ShowBuildOutputInNewWindow) { "Normal" } else { "Hidden" }
# Build our object that will be returned.
# Build our hash table that will be returned.
$result = @{}
$result.BuildSucceeded = $null
$result.BuildLogFilePath = $buildLogFilePath
@ -280,19 +308,20 @@ function Invoke-MsBuild
$result.CommandUsedToBuild = [string]::Empty
$result.Message = [string]::Empty
$result.MsBuildProcess = $null
$result.BuildDuration = [TimeSpan]::Zero
# Try and build the solution.
try
{
# Get the verbosity to use for the MsBuild log file.
$verbosityLevel = switch ($LogVerbosityLevel) {
{ ($_ -eq "q") -or ($_ -eq "quiet") -or `
($_ -eq "m") -or ($_ -eq "minimal") -or `
($_ -eq "n") -or ($_ -eq "normal") -or `
($_ -eq "d") -or ($_ -eq "detailed") -or `
($_ -eq "diag") -or ($_ -eq "diagnostic") } { ";verbosity=$_" ;break }
default { "" }
}
$verbosityLevel = switch ($LogVerbosityLevel) {
{ ($_ -eq "q") -or ($_ -eq "quiet") -or `
($_ -eq "m") -or ($_ -eq "minimal") -or `
($_ -eq "n") -or ($_ -eq "normal") -or `
($_ -eq "d") -or ($_ -eq "detailed") -or `
($_ -eq "diag") -or ($_ -eq "diagnostic") } { ";verbosity=$_" ;break }
default { "" }
}
# Build the arguments to pass to MsBuild.
$buildArguments = """$Path"" $MsBuildParameters /fileLoggerParameters:LogFile=""$buildLogFilePath""$verbosityLevel /fileLoggerParameters1:LogFile=""$buildErrorsLogFilePath"";errorsonly"
@ -304,13 +333,26 @@ function Invoke-MsBuild
}
# Get the path to the MsBuild executable.
$msBuildPath = Get-MsBuildPath -Use32BitMsBuild:$Use32BitMsBuild
$msBuildPath = $MsBuildFilePath
[bool] $msBuildPathWasNotProvided = [string]::IsNullOrEmpty($msBuildPath)
if ($msBuildPathWasNotProvided)
{
$msBuildPath = Get-LatestMsBuildPath -Use32BitMsBuild:$Use32BitMsBuild
}
# Get the path to the Visual Studio Developer Command Prompt file.
$vsCommandPromptPath = $VisualStudioDeveloperCommandPromptFilePath
[bool] $vsCommandPromptPathWasNotProvided = [string]::IsNullOrEmpty($vsCommandPromptPath)
if ($vsCommandPromptPathWasNotProvided)
{
$vsCommandPromptPath = Get-LatestVisualStudioCommandPromptPath
}
# If a VS Command Prompt was found, call MsBuild from that since it sets environmental variables that may be needed to build some projects types (e.g. XNA).
$vsCommandPromptPath = Get-VisualStudioCommandPromptPath
if ($vsCommandPromptPath -ne $null)
[bool] $vsCommandPromptPathWasFound = ![string]::IsNullOrEmpty($vsCommandPromptPath)
if ($vsCommandPromptPathWasFound)
{
$cmdArgumentsToRunMsBuild = "/k "" ""$vsCommandPromptPath"" & msbuild "
$cmdArgumentsToRunMsBuild = "/k "" ""$vsCommandPromptPath"" & ""$msBuildPath"" "
}
# Else the VS Command Prompt was not found, so just build using MsBuild directly.
else
@ -358,16 +400,20 @@ function Invoke-MsBuild
}
else
{
if ($ShowBuildOutputInCurrentWindow)
$performBuildScriptBlock =
{
$result.MsBuildProcess = Start-Process cmd.exe -ArgumentList $cmdArgumentsToRunMsBuild -NoNewWindow -PassThru
}
else
{
$result.MsBuildProcess = Start-Process cmd.exe -ArgumentList $cmdArgumentsToRunMsBuild -WindowStyle $windowStyleOfNewWindow -PassThru
if ($ShowBuildOutputInCurrentWindow)
{
$result.MsBuildProcess = Start-Process cmd.exe -ArgumentList $cmdArgumentsToRunMsBuild -NoNewWindow -Wait -PassThru
}
else
{
$result.MsBuildProcess = Start-Process cmd.exe -ArgumentList $cmdArgumentsToRunMsBuild -WindowStyle $windowStyleOfNewWindow -Wait -PassThru
}
}
Wait-Process -InputObject $result.MsBuildProcess
# Perform the build and record how long it takes.
$result.BuildDuration = (Measure-Command -Expression $performBuildScriptBlock)
}
}
# If the build crashed, return that the build didn't succeed.
@ -381,15 +427,15 @@ function Invoke-MsBuild
return $result
}
# If we can't find the build's log file in order to inspect it, write a warning and return null.
if (!(Test-Path -Path $buildLogFilePath))
{
# If we can't find the build's log file in order to inspect it, write a warning and return null.
if (!(Test-Path -Path $buildLogFilePath))
{
$result.BuildSucceeded = $null
$result.Message = "Cannot find the build log file at '$buildLogFilePath', so unable to determine if build succeeded or not."
Write-Warning ($result.Message)
return $result
}
Write-Warning ($result.Message)
return $result
}
# Get if the build failed or not by looking at the log file.
$buildSucceeded = (((Select-String -Path $buildLogFilePath -Pattern "Build FAILED." -SimpleMatch) -eq $null) -and $result.MsBuildProcess.ExitCode -eq 0)
@ -445,7 +491,7 @@ function Open-BuildLogFileWithDefaultProgram([string]$FilePathToOpen, [ref]$Resu
}
}
function Get-VisualStudioCommandPromptPath
function Get-LatestVisualStudioCommandPromptPath
{
<#
.SYNOPSIS
@ -454,39 +500,76 @@ function Get-VisualStudioCommandPromptPath
.DESCRIPTION
Gets the file path to the latest Visual Studio Command Prompt. Returns $null if a path is not found.
#>
[string] $vsCommandPromptPath = Get-VisualStudioCommandPromptPathForVisualStudio2017AndNewer
# We have to use the vswhere.exe tool to locate Visual Studio 2017
$vsWhere = Join-Path $PSScriptRoot '..\..\Tools\vswhere.exe'
$vs2017Instance = & $vsWhere -nologo -format value -property installationPath -latest -requires 'Microsoft.VisualStudio.Component.VC.CLI.Support'
$vs2017CommandPromptPath = $vs2017Instance + '\Common7\Tools\VsDevCmd.bat'
# If VS 2017 or newer VS Command Prompt was not found, check for older versions of VS Command Prompt.
if ([string]::IsNullOrEmpty($vsCommandPromptPath))
{
$vsCommandPromptPath = Get-VisualStudioCommandPromptPathForVisualStudio2015AndPrior
}
return $vsCommandPromptPath
}
function Get-VisualStudioCommandPromptPathForVisualStudio2017AndNewer
{
# Later we can probably make use of the VSSetup.PowerShell module to find the MsBuild.exe: https://github.com/Microsoft/vssetup.powershell
# Or perhaps the VsWhere.exe: https://github.com/Microsoft/vswhere
# But for now, to keep this script PowerShell 2.0 compatible and not rely on external executables, let's look for it ourselve in known locations.
# Example of known locations:
# "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\Tools\VsDevCmd.bat"
[string] $visualStudioDirectoryPath = Get-CommonVisualStudioDirectoryPath
[bool] $visualStudioDirectoryPathDoesNotExist = [string]::IsNullOrEmpty($visualStudioDirectoryPath)
if ($visualStudioDirectoryPathDoesNotExist)
{
return $null
}
# First search for the VS Command Prompt in the expected locations (faster).
$expectedVsCommandPromptPathWithWildcards = "$visualStudioDirectoryPath\*\*\Common7\Tools\VsDevCmd.bat"
$vsCommandPromptPathObjects = Get-Item -Path $expectedVsCommandPromptPathWithWildcards
[bool] $vsCommandPromptWasNotFound = ($vsCommandPromptPathObjects -eq $null) -or ($vsCommandPromptPathObjects.Length -eq 0)
if ($vsCommandPromptWasNotFound)
{
# Recurisvely search the entire Microsoft Visual Studio directory for the VS Command Prompt (slower, but will still work if MS changes folder structure).
Write-Verbose "The Visual Studio Command Prompt was not found at an expected location. Searching more locations, but this will be a little slow."
$vsCommandPromptPathObjects = Get-ChildItem -Path $visualStudioDirectoryPath -Recurse | Where-Object { $_.Name -ieq 'VsDevCmd.bat' }
}
$vsCommandPromptPathObjectsSortedWithNewestVersionsFirst = $vsCommandPromptPathObjects | Sort-Object -Property FullName -Descending
$newestVsCommandPromptPath = $vsCommandPromptPathObjectsSortedWithNewestVersionsFirst | Select-Object -ExpandProperty FullName -First 1
return $newestVsCommandPromptPath
}
function Get-VisualStudioCommandPromptPathForVisualStudio2015AndPrior
{
# Get some environmental paths.
$vs2015CommandPromptPath = $env:VS140COMNTOOLS + 'VsDevCmd.bat'
$vs2013CommandPromptPath = $env:VS120COMNTOOLS + 'VsDevCmd.bat'
$vs2012CommandPromptPath = $env:VS110COMNTOOLS + 'VsDevCmd.bat'
$vs2010CommandPromptPath = $env:VS100COMNTOOLS + 'vcvarsall.bat'
$vsCommandPromptPaths = @($vs2017CommandPromptPath, $vs2015CommandPromptPath, $vs2013CommandPromptPath, $vs2012CommandPromptPath, $vs2010CommandPromptPath)
$potentialVsCommandPromptPaths = @($vs2015CommandPromptPath, $vs2013CommandPromptPath, $vs2012CommandPromptPath, $vs2010CommandPromptPath)
# Store the VS Command Prompt to do the build in, if one exists.
$vsCommandPromptPath = $null
foreach ($path in $vsCommandPromptPaths)
$newestVsCommandPromptPath = $null
foreach ($path in $potentialVsCommandPromptPaths)
{
try
[bool] $pathExists = (![string]::IsNullOrEmpty($path)) -and (Test-Path -Path $path -PathType Leaf)
if ($pathExists)
{
if (Test-Path -Path $path)
{
$vsCommandPromptPath = $path
break
}
$newestVsCommandPromptPath = $path
break
}
catch {}
}
# Return the path to the VS Command Prompt if it was found.
return $vsCommandPromptPath
return $newestVsCommandPromptPath
}
function Get-MsBuildPath([switch] $Use32BitMsBuild)
function Get-LatestMsBuildPath([switch] $Use32BitMsBuild)
{
<#
.SYNOPSIS
@ -496,6 +579,73 @@ function Get-MsBuildPath([switch] $Use32BitMsBuild)
Gets the path to the latest version of MsBuild.exe. Throws an exception if MsBuild.exe is not found.
#>
[string] $msBuildPath = $null
$msBuildPath = Get-MsBuildPathForVisualStudio2017AndNewer -Use32BitMsBuild $Use32BitMsBuild
# If VS 2017 or newer MsBuild.exe was not found, check for older versions of MsBuild.
if ([string]::IsNullOrEmpty($msBuildPath))
{
$msBuildPath = Get-MsBuildPathForVisualStudio2015AndPrior -Use32BitMsBuild $Use32BitMsBuild
}
[bool] $msBuildPathWasNotFound = [string]::IsNullOrEmpty($msBuildPath)
if ($msBuildPathWasNotFound)
{
throw 'Could not determine where to find MsBuild.exe.'
}
[bool] $msBuildExistsAtThePathFound = (Test-Path $msBuildPath -PathType Leaf)
if(!$msBuildExistsAtThePathFound)
{
throw "MsBuild.exe does not exist at the expected path, '$msBuildPath'."
}
return $msBuildPath
}
function Get-MsBuildPathForVisualStudio2017AndNewer([switch] $Use32BitMsBuild)
{
# Later we can probably make use of the VSSetup.PowerShell module to find the MsBuild.exe: https://github.com/Microsoft/vssetup.powershell
# Or perhaps the VsWhere.exe: https://github.com/Microsoft/vswhere
# But for now, to keep this script PowerShell 2.0 compatible and not rely on external executables, let's look for it ourselve in known locations.
# Example of known locations:
# "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\MSBuild.exe" - 32 bit
# "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\amd64\MSBuild.exe" - 64 bit
[string] $visualStudioDirectoryPath = Get-CommonVisualStudioDirectoryPath
[bool] $visualStudioDirectoryPathDoesNotExist = [string]::IsNullOrEmpty($visualStudioDirectoryPath)
if ($visualStudioDirectoryPathDoesNotExist)
{
return $null
}
# First search for MsBuild in the expected 32 and 64 bit locations (faster).
$expected32bitPathWithWildcards = "$visualStudioDirectoryPath\*\*\MsBuild\*\Bin\MsBuild.exe"
$expected64bitPathWithWildcards = "$visualStudioDirectoryPath\*\*\MsBuild\*\Bin\amd64\MsBuild.exe"
$msBuildPathObjects = Get-Item -Path $expected32bitPathWithWildcards, $expected64bitPathWithWildcards
[bool] $msBuildWasNotFound = ($msBuildPathObjects -eq $null) -or ($msBuildPathObjects.Length -eq 0)
if ($msBuildWasNotFound)
{
# Recurisvely search the entire Microsoft Visual Studio directory for MsBuild (slower, but will still work if MS changes folder structure).
Write-Verbose "MsBuild.exe was not found at an expected location. Searching more locations, but this will be a little slow."
$msBuildPathObjects = Get-ChildItem -Path $visualStudioDirectoryPath -Recurse | Where-Object { $_.Name -ieq 'MsBuild.exe' }
}
$msBuildPathObjectsSortedWithNewestVersionsFirst = $msBuildPathObjects | Sort-Object -Property FullName -Descending
$newest32BitMsBuildPath = $msBuildPathObjectsSortedWithNewestVersionsFirst | Where-Object { $_.Directory.Name -ine 'amd64' } | Select-Object -ExpandProperty FullName -First 1
$newest64BitMsBuildPath = $msBuildPathObjectsSortedWithNewestVersionsFirst | Where-Object { $_.Directory.Name -ieq 'amd64' } | Select-Object -ExpandProperty FullName -First 1
if ($Use32BitMsBuild)
{
return $newest32BitMsBuildPath
}
return $newest64BitMsBuildPath
}
function Get-MsBuildPathForVisualStudio2015AndPrior([switch] $Use32BitMsBuild)
{
$registryPathToMsBuildToolsVersions = 'HKLM:\SOFTWARE\Microsoft\MSBuild\ToolsVersions\'
if ($Use32BitMsBuild)
{
@ -514,22 +664,60 @@ function Get-MsBuildPath([switch] $Use32BitMsBuild)
$largestMsBuildToolsVersion = ($msBuildToolsVersions.GetEnumerator() | Sort-Object -Descending -Property Name | Select-Object -First 1).Value
$registryPathToMsBuildToolsLatestVersion = Join-Path -Path $registryPathToMsBuildToolsVersions -ChildPath ("{0:n1}" -f $largestMsBuildToolsVersion)
$msBuildToolsVersionsKeyToUse = Get-Item -Path $registryPathToMsBuildToolsLatestVersion
$msBuildDirectoryPath = $msBuildToolsVersionsKeyToUse | Get-ItemProperty -Name 'MSBuildToolsPath' | Select -ExpandProperty 'MSBuildToolsPath'
$msBuildDirectoryPath = $msBuildToolsVersionsKeyToUse | Get-ItemProperty -Name 'MSBuildToolsPath' | Select-Object -ExpandProperty 'MSBuildToolsPath'
if(!$msBuildDirectoryPath)
{
throw 'The registry on this system does not appear to contain the path to the MsBuild.exe directory.'
return $null
}
# Get the path to the MsBuild executable.
$msBuildPath = (Join-Path -Path $msBuildDirectoryPath -ChildPath 'msbuild.exe')
if(!(Test-Path $msBuildPath -PathType Leaf))
{
throw "MsBuild.exe was not found on this system at the path specified in the registry, '$msBuildPath'."
}
# Build the expected path to the MsBuild executable.
$msBuildPath = (Join-Path -Path $msBuildDirectoryPath -ChildPath 'MsBuild.exe')
return $msBuildPath
}
function Get-CommonVisualStudioDirectoryPath
{
[string] $programFilesDirectory = $null
try
{
$programFilesDirectory = Get-Item 'Env:\ProgramFiles(x86)' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Value
}
catch
{ }
if ([string]::IsNullOrEmpty($programFilesDirectory))
{
$programFilesDirectory = 'C:\Program Files (x86)'
}
# If we're on a 32-bit machine, we need to go straight after the "Program Files" directory.
if (!(Test-Path -Path $programFilesDirectory -PathType Container))
{
try
{
$programFilesDirectory = Get-Item 'Env:\ProgramFiles' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Value
}
catch
{
$programFilesDirectory = $null
}
if ([string]::IsNullOrEmpty($programFilesDirectory))
{
$programFilesDirectory = 'C:\Program Files'
}
}
[string] $visualStudioDirectoryPath = Join-Path -Path $programFilesDirectory -ChildPath 'Microsoft Visual Studio'
[bool] $visualStudioDirectoryPathExists = (Test-Path -Path $visualStudioDirectoryPath -PathType Container)
if (!$visualStudioDirectoryPathExists)
{
return $null
}
return $visualStudioDirectoryPath
}
Export-ModuleMember -Function Invoke-MsBuild