The biggest thing we found lacking in Lync 2013 is no central management of Contact Groups. With Spark we setup the groups and they are loaded whenever a user signs on.
Our users really like having this list and we did not want them to have to manually configure all of the groups. I was not able to find any specifics of exactly how to do this on the web so I decided to roll up my sleeves and do it myself. There are a few more things I would like to add to it and clean up but I wanted to get it out in the wild for other people to use.
Currently this script will only update the Contact Groups for a user who has logged into the server at least once. Additionally if the user has already added some of the groups in the past it will retain the original order of the previously added groups and append the new ones on at the bottom.
You will need 7-Zip installed on your link server to run this script.
Here are the basic steps to use the script:
- Create a reference account and add the Contact Groups you want to load for your other users.
- Log into your Lync Server and from the Lync Management Shell export the user you updated with the following command.
Export-CsUserData -PoolFqdn "YOURLINKPOOL" -UserFilter first.last@something.com -FileName "c:\temp\ExportedUserData.zip"
- Extract the Zip file and open the DocItemSet.xml file. I recommend using Notepad++ with the XML Tools Plug to format that file into something readable.
We are interested in the "ContactGroups" XML node. Each ContactGroup entry in this list corresponds to one of the contact groups you added to the reference account. Copy and paste this out of the XML file. It should look something like this:
- You're ready to copy the script below. Update variables as you see fit in the the Settings section on top. You will definitely have to update the following variables:
$lyncpoolfqdn - to your own FQDN
$replacement - paste your default ContactGroups XML in place of my sanitzed version
########################################################################################################################## # # Name: Update_Lync_2013_Groups.ps1 # Author: Charles Ulrich # Date: 03/08/2013 # Description: Script to update the Contact List for 1 or All Lync 2013 users. # # Requirements: 1. MUST BE RUN ON LYNC SERVER - Export-CSUserData does not like Remote Shell or Implicit Remoting # 2. 7-Zip must be installed - Used to manage the ZIP files from the export and needed for the import. # 3. If not running under Powershell v3.0 see comments in MultipleSelectionBox function. # # ########################################################################################################################## # Settings #Lync Server $lyncpoolfqdn = "YOURLINKSERVERPOOL" #Base File Path $BaseFilePath = "c:\scripts\lync_user_export" #7-Zip Paths $7ZipPath = "c:\Program Files\7-zip\7z.exe" $7ZipOutputDir = $BaseFilePath + "\working" $7ZipOutputParam = "-o" + $7ZipOutputDir $7ZipIncludeFiles = $7ZipOutputDir + "\*.xml" #Lync XML and ZIP file paths $LyncXMLFile = $7ZipOutputDir + "\DocItemSet.xml" $ExportFileNamePath = $BaseFilePath + "\ExportedUserData.zip" $UpdatedFileNamePath = $BaseFilePath + "\UpdatedUserData.zip" # replacement node with child nodes [xml]$replacement = @' <ContactGroups> <ContactGroup Number="1" DisplayName="fg=="/> <ContactGroup Number="2" DisplayName="WJvopsjer3ojfaopj" ExternalUri="Pfaohiweifgouwe90ufjSDAW$jop3958osdkpodasok32480mVkR3JvdXAiPjxlbWFpbC8+PC9n3904kjlkJKSL904="/> <ContactGroup Number="3" DisplayName="SFDEW3523j" ExternalUri="Pfaohiweifgouwe90ufjSDAW$jop39357673425hdfbsart3462twqgaedfgajoisdfoipqhuy38tpy32u980hfiaosdhiogpkR3JvdXAiPjxlbWFpbC8+PC9n3904kjlkJKSL904="/> <ContactGroup Number="4" DisplayName="35uyh" ExternalUri="Pfaohiweifgouwe90ufjSDAW$jop3958osdkpodasok32480mVkR3JvdXAiPjxlbWFpbC8+PC9n3904kjlkJKSL904="/> <ContactGroup Number="5" DisplayName="fbdsfh346" ExternalUri="Pfaohiweifgouwe27y0ahsvokja;sdC9n3904kjlkJKSL904="/> <ContactGroup Number="6" DisplayName="sfdhw3466" ExternalUri="Pfaohvbasrtyq4tyasv58osdkpodasok32480mVkR3JvdXAiPjxlbWFpbC8+PC9n3904kjlkJKSL904="/> <ContactGroup Number="7" DisplayName="asetg4363y6" ExternalUri="Pfaohiweifgouwe90vsdgwerhjudfbsdfkR3JvdXAiPjxlbWFpbC8+PC9n3904kjlkJKSL904="/> <ContactGroup Number="8" DisplayName="634tsagasdg" ExternalUri="Pfaohidsvsdgherhysdkpodasok32480mVkR3JvdXAiPjxlbWFpbC8+PC9n3904kjlkJKSL904="/> <ContactGroup Number="9" DisplayName="ngfd56w3" ExternalUri="Pfaohiweifgouwe90ufjdasok32480mVkR3JvdXAiPjxlbWFpbC8+PC9n3904kjlkJKSL904="/> <ContactGroup Number="10" DisplayName="asdgsa46" ExternalUri="Pfaohiweifgouwe90ufSDfsdtewtgvjopaiwejr302582385ioskjgl;skdaopsok32480mVkR3JvdXAiPjxlbWFpbC8+PC9n3904kjlkJKSL904="/> <ContactGroup Number="11" DisplayName="agasg" ExternalUri="Pfaohiweifgouwe90ufjSDAW$jop3958osdkpodasok32480mVkR3JvdXAiPjxlbWFpbC8+PC9n3904kjlkJKSL904="/> <ContactGroup Number="12" DisplayName="235wgsdfa" ExternalUri="Pfaohiweifgouwe90ufjSDAW$jop3958osdkpodasok32480mVkR3JvdXAiPjxlbWFpbC8+PC9n3904kjlkJKSL904="/> <ContactGroup Number="13" DisplayName="236gvbasetaw" ExternalUri="Pfaohiweifgouwe90ufjSDAW$jop3958osdkpodasok32480mVkR3JvdXAiPjxlbWFpbC8+PC9n3904kjlkJKSL904="/> <ContactGroup Number="14" DisplayName="asdgawqt3aqw" ExternalUri="Pfaohiweifgouwe90ufjSDAW$jop3958osdkpodasok32480mVkR3JvdXAiPjxlbWFpbC8+PC9n3904kjlkJKSL904="/> <ContactGroup Number="15" DisplayName="casdrwqt5" ExternalUri="Pfaohiweifgouwe90ufjSDAW$jop3958osdkpodasok32480mVkR3JvdXAiPjxlbWFpbC8+PC9n3904kjlkJKSL904="/> </ContactGroups> '@ Function MultipleSelectionBox ($inputarray,$prompt,$listboxtype) { # Taken from Technet - http://technet.microsoft.com/en-us/library/ff730950.aspx # This version has been updated to work with Powershell v3.0. # Had top replace $x with $Script:x throughout the function to make it work. # This specifies the scope of the X variable. Not sure why this is needed for v3. # http://social.technet.microsoft.com/Forums/en-SG/winserverpowershell/thread/bc95fb6c-c583-47c3-94c1-f0d3abe1fafc $Script:x = @() [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") $objForm = New-Object System.Windows.Forms.Form $objForm.Text = $prompt $objForm.Size = New-Object System.Drawing.Size(300,600) $objForm.StartPosition = "CenterScreen" $objForm.KeyPreview = $True $objForm.Add_KeyDown({if ($_.KeyCode -eq "Enter") { foreach ($objItem in $objListbox.SelectedItems) {$Script:x += $objItem} $objForm.Close() } }) $objForm.Add_KeyDown({if ($_.KeyCode -eq "Escape") {$objForm.Close()}}) $OKButton = New-Object System.Windows.Forms.Button $OKButton.Location = New-Object System.Drawing.Size(75,520) $OKButton.Size = New-Object System.Drawing.Size(75,23) $OKButton.Text = "OK" $OKButton.Add_Click( { foreach ($objItem in $objListbox.SelectedItems) {$Script:x += $objItem} $objForm.Close() }) $objForm.Controls.Add($OKButton) $CancelButton = New-Object System.Windows.Forms.Button $CancelButton.Location = New-Object System.Drawing.Size(150,520) $CancelButton.Size = New-Object System.Drawing.Size(75,23) $CancelButton.Text = "Cancel" $CancelButton.Add_Click({$objForm.Close()}) $objForm.Controls.Add($CancelButton) $objLabel = New-Object System.Windows.Forms.Label $objLabel.Location = New-Object System.Drawing.Size(10,20) $objLabel.Size = New-Object System.Drawing.Size(280,20) $objLabel.Text = "Please make a selection from the list below:" $objForm.Controls.Add($objLabel) $objListbox = New-Object System.Windows.Forms.Listbox $objListbox.Location = New-Object System.Drawing.Size(10,40) $objListbox.Size = New-Object System.Drawing.Size(260,20) $objListbox.SelectionMode = $listboxtype $inputarray | ForEach-Object {[void] $objListbox.Items.Add($_)} $objListbox.Height = 470 $objForm.Controls.Add($objListbox) $objForm.Topmost = $True $objForm.Add_Shown({$objForm.Activate()}) [void] $objForm.ShowDialog() Return $Script:x } #Load Lync Powershell Commands Import-Module Lync #Who should we update $sora = Read-Host "Do you want to update (A)ll users or a (S)ingle user? (S or A)" If ($sora -eq "S") { # Get user to update $userlist = Get-CSUser $user_email=MultipleSelectionBox $userlist.UserPrincipalName "Choose Lync 2013 User" "One" #Export Single Users Data Export-CsUserData -PoolFqdn $lyncpoolfqdn -UserFilter $user_email -FileName $ExportFileNamePath } Else { #Export All Users Data Export-CsUserData -PoolFqdn $lyncpoolfqdn -FileName $ExportFileNamePath } #Extract the Exported Zip file. Requires 7-Zip Write-Host " " &$7ZipPath e $ExportFileNamePath $7ZipOutputParam Write-Host " " Write-Host " " $original = [xml] (Get-Content $LyncXMLFile) #Set our loop counter to 0 $count = $original.DocItemSet.DocItem.Count + 1 Write-Host "########################################################################" #Loop through all DocItem Elements and replace any with ContactGroups in them For ($i=0; $i -lt $count; $i++) { If (($original.DocItemSet.DocItem[$i].Data.HomedResource.ContactGroups.ContactGroup.Count -gt 0)) { # get the target node Write-Host " " Write-Host "Working on XML Node: " $original.DocItemSet.DocItem[$i].Name Write-Host " " Write-Host "Contact Groups Before: "$original.DocItemSet.DocItem[$i].Data.HomedResource.ContactGroups.ContactGroup.Count Write-Host " " $inner = $original.DocItemSet.DocItem[$i].Data.HomedResource.ContactGroups # import the replacement values $new = $original.ImportNode($replacement.ContactGroups, $true) # replace old node with new one (replacement node) $dump = $original.DocItemSet.DocItem[$i].Data.HomedResource.ReplaceChild($new, $inner) Write-Host "Contact Groups After: "$original.DocItemSet.DocItem[$i].Data.HomedResource.ContactGroups.ContactGroup.Count } } Write-Host " " Write-Host "########################################################################" Write-Host " " #Remove blank xmlns tags created by importing the node $original = [xml] $original.OuterXml.Replace(" xmlns=`"`"", "") # save changes (full path to file) $original.Save($LyncXMLFile) # create updated zip file & $7ZipPath a $UpdatedFileNamePath $7ZipIncludeFiles Write-Host " " Write-Host "########################################################################" Write-Host " " Write-Host "The XML file has been updated with the default groups." Write-Host " " Write-Host "If you want to take a look at the file its path is" Write-Host " " Write-Host $LyncXMLFile Write-Host " " Write-Host "This file will be deleted once this script has finished." Write-Host " " Write-Host "########################################################################" Write-Host " " $sure = Read-Host "About to upload the changes to the server. Are you sure? (Y or N)" If ($sure -eq "Y") { Write-Host " " Write-Host "Updating Server with the new Contact Group Settings." Write-Host " " Write-Host "The user(s) will have no Contact groups until they" Write-Host "log off and back on to Lync." If ($sora -eq "S") { # Update the server with the new User Data Update-CsUserData -Filename $UpdatedFileNamePath -UserFilter $user_email } Else { # Update the server with the new User Data Update-CsUserData -Filename $UpdatedFileNamePath } Write-Host " " } Else { Write-Host " " Write-Host "Update Aborted!!!!!!" Write-Host " " Write-Host "Please Come Again! :) " Write-Host " " } Write-Host "I will clean up the files and close the window after you hit Enter." Write-Host " " PAUSE # Clean Up Remove-Item ($BaseFilePath + "\*") -recurse