TFS Automatic Deployment and Email Script

Share your PowerShell scripts for Deployment Manager

TFS Automatic Deployment and Email Script

Postby SteveGTR » Mon Dec 02, 2013 5:46 pm

Hopefully someone will find this useful. This script is called by a custom TFS process script after our website has been built and the package has been created on the NuGet feed. The xaml file is based on the code posted by Justin at http://thefutureofdeployment.com/callin ... r-exe-tfs/. Instead of executing DeploymentManager.exe from the xaml, my version executes the following powershell script that performs the following tasks:

1) Executes DeploymentManager.exe

2) Archives old NuGet packages

3) Sends out an email message with annotated changes

With a little modification, this script might be useful for your project.

Code: Select all
# To run via -File parameter do the following:
# Set-ExecutionPolicy Unrestricted

# Get the packages that are going to be deployed

$Feed = \"\\\\servername\\DeploymentFeed\";
$Archive = \"\\\\servername\\DeploymentFeedArchive\";

$WebPagePackages = $Feed + \"\\WebPage*.nupkg\";
$MergePageDatabasePackages = $Feed + \"\\MergePage*.nupkg\";

$CurrentWebPackage = Get-ChildItem $WebPagePackages | sort LastWriteTime | select -last 1;
$CurrentMergePagePackage = Get-ChildItem $MergePageDatabasePackages | sort LastWriteTime | select -last 1;

# Execute the deployment manager

&\"C:\\Program Files\\Red Gate\\DeploymentManager.EXE\" create-release --server=http://servername:8080/ --apiKey=YourKey --project=ProjectName --deployto=Development --releasenotes=\"Automatically generated from TFS website build.\" --waitfordeployment --httpConnectTimeout=00:05:00 | Write-Host;

if ($LastExitCode -ne 0)
{
   Write-Host (\"Deployment manager returned error: \" + $LastExitCode);
   
   exit $LastExitCode;
}

# Move all files, but the latest to archive

if ($CurrentWebPackage -ne $null)
{
   Write-Host (\"Current WebPage package: \" + $CurrentWebPackage.Name);

   $Parts = $CurrentWebPackage.Name.Split(\".\");
   
   if ($Parts[4] -ne $null)
   {
      $WebPageBuildNumber = $Parts[4];
      $Version = $Parts[1] + \".\" + $Parts[2] + \".\" + $Parts[3] + \".\" + $Parts[4];
      
      Write-Host (\"Current WebPage Build Number: \" + $WebPageBuildNumber);
      Write-Host (\"Current Deployment Version: \" + $Version);
   }
   
   $ArchiveFiles = Get-ChildItem -Path $WebPagePackages -Exclude $CurrentWebPackage.Name;

   if ($ArchiveFiles -ne $null)
   {
      foreach ($File in $ArchiveFiles)
      {
         Write-Host (\"Archiving: \" + $File.Name);
         Move-Item $File $Archive | Write-Host;
      }
   }
}

if ($CurrentMergePagePackage -ne $null)
{
   Write-Host (\"Current MergePage Database package: \" + $CurrentMergePagePackage.Name);

   $Parts = $CurrentMergePagePackage.Name.Split(\".\");
   
   if ($Parts[3] -ne $null)
   {
      $MergePageBuildNumber = $Parts[0] + \"_\" + $Parts[2] + \".\" + $Parts[3];
      
      Write-Host (\"Current MergePage Database Build Number: \" + $MergePageBuildNumber);      
   }

   $ArchiveFiles = Get-ChildItem -Path $MergePageDatabasePackages -Exclude $CurrentMergePagePackage.Name;

   if ($ArchiveFiles -ne $null)
   {
      foreach ($File in $ArchiveFiles)
      {
         Write-Host (\"Archiving: \" + $File.Name);
         Move-Item $File $Archive | Write-Host;
      }
   }
}

if ($WebPageBuildNumber -ne $null -and $MergePageBuildNumber -ne $null)
{

   #---- Start of code to enumerate build details

   [void][System.Reflection.Assembly]::LoadWithPartialName(\"Microsoft.TeamFoundation.Client\"); 
   [void][System.Reflection.Assembly]::LoadWithPartialName(\"Microsoft.TeamFoundation.Build.Client\");
   [void][System.Reflection.Assembly]::LoadWithPartialName(\"Microsoft.TeamFoundation.Build.Common\");

   if ((Get-PSSnapin -Name Microsoft.TeamFoundation.PowerShell -ErrorAction SilentlyContinue) -eq $null)
   {
      Add-PSSnapin Microsoft.TeamFoundation.PowerShell;
   }

   $html = \"<table>\";

   $Packarray =
      @(
      [pscustomobject]@{PackageName=\"Website package: $CurrentWebPackage\";ProjectName=\"MergePage\";BuildName=\"MergePage\";LocationToSearch=\"$/MergePage\";BuildNumber=$WebPageBuildNumber},
      [pscustomobject]@{PackageName=\"Database package: $CurrentMergePagePackage\";ProjectName=\"PageDatabases\";BuildName=\"MergePage\";LocationToSearch=\"$/PageDatabases/MergePage\";BuildNumber=$MergePageBuildNumber}
   );

   foreach ($p in $Packarray)
   {
      $html +=
         \"<tr>\" +
            \"<td colspan=4>\" +
               $p.PackageName +
            \"</td>\" +
         \"</tr>\";

      $projectName =  $p.ProjectName;
      $buildName = $p.BuildName;
      $locationToSearch = $p.LocationToSearch;
      $buildNumber = $p.BuildNumber;

      $tfsCollectionUrl = \"http://servername:8080/tfs/MergePageCollection\";

      $server = new-object Microsoft.TeamFoundation.Client.TfsTeamProjectCollection(New-Object Uri($tfsCollectionUrl));

      $tfs = get-tfsserver $tfsCollectionUrl;

      $buildServer = $server.GetService([Microsoft.TeamFoundation.Build.Client.IBuildServer]);

      $buildDetail =
         $buildServer.QueryBuilds($projectName, $buildName) |
         where {($_.BuildNumber -eq $buildNumber -or ($_.BuildFinished -eq $true -and $_.Status -eq \"Succeeded\")) -and $_.LabelName -and $_.LabelName -ne \"\" } |
         sort -desc StartTime |
         select BuildNumber, StartTime, FinishTime;

      $vcs = $server.GetService([Microsoft.TeamFoundation.VersionControl.Client.VersionControlServer]);

      $i1 = 0;
      $i2 = $i1 + 1;

      # Construct the date range using label dates

      $LabelDetail = $vcs.QueryLabels($BuildDetail[$i2].BuildNumber, $locationToSearch, $null, $false) | select LastModifiedDate;

      $dateRange = \"D\" + (Get-Date($LabelDetail.LastModifiedDate) -format s) + \"~\";

      $LabelDetail = $vcs.QueryLabels($BuildDetail[$i1].BuildNumber, $locationToSearch, $null, $false) | select LastModifiedDate;

      $dateRange += \"D\" + (Get-Date($LabelDetail.LastModifiedDate) -format s);

      $changeDetail = Get-TfsItemHistory $locationToSearch -Server $tfs -Version $dateRange -Recurse -IncludeItems | sort -desc ChangesetId;

      $changeFound = $false;

      # Because the Webpage building process is still active and hasn't finished, use the current date on the build
      # agent. This is the best I could do.
      if ($BuildDetail[$i1].FinishTime -eq $null -or $BuildDetail[$i1].FinishTime -lt $BuildDetail[$i1].StartTime)
      {
         $FinishTime = Get-Date;
      }
      else
      {
         $FinishTime = $BuildDetail[$i1].FinishTime;
      }

      $html +=
         \"<tr>\" +
            \"<td colspan=2 nowrap>\" +
               \"Build: \" + $BuildDetail[$i1].BuildNumber +
            \"</td>\" +
            \"<td nowrap>\" +
               \"Started: \" + (Get-Date($BuildDetail[$i1].StartTime) -format g) +
            \"</td>\" +
            \"<td nowrap>\" +
               \"Duration: {0:N1} minutes\" -f ($FinishTime - $BuildDetail[$i1].StartTime).TotalMinutes +
            \"</td>\" +
         \"</tr>\";

      foreach ($changeSet in ($changeDetail | select ChangesetId, Committer, CreationDate, Comment))
      {
         $changeFound = $true;
   
         $html +=
            \"<tr>\" +
               \"<td>\" +
                  \"&nbsp;\" +
               \"</td>\" +
               \"<td nowrap>\" +
                  \"Changeset: \" + $changeSet.ChangesetID +
               \"</td>\" +
               \"<td nowrap>\" +
                  \"Date: \" + (Get-Date($changeSet.CreationDate) -format g) +
               \"</td>\" +
               \"<td nowrap>\" +
                  \"User: \" + $changeSet.Committer +
               \"</td>\" +
            \"</tr>\";
         
         if ($changeSet.Comment -and $changeSet.Comment -ne \"\")
         {
            $html +=
               \"<tr>\" +
                  \"<td>\" +
                     \"&nbsp;\" +
                  \"</td>\" +
                  \"<td colspan=3>\" +
                     \"Comments: \" + $changeSet.Comment +
                  \"</td>\" +
               \"</tr>\";
         }
         else
         {
            $html +=
               \"<tr>\" +
                  \"<td>\" +
                     \"&nbsp;\" +
                  \"</td>\" +
                  \"<td colspan=3>\" +
                     \"Comments: None\" +
                  \"</td>\" +
               \"</tr>\";   
         }
   
         foreach ($typeSet in ($changeDetail | where { $_.ChangesetId -eq $changeSet.ChangesetID } | Select-Object -Expand \"Changes\"))
         {
            $html +=
               \"<tr>\" +
                  \"<td>\" +
                     \"&nbsp;\" +
                  \"</td>\" +
                  \"<td>\" +
                     \"&nbsp;\" +
                  \"</td>\" +
                  \"<td colspan=2 nowrap>\" +
                     \"Action: \" + $typeSet.ChangeType +
                  \"</td>\" +
               \"</tr>\";            
      
            foreach ($itemSet in ($typeSet | Select-Object -Expand \"Item\"))
            {
               $html +=
                  \"<tr>\" +
                     \"<td>\" +
                        \"&nbsp;\" +
                     \"</td>\" +
                     \"<td>\" +
                        \"&nbsp;\" +
                     \"</td>\" +
                     \"<td>\" +
                        \"&nbsp;\" +
                     \"</td>\" +               
                     \"<td>\" +
                        \"Item: \" + $itemSet.ServerItem +
                     \"</td>\" +
                  \"</tr>\";      
            }
         }
      }

      if ($changeFound -eq $false)
      {
         $html +=
            \"<tr>\" +
               \"<td>\" +
                  \"&nbsp;\" +
               \"</td>\" +
               \"<td colspan=3>\" +
                  \"Nothing new included in build\" +
               \"</td>\" +
            \"</tr>\";         
      }
   }

   $html += \"</table>\";
}
else
{
   $html = \"Could not determine version information, check the log file.\";
}

#---- End of code

# Email notification messages

$EmailFrom = \"fromemail\";
$EmailTo = \"toemails\";
$Subject = \"Version: \" + $Version + \" deployed to Development\";
$Body = $html;
$SMTPServer = \"smtpserver\";
$SMTPClient = New-Object Net.Mail.SmtpClient($SmtpServer, 587);
$SMTPClient.EnableSsl = $true;
$SMTPClient.Credentials = New-Object System.Net.NetworkCredential(\"validemail\", \"password\");
$Message = New-Object Net.Mail.MailMessage($EmailFrom, $EmailTo, $Subject, $Body);
$Message.IsBodyHtml = $true;
$SMTPClient.Send($Message);

exit 0;
SteveGTR
 
Posts: 67
Joined: Wed Jan 11, 2012 11:19 pm

Postby SteveGTR » Mon Dec 02, 2013 9:14 pm

Here's an example of the information that is formatted in the email:

Code: Select all
Subject: Version: 2.3.31.251 deployed to Development

Website package: \\\\servername\\DeploymentFeed\\WebPage.2.3.31.251.nupkg
Build: 251 Started: 11/29/2013 11:00 PM Duration: 16.3 minutes
  Changeset: 24226 Date: 11/29/2013 9:58 AM User: user1
  Comments: None
    Action: Edit
      Item: $/MergePage/WebPAGE/Reporting/Controls/RevisionList.ascx
Database package: \\\\servername\\DeploymentFeed\\MergePage.0.20131122.1.nupkg
Build: MergePage_20131122.1 Started: 11/22/2013 11:22 AM Duration: 2.4 minutes
  Changeset: 24206 Date: 11/22/2013 11:22 AM User: user2
  Comments: None
    Action: Edit
      Item: $/PageDatabases/MergePage/Stored Procedures/dbo.Admin_Generate_Delinquent_PPR_FFR.sql
    Action: Edit
      Item: $/PageDatabases/MergePage/Stored Procedures/dbo.Admin_Generate_Delinquent_PPR_FFR_Inprocess.sql
    Action: Edit
      Item: $/PageDatabases/MergePage/Stored Procedures/dbo.usp_GetDelinquentTTA.sql
    Action: Edit
      Item: $/PageDatabases/MergePage/Stored Procedures/dbo.uspGetOMBControlNoInfo.sql
    Action: Edit
      Item: $/PageDatabases/MergePage/Views/dbo.vwAttachment.sql
SteveGTR
 
Posts: 67
Joined: Wed Jan 11, 2012 11:19 pm

Postby justin.caldicott » Tue Dec 03, 2013 12:39 pm

Thanks for sharing Steve, this looks very helpful!
Justin Caldicott
Product Manager - Deployment Manager
Red Gate
justin.caldicott
 
Posts: 55
Joined: Wed Apr 20, 2011 5:42 pm


Return to Deployment Manager PowerShell Scripts

Who is online

Users browsing this forum: No registered users and 0 guests