├── .editorconfig ├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── Changelog.md ├── License.md ├── ReadMe.md ├── publishTools ├── HelperScripts │ ├── CommonFunctions.ps1 │ ├── Publish-NewReleaseToGitHub.ps1 │ └── Publish-NewVersionToPowerShellGallery.ps1 └── Publish-NewVersionToPowerShellGalleryAndGitHub.ps1 └── src ├── Invoke-MsBuild ├── Invoke-MsBuild.psd1 └── Invoke-MsBuild.psm1 └── Tests ├── RunTests.ps1 ├── Solution That Should Build Successfully With Warnings ├── SolutionThatShouldBuildWithWarnings.sln └── SolutionThatShouldBuildWithWarnings │ ├── Class1.cs │ ├── Properties │ └── AssemblyInfo.cs │ └── SolutionThatShouldBuildWithWarnings.csproj ├── Solution That Should Build Successfully ├── SolutionThatShouldBuildSuccessfully.sln └── SolutionThatShouldBuildSuccessfully │ ├── Class1.cs │ ├── Properties │ └── AssemblyInfo.cs │ └── SolutionThatShouldBuildSuccessfully.csproj └── Solution That Should Fail Build ├── SolutionThatShouldFailBuild.sln └── SolutionThatShouldFailBuild ├── Class1.cs ├── Properties └── AssemblyInfo.cs └── SolutionThatShouldFailBuild.csproj /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file should only include settings that affect the physical contents of the file, not just how it appears in an editor. 2 | # Do not include personal preference meta-data settings like a tab's `indent_size` in this file; those should be specified in a parent .editorconfig file outside of the repository. 3 | # v1.2 4 | 5 | # Ensure that personal preference meta-settings can be inherited from parent .editorconfig files. 6 | root = false 7 | 8 | [*] 9 | indent_style = tab 10 | end_of_line = crlf 11 | trim_trailing_whitespace = true 12 | 13 | [*.{md,psd1,pp,yml,yaml}] 14 | indent_style = space 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Documents 5 | *.doc diff=astextplain 6 | *.DOC diff=astextplain 7 | *.docx diff=astextplain 8 | *.DOCX diff=astextplain 9 | *.dot diff=astextplain 10 | *.DOT diff=astextplain 11 | *.pdf diff=astextplain 12 | *.PDF diff=astextplain 13 | *.rtf diff=astextplain 14 | *.RTF diff=astextplain 15 | *.md text 16 | *.adoc text 17 | *.textile text 18 | *.mustache text 19 | *.csv text 20 | *.tab text 21 | *.tsv text 22 | *.sql text 23 | 24 | # Graphics 25 | *.png binary 26 | *.jpg binary 27 | *.jpeg binary 28 | *.gif binary 29 | *.tif binary 30 | *.tiff binary 31 | *.ico binary 32 | # SVG treated as an asset (binary) by default. If you want to treat it as text, comment-out the following line and uncomment the line after. 33 | #*.svg binary 34 | *.svg text 35 | *.eps binary 36 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: deadlydog # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: https://www.paypal.me/deadlydogDan # Replace with a single custom sponsorship URL 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.cache 2 | *.cachefile 3 | *.docstates 4 | *.ncrunchsolution 5 | *.pdb 6 | *.suo 7 | *.user 8 | bin/ 9 | obj/ 10 | _[Rr]e[Ss]harper.* 11 | *.appx 12 | *.appxupload 13 | *.appxsym 14 | *.log 15 | *.backup 16 | *.nupkg 17 | AppPackages/ -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v2.7.1 - December 11, 2021 4 | 5 | Fixes: 6 | 7 | - Compare MSBuild version numbers in other languages correctly. 8 | 9 | ## v2.7.0 - December 11, 2021 10 | 11 | Features: 12 | 13 | - Add support for 64-bit Visual Studio versions (e.g. VS 2022). 14 | 15 | ## v2.6.5 - October 29, 2020 16 | 17 | Fixes: 18 | 19 | - Fix wrong variable name so that the `AutoLaunchBuildErrorsLogOnFailure` parameter is obeyed properly. 20 | 21 | ## v2.6.4 - February 1, 2019 22 | 23 | Fixes: 24 | 25 | - Fix hanging indefinitely when waiting for build process to complete. 26 | 27 | ## v2.6.3 - February 1, 2019 28 | 29 | Fixes: 30 | 31 | - Fix not handling 32-bit switch correctly. 32 | 33 | ## v2.6.2 - July 2, 2018 34 | 35 | Fixes: 36 | 37 | - Use native -WhatIf parameter functionality. 38 | 39 | ## v2.6.1 - May 1, 2018 40 | 41 | Fixes: 42 | 43 | - Use `-LiteralPath` parameter for cmdlets to properly support file paths with symbols (e.g. square brackets in file paths). 44 | 45 | ## v2.6.0 - May 14, 2017 46 | 47 | Features: 48 | 49 | - Added a BypassVisualStudioDeveloperCommandPrompt parameter to allow the Visual Studio Developer Command Prompt to be bypassed, as it can sometimes take a long time to load, leading to a performance problem. 50 | - Improved the file path validations performed on the scripts parameters. 51 | 52 | ## v2.5.1 - April 22, 2017 53 | 54 | Fixes: 55 | 56 | - Fix to find the "Program Files" location correctly on 32 bit windows without throwing an error. 57 | 58 | ## v2.5.0 - April 21, 2017 59 | 60 | Features: 61 | 62 | - Added new BuildDuration TimeSpan property to the returned hash table that shows how long the build ran for. 63 | 64 | ## v2.4.2 - April 20, 2017 65 | 66 | Fixes: 67 | 68 | - Fixed bug where MsBuild.exe would not be found on 32-bit Windows OSs. 69 | 70 | ## v2.4.1 - April 20, 2017 71 | 72 | Fixes: 73 | 74 | - Fixed "CurrentCulture is a ReadOnly property" bug on computers running .Net 4.5.2 and lower. 75 | 76 | ## v2.4.0 - April 1, 2017 77 | 78 | Features: 79 | 80 | - Added MsBuildFilePath and VisualStudioDeveloperCommandPromptFilePath script parameters, so users can pass in which versions they would like to use, instead of the script using the latest versions. 81 | 82 | Fixes: 83 | 84 | - Fixed inverted bool logic that was causing the VS Command Prompt to never be used. 85 | 86 | ## v2.3.1 - April 1, 2017 87 | 88 | Fixes: 89 | 90 | - Fixes to truly support VS 2017 MsBuild. 91 | 92 | ## v2.3.0 - March 20, 2017 93 | 94 | Features: 95 | 96 | - Added support to find and use Visual Studio 2017's MsBuild.exe. 97 | 98 | ## v2.2.0 - March 30, 2017 99 | 100 | Features: 101 | 102 | - Added LogVerbosityLevel parameter to adjust the verbosity MsBuild uses to write to the log file. 103 | 104 | Fixes: 105 | 106 | - Fixed bug that prevented us from finding msbuild.exe on some machines. 107 | 108 | ## v2.1.0 - September 20, 2016 109 | 110 | Features: 111 | 112 | - Added new Use32BitMsBuild parameter to allow users to force the 32-bit version of MsBuild.exe to be used instead of the 64-bit version when both are available. 113 | 114 | ## v2.0.0 - May 25, 2016 115 | 116 | Breaking Changes from v1: 117 | 118 | - A hash table with several properties is returned instead of a simple $true/$false/$null value. 119 | - The `GetLogPath` switch is gone and replaced with the `WhatIf` switch. 120 | 121 | Features: 122 | 123 | - A build log file containing only build errors is created alongside the regular build log file. 124 | - The errors build log file can be auto-launched on build failure. 125 | - 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). 126 | - A hash table containing the following properties is now returned: 127 | - BuildSucceeded = $true if the build passed, $false if the build failed, and $null if we are not sure. 128 | - BuildLogFilePath = The path to the builds log file. 129 | - BuildErrorsLogFilePath = The path to the builds error log file. 130 | - ItemToBuildFilePath = The item that MsBuild is ran against. 131 | - CommandUsedToBuild = The full command that is used to invoke MsBuild. This can be useful for inspecting what parameters are - passed to MsBuild.exe. 132 | - Message = A message describing any problems that were encountered by Invoke-MsBuild. This is typically an empty string unless - something went wrong. 133 | - MsBuildProcess = The process that was used to execute MsBuild.exe. 134 | 135 | Changes to make when updating from v1 to v2: 136 | 137 | - To capture/display the build success result, you must change `Invoke-MsBuild ...` to `(Invoke-MsBuild ...).BuildSucceeded`. 138 | - To get the path where the log file will be created, you must change `Invoke-MsBuild ... -GetLogPath` to `(Invoke-MsBuild ... -WhatIf).BuildLogFilePath`. 139 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | 2 | ## The MIT License (MIT) 3 | 4 | Copyright (c) 2013 Daniel Schroeder, iQmetrix 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | # Invoke-MsBuild PowerShell Module 2 | 3 | A PowerShell module to make building projects and solutions with MsBuild easy. It provides features such as: 4 | 5 | * Check if the build succeeded or failed 6 | * Automatically open the build log file when the build fails 7 | * View the build output in the current console window, a new window, or not at all 8 | * Fire-and-forget building (via the -PassThru switch) 9 | 10 | The module simply passes through any MsBuild command-line parameters you supply, so it supports all functionality (e.g. project types, targets, etc.) that you would get by calling MsBuild directly. The module builds using the Visual Studio Command Prompt when available in order to support more project types that MsBuild.exe alone may not support, such as XNA projects. 11 | 12 | ## Getting Started 13 | 14 | You can either download the `Invoke-MsBuild.psm1` file from the [Releases page][ReleasesPageUrl] directly, or [install the module from the PowerShell Gallery][PowerShellGalleryPackageUrl]. 15 | 16 | Here is an example of how to import the Invoke-MsBuild module into your powershell session and call it: 17 | 18 | ```PowerShell 19 | Import-Module -Name "C:\PathTo\Invoke-MsBuild.psm1" 20 | Invoke-MsBuild -Path "C:\Some Folder\MySolution.sln" 21 | ``` 22 | 23 | When the -PassThru switch is provided, the process being used to run MsBuild.exe is returned. 24 | 25 | When the -PassThru switch is not provided, a hash table with the following properties is returned: 26 | 27 | * BuildSucceeded = $true if the build passed, $false if the build failed, and $null if we are not sure. 28 | * BuildLogFilePath = The path to the build's log file. 29 | * BuildErrorsLogFilePath = The path to the build's error log file. 30 | * ItemToBuildFilePath = The item that MsBuild ran against. 31 | * CommandUsedToBuild = The full command that was used to invoke MsBuild. This can be useful for inspecting what parameters are passed to MsBuild.exe. 32 | * Message = A message describing any problems that were encountered by Invoke-MsBuild. This is typically an empty string unless something went wrong. 33 | * MsBuildProcess = The process that was used to execute MsBuild.exe. 34 | * BuildDuration = The amount of time the build took to complete, represented as a TimeSpan. 35 | 36 | ## Examples 37 | 38 | ```PowerShell 39 | $buildResult = Invoke-MsBuild -Path "C:\Some Folder\MySolution.sln" 40 | 41 | if ($buildResult.BuildSucceeded -eq $true) 42 | { 43 | Write-Output ("Build completed successfully in {0:N1} seconds." -f $buildResult.BuildDuration.TotalSeconds) 44 | } 45 | elseif ($buildResult.BuildSucceeded -eq $false) 46 | { 47 | Write-Output ("Build failed after {0:N1} seconds. Check the build log file '$($buildResult.BuildLogFilePath)' for errors." -f $buildResult.BuildDuration.TotalSeconds) 48 | } 49 | elseif ($null -eq $buildResult.BuildSucceeded) 50 | { 51 | Write-Output "Unsure if build passed or failed: $($buildResult.Message)" 52 | } 53 | ``` 54 | 55 | Perform the default MsBuild actions on the Visual Studio solution to build the projects in it, and returns a hash table containing the results. 56 | The PowerShell script will halt execution until MsBuild completes. 57 | 58 | --- 59 | 60 | ```PowerShell 61 | $process = Invoke-MsBuild -Path "C:\Some Folder\MySolution.sln" -PassThru 62 | 63 | while (!$process.HasExited) 64 | { 65 | Write-Host "Solution is still building..." 66 | Start-Sleep -Seconds 1 67 | } 68 | ``` 69 | 70 | Perform the default MsBuild actions on the Visual Studio solution to build the projects in it. 71 | 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. 72 | You can check the process's HasExited property to check if the build has completed yet or not. 73 | 74 | --- 75 | 76 | ```PowerShell 77 | if ((Invoke-MsBuild -Path $pathToSolution).BuildSucceeded -eq $true) 78 | { 79 | Write-Output "Build completed successfully." 80 | } 81 | ``` 82 | 83 | Perform the build against the file specified at $pathToSolution and checks it for success in a single line. 84 | 85 | --- 86 | 87 | ```PowerShell 88 | Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -MsBuildParameters "/target:Clean;Build" -ShowBuildOutputInNewWindow 89 | ``` 90 | 91 | Cleans then Builds the given C# project. 92 | 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. 93 | 94 | --- 95 | 96 | ```PowerShell 97 | Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -ShowBuildOutputInCurrentWindow 98 | ``` 99 | 100 | Builds the given C# project and displays the output from MsBuild in the current terminal window. 101 | 102 | --- 103 | 104 | ```PowerShell 105 | Invoke-MsBuild -Path "C:\MySolution.sln" -MsBuildParameters "/target:Clean;Build /property:Configuration=Release;Platform=x64;BuildInParallel=true /verbosity:Detailed /maxcpucount" 106 | ``` 107 | 108 | Cleans then Builds the given solution, specifying to build the project in parallel in the Release configuration for the x64 platform. 109 | Here the shorter "Params" alias is used instead of the full "MsBuildParameters" parameter name. 110 | 111 | --- 112 | 113 | ```PowerShell 114 | Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -ShowBuildOutputInNewWindow -PromptForInputBeforeClosing -AutoLaunchBuildLogOnFailure 115 | ``` 116 | 117 | Builds the given C# project. 118 | 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 119 | 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. 120 | If the build fails, the build log will automatically be opened in the default text viewer. 121 | 122 | --- 123 | 124 | ```PowerShell 125 | Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -BuildLogDirectoryPath "C:\BuildLogs" -KeepBuildLogOnSuccessfulBuilds -AutoLaunchBuildErrorsLogOnFailure 126 | ``` 127 | 128 | Builds the given C# project. 129 | The build log will be saved in "C:\BuildLogs", and they will not be automatically deleted even if the build succeeds. 130 | If the build fails, the build errors log will automatically be opened in the default text viewer. 131 | 132 | --- 133 | 134 | ```PowerShell 135 | Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -BuildLogDirectoryPath PathDirectory 136 | ``` 137 | 138 | Builds the given C# project. 139 | 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). 140 | 141 | --- 142 | 143 | ```PowerShell 144 | 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" 145 | ``` 146 | 147 | Deploy the Visual Studio Database Project to the database "MyDatabase". 148 | Here the shorter "P" alias is used instead of the full "MsBuildParameters" parameter name. 149 | The shorter alias' of the MsBuild parameters are also used; "/t" instead of "/target", and "/p" instead of "/property". 150 | 151 | --- 152 | 153 | ```PowerShell 154 | Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -WhatIf 155 | ``` 156 | 157 | Returns the result object containing the same property values that would be created if the build was ran with the same parameters. 158 | The BuildSucceeded property will be $null since no build will actually be invoked. 159 | This will display all of the returned object's properties and their values. 160 | 161 | --- 162 | 163 | ```PowerShell 164 | Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" > $null 165 | ``` 166 | 167 | Builds the given C# project, discarding the result object and not displaying its properties. 168 | 169 | ## Full Documentation 170 | 171 | For a full list of available parameters, check out the latest documentation in PowerShell by using `Get-Help Invoke-MsBuild -Full`, or just [look at the file in source control here][DocumentationInSourceControlFileUrl]. 172 | 173 | [ReleasesPageUrl]:https://github.com/deadlydog/Invoke-MsBuild/releases 174 | [PowerShellGalleryPackageUrl]:https://www.powershellgallery.com/packages/Invoke-MsBuild/ 175 | [DocumentationInSourceControlFileUrl]:https://github.com/deadlydog/Invoke-MsBuild/blob/master/src/Invoke-MsBuild/Invoke-MsBuild.psm1#L6 176 | 177 | ## Changelog 178 | 179 | See what's changed in the application over time by viewing [the changelog](Changelog.md). 180 | 181 | ## Donate 182 | 183 | Buy me some pancakes 🥞 for providing this PowerShell module open source and for free :) 184 | 185 | [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.me/deadlydogDan/5USD) 186 | -------------------------------------------------------------------------------- /publishTools/HelperScripts/CommonFunctions.ps1: -------------------------------------------------------------------------------- 1 | function Replace-TextInFile([ValidateScript({Test-Path $_ -PathType Leaf})][string]$filePath, [string]$textToReplace, [string]$replacementText) 2 | { 3 | $fileContents = [System.IO.File]::ReadAllText($filePath) 4 | $newFileContents = $fileContents.Replace($textToReplace, $replacementText) 5 | [System.IO.File]::WriteAllText($filePath, $newFileContents) 6 | } 7 | 8 | function Read-MessageBoxDialog([string]$Message, [string]$WindowTitle, [System.Windows.Forms.MessageBoxButtons]$Buttons = [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]$Icon = [System.Windows.Forms.MessageBoxIcon]::None) 9 | { 10 | Add-Type -AssemblyName System.Windows.Forms 11 | return [System.Windows.Forms.MessageBox]::Show($Message, $WindowTitle, $Buttons, $Icon) 12 | } 13 | 14 | function Read-InputBoxDialog([string]$Message, [string]$WindowTitle, [string]$DefaultText) 15 | { 16 | Add-Type -AssemblyName Microsoft.VisualBasic 17 | return [Microsoft.VisualBasic.Interaction]::InputBox($Message, $WindowTitle, $DefaultText) 18 | } 19 | 20 | function Read-MultiLineInputBoxDialog([string]$Message, [string]$WindowTitle, [string]$DefaultText) 21 | { 22 | <# 23 | .SYNOPSIS 24 | Prompts the user with a multi-line input box and returns the text they enter, or null if they cancelled the prompt. 25 | 26 | .DESCRIPTION 27 | Prompts the user with a multi-line input box and returns the text they enter, or null if they cancelled the prompt. 28 | 29 | .PARAMETER Message 30 | The message to display to the user explaining what text we are asking them to enter. 31 | 32 | .PARAMETER WindowTitle 33 | The text to display on the prompt window's title. 34 | 35 | .PARAMETER DefaultText 36 | The default text to show in the input box. 37 | 38 | .EXAMPLE 39 | $userText = Read-MultiLineInputDialog "Input some text please:" "Get User's Input" 40 | 41 | Shows how to create a simple prompt to get mutli-line input from a user. 42 | 43 | .EXAMPLE 44 | # Setup the default multi-line address to fill the input box with. 45 | $defaultAddress = @' 46 | John Doe 47 | 123 St. 48 | Some Town, SK, Canada 49 | A1B 2C3 50 | '@ 51 | 52 | $address = Read-MultiLineInputDialog "Please enter your full address, including name, street, city, and postal code:" "Get User's Address" $defaultAddress 53 | if ($null -eq $address) 54 | { 55 | Write-Error "You pressed the Cancel button on the multi-line input box." 56 | } 57 | 58 | Prompts the user for their address and stores it in a variable, pre-filling the input box with a default multi-line address. 59 | If the user pressed the Cancel button an error is written to the console. 60 | 61 | .EXAMPLE 62 | $inputText = Read-MultiLineInputDialog -Message "If you have a really long message you can break it apart`nover two lines with the powershell newline character:" -WindowTitle "Window Title" -DefaultText "Default text for the input box." 63 | 64 | Shows how to break the second parameter (Message) up onto two lines using the powershell newline character (`n). 65 | If you break the message up into more than two lines the extra lines will be hidden behind or show ontop of the TextBox. 66 | 67 | .NOTES 68 | Name: Show-MultiLineInputDialog 69 | Author: Daniel Schroeder (originally based on the code shown at http://technet.microsoft.com/en-us/library/ff730941.aspx) 70 | Version: 1.0 71 | #> 72 | Add-Type -AssemblyName System.Drawing 73 | Add-Type -AssemblyName System.Windows.Forms 74 | 75 | # Create the Label. 76 | $label = New-Object System.Windows.Forms.Label 77 | $label.Location = New-Object System.Drawing.Size(10,10) 78 | $label.Size = New-Object System.Drawing.Size(280,20) 79 | $label.AutoSize = $true 80 | $label.Text = $Message 81 | 82 | # Create the TextBox used to capture the user's text. 83 | $textBox = New-Object System.Windows.Forms.TextBox 84 | $textBox.Location = New-Object System.Drawing.Size(10,40) 85 | $textBox.Size = New-Object System.Drawing.Size(575,200) 86 | $textBox.AcceptsReturn = $true 87 | $textBox.AcceptsTab = $false 88 | $textBox.Multiline = $true 89 | $textBox.ScrollBars = 'Both' 90 | $textBox.Text = $DefaultText 91 | 92 | # Create the OK button. 93 | $okButton = New-Object System.Windows.Forms.Button 94 | $okButton.Location = New-Object System.Drawing.Size(415,250) 95 | $okButton.Size = New-Object System.Drawing.Size(75,25) 96 | $okButton.Text = "OK" 97 | $okButton.Add_Click({ $form.Tag = $textBox.Text; $form.Close() }) 98 | 99 | # Create the Cancel button. 100 | $cancelButton = New-Object System.Windows.Forms.Button 101 | $cancelButton.Location = New-Object System.Drawing.Size(510,250) 102 | $cancelButton.Size = New-Object System.Drawing.Size(75,25) 103 | $cancelButton.Text = "Cancel" 104 | $cancelButton.Add_Click({ $form.Tag = $null; $form.Close() }) 105 | 106 | # Create the form. 107 | $form = New-Object System.Windows.Forms.Form 108 | $form.Text = $WindowTitle 109 | $form.Size = New-Object System.Drawing.Size(610,320) 110 | $form.FormBorderStyle = 'FixedSingle' 111 | $form.StartPosition = "CenterScreen" 112 | $form.AutoSizeMode = 'GrowAndShrink' 113 | $form.Topmost = $True 114 | $form.AcceptButton = $okButton 115 | $form.CancelButton = $cancelButton 116 | $form.ShowInTaskbar = $true 117 | 118 | # Add all of the controls to the form. 119 | $form.Controls.Add($label) 120 | $form.Controls.Add($textBox) 121 | $form.Controls.Add($okButton) 122 | $form.Controls.Add($cancelButton) 123 | 124 | # Initialize and show the form. 125 | $form.Add_Shown({$form.Activate()}) 126 | $form.ShowDialog() > $null # Trash the text of the button that was clicked. 127 | 128 | # Return the text that the user entered. 129 | return $form.Tag 130 | } -------------------------------------------------------------------------------- /publishTools/HelperScripts/Publish-NewReleaseToGitHub.ps1: -------------------------------------------------------------------------------- 1 | function Publish-NewReleaseToGitHub($gitHubReleaseParameters) 2 | { 3 | Install-Module -Name New-GitHubRelease -Scope CurrentUser -Force 4 | 5 | $gitHubAccessTokenEnvironmentalVariableName = 'GitHubAccessToken' 6 | $gitHubAccessToken = $gitHubReleaseParameters.GitHubAccessToken 7 | 8 | # If a GitHub Access Token was not provided, check the Environmental variable for it. If it's not there either, prompt the user for it. 9 | $isGitHubAccessTokenProvidedFromPrompt = $false 10 | if ([string]::IsNullOrWhiteSpace($gitHubAccessToken)) 11 | { 12 | $encodedGitHubAccessToken = [Environment]::GetEnvironmentVariable($gitHubAccessTokenEnvironmentalVariableName, "User") 13 | if (![string]::IsNullOrWhiteSpace($encodedGitHubAccessToken)) 14 | { 15 | $gitHubAccessTokenAsBytes = [System.Convert]::FromBase64String($encodedGitHubAccessToken) 16 | $gitHubAccessToken = [System.Text.Encoding]::UTF8.GetString($gitHubAccessTokenAsBytes) 17 | } 18 | 19 | if ([string]::IsNullOrWhiteSpace($gitHubAccessToken)) 20 | { 21 | $gitHubAccessToken = Read-InputBoxDialog -WindowTitle 'GitHub Access Token Required' -Message 'Please enter your GitHub Access Token:' 22 | $isGitHubAccessTokenProvidedFromPrompt = $true 23 | } 24 | } 25 | 26 | if ([string]::IsNullOrWhiteSpace($gitHubAccessToken)) 27 | { 28 | throw 'No GitHub Access Token was provided, so exiting without attempting to publish a new Release.' 29 | } 30 | $gitHubReleaseParameters.GitHubAccessToken = $gitHubAccessToken 31 | 32 | # Publish the new version of the module to GitHub. 33 | Write-Output "Creating new GitHub release..." 34 | $gitHubReleaseCreationResult = New-GitHubRelease @gitHubReleaseParameters 35 | 36 | # If we prompted the user for the Access Token and the publish succeeded, ask them if they want to save it for next time. 37 | if ($isGitHubAccessTokenProvidedFromPrompt -and $gitHubReleaseCreationResult.ReleaseCreationSucceeded) 38 | { 39 | $saveGitHubAccessKeyAnswer = Read-MessageBoxDialog -WindowTitle "Save GitHub Access Token?" -Message 'Would you like to save the GitHub Access Token in an environmental variable so you are not prompted for it next time?' -Buttons YesNo 40 | if ($saveGitHubAccessKeyAnswer -eq 'Yes') 41 | { 42 | $gitHubAccessTokenAsBytes = [System.Text.Encoding]::UTF8.GetBytes($gitHubAccessToken) 43 | $encodedGitHubAccessToken = [System.Convert]::ToBase64String($gitHubAccessTokenAsBytes) 44 | [Environment]::SetEnvironmentVariable($gitHubAccessTokenEnvironmentalVariableName, $encodedGitHubAccessToken, "User") 45 | } 46 | } 47 | 48 | # Let the user know if the new Release was created successfully or not. 49 | if ($gitHubReleaseCreationResult.Succeeded -eq $true) 50 | { 51 | Write-Output "Release published successfully! View it at $($gitHubReleaseCreationResult.ReleaseUrl)" 52 | } 53 | elseif ($gitHubReleaseCreationResult.ReleaseCreationSucceeded -eq $false) 54 | { 55 | throw "The release was not created. Error message is: $($gitHubReleaseCreationResult.ErrorMessage)" 56 | } 57 | elseif ($gitHubReleaseCreationResult.AllAssetUploadsSucceeded -eq $false) 58 | { 59 | throw "The release was created, but not all of the assets were uploaded to it. View it at $($gitHubReleaseCreationResult.ReleaseUrl). Error message is: $($gitHubReleaseCreationResult.ErrorMessage)" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /publishTools/HelperScripts/Publish-NewVersionToPowerShellGallery.ps1: -------------------------------------------------------------------------------- 1 | function Publish-ToPowerShellGallery([string] $moduleDirectoryPath, [string] $powerShellGalleryNuGetApiKey, [bool] $isTestingThisScript) 2 | { 3 | $powerShellGalleryNuGetApiKeyEnvironmentalVariableName = 'PowerShellGalleryNuGetApiKey' 4 | 5 | # If a PowerShell NuGet API Key was not provided, check the Environmental variable for it. If it's not there either, prompt the user for it. 6 | $isPowerShellGalleryNuGetApiKeyProvidedFromPrompt = $false 7 | if ([string]::IsNullOrWhiteSpace($powerShellGalleryNuGetApiKey)) 8 | { 9 | $encodedPowerShellGalleryNuGetApiKey = [Environment]::GetEnvironmentVariable($powerShellGalleryNuGetApiKeyEnvironmentalVariableName, "User") 10 | if (![string]::IsNullOrWhiteSpace($encodedPowerShellGalleryNuGetApiKey)) 11 | { 12 | $powerShellGalleryNuGetApiKeyAsBytes = [System.Convert]::FromBase64String($encodedPowerShellGalleryNuGetApiKey) 13 | $powerShellGalleryNuGetApiKey = [System.Text.Encoding]::UTF8.GetString($powerShellGalleryNuGetApiKeyAsBytes) 14 | } 15 | 16 | if ([string]::IsNullOrWhiteSpace($powerShellGalleryNuGetApiKey)) 17 | { 18 | $powerShellGalleryNuGetApiKey = Read-InputBoxDialog -WindowTitle 'PowerShell Gallery API Key Required' -Message 'Please enter your PowerShell Gallery API Key:' 19 | $isPowerShellGalleryNuGetApiKeyProvidedFromPrompt = $true 20 | } 21 | } 22 | 23 | if ([string]::IsNullOrWhiteSpace($powerShellGalleryNuGetApiKey)) 24 | { 25 | throw 'No PowerShell Gallery API key was provided, so exiting without attempting to publish a new NuGet package.' 26 | } 27 | 28 | if ($isTestingThisScript) 29 | { 30 | Write-Output "Script is in TESTING mode, so we will not actually try to publish the new NuGet package to the PowerShell Gallery." 31 | return 32 | } 33 | 34 | # Publish the new version of the module to the PowerShell Gallery. 35 | Write-Output "Publishing new NuGet package to the PowerShell Gallery..." 36 | try 37 | { 38 | Publish-Module -Path $moduleDirectoryPath -NuGetApiKey $powerShellGalleryNuGetApiKey 39 | } 40 | catch 41 | { 42 | throw $_.Exception.Message 43 | } 44 | 45 | $powerShellGalleryNuGetPackageExpectedUrl = "$powerShellGalleryNuGetPackageUrlWithTrailingSlash$newVersionNumber" 46 | Write-Output "PowerShell Gallery NuGet Package has been published. View it at: $powerShellGalleryNuGetPackageExpectedUrl" 47 | 48 | # If we prompted the user for the API key, ask them if they want to save it for next time. 49 | if ($isPowerShellGalleryNuGetApiKeyProvidedFromPrompt) 50 | { 51 | $savePowerShellGalleryApiKeyAnswer = Read-MessageBoxDialog -WindowTitle "Save PowerShell Gallery API Key?" -Message 'Would you like to save the API key in an environmental variable so you are not prompted for it next time?'-Buttons YesNo 52 | if ($savePowerShellGalleryApiKeyAnswer -eq 'Yes') 53 | { 54 | $powerShellGalleryNuGetApiKeyAsBytes = [System.Text.Encoding]::UTF8.GetBytes($powerShellGalleryNuGetApiKey) 55 | $encodedPowerShellGalleryNuGetApiKey = [System.Convert]::ToBase64String($powerShellGalleryNuGetApiKeyAsBytes) 56 | [Environment]::SetEnvironmentVariable($powerShellGalleryNuGetApiKeyEnvironmentalVariableName, $encodedPowerShellGalleryNuGetApiKey, "User") 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /publishTools/Publish-NewVersionToPowerShellGalleryAndGitHub.ps1: -------------------------------------------------------------------------------- 1 | param 2 | ( 3 | [Parameter(Mandatory=$false)] 4 | [ValidateNotNullOrEmpty()] 5 | [string] $PowerShellGalleryNuGetApiKey, 6 | 7 | [Parameter(Mandatory=$false)] 8 | [ValidateNotNullOrEmpty()] 9 | [string] $GitHubAccessToken 10 | ) 11 | 12 | # Set to True when testing the script to prevent actual publishing to PowerShell Gallery, and to create a Draft of the GitHub Release instead of a published Release. 13 | $isTestingThisScript = $false 14 | 15 | $THIS_SCRIPTS_DIRECTORY = Split-Path $script:MyInvocation.MyCommand.Path 16 | $helperScriptsDirectory = Join-Path -Path $THIS_SCRIPTS_DIRECTORY -ChildPath 'HelperScripts' 17 | $commonFunctionsScriptFilePath = Join-Path -Path $helperScriptsDirectory -ChildPath 'CommonFunctions.ps1' 18 | $publishToPowerShellGalleryScriptFilePath = Join-Path -Path $helperScriptsDirectory -ChildPath 'Publish-NewVersionToPowerShellGallery.ps1' 19 | $publishToGitHubScriptFilePath = Join-Path -Path $helperScriptsDirectory -ChildPath 'Publish-NewReleaseToGitHub.ps1' 20 | $srcDirectoryPath = Join-Path -Path (Split-Path -Path $THIS_SCRIPTS_DIRECTORY -Parent) -ChildPath 'src' 21 | 22 | # Buid the paths to the files to modify and publish. 23 | $moduleDirectoryPath = Join-Path $srcDirectoryPath 'Invoke-MsBuild' 24 | $moduleFilePath = Join-Path $moduleDirectoryPath 'Invoke-MsBuild.psm1' 25 | $manifestFilePath = Join-Path $moduleDirectoryPath 'Invoke-MsBuild.psd1' 26 | 27 | $gitHubUsername = 'deadlydog' 28 | $gitHubRepositoryName = 'Invoke-MsBuild' 29 | $powerShellGalleryNuGetPackageUrlWithTrailingSlash = 'https://www.powershellgallery.com/packages/Invoke-MsBuild/' 30 | 31 | # Dot-source in the other scripts containing functions this script will use. 32 | . $commonFunctionsScriptFilePath 33 | . $publishToPowerShellGalleryScriptFilePath 34 | . $publishToGitHubScriptFilePath 35 | 36 | Clear-Host 37 | 38 | # Regex patterns used to find the current version number and release notes. 39 | $scriptVersionNumberRegexPattern = '(?i)Version:\s*(?.*?)\s*$' 40 | $manifestVersionNumberRegexPattern = "(?i)ModuleVersion = '(?.*?)'" 41 | $manifestReleaseNotesRegexPattern = "(?is)ReleaseNotes = '(?.*?)'" 42 | 43 | # Get the script's current version number. 44 | $currentScriptVersionNumberMatches = Select-String -Path $moduleFilePath -Pattern $scriptVersionNumberRegexPattern | Select-Object -First 1 45 | if ($currentScriptVersionNumberMatches.Matches.Count -le 0 -or !$currentScriptVersionNumberMatches.Matches[0].Success) 46 | { throw "Could not find the script's current version number." } 47 | $currentScriptVersionNumberMatch = $currentScriptVersionNumberMatches.Matches[0] 48 | $currentScriptVersionNumber = $currentScriptVersionNumberMatch.Groups['Version'].Value 49 | $currentScriptVersionNumberLine = $currentScriptVersionNumberMatch.Value 50 | 51 | # Get the manifest's current version number. 52 | $currentManifestVersionNumberMatches = Select-String -Path $manifestFilePath -Pattern $manifestVersionNumberRegexPattern | Select-Object -First 1 53 | if ($currentManifestVersionNumberMatches.Matches.Count -le 0 -or !$currentManifestVersionNumberMatches.Matches[0].Success) 54 | { throw "Could not find the manifest's current version number." } 55 | $currentManifestVersionNumberMatch = $currentManifestVersionNumberMatches.Matches[0] 56 | $currentManifestVersionNumber = $currentManifestVersionNumberMatch.Groups['Version'].Value 57 | $currentManifestVersionNumberLine = $currentManifestVersionNumberMatch.Value 58 | 59 | # Get the manifest's current release notes. 60 | # We have to get the file contents first so that Select-String will search across multiple lines, since the release notes may span multiple lines. 61 | $manifestFileContents = Get-Content -Path $manifestFilePath -Raw 62 | $currentManifestReleaseNotesMatches = Select-String -InputObject $manifestFileContents -Pattern $manifestReleaseNotesRegexPattern | Select-Object -First 1 63 | if ($currentManifestReleaseNotesMatches.Matches.Count -le 0 -or !$currentManifestReleaseNotesMatches.Matches[0].Success) 64 | { throw "Could not find the manifests's current release notes." } 65 | $currentManifestReleaseNotesMatch = $currentManifestReleaseNotesMatches.Matches[0] 66 | $currentManifestReleaseNotes = $currentManifestReleaseNotesMatch.Groups['ReleaseNotes'].Value 67 | $currentManifestReleaseNotesLine = $currentManifestReleaseNotesMatch.Value 68 | 69 | ## Left here for debugging purposes 70 | #$currentScriptVersionNumber 71 | #$currentScriptVersionNumberLine 72 | #$currentManifestVersionNumber 73 | #$currentManifestVersionNumberLine 74 | #$currentManifestReleaseNotes 75 | #$currentManifestReleaseNotesLine 76 | 77 | # Prompt for what version number we should give the script. 78 | $newVersionNumber = Read-InputBoxDialog -WindowTitle 'Version Number' -Message "What should the script's Version Number be?" -DefaultText $currentScriptVersionNumber 79 | if ([string]::IsNullOrWhiteSpace($newVersionNumber)) { throw 'You must specify a version number.' } 80 | $newVersionNumber = $newVersionNumber.Trim() 81 | 82 | # Prompt for the release notes for this version. 83 | $newReleaseNotes = Read-MultiLineInputBoxDialog -WindowTitle 'Release Notes' -Message 'What release notes should be included with this version?' -DefaultText $currentManifestReleaseNotes 84 | if ($null -eq $newReleaseNotes) { throw 'You cancelled out of the release notes prompt.' } 85 | if ($newReleaseNotes.Contains("'")) 86 | { 87 | $errorMessage = 'Single quotes are not allowed in the Release Notes, as they break our ability to parse them with PowerShell. Exiting script.' 88 | Read-MessageBoxDialog -Message $errorMessage -WindowTitle 'Single Quotes Not Allowed In Release Notes' 89 | throw $errorMessage 90 | } 91 | $newReleaseNotes = $newReleaseNotes.Trim() 92 | 93 | # Build the new lines to insert into the files. Wrap manifest values in single quotes in case the are empty strings still (can't replace an empty string). 94 | $newScriptVersionNumberLine = $currentScriptVersionNumberLine.Replace($currentScriptVersionNumber, $newVersionNumber) 95 | $newManifestVersionNumberLine = $currentManifestVersionNumberLine.Replace("'$currentManifestVersionNumber'", "'$newVersionNumber'") 96 | $newManifestReleaseNotesLine = $currentManifestReleaseNotesLine.Replace("'$currentManifestReleaseNotes'", "'$newReleaseNotes'") 97 | 98 | # Update the version number and release notes in the module script and manifest. 99 | Replace-TextInFile -filePath $moduleFilePath -textToReplace $currentScriptVersionNumberLine -replacementText $newScriptVersionNumberLine 100 | Replace-TextInFile -filePath $manifestFilePath -textToReplace $currentManifestVersionNumberLine -replacementText $newManifestVersionNumberLine 101 | Replace-TextInFile -filePath $manifestFilePath -textToReplace $currentManifestReleaseNotesLine -replacementText $newManifestReleaseNotesLine 102 | 103 | Publish-ToPowerShellGallery -moduleDirectoryPath $moduleDirectoryPath -powerShellGalleryNuGetApiKey $PowerShellGalleryNuGetApiKey -isTestingThisScript $isTestingThisScript 104 | 105 | Read-MessageBoxDialog -WindowTitle "Commit and push changes to GitHub" -Message "Please commit the changes made by this script and push them to GitHub. This will ensure that the GitHub Release about to be created places the Tag on the correct (latest) commit. Click the OK button once that is done." -Buttons OK > $null 106 | 107 | $versionNumberIsAPreReleaseVersion = $newVersionNumber -match '-+|[a-zA-Z]+' # (e.g. 1.2.3-alpha). i.e. contains a dash or letters. 108 | $gitHubReleaseParameters = 109 | @{ 110 | GitHubUsername = $gitHubUsername 111 | GitHubRepositoryName = $gitHubRepositoryName 112 | GitHubAccessToken = $GitHubAccessToken 113 | ReleaseName = "$gitHubRepositoryName v" + $newVersionNumber 114 | TagName = "v" + $newVersionNumber 115 | ReleaseNotes = $newReleaseNotes 116 | AssetFilePaths = @($moduleFilePath, $manifestFilePath) 117 | IsPreRelease = $versionNumberIsAPreReleaseVersion 118 | IsDraft = $isTestingThisScript 119 | } 120 | Publish-NewReleaseToGitHub -gitHubReleaseParameters $gitHubReleaseParameters 121 | -------------------------------------------------------------------------------- /src/Invoke-MsBuild/Invoke-MsBuild.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'Invoke-MsBuild' 3 | # 4 | # Generated by: Dan.Schroeder 5 | # 6 | # Generated on: 3/1/2016 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = 'Invoke-MsBuild.psm1' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '2.7.1' 16 | 17 | # Supported PSEditions 18 | # CompatiblePSEditions = @() 19 | 20 | # ID used to uniquely identify this module 21 | GUID = '8ca20938-b92a-42a1-bf65-f644e16a8d9e' 22 | 23 | # Author of this module 24 | Author = 'Dan.Schroeder' 25 | 26 | # Company or vendor of this module 27 | # CompanyName = 'Unknown' 28 | 29 | # Copyright statement for this module 30 | Copyright = '(c) 2016 Dan.Schroeder. All rights reserved.' 31 | 32 | # Description of the functionality provided by this module 33 | Description = 'Executes the MSBuild.exe tool against the specified Visual Studio solution or project file.' 34 | 35 | # Minimum version of the Windows PowerShell engine required by this module 36 | PowerShellVersion = '2.0' 37 | 38 | # Name of the Windows PowerShell host required by this module 39 | # PowerShellHostName = '' 40 | 41 | # Minimum version of the Windows PowerShell host required by this module 42 | # PowerShellHostVersion = '' 43 | 44 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 45 | # DotNetFrameworkVersion = '' 46 | 47 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 48 | # CLRVersion = '' 49 | 50 | # Processor architecture (None, X86, Amd64) required by this module 51 | # ProcessorArchitecture = '' 52 | 53 | # Modules that must be imported into the global environment prior to importing this module 54 | # RequiredModules = @() 55 | 56 | # Assemblies that must be loaded prior to importing this module 57 | # RequiredAssemblies = @() 58 | 59 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 60 | # ScriptsToProcess = @() 61 | 62 | # Type files (.ps1xml) to be loaded when importing this module 63 | # TypesToProcess = @() 64 | 65 | # Format files (.ps1xml) to be loaded when importing this module 66 | # FormatsToProcess = @() 67 | 68 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 69 | # NestedModules = @() 70 | 71 | # 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. 72 | FunctionsToExport = @('Invoke-MsBuild') 73 | 74 | # 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. 75 | CmdletsToExport = @() 76 | 77 | # Variables to export from this module 78 | VariablesToExport = @() 79 | 80 | # 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. 81 | AliasesToExport = @() 82 | 83 | # DSC resources to export from this module 84 | # DscResourcesToExport = @() 85 | 86 | # List of all modules packaged with this module 87 | # ModuleList = @() 88 | 89 | # List of all files packaged with this module 90 | # FileList = @() 91 | 92 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 93 | PrivateData = @{ 94 | 95 | PSData = @{ 96 | 97 | # Tags applied to this module. These help with module discovery in online galleries. 98 | Tags = @('Invoke', 'MsBuild', 'Invoke-MsBuild', 'Build', 'Compile') 99 | 100 | # A URL to the license for this module. 101 | LicenseUri = 'https://github.com/deadlydog/Invoke-MsBuild/blob/master/license.md' 102 | 103 | # A URL to the main website for this project. 104 | ProjectUri = 'https://github.com/deadlydog/Invoke-MsBuild' 105 | 106 | # A URL to an icon representing this module. 107 | # IconUri = '' 108 | 109 | # ReleaseNotes of this module 110 | ReleaseNotes = 'Fixes: 111 | 112 | - Compare MSBuild version numbers in other languages correctly.' 113 | 114 | } # End of PSData hashtable 115 | 116 | } # End of PrivateData hashtable 117 | 118 | # HelpInfo URI of this module 119 | # HelpInfoURI = '' 120 | 121 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 122 | # DefaultCommandPrefix = '' 123 | 124 | } 125 | 126 | -------------------------------------------------------------------------------- /src/Invoke-MsBuild/Invoke-MsBuild.psm1: -------------------------------------------------------------------------------- 1 | #Requires -Version 2.0 2 | 3 | function Invoke-MsBuild 4 | { 5 | <# 6 | .SYNOPSIS 7 | Builds the given Visual Studio solution or project file using MsBuild. 8 | 9 | .DESCRIPTION 10 | Executes the MsBuild.exe tool against the specified Visual Studio solution or project file. 11 | 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). 12 | If using the PathThru switch, the process running MsBuild is returned instead. 13 | 14 | .PARAMETER Path 15 | The path of the Visual Studio solution or project to build (e.g. a .sln or .csproj file). 16 | 17 | .PARAMETER MsBuildParameters 18 | Additional parameters to pass to the MsBuild command-line tool. This can be any valid MsBuild command-line parameters except for the path of 19 | the solution/project to build. 20 | 21 | See http://msdn.microsoft.com/en-ca/library/vstudio/ms164311.aspx for valid MsBuild command-line parameters. 22 | 23 | .PARAMETER Use32BitMsBuild 24 | 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. 25 | 26 | .PARAMETER BuildLogDirectoryPath 27 | The directory path to write the build log files to. 28 | Defaults to putting the log files in the users temp directory (e.g. C:\Users\[User Name]\AppData\Local\Temp). 29 | Use the keyword "PathDirectory" to put the log files in the same directory as the .sln or project file being built. 30 | Two log files are generated: one with the complete build log, and one that contains only errors from the build. 31 | 32 | .PARAMETER LogVerbosity 33 | 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]. 34 | 35 | .PARAMETER AutoLaunchBuildLogOnFailure 36 | If set, this switch will cause the build log to automatically be launched into the default viewer if the build fails. 37 | This log file contains all of the build output. 38 | NOTE: This switch cannot be used with the PassThru switch. 39 | 40 | .PARAMETER AutoLaunchBuildErrorsLogOnFailure 41 | If set, this switch will cause the build errors log to automatically be launched into the default viewer if the build fails. 42 | This log file only contains errors from the build output. 43 | NOTE: This switch cannot be used with the PassThru switch. 44 | 45 | .PARAMETER KeepBuildLogOnSuccessfulBuilds 46 | 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. 47 | NOTE: This switch cannot be used with the PassThru switch. 48 | 49 | .PARAMETER ShowBuildOutputInNewWindow 50 | If set, this switch will cause a command prompt window to be shown in order to view the progress of the build. 51 | By default the build output is not shown in any window. 52 | NOTE: This switch cannot be used with the ShowBuildOutputInCurrentWindow switch. 53 | 54 | .PARAMETER ShowBuildOutputInCurrentWindow 55 | If set, this switch will cause the build process to be started in the existing console window, instead of creating a new one. 56 | By default the build output is not shown in any window. 57 | NOTE: This switch will override the ShowBuildOutputInNewWindow switch. 58 | NOTE: There is a problem with the -NoNewWindow parameter of the Start-Process cmdlet; this is used for the ShowBuildOutputInCurrentWindow switch. 59 | 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. 60 | To avoid the build process from appearing to hang, PromptForInputBeforeClosing only has an effect with ShowBuildOutputInCurrentWindow when running 61 | 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.). 62 | 63 | .PARAMETER PromptForInputBeforeClosing 64 | If set, this switch will prompt the user for input after the build completes, and will not continue until the user presses a key. 65 | NOTE: This switch only has an effect when used with the ShowBuildOutputInNewWindow and ShowBuildOutputInCurrentWindow switches (otherwise build output is not displayed). 66 | NOTE: This switch cannot be used with the PassThru switch. 67 | 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). 68 | NOTE: To avoid the build process from appearing to hang, PromptForInputBeforeClosing only has an effect with ShowBuildOutputInCurrentWindow when running 69 | 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.). 70 | 71 | .PARAMETER MsBuildFilePath 72 | By default this script will locate and use the latest version of MsBuild.exe on the machine. 73 | 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. 74 | 75 | .PARAMETER VisualStudioDeveloperCommandPromptFilePath 76 | By default this script will locate and use the latest version of the Visual Studio Developer Command Prompt to run MsBuild. 77 | 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 78 | the Visual Studio Command Prompt to use. The filename is typically VsDevCmd.bat. 79 | 80 | .PARAMETER BypassVisualStudioDeveloperCommandPrompt 81 | By default this script will locate and use the latest version of the Visual Studio Developer Command Prompt to run MsBuild. 82 | The Visual Studio Developer Command Prompt loads additional variables and paths, so it is sometimes able to build project types that MsBuild cannot build by itself alone. 83 | However, loading those additional variables and paths sometimes may have a performance impact, so this switch may be provided to bypass it and just use MsBuild directly. 84 | 85 | .PARAMETER PassThru 86 | If set, this switch will cause the calling script not to wait until the build (launched in another process) completes before continuing execution. 87 | Instead the build will be started in a new process and that process will immediately be returned, allowing the calling script to continue 88 | execution while the build is performed, and also to inspect the process to see when it completes. 89 | NOTE: This switch cannot be used with the AutoLaunchBuildLogOnFailure, AutoLaunchBuildErrorsLogOnFailure, KeepBuildLogOnSuccessfulBuilds, or PromptForInputBeforeClosing switches. 90 | 91 | .PARAMETER WhatIf 92 | If set, the build will not actually be performed. 93 | 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. 94 | 95 | .OUTPUTS 96 | When the -PassThru switch is provided, the process being used to run MsBuild.exe is returned. 97 | When the -PassThru switch is not provided, a hash table with the following properties is returned: 98 | 99 | BuildSucceeded = $true if the build passed, $false if the build failed, and $null if we are not sure. 100 | BuildLogFilePath = The path to the build's log file. 101 | BuildErrorsLogFilePath = The path to the build's error log file. 102 | ItemToBuildFilePath = The item that MsBuild ran against. 103 | CommandUsedToBuild = The full command that was used to invoke MsBuild. This can be useful for inspecting what parameters are passed to MsBuild.exe. 104 | Message = A message describing any problems that were encountered by Invoke-MsBuild. This is typically an empty string unless something went wrong. 105 | MsBuildProcess = The process that was used to execute MsBuild.exe. 106 | BuildDuration = The amount of time the build took to complete, represented as a TimeSpan. 107 | 108 | .EXAMPLE 109 | $buildResult = Invoke-MsBuild -Path "C:\Some Folder\MySolution.sln" 110 | 111 | if ($buildResult.BuildSucceeded -eq $true) 112 | { 113 | Write-Output ("Build completed successfully in {0:N1} seconds." -f $buildResult.BuildDuration.TotalSeconds) 114 | } 115 | elseif ($buildResult.BuildSucceeded -eq $false) 116 | { 117 | Write-Output ("Build failed after {0:N1} seconds. Check the build log file '$($buildResult.BuildLogFilePath)' for errors." -f $buildResult.BuildDuration.TotalSeconds) 118 | } 119 | elseif ($null -eq $buildResult.BuildSucceeded) 120 | { 121 | Write-Output "Unsure if build passed or failed: $($buildResult.Message)" 122 | } 123 | 124 | Perform the default MsBuild actions on the Visual Studio solution to build the projects in it, and returns a hash table containing the results. 125 | The PowerShell script will halt execution until MsBuild completes. 126 | 127 | .EXAMPLE 128 | $process = Invoke-MsBuild -Path "C:\Some Folder\MySolution.sln" -PassThru 129 | 130 | while (!$process.HasExited) 131 | { 132 | Write-Host "Solution is still building..." 133 | Start-Sleep -Seconds 1 134 | } 135 | 136 | Perform the default MsBuild actions on the Visual Studio solution to build the projects in it. 137 | 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. 138 | You can check the process's HasExited property to check if the build has completed yet or not. 139 | 140 | .EXAMPLE 141 | if ((Invoke-MsBuild -Path $pathToSolution).BuildSucceeded -eq $true) 142 | { 143 | Write-Output "Build completed successfully." 144 | } 145 | 146 | Perform the build against the file specified at $pathToSolution and checks it for success in a single line. 147 | 148 | .EXAMPLE 149 | Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -MsBuildParameters "/target:Clean;Build" -ShowBuildOutputInNewWindow 150 | 151 | Cleans then Builds the given C# project. 152 | 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. 153 | 154 | .EXAMPLE 155 | Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -ShowBuildOutputInCurrentWindow 156 | 157 | Builds the given C# project and displays the output from MsBuild in the current terminal window. 158 | 159 | .EXAMPLE 160 | Invoke-MsBuild -Path "C:\MySolution.sln" -Params "/target:Clean;Build /property:Configuration=Release;Platform=x64;BuildInParallel=true /verbosity:Detailed /maxcpucount" 161 | 162 | Cleans then Builds the given solution, specifying to build the project in parallel in the Release configuration for the x64 platform. 163 | Here the shorter "Params" alias is used instead of the full "MsBuildParameters" parameter name. 164 | 165 | .EXAMPLE 166 | Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -ShowBuildOutputInNewWindow -PromptForInputBeforeClosing -AutoLaunchBuildLogOnFailure 167 | 168 | Builds the given C# project. 169 | 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 170 | 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. 171 | If the build fails, the build log will automatically be opened in the default text viewer. 172 | 173 | .EXAMPLE 174 | Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -BuildLogDirectoryPath "C:\BuildLogs" -KeepBuildLogOnSuccessfulBuilds -AutoLaunchBuildErrorsLogOnFailure 175 | 176 | Builds the given C# project. 177 | The build log will be saved in "C:\BuildLogs", and they will not be automatically deleted even if the build succeeds. 178 | If the build fails, the build errors log will automatically be opened in the default text viewer. 179 | 180 | .EXAMPLE 181 | Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -BuildLogDirectoryPath PathDirectory 182 | 183 | Builds the given C# project. 184 | 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). 185 | 186 | .EXAMPLE 187 | 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" 188 | 189 | Deploy the Visual Studio Database Project to the database "MyDatabase". 190 | Here the shorter "P" alias is used instead of the full "MsBuildParameters" parameter name. 191 | The shorter alias' of the MsBuild parameters are also used; "/t" instead of "/target", and "/p" instead of "/property". 192 | 193 | .EXAMPLE 194 | Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -WhatIf 195 | 196 | Returns the result hash table containing the same property values that would be created if the build was ran with the same parameters. 197 | The BuildSucceeded property will be $null since no build will actually be invoked. 198 | This will display all of the returned hash table's properties and their values. 199 | 200 | .EXAMPLE 201 | Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" > $null 202 | 203 | Builds the given C# project, discarding the result hash table and not displaying its properties. 204 | 205 | .LINK 206 | Project home: https://github.com/deadlydog/Invoke-MsBuild 207 | 208 | .NOTES 209 | Name: Invoke-MsBuild 210 | 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) 211 | Version: 2.7.1 212 | #> 213 | [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName="Wait")] 214 | param 215 | ( 216 | [parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true,HelpMessage="The path to the file to build with MsBuild (e.g. a .sln or .csproj file).")] 217 | [ValidateScript({Test-Path -LiteralPath $_ -PathType Leaf})] 218 | [string] $Path, 219 | 220 | [parameter(Mandatory=$false)] 221 | [Alias("Parameters","Params","P")] 222 | [string] $MsBuildParameters, 223 | 224 | [parameter(Mandatory=$false)] 225 | [switch] $Use32BitMsBuild, 226 | 227 | [parameter(Mandatory=$false,HelpMessage="The directory path to write the build log file to. Use the keyword 'PathDirectory' to put the log file in the same directory as the .sln or project file being built.")] 228 | [ValidateNotNullOrEmpty()] 229 | [Alias("LogDirectory","L")] 230 | [string] $BuildLogDirectoryPath = $env:Temp, 231 | 232 | [parameter(Mandatory=$false)] 233 | [ValidateSet('q','quiet','m','minimal','n','normal','d','detailed','diag','diagnostic')] 234 | [string] $LogVerbosityLevel = 'normal', 235 | 236 | [parameter(Mandatory=$false,ParameterSetName="Wait")] 237 | [ValidateNotNullOrEmpty()] 238 | [switch] $AutoLaunchBuildLogOnFailure, 239 | 240 | [parameter(Mandatory=$false,ParameterSetName="Wait")] 241 | [ValidateNotNullOrEmpty()] 242 | [switch] $AutoLaunchBuildErrorsLogOnFailure, 243 | 244 | [parameter(Mandatory=$false,ParameterSetName="Wait")] 245 | [ValidateNotNullOrEmpty()] 246 | [switch] $KeepBuildLogOnSuccessfulBuilds, 247 | 248 | [parameter(Mandatory=$false)] 249 | [Alias("ShowBuildWindow")] 250 | [switch] $ShowBuildOutputInNewWindow, 251 | 252 | [parameter(Mandatory=$false)] 253 | [switch] $ShowBuildOutputInCurrentWindow, 254 | 255 | [parameter(Mandatory=$false,ParameterSetName="Wait")] 256 | [switch] $PromptForInputBeforeClosing, 257 | 258 | [parameter(Mandatory=$false)] 259 | [ValidateScript({Test-Path -LiteralPath $_ -PathType Leaf})] 260 | [string] $MsBuildFilePath, 261 | 262 | [parameter(Mandatory=$false)] 263 | [ValidateScript({Test-Path -LiteralPath $_ -PathType Leaf})] 264 | [string] $VisualStudioDeveloperCommandPromptFilePath, 265 | 266 | [parameter(Mandatory=$false)] 267 | [switch] $BypassVisualStudioDeveloperCommandPrompt, 268 | 269 | [parameter(Mandatory=$false,ParameterSetName="PassThru")] 270 | [switch] $PassThru 271 | ) 272 | 273 | BEGIN { } 274 | END { } 275 | PROCESS 276 | { 277 | # Turn on Strict Mode to help catch syntax-related errors. 278 | # This must come after a script's/function's param section. 279 | # Forces a function to be the first non-comment code to appear in a PowerShell Script/Module. 280 | Set-StrictMode -Version Latest 281 | 282 | # 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. 283 | [System.Threading.Thread]::CurrentThread.CurrentCulture = [CultureInfo]::InvariantCulture 284 | 285 | # 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. 286 | if (!(Test-Path Variable:Private:AutoLaunchBuildLogOnFailure)) { $AutoLaunchBuildLogOnFailure = $false } 287 | if (!(Test-Path Variable:Private:AutoLaunchBuildErrorsLogOnFailure)) { $AutoLaunchBuildErrorsLogOnFailure = $false } 288 | if (!(Test-Path Variable:Private:KeepBuildLogOnSuccessfulBuilds)) { $KeepBuildLogOnSuccessfulBuilds = $false } 289 | if (!(Test-Path Variable:Private:PromptForInputBeforeClosing)) { $PromptForInputBeforeClosing = $false } 290 | if (!(Test-Path Variable:Private:PassThru)) { $PassThru = $false } 291 | 292 | # If the keyword was supplied, place the log in the same folder as the solution/project being built. 293 | if ($BuildLogDirectoryPath.Equals("PathDirectory", [System.StringComparison]::InvariantCultureIgnoreCase)) 294 | { 295 | $BuildLogDirectoryPath = [System.IO.Path]::GetDirectoryName($Path) 296 | } 297 | 298 | # Always get the full path to the Log files directory. 299 | $BuildLogDirectoryPath = [System.IO.Path]::GetFullPath($BuildLogDirectoryPath) 300 | 301 | # Local Variables. 302 | $solutionFileName = (Get-ItemProperty -LiteralPath $Path).Name 303 | $buildLogFilePath = (Join-Path -Path $BuildLogDirectoryPath -ChildPath $solutionFileName) + ".msbuild.log" 304 | $buildErrorsLogFilePath = (Join-Path -Path $BuildLogDirectoryPath -ChildPath $solutionFileName) + ".msbuild.errors.log" 305 | $windowStyleOfNewWindow = if ($ShowBuildOutputInNewWindow) { "Normal" } else { "Hidden" } 306 | 307 | # Build our hash table that will be returned. 308 | $result = @{} 309 | $result.BuildSucceeded = $null 310 | $result.BuildLogFilePath = $buildLogFilePath 311 | $result.BuildErrorsLogFilePath = $buildErrorsLogFilePath 312 | $result.ItemToBuildFilePath = $Path 313 | $result.CommandUsedToBuild = [string]::Empty 314 | $result.Message = [string]::Empty 315 | $result.MsBuildProcess = $null 316 | $result.BuildDuration = [TimeSpan]::Zero 317 | 318 | # Try and build the solution. 319 | try 320 | { 321 | # Get the verbosity to use for the MsBuild log file. 322 | $verbosityLevel = switch ($LogVerbosityLevel) { 323 | { ($_ -eq "q") -or ($_ -eq "quiet") -or ` 324 | ($_ -eq "m") -or ($_ -eq "minimal") -or ` 325 | ($_ -eq "n") -or ($_ -eq "normal") -or ` 326 | ($_ -eq "d") -or ($_ -eq "detailed") -or ` 327 | ($_ -eq "diag") -or ($_ -eq "diagnostic") } { ";verbosity=$_" ;break } 328 | default { "" } 329 | } 330 | 331 | # Build the arguments to pass to MsBuild. 332 | $buildArguments = """$Path"" $MsBuildParameters /fileLoggerParameters:LogFile=""$buildLogFilePath""$verbosityLevel /fileLoggerParameters1:LogFile=""$buildErrorsLogFilePath"";errorsonly" 333 | 334 | # Get the path to the MsBuild executable. 335 | $msBuildPath = $MsBuildFilePath 336 | [bool] $msBuildPathWasNotProvided = [string]::IsNullOrEmpty($msBuildPath) 337 | if ($msBuildPathWasNotProvided) 338 | { 339 | $msBuildPath = Get-LatestMsBuildPath -Use32BitMsBuild:$Use32BitMsBuild 340 | } 341 | 342 | # If we plan on trying to use the VS Command Prompt, we'll need to get the path to it. 343 | [bool] $vsCommandPromptPathWasFound = $false 344 | if (!$BypassVisualStudioDeveloperCommandPrompt) 345 | { 346 | # Get the path to the Visual Studio Developer Command Prompt file. 347 | $vsCommandPromptPath = $VisualStudioDeveloperCommandPromptFilePath 348 | [bool] $vsCommandPromptPathWasNotProvided = [string]::IsNullOrEmpty($vsCommandPromptPath) 349 | if ($vsCommandPromptPathWasNotProvided) 350 | { 351 | $vsCommandPromptPath = Get-LatestVisualStudioCommandPromptPath 352 | } 353 | $vsCommandPromptPathWasFound = ![string]::IsNullOrEmpty($vsCommandPromptPath) 354 | } 355 | 356 | # If we should use the VS Command Prompt, call MsBuild from that since it sets environmental variables that may be needed to build some projects types (e.g. XNA). 357 | $useVsCommandPrompt = !$BypassVisualStudioDeveloperCommandPrompt -and $vsCommandPromptPathWasFound 358 | if ($useVsCommandPrompt) 359 | { 360 | $cmdArgumentsToRunMsBuild = "/k "" ""$vsCommandPromptPath"" & ""$msBuildPath"" " 361 | } 362 | # Else we won't be using the VS Command Prompt, so just build using MsBuild directly. 363 | else 364 | { 365 | $cmdArgumentsToRunMsBuild = "/k "" ""$msBuildPath"" " 366 | } 367 | 368 | # Append the MsBuild arguments to pass into cmd.exe in order to do the build. 369 | $cmdArgumentsToRunMsBuild += "$buildArguments " 370 | 371 | # If necessary, add a pause to wait for input before exiting the cmd.exe window. 372 | # No pausing allowed when using PassThru or not showing the build output. 373 | # The -NoNewWindow parameter of Start-Process does not behave correctly in the ISE and other PowerShell hosts (doesn't display any build output), 374 | # so only allow it if in the default PowerShell host, since we know that one works. 375 | $pauseForInput = [string]::Empty 376 | if ($PromptForInputBeforeClosing -and !$PassThru ` 377 | -and ($ShowBuildOutputInNewWindow -or ($ShowBuildOutputInCurrentWindow -and $Host.Name -eq "ConsoleHost"))) 378 | { $pauseForInput = "Pause & " } 379 | $cmdArgumentsToRunMsBuild += "& $pauseForInput Exit"" " 380 | 381 | # Record the exact command used to perform the build to make it easier to troubleshoot issues with builds. 382 | $result.CommandUsedToBuild = "cmd.exe $cmdArgumentsToRunMsBuild" 383 | 384 | # If we don't actually want to perform a build (i.e. the -WhatIf parameter was specified), return the object without actually doing the build. 385 | if (!($pscmdlet.ShouldProcess($result.ItemToBuildFilePath, 'MsBuild'))) 386 | { 387 | $result.BuildSucceeded = $null 388 | $result.Message = "The '-WhatIf' switch was specified, so a build was not invoked." 389 | return $result 390 | } 391 | 392 | Write-Debug "Starting new cmd.exe process with arguments ""$cmdArgumentsToRunMsBuild""." 393 | 394 | # Perform the build. 395 | if ($PassThru) 396 | { 397 | if ($ShowBuildOutputInCurrentWindow) 398 | { 399 | return Start-Process cmd.exe -ArgumentList $cmdArgumentsToRunMsBuild -NoNewWindow -PassThru 400 | } 401 | else 402 | { 403 | return Start-Process cmd.exe -ArgumentList $cmdArgumentsToRunMsBuild -WindowStyle $windowStyleOfNewWindow -PassThru 404 | } 405 | } 406 | else 407 | { 408 | $performBuildScriptBlock = 409 | { 410 | if ($ShowBuildOutputInCurrentWindow) 411 | { 412 | $result.MsBuildProcess = Start-Process cmd.exe -ArgumentList $cmdArgumentsToRunMsBuild -NoNewWindow -PassThru 413 | } 414 | else 415 | { 416 | $result.MsBuildProcess = Start-Process cmd.exe -ArgumentList $cmdArgumentsToRunMsBuild -WindowStyle $windowStyleOfNewWindow -PassThru 417 | } 418 | 419 | Wait-Process -InputObject $result.MsBuildProcess 420 | } 421 | 422 | # Perform the build and record how long it takes. 423 | $result.BuildDuration = (Measure-Command -Expression $performBuildScriptBlock) 424 | } 425 | } 426 | # If the build crashed, return that the build didn't succeed. 427 | catch 428 | { 429 | $errorMessage = $_ 430 | $result.Message = "Unexpected error occurred while building ""$Path"": $errorMessage" 431 | $result.BuildSucceeded = $false 432 | 433 | Write-Error ($result.Message) 434 | return $result 435 | } 436 | 437 | # If we can't find the build's log file in order to inspect it, write a warning and return null. 438 | if (!(Test-Path -LiteralPath $buildLogFilePath -PathType Leaf)) 439 | { 440 | $result.BuildSucceeded = $null 441 | $result.Message = "Cannot find the build log file at '$buildLogFilePath', so unable to determine if build succeeded or not." 442 | 443 | Write-Warning ($result.Message) 444 | return $result 445 | } 446 | 447 | # Get if the build succeeded or not. 448 | [bool] $buildOutputDoesNotContainFailureMessage = $null -eq (Select-String -Path $buildLogFilePath -Pattern "Build FAILED." -SimpleMatch) 449 | [bool] $buildReturnedSuccessfulExitCode = $result.MsBuildProcess.ExitCode -eq 0 450 | $buildSucceeded = $buildOutputDoesNotContainFailureMessage -and $buildReturnedSuccessfulExitCode 451 | 452 | # If the build succeeded. 453 | if ($buildSucceeded) 454 | { 455 | $result.BuildSucceeded = $true 456 | 457 | # If we shouldn't keep the log files around, delete them. 458 | if (!$KeepBuildLogOnSuccessfulBuilds) 459 | { 460 | if (Test-Path -LiteralPath $buildLogFilePath -PathType Leaf) { Remove-Item -LiteralPath $buildLogFilePath -Force } 461 | if (Test-Path -LiteralPath $buildErrorsLogFilePath -PathType Leaf) { Remove-Item -LiteralPath $buildErrorsLogFilePath -Force } 462 | } 463 | } 464 | # Else at least one of the projects failed to build. 465 | else 466 | { 467 | $result.BuildSucceeded = $false 468 | $result.Message = "FAILED to build ""$Path"". Please check the build log ""$buildLogFilePath"" for details." 469 | 470 | # Write the error message as a warning. 471 | Write-Warning ($result.Message) 472 | 473 | # If we should show the build logs automatically, open them with the default viewer. 474 | if($AutoLaunchBuildLogOnFailure) 475 | { 476 | Open-BuildLogFileWithDefaultProgram -FilePathToOpen $buildLogFilePath -Result ([ref]$result) 477 | } 478 | if($AutoLaunchBuildErrorsLogOnFailure) 479 | { 480 | Open-BuildLogFileWithDefaultProgram -FilePathToOpen $buildErrorsLogFilePath -Result ([ref]$result) 481 | } 482 | } 483 | 484 | # Return the results of the build. 485 | return $result 486 | } 487 | } 488 | 489 | function Open-BuildLogFileWithDefaultProgram([string]$FilePathToOpen, [ref]$Result) 490 | { 491 | if (Test-Path -LiteralPath $FilePathToOpen -PathType Leaf) 492 | { 493 | Start-Process -verb "Open" $FilePathToOpen 494 | } 495 | else 496 | { 497 | $message = "Could not auto-launch the build log because the expected file does not exist at '$FilePathToOpen'." 498 | $Result.Message += [System.Environment]::NewLine + $message 499 | Write-Warning $message 500 | } 501 | } 502 | 503 | function Get-LatestVisualStudioCommandPromptPath 504 | { 505 | <# 506 | .SYNOPSIS 507 | Gets the file path to the latest Visual Studio Command Prompt. Returns $null if a path is not found. 508 | 509 | .DESCRIPTION 510 | Gets the file path to the latest Visual Studio Command Prompt. Returns $null if a path is not found. 511 | #> 512 | [string] $vsCommandPromptPath = Get-VisualStudioCommandPromptPathForVisualStudio2017AndNewer 513 | 514 | # If VS 2017 or newer VS Command Prompt was not found, check for older versions of VS Command Prompt. 515 | if ([string]::IsNullOrEmpty($vsCommandPromptPath)) 516 | { 517 | $vsCommandPromptPath = Get-VisualStudioCommandPromptPathForVisualStudio2015AndPrior 518 | } 519 | 520 | return $vsCommandPromptPath 521 | } 522 | 523 | function Get-VisualStudioCommandPromptPathForVisualStudio2017AndNewer 524 | { 525 | # Later we can probably make use of the VSSetup.PowerShell module to find the MsBuild.exe: https://github.com/Microsoft/vssetup.powershell 526 | # Or perhaps the VsWhere.exe: https://github.com/Microsoft/vswhere 527 | # But for now, to keep this script PowerShell 2.0 compatible and not rely on external executables, let's look for it ourselves in known locations. 528 | # Example of known locations: 529 | # "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\Tools\VsDevCmd.bat" 530 | 531 | [string] $visualStudioDirectoryPath = Get-CommonVisualStudioDirectoryPath 532 | [bool] $visualStudioDirectoryPathDoesNotExist = [string]::IsNullOrEmpty($visualStudioDirectoryPath) 533 | if ($visualStudioDirectoryPathDoesNotExist) 534 | { 535 | return $null 536 | } 537 | 538 | # First search for the VS Command Prompt in the expected locations (faster). 539 | $expectedVsCommandPromptPathWithWildcards = "$visualStudioDirectoryPath\*\*\Common7\Tools\VsDevCmd.bat" 540 | $vsCommandPromptPathObjects = Get-Item -Path $expectedVsCommandPromptPathWithWildcards 541 | 542 | [bool] $vsCommandPromptWasNotFound = ($null -eq $vsCommandPromptPathObjects) -or ($vsCommandPromptPathObjects.Length -eq 0) 543 | if ($vsCommandPromptWasNotFound) 544 | { 545 | # Recursively search the entire Microsoft Visual Studio directory for the VS Command Prompt (slower, but will still work if MS changes folder structure). 546 | Write-Verbose "The Visual Studio Command Prompt was not found at an expected location. Searching more locations, but this will be a little slow." 547 | $vsCommandPromptPathObjects = Get-ChildItem -Path $visualStudioDirectoryPath -Recurse | Where-Object { $_.Name -ieq 'VsDevCmd.bat' } 548 | } 549 | 550 | $vsCommandPromptPathObjectsSortedWithNewestVersionsFirst = $vsCommandPromptPathObjects | Sort-Object -Property FullName -Descending 551 | 552 | $newestVsCommandPromptPath = $vsCommandPromptPathObjectsSortedWithNewestVersionsFirst | Select-Object -ExpandProperty FullName -First 1 553 | return $newestVsCommandPromptPath 554 | } 555 | 556 | function Get-VisualStudioCommandPromptPathForVisualStudio2015AndPrior 557 | { 558 | # Get some environmental paths. 559 | $vs2015CommandPromptPath = $env:VS140COMNTOOLS + 'VsDevCmd.bat' 560 | $vs2013CommandPromptPath = $env:VS120COMNTOOLS + 'VsDevCmd.bat' 561 | $vs2012CommandPromptPath = $env:VS110COMNTOOLS + 'VsDevCmd.bat' 562 | $vs2010CommandPromptPath = $env:VS100COMNTOOLS + 'vcvarsall.bat' 563 | $potentialVsCommandPromptPaths = @($vs2015CommandPromptPath, $vs2013CommandPromptPath, $vs2012CommandPromptPath, $vs2010CommandPromptPath) 564 | 565 | # Store the VS Command Prompt to do the build in, if one exists. 566 | $newestVsCommandPromptPath = $null 567 | foreach ($path in $potentialVsCommandPromptPaths) 568 | { 569 | [bool] $pathExists = (![string]::IsNullOrEmpty($path)) -and (Test-Path -LiteralPath $path -PathType Leaf) 570 | if ($pathExists) 571 | { 572 | $newestVsCommandPromptPath = $path 573 | break 574 | } 575 | } 576 | 577 | # Return the path to the VS Command Prompt if it was found. 578 | return $newestVsCommandPromptPath 579 | } 580 | 581 | function Get-LatestMsBuildPath_NotUsedYetButCouldBeIfNeeded 582 | { 583 | [bool] $vsSetupExists = $null -ne (Get-Command Get-VSSetupInstance -ErrorAction SilentlyContinue) 584 | if (!$vsSetupExists) 585 | { 586 | Write-Verbose "Importing the VSSetup module in order to determine TF.exe path..." -Verbose 587 | Install-Module VSSetup -Scope CurrentUser -Force 588 | } 589 | [string] $visualStudioInstallationPath = (Get-VSSetupInstance | Select-VSSetupInstance -Latest -Require Microsoft.Component.MSBuild).InstallationPath 590 | $msBuildExecutableFilePath = (Get-ChildItem $visualStudioInstallationPath -Recurse -Filter "MsBuild.exe" | Select-Object -First 1).FullName 591 | return $msBuildExecutableFilePath 592 | } 593 | 594 | function Get-LatestMsBuildPath([switch] $Use32BitMsBuild) 595 | { 596 | <# 597 | .SYNOPSIS 598 | Gets the path to the latest version of MsBuild.exe. Throws an exception if MsBuild.exe is not found. 599 | 600 | .DESCRIPTION 601 | Gets the path to the latest version of MsBuild.exe. Throws an exception if MsBuild.exe is not found. 602 | #> 603 | 604 | [string] $msBuildPath = $null 605 | $msBuildPath = Get-MsBuildPathForVisualStudio2017AndNewer -Use32BitMsBuild $Use32BitMsBuild 606 | 607 | # If VS 2017 or newer MsBuild.exe was not found, check for older versions of MsBuild. 608 | if ([string]::IsNullOrEmpty($msBuildPath)) 609 | { 610 | $msBuildPath = Get-MsBuildPathForVisualStudio2015AndPrior -Use32BitMsBuild $Use32BitMsBuild 611 | } 612 | 613 | [bool] $msBuildPathWasNotFound = [string]::IsNullOrEmpty($msBuildPath) 614 | if ($msBuildPathWasNotFound) 615 | { 616 | throw 'Could not determine where to find MsBuild.exe.' 617 | } 618 | 619 | [bool] $msBuildExistsAtThePathFound = (Test-Path -LiteralPath $msBuildPath -PathType Leaf) 620 | if(!$msBuildExistsAtThePathFound) 621 | { 622 | throw "MsBuild.exe does not exist at the expected path, '$msBuildPath'." 623 | } 624 | 625 | return $msBuildPath 626 | } 627 | 628 | function Get-MsBuildPathForVisualStudio2017AndNewer([bool] $Use32BitMsBuild) 629 | { 630 | # Later we can probably make use of the VSSetup.PowerShell module to find the MsBuild.exe: https://github.com/Microsoft/vssetup.powershell 631 | # Or perhaps the VsWhere.exe: https://github.com/Microsoft/vswhere 632 | # But for now, to keep this script PowerShell 2.0 compatible and not rely on external executables, let's look for it ourselves in known locations. 633 | # Example of known locations: 634 | # "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\MSBuild.exe" - 32 bit 635 | # "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\amd64\MSBuild.exe" - 64 bit 636 | # "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe" -32 bit 637 | # "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\amd64\MSBuild.exe" - 64 bit 638 | 639 | [string] $visualStudioDirectoryPath = Get-CommonVisualStudioDirectoryPath 640 | [bool] $visualStudioDirectoryPathDoesNotExist = [string]::IsNullOrEmpty($visualStudioDirectoryPath) 641 | if ($visualStudioDirectoryPathDoesNotExist) 642 | { 643 | return $null 644 | } 645 | 646 | # First search for MsBuild in the expected 32 and 64 bit locations (faster). 647 | $expected32bitPathWithWildcards = "$visualStudioDirectoryPath\*\*\MsBuild\*\Bin\MsBuild.exe" 648 | $expected64bitPathWithWildcards = "$visualStudioDirectoryPath\*\*\MsBuild\*\Bin\amd64\MsBuild.exe" 649 | $msBuildPathObjects = Get-Item -Path $expected32bitPathWithWildcards, $expected64bitPathWithWildcards 650 | 651 | [bool] $msBuildWasNotFound = ($null -eq $msBuildPathObjects) -or ($msBuildPathObjects.Length -eq 0) 652 | if ($msBuildWasNotFound) 653 | { 654 | # Recursively search the entire Microsoft Visual Studio directory for MsBuild (slower, but will still work if MS changes folder structure). 655 | Write-Verbose "MsBuild.exe was not found at an expected location. Searching more locations, but this will be a little slow." 656 | $msBuildPathObjects = Get-ChildItem -Path $visualStudioDirectoryPath -Recurse | Where-Object { $_.Name -ieq 'MsBuild.exe' } 657 | } 658 | 659 | $msBuildPathObjectsSortedWithNewestVersionsFirst = $msBuildPathObjects | Sort-Object -Property FullName -Descending 660 | 661 | $newest32BitMsBuildPath = $msBuildPathObjectsSortedWithNewestVersionsFirst | Where-Object { $_.Directory.Name -ine 'amd64' } | Select-Object -ExpandProperty FullName -First 1 662 | $newest64BitMsBuildPath = $msBuildPathObjectsSortedWithNewestVersionsFirst | Where-Object { $_.Directory.Name -ieq 'amd64' } | Select-Object -ExpandProperty FullName -First 1 663 | 664 | if ($Use32BitMsBuild) 665 | { 666 | return $newest32BitMsBuildPath 667 | } 668 | return $newest64BitMsBuildPath 669 | } 670 | 671 | function Get-MsBuildPathForVisualStudio2015AndPrior([bool] $Use32BitMsBuild) 672 | { 673 | $registryPathToMsBuildToolsVersions = 'HKLM:\SOFTWARE\Microsoft\MSBuild\ToolsVersions\' 674 | if ($Use32BitMsBuild) 675 | { 676 | # If the 32-bit path exists, use it, otherwise stick with the current path (which will be the 64-bit path on 64-bit machines, and the 32-bit path on 32-bit machines). 677 | $registryPathTo32BitMsBuildToolsVersions = 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\MSBuild\ToolsVersions\' 678 | if (Test-Path -Path $registryPathTo32BitMsBuildToolsVersions) 679 | { 680 | $registryPathToMsBuildToolsVersions = $registryPathTo32BitMsBuildToolsVersions 681 | } 682 | } 683 | 684 | # Backup the current culture and force using English US to ensure version numbers are in the expected format (e.g. 17.0). 685 | $previousCulture = [System.Threading.Thread]::CurrentThread.CurrentCulture 686 | [System.Threading.Thread]::CurrentThread.CurrentCulture = 'en-US' 687 | 688 | # Get the path to the directory that the latest version of MsBuild is in. 689 | $msBuildToolsVersionsStrings = Get-ChildItem -Path $registryPathToMsBuildToolsVersions | Where-Object { $_ -match '[0-9]+\.[0-9]' } | Select-Object -ExpandProperty PsChildName 690 | $msBuildToolsVersions = @{} 691 | $msBuildToolsVersionsStrings | ForEach-Object {$msBuildToolsVersions.Add($_ -as [double], $_)} 692 | $largestMsBuildToolsVersion = ($msBuildToolsVersions.GetEnumerator() | Sort-Object -Descending -Property Name | Select-Object -First 1).Value 693 | $registryPathToMsBuildToolsLatestVersion = Join-Path -Path $registryPathToMsBuildToolsVersions -ChildPath ("{0:n1}" -f $largestMsBuildToolsVersion) 694 | $msBuildToolsVersionsKeyToUse = Get-Item -Path $registryPathToMsBuildToolsLatestVersion 695 | $msBuildDirectoryPath = $msBuildToolsVersionsKeyToUse | Get-ItemProperty -Name 'MSBuildToolsPath' | Select-Object -ExpandProperty 'MSBuildToolsPath' 696 | 697 | # Restore the previous culture now that we're done comparing version numbers. 698 | [System.Threading.Thread]::CurrentThread.CurrentCulture = $previousCulture 699 | 700 | if(!$msBuildDirectoryPath) 701 | { 702 | return $null 703 | } 704 | 705 | # Build the expected path to the MsBuild executable. 706 | $msBuildPath = (Join-Path -Path $msBuildDirectoryPath -ChildPath 'MsBuild.exe') 707 | 708 | return $msBuildPath 709 | } 710 | 711 | function Get-CommonVisualStudioDirectoryPath 712 | { 713 | # As of VS 2022 Visual Studio is 64-bit, so look in "Program Files" directory before "Program Files (x86)". 714 | [string] $programFilesDirectory = $null 715 | try 716 | { 717 | $programFilesDirectory = Get-Item 'Env:\ProgramFiles' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Value 718 | } 719 | catch 720 | { } 721 | 722 | if ([string]::IsNullOrEmpty($programFilesDirectory)) 723 | { 724 | $programFilesDirectory = 'C:\Program Files' 725 | } 726 | 727 | [string] $visualStudioDirectoryPath = Join-Path -Path $programFilesDirectory -ChildPath 'Microsoft Visual Studio' 728 | [bool] $visualStudioDirectoryPathExists = (Test-Path -LiteralPath $visualStudioDirectoryPath -PathType Container) 729 | if (!$visualStudioDirectoryPathExists) 730 | { 731 | # Look for Visual Studio in "Program Files (x86)" directory. 732 | try 733 | { 734 | $programFilesDirectory = Get-Item 'Env:\ProgramFiles(x86)' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Value 735 | } 736 | catch 737 | { 738 | $programFilesDirectory = $null 739 | } 740 | 741 | if ([string]::IsNullOrEmpty($programFilesDirectory)) 742 | { 743 | $programFilesDirectory = 'C:\Program Files (x86)' 744 | } 745 | } 746 | 747 | $visualStudioDirectoryPath = Join-Path -Path $programFilesDirectory -ChildPath 'Microsoft Visual Studio' 748 | $visualStudioDirectoryPathExists = (Test-Path -LiteralPath $visualStudioDirectoryPath -PathType Container) 749 | if (!$visualStudioDirectoryPathExists) 750 | { 751 | return $null 752 | } 753 | return $visualStudioDirectoryPath 754 | } 755 | 756 | Export-ModuleMember -Function Invoke-MsBuild 757 | -------------------------------------------------------------------------------- /src/Tests/RunTests.ps1: -------------------------------------------------------------------------------- 1 | # Turn on Strict Mode to help catch syntax-related errors. 2 | # This must come after a script's/function's param section. 3 | # Forces a function to be the first non-comment code to appear in a PowerShell Module. 4 | Set-StrictMode -Version Latest 5 | 6 | # Clear the screen before running our tests. 7 | Clear-Host 8 | 9 | # Get the directory that this script is in. 10 | $THIS_SCRIPTS_DIRECTORY = Split-Path $script:MyInvocation.MyCommand.Path 11 | 12 | # Import Invoke-MsBuild. Use -Force to make sure we reload it in case we have been making changes to it. 13 | Import-Module -Name (Join-Path (Join-Path (Split-Path -Path $THIS_SCRIPTS_DIRECTORY -Parent) 'Invoke-MsBuild') 'Invoke-MsBuild.psm1') -Force 14 | 15 | $pathToGoodSolution = (Join-Path $THIS_SCRIPTS_DIRECTORY "Solution That Should Build Successfully\SolutionThatShouldBuildSuccessfully.sln") 16 | $pathToWarningSolution = (Join-Path $THIS_SCRIPTS_DIRECTORY "Solution That Should Build Successfully With Warnings\SolutionThatShouldBuildWithWarnings.sln") 17 | $pathToBrokenSolution = (Join-Path $THIS_SCRIPTS_DIRECTORY "Solution That Should Fail Build\SolutionThatShouldFailBuild.sln") 18 | $invalidPath = (Join-Path $THIS_SCRIPTS_DIRECTORY "invalid\path") 19 | 20 | $testNumber = 0 21 | 22 | Write-Host ("{0}. Build solution..." -f ++$testNumber) 23 | $buildResult = (Invoke-MsBuild -Path $pathToGoodSolution) 24 | if ($buildResult.BuildSucceeded -eq $true) { Write-Host ("Passed in {0:N1} seconds" -f $buildResult.BuildDuration.TotalSeconds) } else { throw "Test $testNumber failed." } 25 | 26 | Write-Host ("{0}. Build solution with warnings..." -f ++$testNumber) 27 | if ((Invoke-MsBuild -Path $pathToWarningSolution).BuildSucceeded -eq $true) { Write-Host "Passed" } else { throw "Test $testNumber failed." } 28 | 29 | Write-Host ("{0}. Build solution using 32-bit MsBuild..." -f ++$testNumber) 30 | if ((Invoke-MsBuild -Path $pathToGoodSolution -Use32BitMsBuild).BuildSucceeded -eq $true) { Write-Host "Passed" } else { throw "Test $testNumber failed." } 31 | 32 | Write-Host ("{0}. Build solution witout using the VS Developer Command Prompt..." -f ++$testNumber) 33 | if ((Invoke-MsBuild -Path $pathToGoodSolution -BypassVisualStudioDeveloperCommandPrompt).BuildSucceeded -eq $true) { Write-Host "Passed" } else { throw "Test $testNumber failed." } 34 | 35 | Write-Host ("{0}. Build solution via piping..." -f ++$testNumber) 36 | if (($pathToGoodSolution | Invoke-MsBuild).BuildSucceeded -eq $true) { Write-Host "Passed" } else { throw "Test $testNumber failed." } 37 | 38 | Write-Host ("{0}. Build multiple solutions (3) via piping..." -f ++$testNumber) 39 | if (($pathToGoodSolution, $pathToGoodSolution, $pathToGoodSolution | Invoke-MsBuild).BuildSucceeded -eq @($true, $true, $true)) { Write-Host "Passed" } else { throw "Test $testNumber failed." } 40 | 41 | Write-Host ("{0}. Build multiple solutions (3) via piping, where the 2nd one is an invalid path... Should get an ERROR and 2 Trues." -f ++$testNumber) 42 | ($pathToGoodSolution, $invalidPath, $pathToGoodSolution | Invoke-MsBuild).BuildSucceeded 43 | 44 | Write-Host ("{0}. Build broken solution... Should see a Warning and then Passed." -f ++$testNumber) 45 | if ((Invoke-MsBuild -Path $pathToBrokenSolution).BuildSucceeded -eq $false) { Write-Host "Passed" } else { throw "Test $testNumber failed." } 46 | 47 | Write-Host ("{0}. Using -WhatIf switch... Should see object's properties and values." -f ++$testNumber) 48 | Invoke-MsBuild -Path $pathToGoodSolution -WhatIf 49 | 50 | Write-Host ("{0}. Build solution... Should see object's properties and values." -f ++$testNumber) 51 | Invoke-MsBuild -Path $pathToGoodSolution 52 | 53 | Write-Host ("{0}. Using -PassThru switch... Should see a few building messages." -f ++$testNumber) 54 | $process = Invoke-MsBuild -Path $pathToGoodSolution -PassThru 55 | while (!$process.HasExited) 56 | { 57 | Write-Host "Solution is still buildling..." 58 | Start-Sleep -Milliseconds 250 59 | } 60 | 61 | Write-Host ("{0}. Using -ShowBuildOutputInNewWindow switch... Should see a new window that shows the build progress." -f ++$testNumber) 62 | Invoke-MsBuild -Path $pathToGoodSolution -ShowBuildOutputInNewWindow > $null 63 | 64 | Write-Host ("{0}. Using -ShowBuildOutputInCurrentWindow switch... Should see build progress in this window (when ran from regular PowerShell console window)." -f ++$testNumber) 65 | Invoke-MsBuild -Path $pathToGoodSolution -ShowBuildOutputInCurrentWindow > $null 66 | 67 | Write-Host ("{0}. Build solution in parallel using MsBuild /m switch..." -f ++$testNumber) 68 | if ((Invoke-MsBuild -Path $pathToGoodSolution -MsBuildParameters '/m').BuildSucceeded -eq $true) { Write-Host "Passed" } else { throw "Test $testNumber failed." } 69 | -------------------------------------------------------------------------------- /src/Tests/Solution That Should Build Successfully With Warnings/SolutionThatShouldBuildWithWarnings.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26403.7 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SolutionThatShouldBuildWithWarnings", "SolutionThatShouldBuildWithWarnings\SolutionThatShouldBuildWithWarnings.csproj", "{9E580A1D-0726-4019-AD9A-BB0BEC949E15}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {9E580A1D-0726-4019-AD9A-BB0BEC949E15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {9E580A1D-0726-4019-AD9A-BB0BEC949E15}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {9E580A1D-0726-4019-AD9A-BB0BEC949E15}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {9E580A1D-0726-4019-AD9A-BB0BEC949E15}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/Tests/Solution That Should Build Successfully With Warnings/SolutionThatShouldBuildWithWarnings/Class1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SolutionThatShouldBuildWithWarnings 8 | { 9 | public class Class1 10 | { 11 | private void FunctionThatGeneratesAWarning() 12 | { 13 | return; 14 | int unreachableCodeAndUnusedVariableWarningGeneratorLine = 0; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Tests/Solution That Should Build Successfully With Warnings/SolutionThatShouldBuildWithWarnings/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SolutionThatShouldBuildWithWarnings")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SolutionThatShouldBuildWithWarnings")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("9e580a1d-0726-4019-ad9a-bb0bec949e15")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/Tests/Solution That Should Build Successfully With Warnings/SolutionThatShouldBuildWithWarnings/SolutionThatShouldBuildWithWarnings.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 9e580a1d-0726-4019-ad9a-bb0bec949e15 8 | Library 9 | Properties 10 | SolutionThatShouldBuildWithWarnings 11 | SolutionThatShouldBuildWithWarnings 12 | v4.5.2 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/Tests/Solution That Should Build Successfully/SolutionThatShouldBuildSuccessfully.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SolutionThatShouldBuildSuccessfully", "SolutionThatShouldBuildSuccessfully\SolutionThatShouldBuildSuccessfully.csproj", "{48F21173-0375-4396-965C-470E4A6EF4B5}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Release|Any CPU = Release|Any CPU 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {48F21173-0375-4396-965C-470E4A6EF4B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 13 | {48F21173-0375-4396-965C-470E4A6EF4B5}.Debug|Any CPU.Build.0 = Debug|Any CPU 14 | {48F21173-0375-4396-965C-470E4A6EF4B5}.Release|Any CPU.ActiveCfg = Release|Any CPU 15 | {48F21173-0375-4396-965C-470E4A6EF4B5}.Release|Any CPU.Build.0 = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /src/Tests/Solution That Should Build Successfully/SolutionThatShouldBuildSuccessfully/Class1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SolutionThatShouldBuildSuccessfully 8 | { 9 | public class Class1 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Tests/Solution That Should Build Successfully/SolutionThatShouldBuildSuccessfully/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SolutionThatShouldBuildSuccessfully")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SolutionThatShouldBuildSuccessfully")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("e00ac323-cf81-47fc-b93c-44c1b02ef59b")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/Tests/Solution That Should Build Successfully/SolutionThatShouldBuildSuccessfully/SolutionThatShouldBuildSuccessfully.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {48F21173-0375-4396-965C-470E4A6EF4B5} 8 | Library 9 | Properties 10 | SolutionThatShouldBuildSuccessfully 11 | SolutionThatShouldBuildSuccessfully 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 53 | -------------------------------------------------------------------------------- /src/Tests/Solution That Should Fail Build/SolutionThatShouldFailBuild.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SolutionThatShouldFailBuild", "SolutionThatShouldFailBuild\SolutionThatShouldFailBuild.csproj", "{11D4350D-9BF3-4F66-949A-567235DAFAB5}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Release|Any CPU = Release|Any CPU 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {11D4350D-9BF3-4F66-949A-567235DAFAB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 13 | {11D4350D-9BF3-4F66-949A-567235DAFAB5}.Debug|Any CPU.Build.0 = Debug|Any CPU 14 | {11D4350D-9BF3-4F66-949A-567235DAFAB5}.Release|Any CPU.ActiveCfg = Release|Any CPU 15 | {11D4350D-9BF3-4F66-949A-567235DAFAB5}.Release|Any CPU.Build.0 = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /src/Tests/Solution That Should Fail Build/SolutionThatShouldFailBuild/Class1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SolutionThatShouldFailBuild 8 | { 9 | public class Class1 10 | { 11 | invalid syntax that should break build 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Tests/Solution That Should Fail Build/SolutionThatShouldFailBuild/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SolutionThatShouldFailBuild")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SolutionThatShouldFailBuild")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("1b66a5d3-f6e3-472d-b43c-0892012d7485")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/Tests/Solution That Should Fail Build/SolutionThatShouldFailBuild/SolutionThatShouldFailBuild.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {11D4350D-9BF3-4F66-949A-567235DAFAB5} 8 | Library 9 | Properties 10 | SolutionThatShouldFailBuild 11 | SolutionThatShouldFailBuild 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 53 | --------------------------------------------------------------------------------