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' RootModule = 'Invoke-MsBuild.psm1'
# Version number of this module. # Version number of this module.
ModuleVersion = '2.2.0' ModuleVersion = '2.5.1'
# Supported PSEditions
# CompatiblePSEditions = @()
# ID used to uniquely identify this module # ID used to uniquely identify this module
GUID = '8ca20938-b92a-42a1-bf65-f644e16a8d9e' GUID = '8ca20938-b92a-42a1-bf65-f644e16a8d9e'
@ -38,10 +41,10 @@ PowerShellVersion = '2.0'
# Minimum version of the Windows PowerShell host required by this module # Minimum version of the Windows PowerShell host required by this module
# PowerShellHostVersion = '' # 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 = '' # 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 = '' # CLRVersion = ''
# Processor architecture (None, X86, Amd64) required by this module # 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 # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
# NestedModules = @() # NestedModules = @()
# Functions to export from this module # 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 = '*' FunctionsToExport = @('Invoke-MsBuild')
# Cmdlets to export from this module # 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 = '*' CmdletsToExport = @()
# Variables to export from this module # Variables to export from this module
VariablesToExport = '*' VariablesToExport = @()
# Aliases to export from this module # 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 = '*' AliasesToExport = @()
# DSC resources to export from this module # DSC resources to export from this module
# DscResourcesToExport = @() # DscResourcesToExport = @()
@ -104,38 +107,7 @@ PrivateData = @{
# IconUri = '' # IconUri = ''
# ReleaseNotes of this module # ReleaseNotes of this module
ReleaseNotes = '- Added LogVerbosityLevel parameter to adjust the verbosity MsBuild uses to write to the log file. ReleaseNotes = '- Fix to find the "Program Files" location correctly on 32 bit windows without throwing an error.'
- 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'
} # End of PSData hashtable } # End of PSData hashtable

View File

@ -5,185 +5,205 @@ function Invoke-MsBuild
<# <#
.SYNOPSIS .SYNOPSIS
Builds the given Visual Studio solution or project file using MsBuild. Builds the given Visual Studio solution or project file using MsBuild.
.DESCRIPTION .DESCRIPTION
Executes the MsBuild.exe tool against the specified Visual Studio solution or project file. Executes the MsBuild.exe tool against the specified Visual Studio solution or project file.
Returns a hash table with properties for determining if the build succeeded or not, as well as other information (see the OUTPUTS section for list of properties). Returns a hash table with properties for determining if the build succeeded or not, as well as other information (see the OUTPUTS section for list of properties).
If using the PathThru switch, the process running MsBuild is returned instead. If using the PathThru switch, the process running MsBuild is returned instead.
.PARAMETER Path .PARAMETER Path
The path of the Visual Studio solution or project to build (e.g. a .sln or .csproj file). The path of the Visual Studio solution or project to build (e.g. a .sln or .csproj file).
.PARAMETER MsBuildParameters .PARAMETER MsBuildParameters
Additional parameters to pass to the MsBuild command-line tool. This can be any valid MsBuild command-line parameters except for the path of Additional parameters to pass to the MsBuild command-line tool. This can be any valid MsBuild command-line parameters except for the path of
the solution/project to build. the solution/project to build.
See http://msdn.microsoft.com/en-ca/library/vstudio/ms164311.aspx for valid MsBuild command-line parameters. See http://msdn.microsoft.com/en-ca/library/vstudio/ms164311.aspx for valid MsBuild command-line parameters.
.PARAMETER Use32BitMsBuild .PARAMETER Use32BitMsBuild
If this switch is provided, the 32-bit version of MsBuild.exe will be used instead of the 64-bit version when both are available. If this switch is provided, the 32-bit version of MsBuild.exe will be used instead of the 64-bit version when both are available.
.PARAMETER BuildLogDirectoryPath .PARAMETER BuildLogDirectoryPath
The directory path to write the build log files to. The directory path to write the build log files to.
Defaults to putting the log files in the users temp directory (e.g. C:\Users\[User Name]\AppData\Local\Temp). Defaults to putting the log files in the users temp directory (e.g. C:\Users\[User Name]\AppData\Local\Temp).
Use the keyword "PathDirectory" to put the log files in the same directory as the .sln or project file being built. 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. 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 .PARAMETER AutoLaunchBuildLogOnFailure
If set, this switch will cause the build log to automatically be launched into the default viewer if the build fails. 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. This log file contains all of the build output.
NOTE: This switch cannot be used with the PassThru switch. NOTE: This switch cannot be used with the PassThru switch.
.PARAMETER AutoLaunchBuildErrorsLogOnFailure .PARAMETER AutoLaunchBuildErrorsLogOnFailure
If set, this switch will cause the build errors log to automatically be launched into the default viewer if the build fails. If set, this switch will cause the build errors log to automatically be launched into the default viewer if the build fails.
This log file only contains errors from the build output. This log file only contains errors from the build output.
NOTE: This switch cannot be used with the PassThru switch. NOTE: This switch cannot be used with the PassThru switch.
.PARAMETER KeepBuildLogOnSuccessfulBuilds .PARAMETER KeepBuildLogOnSuccessfulBuilds
If set, this switch will cause the MsBuild log file to not be deleted on successful builds; normally it is only kept around on failed builds. If set, this switch will cause the MsBuild log file to not be deleted on successful builds; normally it is only kept around on failed builds.
NOTE: This switch cannot be used with the PassThru switch. NOTE: This switch cannot be used with the PassThru switch.
.PARAMETER ShowBuildOutputInNewWindow .PARAMETER ShowBuildOutputInNewWindow
If set, this switch will cause a command prompt window to be shown in order to view the progress of the build. If set, this switch will cause a command prompt window to be shown in order to view the progress of the build.
By default the build output is not shown in any window. By default the build output is not shown in any window.
NOTE: This switch cannot be used with the ShowBuildOutputInCurrentWindow switch. NOTE: This switch cannot be used with the ShowBuildOutputInCurrentWindow switch.
.PARAMETER ShowBuildOutputInCurrentWindow .PARAMETER ShowBuildOutputInCurrentWindow
If set, this switch will cause the build process to be started in the existing console window, instead of creating a new one. If set, this switch will cause the build process to be started in the existing console window, instead of creating a new one.
By default the build output is not shown in any window. By default the build output is not shown in any window.
NOTE: This switch will override the ShowBuildOutputInNewWindow switch. NOTE: This switch will override the ShowBuildOutputInNewWindow switch.
NOTE: There is a problem with the -NoNewWindow parameter of the Start-Process cmdlet; this is used for the ShowBuildOutputInCurrentWindow switch. NOTE: There is a problem with the -NoNewWindow parameter of the Start-Process cmdlet; this is used for the ShowBuildOutputInCurrentWindow switch.
The bug is that in some PowerShell consoles, the build output is not directed back to the console calling this function, so nothing is displayed. The bug is that in some PowerShell consoles, the build output is not directed back to the console calling this function, so nothing is displayed.
To avoid the build process from appearing to hang, PromptForInputBeforeClosing only has an effect with ShowBuildOutputInCurrentWindow when running 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.). 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 PromptForInputBeforeClosing .PARAMETER PromptForInputBeforeClosing
If set, this switch will prompt the user for input after the build completes, and will not continue until the user presses a key. If set, this switch will prompt the user for input after the build completes, and will not continue until the user presses a key.
NOTE: This switch only has an effect when used with the ShowBuildOutputInNewWindow and ShowBuildOutputInCurrentWindow switches (otherwise build output is not displayed). NOTE: This switch only has an effect when used with the ShowBuildOutputInNewWindow and ShowBuildOutputInCurrentWindow switches (otherwise build output is not displayed).
NOTE: This switch cannot be used with the PassThru switch. NOTE: This switch cannot be used with the PassThru switch.
NOTE: The user will need to provide input before execution will return back to the calling script (so do not use this switch for automated builds). NOTE: The user will need to provide input before execution will return back to the calling script (so do not use this switch for automated builds).
NOTE: To avoid the build process from appearing to hang, PromptForInputBeforeClosing only has an effect with ShowBuildOutputInCurrentWindow when running 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.). 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 .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. 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 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. 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. 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 .PARAMETER WhatIf
If set, the build will not actually be performed. 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 .OUTPUTS
When the -PassThru switch is provided, the process being used to run MsBuild.exe is returned. 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: 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. 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. BuildLogFilePath = The path to the build's log file.
BuildErrorsLogFilePath = The path to the build's error log file. BuildErrorsLogFilePath = The path to the build's error log file.
ItemToBuildFilePath = The item that MsBuild is ran against. ItemToBuildFilePath = The item that MsBuild 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. 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. 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. 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 .EXAMPLE
$buildResult = Invoke-MsBuild -Path "C:\Some Folder\MySolution.sln" $buildResult = Invoke-MsBuild -Path "C:\Some Folder\MySolution.sln"
if ($buildResult.BuildSucceeded -eq $true) if ($buildResult.BuildSucceeded -eq $true)
{ Write-Host "Build completed successfully." } {
else if (!$buildResult.BuildSucceeded -eq $false) Write-Output ("Build completed successfully in {0:N1} seconds." -f $buildResult.BuildDuration.TotalSeconds)
{ Write-Host "Build failed. Check the build log file $($buildResult.BuildLogFilePath) for errors." } }
else if ($buildResult.BuildSucceeded -eq $null) elseif ($buildResult.BuildSucceeded -eq $false)
{ Write-Host "Unsure if build passed or failed: $($buildResult.Message)" } {
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. 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. The PowerShell script will halt execution until MsBuild completes.
.EXAMPLE .EXAMPLE
$process = Invoke-MsBuild -Path "C:\Some Folder\MySolution.sln" -PassThru $process = Invoke-MsBuild -Path "C:\Some Folder\MySolution.sln" -PassThru
while (!$process.HasExited) while (!$process.HasExited)
{ {
Write-Host "Solution is still buildling..." Write-Host "Solution is still buildling..."
Start-Sleep -Seconds 1 Start-Sleep -Seconds 1
} }
Perform the default MsBuild actions on the Visual Studio solution to build the projects in it. Perform the default MsBuild actions on the Visual Studio solution to build the projects in it.
The PowerShell script will not halt execution; instead it will return the process running MsBuild.exe back to the caller while the build is performed. The PowerShell script will not halt execution; instead it will return the process running MsBuild.exe back to the caller while the build is performed.
You can check the process's HasExited property to check if the build has completed yet or not. You can check the process's HasExited property to check if the build has completed yet or not.
.EXAMPLE .EXAMPLE
if ((Invoke-MsBuild -Path $pathToSolution).BuildSucceeded -eq $true) 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. Perfom the build against the file specified at $pathToSolution and checks it for success in a single line.
.EXAMPLE .EXAMPLE
Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -MsBuildParameters "/target:Clean;Build" -ShowBuildOutputInNewWindow Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -MsBuildParameters "/target:Clean;Build" -ShowBuildOutputInNewWindow
Cleans then Builds the given C# project. 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 .EXAMPLE
Invoke-MsBuild -Path "C:\MySolution.sln" -Params "/target:Clean;Build /property:Configuration=Release;Platform=x64;BuildInParallel=true /verbosity:Detailed /maxcpucount" Invoke-MsBuild -Path "C:\MySolution.sln" -Params "/target:Clean;Build /property:Configuration=Release;Platform=x64;BuildInParallel=true /verbosity:Detailed /maxcpucount"
Cleans then Builds the given solution, specifying to build the project in parallel in the Release configuration for the x64 platform. Cleans then Builds the given solution, specifying to build the project in parallel in the Release configuration for the x64 platform.
Here the shorter "Params" alias is used instead of the full "MsBuildParameters" parameter name. Here the shorter "Params" alias is used instead of the full "MsBuildParameters" parameter name.
.EXAMPLE .EXAMPLE
Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -ShowBuildOutputInNewWindow -PromptForInputBeforeClosing -AutoLaunchBuildLogOnFailure Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -ShowBuildOutputInNewWindow -PromptForInputBeforeClosing -AutoLaunchBuildLogOnFailure
Builds the given C# project. 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, and it will not close until the user A window displaying the output from MsBuild will be shown so the user can view the progress of the build, and it will not close until the user
gives the window some input after the build completes. This function will also not return until the user gives the window some input, halting the powershell script execution. gives the window some input after the build completes. This function will also not return until the user gives the window some input, halting the powershell script execution.
If the build fails, the build log will automatically be opened in the default text viewer. If the build fails, the build log will automatically be opened in the default text viewer.
.EXAMPLE .EXAMPLE
Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -BuildLogDirectoryPath "C:\BuildLogs" -KeepBuildLogOnSuccessfulBuilds -AutoLaunchBuildErrorsLogOnFailure Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -BuildLogDirectoryPath "C:\BuildLogs" -KeepBuildLogOnSuccessfulBuilds -AutoLaunchBuildErrorsLogOnFailure
Builds the given C# project. Builds the given C# project.
The build log will be saved in "C:\BuildLogs", and they will not be automatically deleted even if the build succeeds. The build log will be saved in "C:\BuildLogs", and they will not be automatically deleted even if the build succeeds.
If the build fails, the build errors log will automatically be opened in the default text viewer. If the build fails, the build errors log will automatically be opened in the default text viewer.
.EXAMPLE .EXAMPLE
Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -BuildLogDirectoryPath PathDirectory Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -BuildLogDirectoryPath PathDirectory
Builds the given C# project. Builds the given C# project.
The keyword 'PathDirectory' is used, so the build log will be saved in "C:\Some Folder\", which is the same directory as the project being built (i.e. directory specified in the Path). The keyword 'PathDirectory' is used, so the build log will be saved in "C:\Some Folder\", which is the same directory as the project being built (i.e. directory specified in the Path).
.EXAMPLE .EXAMPLE
Invoke-MsBuild -Path "C:\Database\Database.dbproj" -P "/t:Deploy /p:TargetDatabase=MyDatabase /p:TargetConnectionString=`"Data Source=DatabaseServerName`;Integrated Security=True`;Pooling=False`" /p:DeployToDatabase=True" Invoke-MsBuild -Path "C:\Database\Database.dbproj" -P "/t:Deploy /p:TargetDatabase=MyDatabase /p:TargetConnectionString=`"Data Source=DatabaseServerName`;Integrated Security=True`;Pooling=False`" /p:DeployToDatabase=True"
Deploy the Visual Studio Database Project to the database "MyDatabase". Deploy the Visual Studio Database Project to the database "MyDatabase".
Here the shorter "P" alias is used instead of the full "MsBuildParameters" parameter name. Here the shorter "P" alias is used instead of the full "MsBuildParameters" parameter name.
The shorter alias' of the MsBuild parameters are also used; "/t" instead of "/target", and "/p" instead of "/property". The shorter alias' of the MsBuild parameters are also used; "/t" instead of "/target", and "/p" instead of "/property".
.EXAMPLE .EXAMPLE
Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -WhatIf 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. 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 .EXAMPLE
Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" > $null 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 .LINK
Project home: https://github.com/deadlydog/Invoke-MsBuild Project home: https://github.com/deadlydog/Invoke-MsBuild
.NOTES .NOTES
Name: Invoke-MsBuild 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) 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")] [CmdletBinding(DefaultParameterSetName="Wait")]
param param
@ -204,9 +224,9 @@ function Invoke-MsBuild
[Alias("LogDirectory","L")] [Alias("LogDirectory","L")]
[string] $BuildLogDirectoryPath = $env:Temp, [string] $BuildLogDirectoryPath = $env:Temp,
[parameter(Mandatory=$false)] [parameter(Mandatory=$false)]
[ValidateSet('q','quiet','m','minimal','n','normal','d','detailed','diag','diagnostic')] [ValidateSet('q','quiet','m','minimal','n','normal','d','detailed','diag','diagnostic')]
[string] $LogVerbosityLevel = 'normal', [string] $LogVerbosityLevel = 'normal',
[parameter(Mandatory=$false,ParameterSetName="Wait")] [parameter(Mandatory=$false,ParameterSetName="Wait")]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
@ -230,6 +250,14 @@ function Invoke-MsBuild
[parameter(Mandatory=$false,ParameterSetName="Wait")] [parameter(Mandatory=$false,ParameterSetName="Wait")]
[switch] $PromptForInputBeforeClosing, [switch] $PromptForInputBeforeClosing,
[parameter(Mandatory=$false)]
[ValidateScript({Test-Path $_})]
[string] $MsBuildFilePath,
[parameter(Mandatory=$false)]
[ValidateScript({Test-Path $_})]
[string] $VisualStudioDeveloperCommandPromptFilePath,
[parameter(Mandatory=$false,ParameterSetName="PassThru")] [parameter(Mandatory=$false,ParameterSetName="PassThru")]
[switch] $PassThru, [switch] $PassThru,
@ -245,33 +273,33 @@ function Invoke-MsBuild
# This must come after a script's/function's param section. # This must come after a script's/function's param section.
# Forces a function to be the first non-comment code to appear in a PowerShell Script/Module. # Forces a function to be the first non-comment code to appear in a PowerShell Script/Module.
Set-StrictMode -Version Latest 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
# 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. # 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.
if (!(Test-Path Variable:Private:AutoLaunchBuildLogOnFailure)) { $AutoLaunchBuildLogOnFailure = $false } [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 }
if (!(Test-Path Variable:Private:AutoLaunchBuildLogOnFailure)) { $AutoLaunchBuildErrorsLogOnFailure = $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: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 the keyword was supplied, place the log in the same folder as the solution/project being built.
if ($BuildLogDirectoryPath.Equals("PathDirectory", [System.StringComparison]::InvariantCultureIgnoreCase)) if ($BuildLogDirectoryPath.Equals("PathDirectory", [System.StringComparison]::InvariantCultureIgnoreCase))
{ {
$BuildLogDirectoryPath = [System.IO.Path]::GetDirectoryName($Path) $BuildLogDirectoryPath = [System.IO.Path]::GetDirectoryName($Path)
} }
# Always get the full path to the Log files directory. # Always get the full path to the Log files directory.
$BuildLogDirectoryPath = [System.IO.Path]::GetFullPath($BuildLogDirectoryPath) $BuildLogDirectoryPath = [System.IO.Path]::GetFullPath($BuildLogDirectoryPath)
# Local Variables. # Local Variables.
$solutionFileName = (Get-ItemProperty -Path $Path).Name $solutionFileName = (Get-ItemProperty -Path $Path).Name
$buildLogFilePath = (Join-Path -Path $BuildLogDirectoryPath -ChildPath $solutionFileName) + ".msbuild.log" $buildLogFilePath = (Join-Path -Path $BuildLogDirectoryPath -ChildPath $solutionFileName) + ".msbuild.log"
$buildErrorsLogFilePath = (Join-Path -Path $BuildLogDirectoryPath -ChildPath $solutionFileName) + ".msbulid.errors.log" $buildErrorsLogFilePath = (Join-Path -Path $BuildLogDirectoryPath -ChildPath $solutionFileName) + ".msbulid.errors.log"
$windowStyleOfNewWindow = if ($ShowBuildOutputInNewWindow) { "Normal" } else { "Hidden" } $windowStyleOfNewWindow = if ($ShowBuildOutputInNewWindow) { "Normal" } else { "Hidden" }
# Build our object that will be returned. # Build our hash table that will be returned.
$result = @{} $result = @{}
$result.BuildSucceeded = $null $result.BuildSucceeded = $null
$result.BuildLogFilePath = $buildLogFilePath $result.BuildLogFilePath = $buildLogFilePath
@ -280,20 +308,21 @@ function Invoke-MsBuild
$result.CommandUsedToBuild = [string]::Empty $result.CommandUsedToBuild = [string]::Empty
$result.Message = [string]::Empty $result.Message = [string]::Empty
$result.MsBuildProcess = $null $result.MsBuildProcess = $null
$result.BuildDuration = [TimeSpan]::Zero
# Try and build the solution. # Try and build the solution.
try try
{ {
# Get the verbosity to use for the MsBuild log file. # Get the verbosity to use for the MsBuild log file.
$verbosityLevel = switch ($LogVerbosityLevel) { $verbosityLevel = switch ($LogVerbosityLevel) {
{ ($_ -eq "q") -or ($_ -eq "quiet") -or ` { ($_ -eq "q") -or ($_ -eq "quiet") -or `
($_ -eq "m") -or ($_ -eq "minimal") -or ` ($_ -eq "m") -or ($_ -eq "minimal") -or `
($_ -eq "n") -or ($_ -eq "normal") -or ` ($_ -eq "n") -or ($_ -eq "normal") -or `
($_ -eq "d") -or ($_ -eq "detailed") -or ` ($_ -eq "d") -or ($_ -eq "detailed") -or `
($_ -eq "diag") -or ($_ -eq "diagnostic") } { ";verbosity=$_" ;break } ($_ -eq "diag") -or ($_ -eq "diagnostic") } { ";verbosity=$_" ;break }
default { "" } default { "" }
} }
# Build the arguments to pass to MsBuild. # Build the arguments to pass to MsBuild.
$buildArguments = """$Path"" $MsBuildParameters /fileLoggerParameters:LogFile=""$buildLogFilePath""$verbosityLevel /fileLoggerParameters1:LogFile=""$buildErrorsLogFilePath"";errorsonly" $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. # 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). # 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 [bool] $vsCommandPromptPathWasFound = ![string]::IsNullOrEmpty($vsCommandPromptPath)
if ($vsCommandPromptPath -ne $null) 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 the VS Command Prompt was not found, so just build using MsBuild directly.
else else
@ -320,10 +362,10 @@ function Invoke-MsBuild
# Append the MsBuild arguments to pass into cmd.exe in order to do the build. # Append the MsBuild arguments to pass into cmd.exe in order to do the build.
$cmdArgumentsToRunMsBuild += "$buildArguments " $cmdArgumentsToRunMsBuild += "$buildArguments "
# If necessary, add a pause to wait for input before exiting the cmd.exe window. # If necessary, add a pause to wait for input before exiting the cmd.exe window.
# No pausing allowed when using PassThru or not showing the build output. # No pausing allowed when using PassThru or not showing the build output.
# The -NoNewWindow parameter of Start-Process does not behave correctly in the ISE and other PowerShell hosts (doesn't display any build output), # The -NoNewWindow parameter of Start-Process does not behave correctly in the ISE and other PowerShell hosts (doesn't display any build output),
# so only allow it if in the default PowerShell host, since we know that one works. # so only allow it if in the default PowerShell host, since we know that one works.
$pauseForInput = [string]::Empty $pauseForInput = [string]::Empty
if ($PromptForInputBeforeClosing -and !$PassThru ` if ($PromptForInputBeforeClosing -and !$PassThru `
@ -333,7 +375,7 @@ function Invoke-MsBuild
# Record the exact command used to perform the build to make it easier to troubleshoot issues with builds. # Record the exact command used to perform the build to make it easier to troubleshoot issues with builds.
$result.CommandUsedToBuild = "cmd.exe $cmdArgumentsToRunMsBuild" $result.CommandUsedToBuild = "cmd.exe $cmdArgumentsToRunMsBuild"
# If we don't actually want to perform a build, return . # If we don't actually want to perform a build, return .
if ($WhatIf) if ($WhatIf)
{ {
@ -341,9 +383,9 @@ function Invoke-MsBuild
$result.Message = "The '-WhatIf' switch was specified, so a build was not invoked." $result.Message = "The '-WhatIf' switch was specified, so a build was not invoked."
return $result return $result
} }
Write-Debug "Starting new cmd.exe process with arguments ""$cmdArgumentsToRunMsBuild""." Write-Debug "Starting new cmd.exe process with arguments ""$cmdArgumentsToRunMsBuild""."
# Perform the build. # Perform the build.
if ($PassThru) if ($PassThru)
{ {
@ -358,16 +400,20 @@ function Invoke-MsBuild
} }
else else
{ {
if ($ShowBuildOutputInCurrentWindow) $performBuildScriptBlock =
{ {
$result.MsBuildProcess = Start-Process cmd.exe -ArgumentList $cmdArgumentsToRunMsBuild -NoNewWindow -PassThru if ($ShowBuildOutputInCurrentWindow)
} {
else $result.MsBuildProcess = Start-Process cmd.exe -ArgumentList $cmdArgumentsToRunMsBuild -NoNewWindow -Wait -PassThru
{ }
$result.MsBuildProcess = Start-Process cmd.exe -ArgumentList $cmdArgumentsToRunMsBuild -WindowStyle $windowStyleOfNewWindow -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. # If the build crashed, return that the build didn't succeed.
@ -376,20 +422,20 @@ function Invoke-MsBuild
$errorMessage = $_ $errorMessage = $_
$result.Message = "Unexpected error occurred while building ""$Path"": $errorMessage" $result.Message = "Unexpected error occurred while building ""$Path"": $errorMessage"
$result.BuildSucceeded = $false $result.BuildSucceeded = $false
Write-Error ($result.Message) Write-Error ($result.Message)
return $result return $result
} }
# If we can't find the build's log file in order to inspect it, write a warning and return null. # 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 (!(Test-Path -Path $buildLogFilePath))
{ {
$result.BuildSucceeded = $null $result.BuildSucceeded = $null
$result.Message = "Cannot find the build log file at '$buildLogFilePath', so unable to determine if build succeeded or not." $result.Message = "Cannot find the build log file at '$buildLogFilePath', so unable to determine if build succeeded or not."
Write-Warning ($result.Message) Write-Warning ($result.Message)
return $result return $result
} }
# Get if the build failed or not by looking at the log file. # 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) $buildSucceeded = (((Select-String -Path $buildLogFilePath -Pattern "Build FAILED." -SimpleMatch) -eq $null) -and $result.MsBuildProcess.ExitCode -eq 0)
@ -398,7 +444,7 @@ function Invoke-MsBuild
if ($buildSucceeded) if ($buildSucceeded)
{ {
$result.BuildSucceeded = $true $result.BuildSucceeded = $true
# If we shouldn't keep the log files around, delete them. # If we shouldn't keep the log files around, delete them.
if (!$KeepBuildLogOnSuccessfulBuilds) if (!$KeepBuildLogOnSuccessfulBuilds)
{ {
@ -411,7 +457,7 @@ function Invoke-MsBuild
{ {
$result.BuildSucceeded = $false $result.BuildSucceeded = $false
$result.Message = "FAILED to build ""$Path"". Please check the build log ""$buildLogFilePath"" for details." $result.Message = "FAILED to build ""$Path"". Please check the build log ""$buildLogFilePath"" for details."
# Write the error message as a warning. # Write the error message as a warning.
Write-Warning ($result.Message) Write-Warning ($result.Message)
@ -434,68 +480,172 @@ function Invoke-MsBuild
function Open-BuildLogFileWithDefaultProgram([string]$FilePathToOpen, [ref]$Result) function Open-BuildLogFileWithDefaultProgram([string]$FilePathToOpen, [ref]$Result)
{ {
if (Test-Path -Path $FilePathToOpen -PathType Leaf) if (Test-Path -Path $FilePathToOpen -PathType Leaf)
{ {
Start-Process -verb "Open" $FilePathToOpen Start-Process -verb "Open" $FilePathToOpen
} }
else else
{ {
$message = "Could not auto-launch the build log because the expected file does not exist at '$FilePathToOpen'." $message = "Could not auto-launch the build log because the expected file does not exist at '$FilePathToOpen'."
$Result.Message += [System.Environment]::NewLine + $message $Result.Message += [System.Environment]::NewLine + $message
Write-Warning $message Write-Warning $message
} }
} }
function Get-VisualStudioCommandPromptPath function Get-LatestVisualStudioCommandPromptPath
{ {
<# <#
.SYNOPSIS .SYNOPSIS
Gets the file path to the latest Visual Studio Command Prompt. Returns $null if a path is not found. Gets the file path to the latest Visual Studio Command Prompt. Returns $null if a path is not found.
.DESCRIPTION .DESCRIPTION
Gets the file path to the latest Visual Studio Command Prompt. Returns $null if a path is not found. 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 # If VS 2017 or newer VS Command Prompt was not found, check for older versions of VS Command Prompt.
$vsWhere = Join-Path $PSScriptRoot '..\..\Tools\vswhere.exe' if ([string]::IsNullOrEmpty($vsCommandPromptPath))
$vs2017Instance = & $vsWhere -nologo -format value -property installationPath -latest -requires 'Microsoft.VisualStudio.Component.VC.CLI.Support' {
$vs2017CommandPromptPath = $vs2017Instance + '\Common7\Tools\VsDevCmd.bat' $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. # Get some environmental paths.
$vs2015CommandPromptPath = $env:VS140COMNTOOLS + 'VsDevCmd.bat' $vs2015CommandPromptPath = $env:VS140COMNTOOLS + 'VsDevCmd.bat'
$vs2013CommandPromptPath = $env:VS120COMNTOOLS + 'VsDevCmd.bat' $vs2013CommandPromptPath = $env:VS120COMNTOOLS + 'VsDevCmd.bat'
$vs2012CommandPromptPath = $env:VS110COMNTOOLS + 'VsDevCmd.bat' $vs2012CommandPromptPath = $env:VS110COMNTOOLS + 'VsDevCmd.bat'
$vs2010CommandPromptPath = $env:VS100COMNTOOLS + 'vcvarsall.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. # Store the VS Command Prompt to do the build in, if one exists.
$vsCommandPromptPath = $null $newestVsCommandPromptPath = $null
foreach ($path in $vsCommandPromptPaths) foreach ($path in $potentialVsCommandPromptPaths)
{ {
try [bool] $pathExists = (![string]::IsNullOrEmpty($path)) -and (Test-Path -Path $path -PathType Leaf)
if ($pathExists)
{ {
if (Test-Path -Path $path) $newestVsCommandPromptPath = $path
{ break
$vsCommandPromptPath = $path
break
}
} }
catch {}
} }
# Return the path to the VS Command Prompt if it was found. # 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 .SYNOPSIS
Gets the path to the latest version of MsBuild.exe. Throws an exception if MsBuild.exe is not found. Gets the path to the latest version of MsBuild.exe. Throws an exception if MsBuild.exe is not found.
.DESCRIPTION .DESCRIPTION
Gets the path to the latest version of MsBuild.exe. Throws an exception if MsBuild.exe is not found. 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\' $registryPathToMsBuildToolsVersions = 'HKLM:\SOFTWARE\Microsoft\MSBuild\ToolsVersions\'
if ($Use32BitMsBuild) if ($Use32BitMsBuild)
{ {
@ -514,22 +664,60 @@ function Get-MsBuildPath([switch] $Use32BitMsBuild)
$largestMsBuildToolsVersion = ($msBuildToolsVersions.GetEnumerator() | Sort-Object -Descending -Property Name | Select-Object -First 1).Value $largestMsBuildToolsVersion = ($msBuildToolsVersions.GetEnumerator() | Sort-Object -Descending -Property Name | Select-Object -First 1).Value
$registryPathToMsBuildToolsLatestVersion = Join-Path -Path $registryPathToMsBuildToolsVersions -ChildPath ("{0:n1}" -f $largestMsBuildToolsVersion) $registryPathToMsBuildToolsLatestVersion = Join-Path -Path $registryPathToMsBuildToolsVersions -ChildPath ("{0:n1}" -f $largestMsBuildToolsVersion)
$msBuildToolsVersionsKeyToUse = Get-Item -Path $registryPathToMsBuildToolsLatestVersion $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) 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. # Build the expected path to the MsBuild executable.
$msBuildPath = (Join-Path -Path $msBuildDirectoryPath -ChildPath 'msbuild.exe') $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'."
}
return $msBuildPath 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 Export-ModuleMember -Function Invoke-MsBuild