Loading

Управление контактами в Lync 2013

30.07.2014 - 14:19

Появилась необычная задача из-за необычной проблемы: при создании пользователя в Lync 2013, его список контактов пустой, пока он сам не добавит необходимые контакты. Но, как быть, когда пользователь новый, ещё не знает всех сотрудников, стесняется и т.д.?
Есть несколько решений, платных и бесплатных, удобных и не очень. Например, бесплатные:

Но, попробовав первый вариант, в силу непонятного изложения инструкции, запорол SQL2008R2 сервер. Сегодня нашёл иной подход к решению задачи: импортировать список групп рассылок всем контактам. Мне подвернулся скрипт, который проводит все необходимые действия и, даже!!, ведёт себя вежливо. Детальное описание скрипта от автора Charles Ulrich.
Это не все контакты в одном контейнере, но что мешает их всех добавить в "Избранные"?

Итак, необходимо (все действия в командной строке и шелле выполняются от имени администратора и пользователя-администратора Lync 2013):

  1. Добавить все существующие группы рассылки выбранному пользователю, с которого будем экспортировать на всех пользователей
  2. Так как скрипт предполагается запускать на сервере Lync 2013, во избежание возможных ошибок по удалённой сессии, необходимо установить 7zip на сервер. 7zip проводит распаковку/запаковку информации при экспорте/импорте
  3. На сервере Lync 2013 выполнить команду:
    Export-CsUserData -PoolFqdn "YOURLINKPOOL" -UserFilter <a href="mailto:first.last@something.com">first.last@something.com</a> -FileName "c:\temp\ExportedUserData.zip"
    , где необходимо заменить YOURLINKPOOL на имя Вашего пула и вместо "first.last@something.com" указать почтовый адрес или sip-адрес пользователя. Каталог для экспорта, думаю, создать не сложно.
  4. Распаковать полученный файл экспорта, получив, при этом, файл "DocItemSet.xml".
  5. Открыть полученный файл в каком-либо редакторе для выделения групп, где найти блок, обозначенный "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>
  6. Сохранить выделенный блок, так как его нужно записать в скрипт.
  7. Использовать скрипт, представленный ниже. Так как у автора внутреннее имя домена = внешнему имени домена, он использовал поле 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: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.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!

Ваша оценка: Нет Средняя: 5 (1 голос)