Управление контактами в Lync 2013
Появилась необычная задача из-за необычной проблемы: при создании пользователя в Lync 2013, его список контактов пустой, пока он сам не добавит необходимые контакты. Но, как быть, когда пользователь новый, ещё не знает всех сотрудников, стесняется и т.д.?
Есть несколько решений, платных и бесплатных, удобных и не очень. Например, бесплатные:
Но, попробовав первый вариант, в силу непонятного изложения инструкции, запорол SQL2008R2 сервер. Сегодня нашёл иной подход к решению задачи: импортировать список групп рассылок всем контактам. Мне подвернулся скрипт, который проводит все необходимые действия и, даже!!, ведёт себя вежливо. Детальное описание скрипта от автора Charles Ulrich.
Это не все контакты в одном контейнере, но что мешает их всех добавить в "Избранные"?
Итак, необходимо (все действия в командной строке и шелле выполняются от имени администратора и пользователя-администратора Lync 2013):
- Добавить все существующие группы рассылки выбранному пользователю, с которого будем экспортировать на всех пользователей
- Так как скрипт предполагается запускать на сервере Lync 2013, во избежание возможных ошибок по удалённой сессии, необходимо установить 7zip на сервер. 7zip проводит распаковку/запаковку информации при экспорте/импорте
- На сервере Lync 2013 выполнить команду:
, где необходимо заменить YOURLINKPOOL на имя Вашего пула и вместо "first.last@something.com" указать почтовый адрес или sip-адрес пользователя. Каталог для экспорта, думаю, создать не сложно.Export-CsUserData -PoolFqdn "YOURLINKPOOL" -UserFilter <a href="mailto:first.last@something.com">first.last@something.com</a> -FileName "c:\temp\ExportedUserData.zip" - Распаковать полученный файл экспорта, получив, при этом, файл "DocItemSet.xml".
- Открыть полученный файл в каком-либо редакторе для выделения групп, где найти блок, обозначенный "contactgroups". Что-то типа этого:
<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=">
</contactgroup> - Сохранить выделенный блок, так как его нужно записать в скрипт.
- Использовать скрипт, представленный ниже. Так как у автора внутреннее имя домена = внешнему имени домена, он использовал поле SAMAccountName для поиска и выделения пользователей. В данном случае, поле SAMAccountName равно полю SipAddress. В моём случае это не пройдёт, так как имена доменов отличаются. Поэтому, я имел наглость чуточку исправить скрипт и, заодно, добавить "UserFriend-ность" при выборе пользователя из списка.
Для полноценного функционирования скрипта, необходимо:
- Запускать только на сервере Lync 2013 Front-End
- Установить на сервер архиватор 7zip и проверить пути в скрипте, в которых указан архиватор.
- В переменную $lyncpoolfqdn указать название пула Lync 2013 (для Standard Edition - это FQDN Lync 2013 Front-End сервера).
- Ниже в скрипте, найти блок, выделенный
, внутри которого вписать свои группы. Данные группы мы находили раньше в файле "DocItemSet.xml".
Скрипт:
#
# 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 DisplayName="fg==" Number="1"/>
<ContactGroup DisplayName="UGlubmVkIENvbnRhY3Rz" Number="2" ExternalUri="PGdyb3VwRXh0ZW5zaW9uIGdyb3VwVHlwZT0icGlubmVkR3JvdXAiPjxlbWFpbC8+PC9ncm91cEV4dGVuc2lvbj4="/>
</ContactGroups>
'@
Function MultipleSelectionBox ($inputarray,$prompt,$listboxtype) {
# Taken from Technet - <a href="http://technet.microsoft.com/en-us/library/ff730950.aspx<br />
#" title="http://technet.microsoft.com/en-us/library/ff730950.aspx<br />
#">http://technet.microsoft.com/en-us/library/ff730950.aspx<br />
#</a> 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.
# <a href="http://social.technet.microsoft.com/Forums/en-SG/winserverpowershell/thread/bc95fb6c-c583-47c3-94c1-f0d3abe1fafc<br />
" title="http://social.technet.microsoft.com/Forums/en-SG/winserverpowershell/thread/bc95fb6c-c583-47c3-94c1-f0d3abe1fafc<br />
">http://social.technet.microsoft.com/Forums/en-SG/winserverpowershell/thr...</a>
$Script = @()
[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 += $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 += $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
}
#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.Name "Choose Lync 2013 User" "One"
$user_email=Get-CsUser -Filter {Name -eq $user_email}
#Export Single Users Data
Export-CsUserData -PoolFqdn $lyncpoolfqdn -UserFilter (($user_email.sipAddress).Substring(4)) -FileName $ExportFileNamePath -Force
}
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.sipAddress).Substring(4))
}
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
Особая благодарность автору скрипта Charles Ulrich!
- Войдите на сайт для отправки комментариев
- Версия для печати