Public/Start-MsBuild.ps1

function Start-MSBuild {
    <#
    .SYNOPSIS
      Runs MSBuild with binary logging enabled by default
 
    .DESCRIPTION
      Wraps the call to MSBuild using common settings to make command line building repeatable.
      By default, uses binary logging for better performance and structured log output that can be viewed with the MSBuild Structured Log Viewer.
 
    .PARAMETER AdditionalArguments
      Allows you to add additional arguments to the MSBuild command. Can be a string or array of strings.
 
    .PARAMETER LogToConsole
      Enables console logging, which will slow down the build process but give you immediate visual feedback.
      Handy for debugging build issues.
 
    .PARAMETER Clean
      Runs the build with the Clean target before the main build to remove all build artifacts.
 
    .PARAMETER Restore
      Runs the build with the Restore target before the main build to restore NuGet packages.
 
    .PARAMETER CleanNugetCache
      Calls 'dotnet nuget locals all --clear' to remove all cached packages. You will need to restore after this.
 
    .PARAMETER Release
      Sets the build configuration to Release instead of Debug.
 
    .PARAMETER Nuke
      Deletes all folders with names specified in NukeFolders parameter (default: bin, obj, node_modules, out, TestResults).
 
    .PARAMETER GitNuke
      Performs a complete git reset: deletes all extra files, resets the git repo to the last commit, and pulls the latest changes.
      WARNING: This will permanently delete uncommitted changes.
 
    .PARAMETER ShowBuildSummary
      Shows the build performance summary in the console output when LogToConsole is enabled.
 
    .PARAMETER SkipToolsRestore
      By default, 'dotnet tool restore' is run before the build. This switch skips that step.
 
    .PARAMETER LogVerbosity
      Sets the verbosity level for MSBuild logging. Valid values: quiet, minimal, normal, detailed, diagnostic.
      Default is 'minimal'.
 
    .PARAMETER NukeFolders
      Specifies which folder names to delete when using the Nuke parameter.
      Default folders: bin, obj, node_modules, out, TestResults.
 
    .PARAMETER MessageCallback
      A ScriptBlock that handles informational messages during the build process.
      Default writes to Write-Information.
 
    .PARAMETER ErrorCallback
      A ScriptBlock that handles error messages during the build process.
      Default writes to Write-Error.
 
    .EXAMPLE
      Start-MSBuild
 
      Runs a basic debug build with binary logging enabled.
 
    .EXAMPLE
      Start-MSBuild -Release -Clean
 
      Runs a clean release build.
 
    .EXAMPLE
      Start-MSBuild -LogToConsole -LogVerbosity detailed
 
      Runs a build with detailed console output instead of binary logging.
 
    .EXAMPLE
      Start-MSBuild -Restore -Clean -Release
 
      Performs a complete build: restore packages, clean artifacts, then build in release mode.
 
    .EXAMPLE
      Start-MSBuild -Nuke -AdditionalArguments @("/p:PublishProfile=FolderProfile", "/p:PublishUrl=bin\Release\Publish")
 
      Deletes build folders and runs build with additional MSBuild arguments.
 
    .EXAMPLE
      Start-MSBuild -CleanNugetCache -Restore
 
      Clears the NuGet cache and restores packages before building.
 
    .EXAMPLE
      Start-MSBuild -LogToConsole -ShowBuildSummary -LogVerbosity normal
 
      Runs build with console logging, showing build performance summary with normal verbosity.
  #>

    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [Parameter(Position = 0)]
        $AdditionalArguments = @(),
        [Switch] $LogToConsole,
        [Switch] $Clean,
        [Switch] $Restore,
        [Switch] $CleanNugetCache,
        [Switch] $Release,
        [Switch] $Nuke,
        [Switch] $GitNuke,
        [Switch] $ShowBuildSummary,
        [Switch] $SkipToolsRestore,
        [ValidateSet("quiet", "minimal", "normal", "detailed", "diagnostic")]
        [string]$LogVerbosity = "minimal",
        $NukeFolders = @("bin", "obj", "node_modules", "out", "TestResults"),
        [scriptblock]$MessageCallback = { param($message) Write-Information "$message" },
        [scriptblock]$ErrorCallback = { param($exception) Write-Error $exception }
    )

    begin {
        Write-HeadingBlock (Get-ModuleHeaderInfo)
        Write-Information "Executing MSBuild with following settings"
        Write-Information " Visual Studio Version: $vsDefault"
        Write-Information " Default Build Arguments: $msBuildArguments"
        Write-Information "Structured Log Viewer Path: $StructuredLogViewerPath"
        if ($LogToConsole) { Write-Information "- Logging to Console Enabled" }
        if ($Nuke) { Write-Information "- Removing all folders called ($($NukeFolders -join ","))" }
        if ($Clean) { Write-Information "- Clean Enabled" }
        if ($Restore) { Write-Information "- Restore Enabled" }
        if ($CleanNugetCache) { Write-Information "- Clean Nuget Cache Enabled" }
        if ($Release) { Write-Information "- Release Configuration Enabled" }
        Write-Information "Additional Arguments: $($AdditionalArguments)"
    }

    process {

        $sw = [Diagnostics.Stopwatch]::StartNew()
        $msBuildArgumentsUsed = $msBuildArguments
        if ($LogToConsole) {
            if ($ShowBuildSummary) {
                $msBuildArgumentsUsed += "/consoleLoggerParameters:PerformanceSummary;Summary;Verbosity=$LogVerbosity"
            } else {
                $msBuildArgumentsUsed += "/consoleLoggerParameters:Verbosity=$LogVerbosity"
            }
        } else {
            $msBuildArgumentsUsed += '/noConsoleLogger'
            $msBuildArgumentsUsed += '/binaryLogger'
        }

        if ($Release) {
            $msBuildArgumentsUsed += '/p:Configuration="Release"'
        } else {
            $msBuildArgumentsUsed += '/p:Configuration="Debug"'
        }

        $msBuildArgumentsUsed += "/verbosity:$LogVerbosity"

        $msBuildArgumentsUsed += $AdditionalArguments
        if ($null -eq (Get-Command "msbuild.exe" -ErrorAction SilentlyContinue)) {
            Write-Information "Unable to find msbuild.exe in your PATH, loading VS $vsDefault"
            switch ($vsDefault) {
                "17" { Use-VS2022 }
                "16" { Use-VS2019 }
                "15" { Use-VS2017 }
                Default { Use-VS2019 }
            }
        }

        if ($null -eq (Get-Command "msbuild.exe" -ErrorAction SilentlyContinue)) {
            Write-Information "Unable to find msbuild.exe in your PATH, unable to build."
        } else {
            if ($CleanNugetCache) {
                Write-Information "Cleaning the NUGET Cache"
                if ($PSCmdlet.ShouldProcess("Start-Process", "dotnet nuget locals all --clear")) {
                    $ExitCode, $Output = Invoke-Call "dotnet" @('nuget', 'locals', 'all', '--clear') $MessageCallback $ErrorCallback
                }
            }

            if ($Nuke) {
                Write-Information "Deleting folders $($NukeFolders -join ",") "
                if ($PSCmdlet.ShouldProcess("Start-Process", "Get-ChildItem ./ -include $($NukeFolders -join ",") -Recurse | ForEach-Object { [IO.Directory]::Delete(`$_.FullName, `$true) }")) {
                    Get-ChildItem ./ -Include $NukeFolders -Recurse | ForEach-Object { [IO.Directory]::Delete($_.FullName, $true) }
                }
            }

            if ($GitNuke) {
                Write-Information "Resetting git repository to last commit and pulling latest changes"
                if ($PSCmdlet.ShouldProcess("Start-Process", "git clean -fdx")) {
                    $ExitCode, $Output = Invoke-Call "git" @('clean', '-fdx') $MessageCallback $ErrorCallback
                }
                if ($PSCmdlet.ShouldProcess("Start-Process", "git reset HEAD~1 --hard")) {
                    $ExitCode, $Output = Invoke-Call "git" @('reset', 'HEAD~1', '--hard') $MessageCallback $ErrorCallback
                }
                if ($PSCmdlet.ShouldProcess("Start-Process", "git pull")) {
                    $ExitCode, $Output = Invoke-Call "git" @('pull') $MessageCallback $ErrorCallback
                }
            }

            if (-not $SkipToolsRestore) {
                Write-Information "Running dotnet tool restore"
                if ($PSCmdlet.ShouldProcess("Start-Process", "dotnet tool restore")) {
                    $ExitCode, $Output = Invoke-Call "dotnet" @('tool', 'restore') $MessageCallback $ErrorCallback
                }
            }

            if ($Restore) {
                Write-Information "Running restore target"
                if ($PSCmdlet.ShouldProcess("Start-Process", "msbuild $msBuildArgumentsUsed /t:`"Restore`"")) {
                    $ExitCode, $Output = Invoke-Call "msbuild" @($msBuildArgumentsUsed.Replace("/binaryLogger", "/binaryLogger:restore.binlog"), '/t:"Restore"') $MessageCallback $ErrorCallback
                }
            }

            if ($Clean) {
                Write-Information "Running clean target"
                if ($PSCmdlet.ShouldProcess("Start-Process", "msbuild $msBuildArgumentsUsed /t:`"Clean`"")) {
                    $ExitCode, $Output = Invoke-Call "msbuild" @($msBuildArgumentsUsed.Replace("/binaryLogger", "/binaryLogger:clean.binlog"), '/t:"Clean"') $MessageCallback $ErrorCallback
                }
            }

            Write-Information "Running build target"
            if ($PSCmdlet.ShouldProcess("Start-Process", "msbuild $msBuildArgumentsUsed")) {
                $ExitCode, $Output = Invoke-Call "msbuild" $msBuildArgumentsUsed $MessageCallback $ErrorCallback
                if ($ExitCode -gt 0) { Write-Error "MSBuild returned error code $($ExitCode)" }
            }
        }
        $sw.Stop()
        Write-Information "Build took $($sw.Elapsed.TotalMinutes) minutes"
        if ((Get-Command $StructuredLogViewerPath) -and -not $LogToConsole) {
            if (-not (Get-Process StructuredLogViewer -ErrorAction SilentlyContinue)) {
                $StructuredLogViewerPath = (Get-Command StructuredLogViewer.exe).Source
                if ($PSCmdlet.ShouldProcess("&", "$StructuredLogViewerPath $PWD\msbuild.binlog")) {
                    Start-Process -FilePath $StructuredLogViewerPath -ArgumentList "$PWD\msbuild.binlog" -NoNewWindow
                }
            }
        }

    }

    end {
    }
}