29 January 2014

Lync Backups with PowerShell

** EDIT: After running this script more, I noticed that running from a Scheduled Task was hit or miss on copying files to the DFS share. I have cleaned up the robocopy command and added some basic error checking to this portion of the script. **

Backups of any production system are always a good idea. SQL backups and vmdk backups of the virtual machines are all great, but may not be the solution you need.

Microsoft has posted the following recommendations for Lync backups.
2010 Backups
2013 Backups

Since I have an environment that contains Lync 2010 and 2013 I wanted one script that would back up my entire environment. The script I have written backs up all the core settings (topology, LIS, voice data, policies, etc) and user information for both Lync 2010 and Lync 2013. It backs the information up locally to the server and then copies the information to a DFS fileshare. The backups are kept for 30 days and then deleted.

To backup the 2010 user data, I copied dbimpexp.exe locally to the 2013 server. Since the output xml file is quite large in my case, I zip the file and delete it before copying everything to the fileshare.

Once the backup and copy are complete a basic email report is sent out.

I have the script set up as a Scheduled Task that runs twice a week. Make sure whatever account you schedule this with has Full Control of the Lync share.

You can download the script here. *Please note there is currently a compatibility issue with IE and some of the code is not displaying here. Please download the script or load it in another browser.*

#############################
#
#   Export Script for Backup 
#   Written by Brad Roberts
#   Backs up user data for 2010/2013, config data - copies to $drFolderPath below
#   Updated 27 February 2014
#
#############################
 
Function Add-Zip{
 Param([string]$ZipFilename)
 If(-Not (Test-Path($ZipFilename))){
  Set-Content $ZipFilename ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
  (Dir $ZipFilename).IsReadOnly = $False 
 }
 $ShellApplication = New-Object -COM Shell.Application
 $ZipPackage = $ShellApplication.NameSpace($ZipFilename)
 ForEach($File in $Input){ 
  $ZipPackage.CopyHere($File.FullName)
  Start-Sleep -Milliseconds 15000
 }
}
 
### Import Lync Module 
Import-Module "C:\Program Files\Common Files\Microsoft Lync Server 2013\Modules\Lync\Lync.psd1"
 
### Variables To Set 
$folderPath = "C:\Backup" 
$lengthOfBackup = "-30" 
$drFolderPath = "\\thatucguy.com\Lync\Backup" 
$poolFQDN = "lync-2013-pool.thatucguy.com"
 
### Production – Delete Older Than x Days 
get-childitem $folderPath -recurse | where {$_.lastwritetime -lt (get-date).adddays($lengthOfBackup) -and -not $_.psiscontainer} |% {remove-item $_.fullname -force }
 
### Production – Delete Empty Folders 
$a = Get-ChildItem $folderPath -recurse | Where-Object {$_.PSIsContainer -eq $True} 
$a | Where-Object {$_.GetFiles().Count -eq 0} | Remove-Item
 
### Production – Get Date and Create Folder 
$currDate = get-date -uformat "%a-%m-%d-%Y-%H-%M" 
New-Item $folderPath\$currDate -Type Directory
 
### Delete Older Than x Days – DR Side 
get-childitem $drFolderPath -recurse | where {$_.lastwritetime -lt (get-date).adddays($lengthOfBackup) -and -not $_.psiscontainer} |% {remove-item $_.fullname -force }
 
### Delete Empty Folders – DR Side 
$a = Get-ChildItem $drFolderPath -recurse | Where-Object {$_.PSIsContainer -eq $True} 
$a | Where-Object {$_.GetFiles().Count -eq 0} | Remove-Item
 
### Message Out 
Write-Host -ForegroundColor Green "Backup to server in progress"
$strTranscript += "
Backup to server in progress...
"
 
### Export CMS/XDS and LIS 
Export-CsConfiguration -FileName $folderPath\$currDate\XdsConfig.zip 
Export-CsLisConfiguration -FileName $folderPath\$currDate\LisConfig.zip
 
### Export Voice Information
Get-CsDialPlan | Export-Clixml -path $folderPath\$currDate\DialPlan.xml
Get-CsVoicePolicy | Export-Clixml -path $folderPath\$currDate\VoicePolicy.xml
Get-CsVoiceRoute | Export-Clixml -path $folderPath\$currDate\VoiceRoute.xml
Get-CsPstnUsage | Export-Clixml -path $folderPath\$currDate\PSTNUsage.xml
Get-CsVoiceConfiguration | Export-Clixml -path $folderPath\$currDate\VoiceConfiguration.xml
Get-CsTrunkConfiguration | Export-Clixml -path $folderPath\$currDate\TrunkConfiguration.xml
 
### Export RGS Config 
Export-CsRgsConfiguration -Source "service:ApplicationServer:$poolFQDN" -FileName $folderPath\$currDate\RgsConfig.zip
Write-Host -ForegroundColor Green "XDS, LIS and RGS backup to server is completed." 
$strTranscript += "
XDS, LIS and RGS backup to server is complete."
 
### Export User Information 
# Export 2013 data
Export-CsUserData -PoolFqdn $poolFQDN -FileName $folderPath\$currDate\Lync2013UserData.zip
$strTranscript += "
Export of Lync 2013 user data complete."
# Export 2010 data
C:\Admin\Software\dbimpexp /hrxmlfile:"$folderPath\$currDate\Lync2010UserData.xml" /sqlserver:sql-01
$strTranscript += "
Export of Lync 2010 user data complete."
 
#Create new zip file
Get-ChildItem $folderPath\$currDate\Lync2010UserData.xml | Add-Zip $folderPath\$currDate\Lync2010UserData.zip
Remove-Item $folderPath\$currDate\Lync2010UserData.xml
 
### Copy Files to DR Server 
robocopy $folderPath\$currDate $drFolderPath\$currDate /mir /S /tbd
# Check for last file written
if (Test-Path $drFolderPath\$currDate\* -include XdsConfig.zip) {
   $strTranscript += "

Files copied to $drfolderPath\$currDate"
   }
else  {
   $strTranscript += "

Files were NOT copied to $drfolderPath\$currDate. Please validate backup."
   }
 
### Email Transcript
$strSMTPServer = "smtp.thatucguy.com"
$msg = new-object Net.Mail.MailMessage
$smtp = new-object Net.Mail.SmtpClient($strSMTPServer)
 
$strHTMLHeader = $strHTMLHeader + ""
$strHTMLHeader = $strHTMLHeader + ""
$strHTMLHeader = $strHTMLHeader + ""
$strHTMLHeader = $strHTMLHeader + ""
$strHTMLHeader = $strHTMLHeader + ""
 
$strComputerName = gc env:computername
$dtTimeNow = get-date
$strScriptInfo = "


Script Info
Script Name:  " + $MyInvocation.MyCommand.Definition + "
Time:  " + $dtTimeNow + "
Run From:  " + $strComputerName
$strHTMLFooter = $strHTMLFooter + $strScriptInfo
$strHTMLFooter = $strHTMLFooter + ""
$strHTMLFooter = $strHTMLFooter + ""
#$strTranscript = Get-Content $logpath
$strHTMLBody = $strHTMLHeader + $strTranscript + $strHTMLFooter
 
$msg = New-Object System.Net.Mail.MailMessage 
$msg.From = "no-reply@thatucguy.com" 
$msg.To.Add("lync.admin@thatucguy.com") 
$msg.Subject = "Lync Backup Report - " + (Get-Date -format D)
$msg.IsBodyHtml = $true
$msg.body = $strHTMLBody
$smtp.Send($msg)

You can download the script here.
Remember to always test your scripts first in your environment. I cannot be held responsible for any adverse affects.

Hope this helps!

~ Brad

Fun with Conference Calls

Its always good to be able to laugh at yourself. I think we have all been on conference calls like this one.


~ Brad

28 January 2014

PowerShell Philosophy

Love it or hate it, PowerShell is a great tool. When I first started out in Lync I would only touch it if I had no other option. These days I do a lot of PowerShell and a lot though the Lync Control Panel, it all depends on what I am trying to accomplish.

My current job has really forced me into learning PowerShell and I'm glad it did. When you are trying to manage 16,000+ users and 14 servers, there are some things that are just so much easier to do via scripting.

I am decent in PowerShell, but there's a lot that I am still learning. Like most of you, I have done my share of looking for scripts online, and then customizing them to my needs. So here its time for me to give some of that back. Hopefully they will be useful to you.

~ Brad