Archive

Posts Tagged ‘script’

Create and publish your NuGet package in one click with the New-NuGetPackage PowerShell script

June 7th, 2013 No comments

I’ve spent a good chunk of time investigating how nuget.exe works and creating a PowerShell script called New-NuGetPackage to make it dirt simple to pack and push new NuGet packages.

Here’s a list of some of the script’s features:

  • Create the .nupkg package file and optionally push the package to the NuGet Gallery (or a custom gallery).
  • Can be ran from Windows Explorer (i.e. double-click it) or called via PowerShell if you want to be able to pass in specific parameters or suppress prompts.
  • Can prompt user for version number and release notes (prompts are prefilled with previous version number and release notes) or can suppress all prompts.

This makes packing and pushing your NuGet packages quick and easy, whether doing it manually or integrating it into your build system.  Creating NuGet packages wasn’t overly complicated before, but this makes it even simpler and less tedious.

Go to the codeplex page to download the script and start automating your NuGet package creating today.  The codeplex documentation describes the script in much more detail, as well as step by step instructions on how to get setup to start using it.

[UPDATE] I have also used this script in a new NuGet package that will automatically create a NuGet package for your own projects without you having to do anything. Read about it here. [/UPDATE]

 

Additional NuGet Information

During my investigation I compiled a list of what happens when doing “nuget spec” and “nuget pack” against the various different file types (e.g. dll vs. project vs. nuspec).  Someone else may find this information useful, so here it is:

Spec a Project or DLL directly (e.g. "nuget spec PathToFile"):
- Creates a partial .nuspec; still has placeholder info for some fields (e.g. Id, Dependencies).
- Creates [full file name with extension].nuspec file.
- The generated .nuspec file is meant to still be manually updated before making a package from it.

// TestProject.csproj.nuspec
<?xml version="1.0"?>
<package >
  <metadata>
    <id>C:\dev\TFS\RQ\Dev\Tools\DevOps\New-NuGetPackage\TestProject\TestProject\TestProject.csproj</id>
    <version>1.0.0</version>
    <authors>Dan Schroeder</authors>
    <owners>Dan Schroeder</owners>
    <licenseUrl>http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE</licenseUrl>
    <projectUrl>http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE</projectUrl>
    <iconUrl>http://ICON_URL_HERE_OR_DELETE_THIS_LINE</iconUrl>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>Package description</description>
    <releaseNotes>Summary of changes made in this release of the package.</releaseNotes>
    <copyright>Copyright 2013</copyright>
    <tags>Tag1 Tag2</tags>
    <dependencies>
      <dependency id="SampleDependency" version="1.0" />
    </dependencies>
  </metadata>
</package>
=====================================================================
Spec a DLL using "nuget spec" from the same directory:
- Creates a partial .nuspec; still has placeholder info for some fields (e.g. Id, Dependencies).
- Creates "Package.nuspec" file.
- The generated .nuspec file is meant to still be manually updated before making a package from it.

// Package.nuspec
<?xml version="1.0"?>
<package >
  <metadata>
    <id>Package</id>
    <version>1.0.0</version>
    <authors>Dan Schroeder</authors>
    <owners>Dan Schroeder</owners>
    <licenseUrl>http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE</licenseUrl>
    <projectUrl>http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE</projectUrl>
    <iconUrl>http://ICON_URL_HERE_OR_DELETE_THIS_LINE</iconUrl>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>Package description</description>
    <releaseNotes>Summary of changes made in this release of the package.</releaseNotes>
    <copyright>Copyright 2013</copyright>
    <tags>Tag1 Tag2</tags>
    <dependencies>
      <dependency id="SampleDependency" version="1.0" />
    </dependencies>
  </metadata>
</package>
=====================================================================
Spec a Project using "nuget spec" from the same directory:
- Creates a template .nuspec using the proper properties and dependencies pulled from the file.
- Creates [file name without extension].nuspec file.
- The generated .nuspec file can be used to pack with, assuming you are packing the Project and not the .nuspec directly.

// TestProject.nuspec
<?xml version="1.0"?>
<package >
  <metadata>
    <id>$id$</id>
    <version>$version$</version>
    <title>$title$</title>
    <authors>$author$</authors>
    <owners>$author$</owners>
    <licenseUrl>http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE</licenseUrl>
    <projectUrl>http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE</projectUrl>
    <iconUrl>http://ICON_URL_HERE_OR_DELETE_THIS_LINE</iconUrl>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>$description$</description>
    <releaseNotes>Summary of changes made in this release of the package.</releaseNotes>
    <copyright>Copyright 2013</copyright>
    <tags>Tag1 Tag2</tags>
  </metadata>
</package>
=====================================================================
Pack a Project (without accompanying template .nuspec):
- Does not generate a .nuspec file; just creates the .nupkg file with proper properties and dependencies pulled from project file.
- Throws warnings about any missing data in the project file (e.g. Description, Author), but still generates the package.

=====================================================================
Pack a Project (with accompanying template .nuspec):
- Expects the [file name without extension].nuspec file to exist in same directory as project file, otherwise it doesn't use a .nuspec file for the packing.
- Throws errors about any missing data in the project file if the .nuspec uses tokens (e.g. $description$, $author$) and these aren't defined in the project, so the package is not generated.

=====================================================================
Cannot pack a .dll directly

=====================================================================
Pack a .nuspec:
- Creates the .nupkg file with properties and dependencies defined in .nuspec file.
- .nuspec file cannot have any placeholder values (e.g. $id$, $version$).

Fix Problem Where Windows PowerShell Cannot Run Script Whose Path Contains Spaces

May 28th, 2013 4 comments

Most people will likely find the “Run script path with spaces from File Explorer” (to be able to double click a PS script whose path contains spaces to run it) section below the most helpful.  Most of the other content in this post can be found elsewhere, but I provide it for context and completeness.

 

Make running (instead of editing) the default PowerShell script action

The default Windows action when you double click on a PowerShell script is to open it in an editor, rather than to actually run the script.  If this bugs you, it’s easy enough to fix.  Just right-click on the script, go to “Open with” –> “Choose default program…”, and then select Windows PowerShell, making sure the “Use this app for all .ps1 files” option is checked (this might be called “Always use the selected program to open this kind of file” or something else depending on which version of Windows you are using).

ChooseDefaultPowerShellApplication     MakeWindowsPowerShellDefaultApplication

If you don’t mind opening in an editor as the default action, then to run the script you can just right-click on the script and choose “Open with” –> “Windows PowerShell”.  This is probably how 90% of people run their PowerShell scripts; power uses might run their scripts directly from the PowerShell command prompt.

 

Error message when trying to run a script whose path contains spaces

So the problem that the 90% of people are likely to encounter is that as soon as the script path has a space in it (either in the filename itself or in the directory path the file resides in), they will see the powershell console flash some red text at them for about 1/10th of a second before it closes, and they will be wondering why the script did not run; or worse, they won’t know that it didn’t run (see the “Keep PowerShell Console Open” section below).  If they are lucky enough to press Print Screen at the right moment, or decide to open up a PowerShell console and run from there, they might see an error message similar to this:

Powershell Invalid Path Error Message

“The term ‘C:\My’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.”

So the path to the script I was trying to run is "C:\My Folder\My PowerShell Script.ps1", but from the error you can see that it cut the path off at the first space.

 

Run script path with spaces from PowerShell console

So the typical work around for this is to open a PowerShell console and run the script by enclosing the path in double quotes.

Windows 8 Pro Tip: You can open the PowerShell console at your current directory in File Explorer by choosing File –> Open Windows PowerShell.

Open Powershell From File Explorer

If you simply try to run the script by enclosing the path to the script in double quotes you will just see the path spit back at you in the console, instead of actually running the script.

Try to run script with spaces the wrong way

The trick is that you have to put “& “ before the script path to actually run the script.  Also, if you are trying to run a script from the current directory without using the full path, you will need to put “.\” before the relative script filename.

Run PowerShell script the right way

 

Run script path with spaces from File Explorer

So when we are in the PowerShell console we can manually type the path enclosed in double quotes, but what do we do when simply trying to run the file from File Explorer (i.e. Windows Explorer in Windows 7 and previous) by double clicking it? 

The answer: Edit the registry to pass the file path to powershell.exe with the path enclosed in quotes.

The problem is that the “HKEY_CLASSES_ROOT\Applications\powershell.exe\shell\open\command” registry key value looks like this:

"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "%1"

but we want it to look like this:

"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "& \"%1\""

 

So if you want to go manually edit that key by hand you can, or you can simply download the registry script below and then double click the .reg file to have it update the registry key value for you (choose Yes when asked if you want to continue).

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Applications\powershell.exe\shell\open\command]
@="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" \"& \\\"%1\\\"\""

IMHO this seems like a bug with the PowerShell installer (and Windows since PowerShell is built into Windows 7 and up), so please go up-vote the bug I submitted to get this fixed.

So now you can run your PowerShell scripts from File Explorer regardless of whether their path contains spaces or not Smile.  For those interested, this is the post that got me thinking about using the registry to fix this problem.

 

Bonus: Keep PowerShell console open when script is ran from File Explorer

Update – This Bonus section now has its own updated dedicated post here that you should use instead.

When running a script by double-clicking it, if the script completes very quickly the user will see the PowerShell console appear very briefly and then disappear.  If the script gives output that the user wants to see, or if it throws an error, the user won’t have time to read the text.  The typical work around is to open the PowerShell console and manually run the script.  The other option is to adjust our new registry key value a bit.

So to keep the PowerShell console window open after the script completes, we just need to change our new key value to use the –NoExit switch:

"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" –NoExit "& \"%1\""

And here is the .reg script with the –NoExit switch included:

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Applications\powershell.exe\shell\open\command]
@="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -NoExit \"& \\\"%1\\\"\""

I hope you find this information as useful as I did.  Happy coding!

Invoke-MsBuild Powershell Module

April 5th, 2013 2 comments

Update: I’ve moved this project to it’s own new home at https://invokemsbuild.codeplex.com.  All updates will be made there.

I’ve spent a little while creating a powershell module that can be used to call MsBuild.  It returns whether the build succeeded or not, and runs through the Visual Studio command prompt if possible, since some projects can’t be built by calling msbuild directly (e.g. XNA projects).  It also provides several other parameters to do things like show the window performing the build, automatically open the build log if the build fails, etc.

Here is the script (copy-paste the code into a file called Invoke-MsBuild.psm1 go download the updated version):

function Invoke-MsBuild
{
<#
    .SYNOPSIS
    Builds the given Visual Studio solution or project file using MSBuild.
     
    .DESCRIPTION
    Executes the MSBuild.exe tool against the specified Visual Studio solution or project file.
    Returns true if the build succeeded, false if the build failed.
    If using the PathThru switch, the process running MSBuild is returned instead.
     
    .PARAMETER Path
    The path of the Visual Studio solution or project to build (e.g. a .sln or .csproj file).
     
    .PARAMETER MsBuildParameters
    Additional parameters to pass to the MsBuild command-line tool. This can be any valid MsBuild command-line parameters except for the path of
    the solution/project to build.
 
http://msdn.microsoft.com/en-ca/library/vstudio/ms164311.aspx
 
    .PARAMETER $BuildLogDirectoryPath
    The directory path to write the build log file to.
    Defaults to putting the log file in the users temp directory (e.g. C:\Users\[User Name]\AppData\Local\Temp).
    Use the keyword "PathDirectory" to put the log file in the same directory as the .sln or project file being built.
     
    .PARAMETER AutoLaunchBuildLog
    If set, this switch will cause the build log to automatically be launched into the default viewer if the build fails.
    NOTE: This switch cannot be used with the PassThru switch.
     
    .PARAMETER KeepBuildLogOnSuccessfulBuilds
    If set, this switch will cause the msbuild log file to not be deleted on successful builds; normally it is only kept around on failed builds.
    NOTE: This switch cannot be used with the PassThru switch.
     
    .PARAMETER ShowBuildWindow
    If set, this switch will cause a command prompt window to be shown in order to view the progress of the build.
     
    .PARAMETER ShowBuildWindowAndPromptForInputBeforeClosing
    If set, this switch will cause a command prompt window to be shown in order to view the progress of the build, and it will remain open
    after the build completes until the user presses a key on it.
    NOTE: If not using PassThru, the user will need to provide input before execution will return back to the calling script.
     
    .PARAMETER PassThru
    If set, this switch will cause the script not to wait until the build (launched in another process) completes before continuing execution.
    Instead the build will be started in a new process and that process will immediately be returned, allowing the calling script to continue
    execution while the build is performed, and also to inspect the process to see when it completes.
    NOTE: This switch cannot be used with the AutoLaunchBuildLog or KeepBuildLogOnSuccessfulBuilds switches.
     
    .PARAMETER GetLogPath
    If set, the build will not actually be performed.
    Instead it will just return the full path of the MsBuild Log file that would be created if the build is performed with the same parameters.
     
    .EXAMPLE
    Invoke-MsBuild -Path "C:\Some Folder\MySolution.sln"
     
    Perform the default MSBuild actions on the Visual Studio solution to build the projects in it.
    The PowerShell script will halt execution until MsBuild completes.
     
    .EXAMPLE
    Invoke-MsBuild -Path "C:\Some Folder\MySolution.sln" -PassThru
     
    Perform the default MSBuild actions on the Visual Studio solution to build the projects in it.
    The PowerShell script will not halt execution; instead it will return the process performing MSBuild actions back to the caller while the action is performed.
     
    .EXAMPLE
    Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -MsBuildParameters "/target:Clean;Build" -ShowBuildWindow
     
    Cleans then Builds the given C# project.
    A window displaying the output from MsBuild will be shown so the user can view the progress of the build.
     
    .EXAMPLE
    Invoke-MsBuild -Path "C:\MySolution.sln" -Params "/target:Clean;Build /property:Configuration=Release;Platform=x64;BuildInParallel=true /verbosity:Detailed /maxcpucount"
     
    Cleans then Builds the given solution, specifying to build the project in parallel in the Release configuration for the x64 platform.
    Here the shorter "Params" alias is used instead of the full "MsBuildParameters" parameter name.
     
    .EXAMPLE
    Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -ShowBuildWindowAndPromptForInputBeforeClosing -AutoLaunchBuildLog
     
    Builds the given C# project.
    A window displaying the output from MsBuild will be shown so the user can view the progress of the build, and it will not close until the user
    gives the window some input. This function will also not return until the user gives the window some input, halting the powershell script execution.
    If the build fails, the build log will automatically be opened in the default text viewer.
     
    .EXAMPLE
    Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -BuildLogDirectoryPath "C:\BuildLogs" -KeepBuildLogOnSuccessfulBuilds -AutoLaunchBuildLog
     
    Builds the given C# project.
    The build log will be saved in "C:\BuildLogs", and they will not be automatically deleted even if the build succeeds.
    If the build fails, the build log will automatically be opened in the default text viewer.
     
    .EXAMPLE
    Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -BuildLogDirectoryPath PathDirectory
     
    Builds the given C# project.
    The build log will be saved in "C:\Some Folder\", which is the same directory as the project being built (i.e. directory specified in the Path).
     
    .EXAMPLE
    Invoke-MsBuild -Path "C:\Database\Database.dbproj" -P "/t:Deploy /p:TargetDatabase=MyDatabase /p:TargetConnectionString=`"Data Source=DatabaseServerName`;Integrated Security=True`;Pooling=False`" /p:DeployToDatabase=True"
     
    Deploy the Visual Studio Database Project to the database "MyDatabase".
    Here the shorter "P" alias is used instead of the full "MsBuildParameters" parameter name.
    The shorter alias' of the msbuild parameters are also used; "/t" instead of "/target", and "/p" instead of "/property".
     
    .EXAMPLE
    Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -BuildLogDirectoryPath "C:\BuildLogs" -GetLogPath
     
    Returns the full path to the MsBuild Log file that would be created if the build was ran with the same parameters.
    In this example the returned log path might be "C:\BuildLogs\MyProject.msbuild.log".
    If the BuildLogDirectoryPath was not provided, the returned log path might be "C:\Some Folder\MyProject.msbuild.log".
     
    .NOTES
    Name:   Invoke-MsBuild
    Author: Daniel Schroeder (originally based on the module at http://geekswithblogs.net/dwdii/archive/2011/05/27/part-2-automating-a-visual-studio-build-with-powershell.aspx)
    Version: 1.1
#>
    [CmdletBinding(DefaultParameterSetName="Wait")]
    param
    (
        [parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true,HelpMessage="The path to the file to build with MsBuild (e.g. a .sln or .csproj file).")]
        [ValidateScript({Test-Path $_})]
        [string] $Path,
 
        [parameter(Mandatory=$false)]
        [Alias("Params")]
        [Alias("P")]
        [string] $MsBuildParameters,
 
        [parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [Alias("L")]
        [string] $BuildLogDirectoryPath = $env:Temp,
 
        [parameter(Mandatory=$false,ParameterSetName="Wait")]
        [ValidateNotNullOrEmpty()]
        [Alias("AutoLaunch")]
        [Alias("A")]
        [switch] $AutoLaunchBuildLogOnFailure,
 
        [parameter(Mandatory=$false,ParameterSetName="Wait")]
        [ValidateNotNullOrEmpty()]
        [Alias("Keep")]
        [Alias("K")]
        [switch] $KeepBuildLogOnSuccessfulBuilds,
 
        [parameter(Mandatory=$false)]
        [Alias("Show")]
        [Alias("S")]
        [switch] $ShowBuildWindow,
 
        [parameter(Mandatory=$false)]
        [Alias("Prompt")]
        [switch] $ShowBuildWindowAndPromptForInputBeforeClosing,
 
        [parameter(Mandatory=$false,ParameterSetName="PassThru")]
        [switch] $PassThru,
         
        [parameter(Mandatory=$false)]
        [Alias("Get")]
        [Alias("G")]
        [switch] $GetLogPath
    )
 
    BEGIN { }
    END { }
    PROCESS
    {
        # Turn on Strict Mode to help catch syntax-related errors.
        #   This must come after a script's/function's param section.
        #   Forces a function to be the first non-comment code to appear in a PowerShell Script/Module.
        Set-StrictMode -Version Latest
 
        # If the keyword was supplied, place the log in the same folder as the solution/project being built.
        if ($BuildLogDirectoryPath.Equals("PathDirectory", [System.StringComparison]::InvariantCultureIgnoreCase))
        {
            $BuildLogDirectoryPath = [System.IO.Path]::GetDirectoryName($Path)
        }
 
        # Store the VS Command Prompt to do the build in, if one exists.
        $vsCommandPrompt = Get-VisualStudioCommandPromptPath
 
        # Local Variables.
        $solutionFileName = (Get-ItemProperty -Path $Path).Name
        $buildLogFilePath = (Join-Path $BuildLogDirectoryPath $solutionFileName) + ".msbuild.log"
        $windowStyle = if ($ShowBuildWindow -or $ShowBuildWindowAndPromptForInputBeforeClosing) { "Normal" } else { "Hidden" }
        $buildCrashed = $false;
     
        # If all we want is the path to the Log file that will be generated, return it.
        if ($GetLogPath)
        {
            return $buildLogFilePath
        }
 
        # Try and build the solution.
        try
        {
            # Build the arguments to pass to MsBuild.
            $buildArguments = """$Path"" $MsBuildParameters /fileLoggerParameters:LogFile=""$buildLogFilePath"""
 
            # If a VS Command Prompt was found, call MSBuild from that since it sets environmental variables that may be needed to build some projects.
            if ($vsCommandPrompt -ne $null)
            {
                $cmdArgumentsToRunMsBuild = "/k "" ""$vsCommandPrompt"" & msbuild "
            }
            # Else the VS Command Prompt was not found, so just build using MSBuild directly.
            else
            {
                # Get the path to the MsBuild executable.
                $msBuildPath = Get-MsBuildPath
                $cmdArgumentsToRunMsBuild = "/k "" ""$msBuildPath"" "
            }
             
            # Append the MSBuild arguments to pass into cmd.exe in order to do the build.
            $pauseForInput = if ($ShowBuildWindowAndPromptForInputBeforeClosing) { "Pause & " } else { "" }
            $cmdArgumentsToRunMsBuild += "$buildArguments & $pauseForInput Exit"" "
 
            Write-Debug "Starting new cmd.exe process with arguments ""$cmdArgumentsToRunMsBuild""."
 
            # Perform the build.
            if ($PassThru)
            {
                return Start-Process cmd.exe -ArgumentList $cmdArgumentsToRunMsBuild -WindowStyle $windowStyle -PassThru
            }
            else
            {
                Start-Process cmd.exe -ArgumentList $cmdArgumentsToRunMsBuild -WindowStyle $windowStyle -Wait
            }
        }
        catch
        {
            $buildCrashed = $true;
            $errorMessage = $_
            Write-Error ("Unexpect error occured while building ""$Path"": $errorMessage" );
        }
 
        # If the build crashed, return that the build didn't succeed.
        if ($buildCrashed)
        {
            return $false
        }
     
        # Get if the build failed or not by looking at the log file.
        $buildSucceeded = ((Select-String -Path $buildLogFilePath -Pattern "Build FAILED." -SimpleMatch) -eq $null)
 
        # If the build succeeded.
        if ($buildSucceeded)
        {
            # If we shouldn't keep the log around, delete it.
            if (!$KeepBuildLogOnSuccessfulBuilds)
            {
                Remove-Item -Path $buildLogFilePath -Force
            }
        }
        # Else at least one of the projects failed to build.
        else
        {
            # Write the error message as a warning.
            Write-Warning "FAILED to build ""$Path"". Please check the build log ""$buildLogFilePath"" for details."
 
            # If we should show the build log automatically, open it with the default viewer.
            if($AutoLaunchBuildLogOnFailure)
            {
                Start-Process -verb "Open" $buildLogFilePath;
            }
        }
     
        # Return if the Build Succeeded or Failed.
        return $buildSucceeded
    }
}
 
function Get-VisualStudioCommandPromptPath
{
 <#
    .SYNOPSIS
        Gets the file path to the latest Visual Studio Command Prompt. Returns $null if a path is not found.
     
    .DESCRIPTION
        Gets the file path to the latest Visual Studio Command Prompt. Returns $null if a path is not found.
    #>
 
# Get some environmental paths.
$vs2010CommandPrompt = $env:VS100COMNTOOLS + "vcvarsall.bat"
$vs2012CommandPrompt = $env:VS110COMNTOOLS + "VsDevCmd.bat"
 
# Store the VS Command Prompt to do the build in, if one exists.
$vsCommandPrompt = $null
if (Test-Path $vs2012CommandPrompt)
{
    $vsCommandPrompt = $vs2012CommandPrompt
}
elseif (Test-Path $vs2010CommandPrompt)
{
    $vsCommandPrompt = $vs2010CommandPrompt
}
 
# Return the path to the VS Command Prompt if it was found.
return $vsCommandPrompt
}
 
function Get-MsBuildPath
{
 <#
    .SYNOPSIS
    Gets the path to the latest version of MsBuild.exe. Returns $null if a path is not found.
     
    .DESCRIPTION
    Gets the path to the latest version of MsBuild.exe. Returns $null if a path is not found.
#>
 
# Array of valid MsBuild versions
$Versions = @("4.0", "3.5", "2.0")
 
# Loop through each version from largest to smallest
foreach ($Version in $Versions)
{
    # Try to find an instance of that particular version in the registry
    $RegKey = "HKLM:\SOFTWARE\Microsoft\MSBuild\ToolsVersions\${Version}"
    $ItemProperty = Get-ItemProperty $RegKey -ErrorAction SilentlyContinue
 
    # If registry entry exsists, then get the msbuild path and retrun
    if ($ItemProperty -ne $null)
    {
        return Join-Path $ItemProperty.MSBuildToolsPath -ChildPath MsBuild.exe
    }
}
 
# Return that we were not able to find MsBuild.exe.
return $null
}
Export-ModuleMember -Function Invoke-MsBuild

 

# Import the module used to build the .sln and project files.
Import-Module -Name [DirectoryContainingModule]\Invoke-MsBuild.psm1
Invoke-MsBuild -Path "[Path to .sln file]" -MsBuildParameters "/target:Clean;Build /property:Configuration=Release;Platform=""Mixed Platforms"" /verbosity:Quiet"

 

And here’s an example of how to use it (assuming you saved it to a file called “Invoke-MsBuild.psm1”:

If you have any suggestions, please comment.

Feel free to use this in your own scripts.  Happy coding!