The in-place upgrade feature allows you to upgrade your Lync 2013 servers to Skype for Business without the need to build and migrate as in previous versions. I have successfully used this method numerous times now, and have been surprised with how easy and reliable it is, providing you following a well thought out plan; trouble is, there is a lack of detail in the official documentation to aid you in this. This article supplements my in-place upgrade guide, details my process, and fills in some gaps.
First things first, take a backup!
If things go wrong you’re going to need a backup. The only way to do this easily and correctly, is by using Lasse Wedø’s back script which you can download here.
Make sure your environment is healthy
- Check your “Lync Server” event logs for anything out of the ordinary
- Make sure all your Lync services are running
- Check that replication is up to date:
1Get-CsManagementStoreReplicationStatus | FT - If you have pool pairing enabled, check that its in a healthy state:
1(Get-CsBackupServiceStatus -PoolFqdn <PoolFQDN>).BackupModules - If you are upgrading an Enterprise Edition pool, check the fabric state:
1Get-CsPoolFabricState -PoolFqdn <PoolFQDN>
Take note of the CMS master
Microsoft recommends upgrading from the inside out; meaning Front End servers first, other internal servers, then Edge servers. They also recommend that the CMS is upgraded last, however that is not strictly required, and you could upgrade this while still having Lync 2013 servers in the mix. In any case you’ll want to take note of the pool that hosts the CMS. You can check this using the following command:
1 |
Get-CsManagementStoreReplicationStatus -CentralManagementStoreStatus |
When it comes time to upgrading the pool owning the CMS, you might actually want to move it another pool so that it stays online while you are performing the upgrade. You don’t want the CMS offline for too long, as this will cause clients to go in to limited functionality mode after 20 minutes by default.
Keep things online while you upgrade
If you have the luxury of a multiple pool deployment, you can shuffle users and services around so you can keep them online while you upgrade. Most commonly you’ll want to move your users, conference directories and Response Groups.
Other things to take note of
- Check your Kerboros configuration – if you are using Kerboros, you’ll need to re-create the account after the upgrade
- After the upgrade is complete, apply the latest Cumulative Update, and don’t forget to apply the database updates as per the release notes!
- Skype for Business now includes a call quality monitor that prompts users to rate their call – this is on by default in Skype for Business. Let your users know, or turn this off by policy:
1Set-CsClientPolicy -RateMyCallDisplayPercentage 0 - If you are running the Lync 2013/Skype for Business 2015 client, you have two skin options. You can control this by policy, but by default in Skype for Business, users will see the Skype for Business look and feel. Notify your users of this change, or change this default:
1Set-CsClientPolicy -EnableSkypeUI $false - Skype for Business no longer uses the Company_Phone_Number_Normalization_Rules.txt file to normalise numbers from AD to the Address Book. Instead this has move to PowerShell, and you can import the existing rules from this file using PowerShell e.g:
1Import-CsCompanyPhoneNormalizationRules -Filename \\<LyncShare>\2-WebServices-71\ABFiles\Company_Phone_Number_Normalization_Rules.txt -Identity Global
Make your life easy by scripting
Using scripts will drastically reduce the amount of time it takes to complete an online upgrade. Here are some examples to get you started; make sure you understand what these do, and customise them to suit your needs – dont just run them!
Set some common variables
These common variables are used throughout the following examples.
1 2 3 |
$PoolToBeUpgraded = "<pooltoupgrade.domain.com>" #the pool that is to be upgraded $TempPool = "<temppool.domain.com>" #where you will temporarily move users $WorkingDirectory = "E:\SfB Upgrade Exports" #working directory to save backups and files required |
Take a record of users on the pool to be upgraded
1 2 |
#Get users on pool to be upgraded Get-CsUser | Where {$_.RegistrarPool.ToString() -eq $PoolToBeUpgraded} | Select DisplayName, SipAddress, RegistrarPool | Export-Csv "$WorkingDirectory\$PoolToBeUpgraded`_Users_Before_Move.csv" -NoTypeInformation |
Take a record of conference directories on the pool to be upgraded
1 2 |
#Get conference directories on pool to be upgraded Get-CsConferenceDirectory | Where {$_.ServiceId.ToString() -eq "UserServer:$PoolToBeUpgraded"} | Select Identity, ServiceId | Export-Csv "$WorkingDirectory\$PoolToBeUpgraded`_ConfDirs_Before_Move.csv" -NoTypeInformation |
Take a record of Workflows with managers
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#Workflows with managers - managers may need to be re-added after the upgrade $workflows = Get-CsRgsWorkflow | Where {$_.ManagersByUri.Count -gt 0 -and $_.OwnerPool.ToString() -eq $PoolToBeUpgraded} | Select Name, Identity, ManagersByUri $wfArray = @() foreach ($wf in $workflows) { $obj = new-object PSObject $obj | add-member -membertype NoteProperty -name "Name" -value $wf.Name $obj | add-member -membertype NoteProperty -name "Identiy" -value $wf.Identity $obj | add-member -membertype NoteProperty -name "ManagersByUri" -value ($wf.ManagersByUri -join ";") $wfArray += $obj } $wfArray $wfArray | Export-Csv "$WorkingDirectory\$PoolToBeUpgraded`_RGS_With_Managers.csv" -NoTypeInformation |
Migrate users to a temporary pool
1 2 3 4 5 6 7 8 |
#Move users to temp pool $Users = Import-Csv "$WorkingDirectory\$PoolToBeUpgraded`_Users_Before_Move.csv" foreach ($user in $Users) { Write-Host "Moving user: $($user.DisplayName)" Move-CsUser -Identity $user.SipAddress -Target $TempPool -Confirm:$false #-MoveConferenceData } |
Migrate conference directories to a temporary pool
1 2 3 4 5 6 7 8 |
#Move conferencing directories to temp pool $ConfDirs = Import-Csv "$WorkingDirectory\$PoolToBeUpgraded`_ConfDirs_Before_Move.csv" foreach ($confDir in $ConfDirs) { Write-Host "Moving conference directory with ID: $($confDir.Identity) ServiceId: $($confDir.ServiceId)" Move-CsConferenceDirectory -TargetPool $TempPool -Identity $confDir.Identity -Confirm:$false } |
Migrate your Response Groups
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#BACKUP RGS FROM ORIGINATING POOL:: $Date = Get-Date $DateFileName = $Date.ToString("dd-MM-yyyy-HH:mm:ss").Replace(":",".") $RGSRestoreFile = "$WorkingDirectory\$PoolToBeUpgraded" + "_RGSBackup_MoveToTempPool_" + $DateFileName + ".zip" #Check Worksflows Get-CsRgsWorkflow -Identit "service:ApplicationServer:$PoolToBeUpgraded" | Select-Object Identity, OwnerPool, Name, LineUri | Format-Table #Export Config Export-CsRgsConfiguration –Source "service:ApplicationServer:$PoolToBeUpgraded" –Filename $RGSRestoreFile #MOVE TO TEMP POOL:: #Import Response Group configuration to temp pool Import-CsRgsConfiguration -Destination "service:ApplicationServer:$TempPool" -FileName $RGSRestoreFile #-Force #Gets RGS workflows that are owned by the originating pool and currently hosted on the temp pool (confirm your moved workflows are on this list) Write-Host "RGS workflows owned by failed pool $PoolToBeUpgraded and currently hosted on $TempPool :" Get-CsRgsWorkflow -Identity "service:ApplicationServer:$TempPool" -Owner "service:ApplicationServer:$PoolToBeUpgraded" | Select-Object Identity, OwnerPool, Name, LineUri | Format-Table |
Move users back to originating pool
1 2 3 4 5 6 7 8 9 |
#MOVE USERS BACK-------------------------------------------------------------- #Move users back: Using generated Csv, run the following from PowerShell to move users back to their originating pool $Users = Import-Csv "$WorkingDirectory\$PoolToBeUpgraded`_Users_Before_Move.csv" foreach ($user in $Users) { Write-Host "Moving user: $($user.DisplayName)" Move-CsUser -Identity $user.SipAddress -Target $PoolToBeUpgraded -Confirm:$false -MoveConferenceData } |
Move conference directories back to originating pool
1 2 3 4 5 6 7 8 9 |
#MOVE CONFERENCE DIRECTORIES BACK-------------------------------------------------------------- #Move coference directories back: Using PowerShell migrate the conference directories back to their originating pool $ConfDirs = Import-Csv "$WorkingDirectory\$PoolToBeUpgraded`_ConfDirs_Before_Move.csv" foreach ($confDir in $ConfDirs) { Write-Host "Moving conference directory with ID: $($confDir.Identity) ServiceId: $($confDir.ServiceId)" Move-CsConferenceDirectory -TargetPool $PoolToBeUpgraded -Identity $confDir.Identity -Confirm:$false } |
Move Response Groups back to originating pool
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#MOVE RESPONSE GROUPS BACK-------------------------------------------------------------- #MOVE BACK TO ORIGINATING POOL:: #Export RGS config from the temp pool where the owner is the originating pool, then remove them ready to import back to originating pool. $Date = Get-Date $DateFileName = $Date.ToString("dd-MM-yyyy-HH:mm:ss").Replace(":",".") $RGSTempPoolFullBackup = "$WorkingDirectory\$PoolToBeUpgraded" + "_RGSBackup_MoveBackFull_" + $DateFileName + ".zip" $RGSBackupMoveBack = "$WorkingDirectory\$PoolToBeUpgraded" + "_RGSBackup_MoveBack_" + $DateFileName + ".zip" #Take a full backup of Workflows hosted by the temp pool Export-CsRgsConfiguration –Source "service:ApplicationServer:$TempPool" –Filename $RGSTempPoolFullBackup #Take a backup from the temp pool of the workflows owned by the originating pool, then remove them from the temp pool Export-CsRgsConfiguration –Source "service:ApplicationServer:$TempPool" –Owner "service:ApplicationServer:$PoolToBeUpgraded" –Filename $RGSBackupMoveBack -RemoveExportedConfiguration #-Force #Import RGS config back to the originating pool Import-CsRgsConfiguration –Destination "service:ApplicationServer:$PoolToBeUpgraded" –OverwriteOwner –Filename $RGSBackupMoveBack #-Force #Gets RGS workflows that are owned and hosted by the originating pool (confirm your moved workflows ARE on this list) Write-Host "RGS workflows owned and hosted by originating pool $PoolToBeUpgraded :" Get-CsRgsWorkflow -Identity "service:ApplicationServer:$PoolToBeUpgraded" -Owner "service:ApplicationServer:$PoolToBeUpgraded" | Select-Object Name, PrimaryUri, LineUri | Format-Table #Check that the temp pool is no longer hosting any of the original pools Workflows (should be none) Get-CsRgsWorkflow -Identity "service:ApplicationServer:$TempPool" -Owner "service:ApplicationServer:$PoolToBeUpgraded" | Select-Object Identity, OwnerPool, Name, LineUri | Format-Table po |
Happy upgrading
Hopefully this gives you a bit more awareness around the upgrade process and what you need to consider. Remember to check out my in-place upgrade guide that takes you through the overall process. Happy upgrading!
Great and helpful article Andrew, thanks!.
Thank you, appreciate that!
Great article, will be putting it to the test in my next in-place upgrade