Archive

Posts Tagged ‘MSBuild’

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!

Using MSBuild to publish a VS 2012 SSDT .sqlproj database project the same way as a VS 2010 .dbproj database project (using command line arguments to specify the database to publish to)

March 18th, 2013 20 comments

Post and code updated on March 21, 2013, and again on March 22, 2013.

We recently upgraded from VS (Visual Studio) 2010 to VS 2012, and with it had to upgrade our .dbproj database project to a .sqlproj.  When making the switch I realized that .sqlproj database projects do not support specifying the database to deploy to as MSBuild command line arguments; instead you have to pass in the path to an xml file that has the necessary information.

So with the old .dbproj database project, you could deploy it to a database using:

MSBuild /t:Deploy /p:TargetDatabase="[DbName]";TargetConnectionString="Data Source=[Db.Server];Integrated Security=True;Pooling=False" /p:DeployToDatabase="True" "[PathToBranch]Database\Database.dbproj"

But with the new .sqlproj database project you have to do:

MSBuild /t:Publish /p:SqlPublishProfilePath="myPublishFile.publish.xml" "[PathToBranch]Database\Database.sqlproj"

Where “myPublishFile.publish.xml” contains the database server and name to publish to.

One other minor thing to note is that it is called “deploying” the database with .dbproj, and is called “publishing” the database with .sqlproj; so when I say Deploy or Publish, I mean the same thing.

We use TFS at my organization and while making new builds for our Test environment, we have the build process deploy the database solution to our various Test databases.  This would mean that for us I would either need to:

1 – create a new [DbName].publish.xml file for each database, check it into source control, and update the build template to know about the new file, or

2 – update the file contents of our myPublishFile.publish.xml file dynamically during the build to replace the Database Name and Server in the file before publishing to the database (i.e. read in file contents, replace string, write file contents back to file, publish to DB, repeat).

Option 1 means more work every time I want to add a new Test database to publish to.  Option 2 is better, but still means having to update my TF Build template and create a new activity to read/write the new contents to the file.

Instead, there is a 3rd option, which is to simply add the code below to the bottom of the .sqlproj file.  This will add some new MSBuild targets to the .sqlproj that will allow us to specify the database name and connection string using similar MSBuild command line parameters that we used to deploy the .dbproj project.

The code presented here is based on this post, but the author has closed the comments section on that post and has not replied to my emails about the bugs in his code and example, so I thought I would share my modified and enhanced solution.

  <!-- 
	Custom targets and properties added so that we can specify the database to publish to using command line parameters with VS 2012 .sqlproj projects, like we did with VS 2010 .dbproj projects.
	This allows us to specify the MSBuild command-line parameters TargetDatabaseName, and TargetConnectionString when Publishing, and PublishToDatabase when Building.
	I also stumbled across the undocumented parameter, PublishScriptFileName, which can be used to specify the generated sql script file name, just like DeployScriptFileName used to in VS 2010 .dbproj projects.
	Taken from: http://blog.danskingdom.com/using-msbuild-to-publish-a-vs-2012-ssdt-sqlproj-database-project-the-same-way-as-a-vs-2010-dbproj-database-project/
  -->
  <PropertyGroup Condition="'$(TargetDatabaseName)' != '' Or '$(TargetConnectionString)' != ''">
    <PublishToDatabase Condition="'$(PublishToDatabase)' == ''">False</PublishToDatabase>
    <TargetConnectionStringXml Condition="'$(TargetConnectionString)' != ''">
      &lt;TargetConnectionString xdt:Transform="Replace"&gt;$(TargetConnectionString)&lt;/TargetConnectionString&gt;
    </TargetConnectionStringXml>
    <TargetDatabaseXml Condition="'$(TargetDatabaseName)' != ''">
      &lt;TargetDatabaseName xdt:Transform="Replace"&gt;$(TargetDatabaseName)&lt;/TargetDatabaseName&gt;
    </TargetDatabaseXml>
    <TransformPublishXml>
        &lt;Project xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"&gt;
        &lt;PropertyGroup&gt;$(TargetConnectionStringXml)$(TargetDatabaseXml)&lt;/PropertyGroup&gt;
        &lt;/Project&gt;
    </TransformPublishXml>
    <SqlPublishProfilePath Condition="'$([System.IO.Path]::IsPathRooted($(SqlPublishProfilePath)))' == 'False'">$(MSBuildProjectDirectory)\$(SqlPublishProfilePath)</SqlPublishProfilePath>
    <!-- In order to do a transform, we HAVE to change the SqlPublishProfilePath -->
    <TransformOutputFile>$(MSBuildProjectDirectory)\Transformed_$(TargetDatabaseName).publish.xml</TransformOutputFile>
    <TransformScope>$([System.IO.Path]::GetFullPath($(MSBuildProjectDirectory)))</TransformScope>
    <TransformStackTraceEnabled Condition="'$(TransformStackTraceEnabled)'==''">False</TransformStackTraceEnabled>
  </PropertyGroup>
  <Target Name="AfterBuild" Condition="'$(PublishToDatabase)'=='True'">
    <CallTarget Targets="Publish" />
  </Target>
  <UsingTask TaskName="ParameterizeTransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll" />
  <Target Name="BeforePublish" Condition="'$(TargetDatabaseName)' != '' Or '$(TargetConnectionString)' != ''">
    <Message Text="TargetDatabaseName = '$(TargetDatabaseName)', TargetConnectionString = '$(TargetConnectionString)', PublishScriptFileName = '$(PublishScriptFileName)', Transformed Sql Publish Profile Path = '$(TransformOutputFile)'" Importance="high" />
    <!-- If TargetDatabaseName or TargetConnectionString, is passed in then we use the tokenize transform to create a parameterized sql publish file -->
    <Error Condition="!Exists($(SqlPublishProfilePath))" Text="The SqlPublishProfilePath '$(SqlPublishProfilePath)' does not exist, please specify a valid file using msbuild /p:SqlPublishProfilePath='Path'" />
    <ParameterizeTransformXml Source="$(SqlPublishProfilePath)" IsSourceAFile="True" Transform="$(TransformPublishXml)" IsTransformAFile="False" Destination="$(TransformOutputFile)" IsDestinationAFile="True" Scope="$(TransformScope)" StackTrace="$(TransformStackTraceEnabled)" SourceRootPath="$(MSBuildProjectDirectory)" />
    <PropertyGroup>
      <SqlPublishProfilePath>$(TransformOutputFile)</SqlPublishProfilePath>
    </PropertyGroup>
  </Target>

 

So after adding this code at the bottom of the .sqlproj file (above the </Project> tag though), you can now build and publish the database solution from the MSBuild command line using:

MSBuild /t:Build /p:TargetDatabaseName="[DbName]";TargetConnectionString="Data Source=[Db.Server];Integrated Security=True;Pooling=False" /p:PublishToDatabase="True" /p:SqlPublishProfilePath="Template.publish.xml" "[PathToBranch]\Database\Database.sqlproj"

Here you can see the 3 new parameters that we’ve added being used: TargetDatabaseName, TargetConnectionString, and PublishToDatabase.

When the TargetDatabaseName or TargetConnectionString parameters are provided we generated a new transformed .publish.xml file, which is the same as the provided “Template.publish.xml” file, but with the database and connection string values replaced with the provided values.

The PublishToDatabase parameter allows us to publish to the database immediately after the project is built; without this you would have to first call MSBuild to Build the database project, and then call MSBuild again to Publish it (or perhaps using “/t:Build;Publish” would work, but I didn’t test that).

If you want to simply publish the database project without building first (generally not recommended), you can do:

MSBuild /t:Publish /p:TargetDatabaseName="[DbName]";TargetConnectionString="Data Source=[Db.Server];Integrated Security=True;Pooling=False" /p:SqlPublishProfilePath="Template.publish.xml" "[PathToBranch]\Database\Database.sqlproj"

Be careful though, since if you don’t do a Build first, any changes that have been made since the last time the .sqlproj file was built on your machine won’t be published to the database.

Notice that I still have to provide a path to the template publish.xml file to transform, and that the path to this file is relative to the .sqlproj file (in this example the Template.publish.xml and .sqlproj files are in the same directory).  You can simply use one of the publish.xml files generated by Visual Studio, and then the TargetDatabaseName and TargetConnectionString xml element values will be replaced with those given in the command line parameters.  This allows you to still define any other publish settings as usual in the xml file.

Also notice that the PublishToDatabase parameter is only used when doing a Build, not a Publish; providing it when doing a Publish will not hurt anything though.

While creating my solution, I also accidentally stumbled upon what seems to be an undocumented SSDT parameter, PublishScriptFileName.  While the DeployScriptFileName parameter could be used in VS 2010 .dbproj projects to change the name of the generated .sql file, I noticed that changing its value in the .publish.xml file didn’t seem to have any affect at all (so I’m not really sure why Visual Studio puts it in there).  I randomly decided to try passing in PublishScriptFileName from the command line, and blamo, it worked!  I tried changing the <DeployScriptFileName> element in the .publish.xml file to <PublishScriptFileName>, but it still didn’t seem to have any effect.

So now if I wanted to deploy my database project to 3 separate databases, I could do so with the following code to first Build the project, and the Publish it to the 3 databases:

MSBuild /t:Build "[PathToBranch]\Database\Database.sqlproj"
MSBuild /t:Publish /p:TargetDatabaseName="[DbName1]";TargetConnectionString="Data Source=[Db.Server];Integrated Security=True;Pooling=False" /p:PublishScriptFileName="[DbName1].sql" /p:SqlPublishProfilePath="Template.publish.xml" "[PathToBranch]\Database\Database.sqlproj"
MSBuild /t:Publish /p:TargetDatabaseName="[DbName2]";TargetConnectionString="Data Source=[Db.Server];Integrated Security=True;Pooling=False" /p:PublishScriptFileName="[DbName2].sql" /p:SqlPublishProfilePath="Template.publish.xml" "[PathToBranch]\Database\Database.sqlproj"
MSBuild /t:Publish /p:TargetDatabaseName="[DbName3]";TargetConnectionString="Data Source=[Db.Server];Integrated Security=True;Pooling=False" /p:PublishScriptFileName="[DbName3].sql" /p:SqlPublishProfilePath="Template.publish.xml" "[PathToBranch]\Database\Database.sqlproj"

You could also instead just call MSBuild using the Build target with the PublishToDatabase parameter (which might actually be the safer bet); whatever you prefer.  I have found that once the database project is built once, as long as no changes are made to it then subsequent “builds” of the project only take a second or two since it detects that no changes have been made and skips doing the build.

If you have any questions or feedback, let me know.

Happy coding!

Parallel MSBuild FTW – Build faster in parallel

March 31st, 2012 1 comment

Hey everyone, I just discovered this great post yesterday that shows how to have msbuild build projects in parallel Smile

Basically all you need to do is pass the switches “/m:[NumOfCPUsToUse] /p:BuildInParallel=true” into MSBuild.

Example to use 4 cores/processes (If you just pass in “/m” it will use all CPU cores):

MSBuild /m:4 /p:BuildInParallel=true "C:devClient.sln"

Obviously this trick will only be useful on PCs with multi-core CPUs (which we should all have by now) and solutions with multiple projects; So there’s no point using it for solutions that only contain one project.  Also, testing shows that using multiple processes does not speed up Team Foundation Database deployments either in case you’re curious Winking smile

Also, I found that if I didn’t explicitly use “/p:BuildInParallel=true” I would get many build errors (even though the MSDN documentation says that it is true by default).

The poster boasts compile time improvements up to 59%, but the performance boost you see will vary depending on the solution and its project dependencies.  I tested with building a solution at my office, and here are my results (runs are in seconds):

# of Processes 1st Run 2nd Run 3rd Run Avg Performance
1 192 195 200 195.67 100%
2 155 156 156 155.67 79.56%
4 146 149 146 147.00 75.13%
8 136 136 138 136.67 69.85%

 

So I updated all of our build scripts to build using 2 cores (~20% speed boost), since that gives us the biggest bang for our buck on our solution without bogging down a machine, and developers may sometimes compile more than 1 solution at a time.  I’ve put the any-PC-safe batch script code at the bottom of this post.

The poster also has a follow-up post showing how to add a button and keyboard shortcut to the Visual Studio IDE to have VS build in parallel as well (so you don’t have to use a build script); if you do this make sure you use the .Net 4.0 MSBuild, not the 3.5 one that he shows in the screenshot.  While this did work for me, I found it left an MSBuild.exe process always hanging around afterwards for some reason, so watch out (batch file doesn’t have this problem though).  Also, you do get build output, but it may not be the same that you’re used to, and it doesn’t say “Build succeeded” in the status bar when completed, so I chose to not make this my default Visual Studio build option, but you may still want to.

Happy building!

:: Calculate how many Processes to use to do the build.
SET NumberOfProcessesToUseForBuild=1 
SET BuildInParallel=false
if %NUMBER_OF_PROCESSORS% GTR 2 (
                SET NumberOfProcessesToUseForBuild=2
                SET BuildInParallel=true
)
MSBuild /maxcpucount:%NumberOfProcessesToUseForBuild% /p:BuildInParallel=%BuildInParallel% "C:\dev\Client.sln"

Setting up keyboard shortcuts to build solutions in MSBuild

June 1st, 2011 No comments

One of the greatest benefits of building your solution flies in MSBuild (vs in Visual Studio directly) is that it doesn’t lock up the Visual Studio UI, which can be a huge pain if you have a large solution that takes several minutes (or longer) to build.  Building your solution in MSBuild leaves you free to inspect code in Visual Studio while your solution is building.  The only problem is that to do this you have to open a command prompt and type the command + path every time to build.

If you want to be able to right-click on a solution file and build it in MSBuild from the Windows Explorer context menu, check out MSBuildShellExtension (it’s free).  Being able to build right from Windows Explorer (without having to even open Visual Studio) is cool and may be enough to passify you, but I wanted to be able to build my solution file at anytime from anywhere on my PC with a keyboard shortcut.

Below I outline how I’ve setup my system to build my solution files in MSBuild with a quick keystroke.  Setup only takes a few minutes and requires AutoHotkey to be installed (it’s free and awesome).

Step 1 – Install AutoHotkey.

Step 2 – Create a shortcut to the Visual Studio Command Prompt (2010), move it directly to the C: drive, and make sure it is called “Visual Studio Command Prompt (2010)” (it is referenced at this location with this name by the AutoHotkey script in the following steps, but can be changed if needed).

VS2010CommandPrompt

CDrive

Step 3 – Create your AutoHotkey script……luckily, you don’t have to create it from scratch; you can use mine as your template and just adjust it to your liking . So copy and paste the script in the textbox below into a new text file, and then save it with the extension ".ahk", which is the AutoHotkey script extension (so you can just call it "AutoHotkeyScript.ahk" for example).  You will need to modify the code directory paths and solution file names in the script to match yours to build your solutions, but I’ve commented the script fairly thoroughly so it’s easy to see what it’s doing.

In my office we have both a Client solution and a Server solution, so I have the script setup to build the client solution with WindowsKey+C and the server solution with WindowsKey+S. We also work in multiple branches, so I have global variables near the top of the script that I can set to true to quickly switch between Development, QA, and Release branches.  I also have WindowsKey+U configured to open the code directory and WindowsKey+Q to open the database directory.  Obviously you can change the keyboard mappings to your liking; these are just the ones that I prefer.  As a side note here, just be aware that these will override the default windows key shortcuts; so in my case WindowsKey+U no longer opens up the Windows Ease of Access Center window.

; IMPORTANT INFO ABOUT GETTING STARTED: Lines that start with a
; semicolon, such as this one, are comments.  They are not executed.
 
; This script has a special filename and path because it is automatically
; launched when you run the program directly.  Also, any text file whose
; name ends in .ahk is associated with the program, which means that it
; can be launched simply by double-clicking it.  You can have as many .ahk
; files as you want, located in any folder.  You can also run more than
; one ahk file simultaneously and each will get its own tray icon.
 
; Make it so only one instance of this script can run at a time (and reload the script if another instance of it tries to run)
#SingleInstance force
 
;==========================================================
; Global Variables - Path settings, customization, etc.
;==========================================================
 
; Set one of these to &quot;true&quot; to build from the Staging or Release branches, otherwise we'll use the development branch.
_UsingTFSStagingBranch := false
_UsingTFSReleaseCandidate := false
 
; Specify the Code Folder containing the Solution files to build
if (_UsingTFSReleaseCandidate == true)
{
    ; The directory of the current build's Code folder
    _CodeFolder := &quot;C:\dev\TFS\RQ4TeamProject\Release\RQ4\4.2.0\&quot;
}
else if (_UsingTFSStagingBranch == true)
{
    ; The directory of the current build's Code folder
    _CodeFolder := &quot;C:\dev\TFS\RQ4TeamProject\Staging\RQ4\&quot;
}
else
{
    ; The directory of the current build's Code folder
    _CodeFolder := &quot;C:\dev\TFS\RQ4TeamProject\Dev\RQ4\Core\&quot;
}
 
; Path to the database folder
_DatabaseFolder := &quot;C:\dev&quot;
 
; The path to the Visual Studio Command Prompt link
_VSCommandPromptPath := &quot;C:\Visual Studio Command Prompt (2010).lnk&quot;
_VSCommandPromptWindowName := &quot;Administrator: Visual Studio Command Prompt (2010)&quot;
 
; The position I want the MS Build window to move to when opened
_MSBuildWindowPositionX := 400
_MSBuildWindowPositionY := 270
 
; The MSBuild command to use
_MSBuildCommand := &quot;msbuild&quot; ; /verbosity:minimal&quot;
 
 
;==========================================================
; WindowsKey+C - Build the Client.sln
;==========================================================
#c UP::
 
; Make sure the keys have been released before continuing to avoid accidental commands
KeyWait LWin
;KeyWait c
 
;BuildSolution(_CodeFolder . &quot;RQ4.Client.sln&quot;)
BuildSolution(&quot;RQ4.Client.sln&quot;)
 
return
 
;==========================================================
; WindowsKey+S - Build the Server.sln
;==========================================================
#s UP::
 
; Make sure the keys have been released before continuing to avoid accidental commands
KeyWait LWin
;KeyWait s
 
BuildSolution(&quot;RQ4.Server.sln&quot;)
 
return
 
;==========================================================
; WindowsKey+B - Build the Server.sln then Client.sln
;==========================================================
#b UP::
 
; Make sure the keys have been released before continuing to avoid accidental commands
KeyWait LWin
;KeyWait b
 
BuildSolution(&quot;RQ4.Server.sln&quot;)
BuildSolution(&quot;RQ4.Client.sln&quot;)
 
return
 
;==========================================================
; WindowsKey+U - Open the Code folder
;==========================================================
#u UP::Run %_CodeFolder%
 
;==========================================================
; WindowsKey+Q - Open the Database folder
;==========================================================
#q UP::Run %_DatabaseFolder%
 
 
;==========================================================
; Functions
;==========================================================
BuildSolution(solutionPath)
{
    ; Let this function know that all variables except the passed in parameters are global variables.
    global
 
    ; If the Visual Studio Command Prompt is already open
    if WinExist(_VSCommandPromptWindowName)
    {
        ; Put it in focus
        WinActivate
    }
    ; Else the VS Command Prompt is not already open
    else
    {
        ; So open the Visual Studio 2008 Command Prompt
        Run %_VSCommandPromptPath%
         
        ; Make sure this window is in focus before sending commands
        WinWaitActive, %_VSCommandPromptWindowName%
         
        ; If the window wasn't opened for some reason
        if Not WinExist(_VSCommandPromptWindowName)
        {
            ; Display an error message that the VS Command Prompt couldn't be opened
            MsgBox, There was a problem opening %_VSCommandPromptPath%
         
            ; Exit, returning failure
            return false
        }
    }
 
    ; Make sure this window is in focus before sending commands
    WinWaitActive, %_VSCommandPromptWindowName%
 
    ; Move the window to the position I like it to be
    WinMove, _MSBuildWindowPositionX, _MSBuildWindowPositionY
 
    ; Set it to the correct directory
    SendInput cd %_CodeFolder% {Enter}
 
    ;MsgBox %solutionPath%  ; Message box to display the Solution Path for debugging purposes
     
    ; Build the solution file
    SendInput %_MSBuildCommand% %solutionPath% {Enter}
     
    ; Return success
    return true
}

Step 4 – Have your AutoHotkey script automatically start when you login to Windows, so that you don’t have to manually launch it all the time.

Method 1:

This method is the easiest, but I discovered it after Method 2 (below).  Simply open up the Windows Start Menu, navigate to the Startup folder within All Programs, right-click on it and choose Open All Users.  Then simply paste a shortcut to your AutoHotkey script in this folder.  That’s it; the script will now launch whenever any user logs into Windows.  If you only want the script to run when you log into Windows (no other users), then just choose Open instead of Open All Users when right-clicking on the Startup folder.

 indexindex2

Method 2:

Open the Windows Task Scheduler and create a new Basic Task.  Give it a name and description (something like “launch AutoHotkey script at login”), and then specify to have it run “When I log on”.  Then specify that you want it to “Start a program”, and then point it towards the AutoHotkey script you created in Step 3.  Before you finish the wizard, check off “Open the Properties dialog for this task when I click Finish”.  When that Properties dialog opens up, go to the Conditions tab and make sure none of the checkboxes under the Power category are checked off; this will ensure the script still launches if you are on a laptop and not plugged into AC power.  If you need your script to “Run as admin”, then on the General tab check off “Run with highest privileges”; this may be required for your script to perform certain actions the require admin privileges, so you can check it off just to be safe.

Open Task Scheduler(1)

 Create Basic Task in Task Scheduler

 

Basic Task Conditions

Run Scheduled Task as Admin_2

And that’s it.  Now you can build your solution file in MSBuild with a quick keystroke from anywhere on your computer.  I have chosen to use the Windows Key for my shortcut keys, but you don’t have to; you can use whatever keyboard shortcut you want.  And feel free to modify the script I provided to do whatever else you want; AutoHotkey is very powerful and can be used for so many things, so be sure to checkout their website for more examples of scripts and what it can do.  For example lots of people use it to automatically spell-correct as they type, or to automatically expand abbreviations (so I could type DS and hit tab, and have it expand to Daniel Schroeder, or type MyAddress and have it put my address in).

Happy Coding!