Exchange 2013 尝试将全球通讯簿 GAL 复制到所有用户,以便它显示在 Android 和 IOS 离线

Exchange 2013 Trying to copy Global Address Book GAL to ALL users so it shows on Android and IOS offline

我的最终目标是让下面的代码找到并 运行 针对域中的所有用户,而无需手动输入每个用户,但我不知道如何。然后我会把脚本放在任务调度程序中。

这是这背后的故事和信息,以防对其他人有所帮助。

我的首席运营官希望我们的全球地址簿能够离线显示在每个人的设备中(即 Android、IOS、Windows 等)。我找到了一种方法,使用 Steve Goodmans method/code --> http://www.stevieg.org/2012/02/importing-global-address-list-entries-into-a-users-contacts-folder 在 Exchange Powershell 中一次一个用户执行此操作。 对我自己的用户名执行此操作后,我可以离线查看 phone 上的 GAL 信息。而且,如果有人从他们的 phone 给我打电话,它现在会通过从导入的 GAL 中提取他们的名字显示在我的 phone 上。

基本上,使用他的代码,我将全球地址簿作为单独的地址簿复制给每个人。如果您查看 Outlook,您将看到您的联系人,并且您将看到名为 OrgContacts 的新地址簿(在本例中)。当您的设备下次同步交换时,此地址簿也会同步,并且您拥有整个公司。我们有几百个用户,所以这没什么大不了的。

到目前为止,我使用的代码是一次一个用户。我需要帮助才能找到所有用户名并执行。我尝试在 运行 字符串中使用通配符,但没有用。如果有的话,我也愿意接受一种完全不同的方式来实现这一目标。

非常感谢您的宝贵时间,

为了运行我使用这个的每个用户的代码...

# example (Billy Smith network username is basmith)
.\Copy-OrgContactsToUserContacts.ps1 -Mailbox basmith -FolderName OrgContacts

这是交换电源 shell 代码...

param([string]$Mailbox,[string]$FolderName="OrgContacts");
#
# Copy-OrgContactsToUserMailboxContacts.ps1
#
# THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE
# RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
#
# Parameters
#  Mandatory:
# -MailboxFolder : Folder to "own" for these contacts.
#
# Creates s OrgContacts folder in the Mailbox and adds the contacts into it. Does not attempt to 


$EwsUrl = ([array](Get-WebServicesVirtualDirectory))[0].InternalURL.AbsoluteURI

$ContactMapping=@{
    "FirstName" = "GivenName";
    "LastName" = "Surname";
    "Company" = "CompanyName";
    "Department" = "Department";
    "Title" = "JobTitle";
    "WindowsEmailAddress" = "Email:EmailAddress1";
    "Phone" = "Phone:BusinessPhone";
    "MobilePhone" = "Phone:MobilePhone";
}

$UserMailbox  = Get-Mailbox $Mailbox

if (!$UserMailbox)
{
    throw "Mailbox $($Mailbox) not found";
    exit;
}

$EmailAddress = $UserMailbox.PrimarySMTPAddress

# Load EWS Managed API
[void][Reflection.Assembly]::LoadFile("C:\Program Files\Microsoft\Exchange\Web Services.2\Microsoft.Exchange.WebServices.dll");

$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)
$service.UseDefaultCredentials = $true;
$service.URL = New-Object Uri($EwsUrl);

# Search for an existing copy of the Folder to store Org contacts 
$service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
$RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot)
$RootFolder.Load()

$service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
$FolderView = new-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)
$ContactsFolderSearch = $RootFolder.FindFolders($FolderView) | Where {$_.DisplayName -eq $FolderName}
if ($ContactsFolderSearch)
{
    # Empty if found
    $service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
    $ContactsFolder = [Microsoft.Exchange.WebServices.Data.ContactsFolder]::Bind($service,$ContactsFolderSearch.Id);
    $ContactsFolder.Empty([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete, $true)
} else {

    # Create new contacts folder
    $service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
    $ContactsFolder = New-Object Microsoft.Exchange.WebServices.Data.ContactsFolder($service);
    $ContactsFolder.DisplayName = $FolderName
    $ContactsFolder.Save([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot)
    # Search for the new folder instance
    $RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot)
    $RootFolder.Load()
    $FolderView = new-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)
    $ContactsFolderSearch = $RootFolder.FindFolders($FolderView) | Where {$_.DisplayName -eq $FolderName}
    $ContactsFolder = [Microsoft.Exchange.WebServices.Data.ContactsFolder]::Bind($service,$ContactsFolderSearch.Id);
}

# Add contacts
$Users = get-user -Filter {WindowsEmailAddress -ne $null -and (MobilePhone -ne $null -or Phone -ne $null) -and WindowsEmailAddress -ne $EmailAddress} 
$Users = $Users | select DisplayName,FirstName,LastName,Title,Company,Department,WindowsEmailAddress,Phone,MobilePhone

foreach ($ContactItem in $Users)
{
    $service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);

    $ExchangeContact = New-Object Microsoft.Exchange.WebServices.Data.Contact($service);
    if ($ContactItem.FirstName -and $ContactItem.LastName)
    {
        $ExchangeContact.NickName = $ContactItem.FirstName + " " + $ContactItem.LastName;
    }
    elseif ($ContactItem.FirstName -and !$ContactItem.LastName)
    {
        $ExchangeContact.NickName = $ContactItem.FirstName;
    }
    elseif (!$ContactItem.FirstName -and $ContactItem.LastName)
    {
        $ExchangeContact.NickName = $ContactItem.LastName;
    }
    elseif (!$ContactItem.FirstName -and !$ContactItem.LastName)
    {
        $ExchangeContact.NickName = $ContactItem.DisplayName;
        $ContactItem.FirstName = $ContactItem.DisplayName;
    }

    $ExchangeContact.DisplayName = $ExchangeContact.NickName;
    $ExchangeContact.FileAs = $ExchangeContact.NickName;

    # This uses the Contact Mapping above to save coding each and every field, one by one. Instead we look for a mapping and perform an action on
    # what maps across. As some methods need more "code" a fake multi-dimensional array (seperated by :'s) is used where needed.
    foreach ($Key in $ContactMapping.Keys)
    {
        # Only do something if the key exists
        if ($ContactItem.$Key)
        {
            # Will this call a more complicated mapping?
            if ($ContactMapping[$Key] -like "*:*")
            {
                # Make an array using the : to split items.
                $MappingArray = $ContactMapping[$Key].Split(":")
                # Do action
                switch ($MappingArray[0])
                {
                    "Email"
                    {
                        $ExchangeContact.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::($MappingArray[1])] = $ContactItem.$Key.ToString();
                    }
                    "Phone"
                    {
                        $ExchangeContact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::($MappingArray[1])] = $ContactItem.$Key;
                    }
                }                
            } else {
                $ExchangeContact.($ContactMapping[$Key]) = $ContactItem.$Key;            
            }

        }    
    }
    # Save the contact    
    $ExchangeContact.Save($ContactsFolder.Id);

    # Provide output that can be used on the pipeline
    $Output_Object = New-Object Object;
    $Output_Object | Add-Member NoteProperty FileAs $ExchangeContact.FileAs;
    $Output_Object | Add-Member NoteProperty GivenName $ExchangeContact.GivenName;
    $Output_Object | Add-Member NoteProperty Surname $ExchangeContact.Surname;
    $Output_Object | Add-Member NoteProperty EmailAddress1 $ExchangeContact.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::EmailAddress1]
    $Output_Object;
}

The code I've used so far is one user at a time. I need help making it find all the usernames and executing. I tried wildcards in the run string but that didn't work. I'm also open to a whole different way to accomplish this if there is one.

您只是想输入 Exchange 组织中的邮箱名称吗?如果是这样,那么只需使用 Get-Mailbox eg

Get-Mailbox -ResultSize Unlimited | foreach-object{
          $UserName = $_.SamAccountName

 }

将您的其他代码放入函数(或 cmdlet)中并为每个邮箱调用该函数

如果您想在计划任务中使用 Exchange 管理 shell cmdlet,那么您需要执行类似 http://mikepfeiffer.net/2010/02/creating-scheduled-tasks-for-exchange-2010-powershell-scripts/

的操作

干杯 格伦

首先,格伦,谢谢你给我链接,让我仔细考虑了这个问题。你可能已经把它拼出来了,但我在这个过程中学到了一些东西。

使用风险自负!我不是程序员。我不会只在生产机器上执行此操作,但这取决于您。 这会将所有联系人复制到所有用户。下次他们的 phone 或设备同步时,他们将在离线时拥有所有联系人。它也将在 Outlook 离线版的联系人中可用。我们只有150人。我不认为我会建议更多。就我们而言,运行 花了一个多小时。可能有一种方法可以构建 GAL,然后将其复制给每个用户,而不是像这样一遍又一遍地构建 GAL 的副本。但是,我对此知之甚少,无法做到这一点。

我让它工作了。这不是优雅的方式,但它有效。

我将一步一步地放在这里,以防有人想要构建它。您唯一需要更改的是下面 ForEach 循环(第二个代码块)中的域名或 OU。

首先,我将此代码(第一个代码块)保存为 Copy-OrgContactsToUserContacts。ps1 来自 Steve Goodmans method/code --> http://www.stevieg.org/2012/02/importing-global-address-list-entries-into-a-users-contacts-folder

param([string]$Mailbox,[string]$FolderName="OrgContacts");
#
# Copy-OrgContactsToUserMailboxContacts.ps1
#
# THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE
# RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
#
# Parameters
#  Mandatory:
# -MailboxFolder : Folder to "own" for these contacts.
#
# Creates s OrgContacts folder in the Mailbox and adds the contacts into it. Does not attempt to 


$EwsUrl = ([array](Get-WebServicesVirtualDirectory))[0].InternalURL.AbsoluteURI

$ContactMapping=@{
    "FirstName" = "GivenName";
    "LastName" = "Surname";
    "Company" = "CompanyName";
    "Department" = "Department";
    "Title" = "JobTitle";
    "WindowsEmailAddress" = "Email:EmailAddress1";
    "Phone" = "Phone:BusinessPhone";
    "MobilePhone" = "Phone:MobilePhone";
}

$UserMailbox  = Get-Mailbox $Mailbox

if (!$UserMailbox)
{
    throw "Mailbox $($Mailbox) not found";
    exit;
}

$EmailAddress = $UserMailbox.PrimarySMTPAddress

# Load EWS Managed API
[void][Reflection.Assembly]::LoadFile("C:\Program Files\Microsoft\Exchange\Web Services.2\Microsoft.Exchange.WebServices.dll");

$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)
$service.UseDefaultCredentials = $true;
$service.URL = New-Object Uri($EwsUrl);

# Search for an existing copy of the Folder to store Org contacts 
$service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
$RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot)
$RootFolder.Load()

$service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
$FolderView = new-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)
$ContactsFolderSearch = $RootFolder.FindFolders($FolderView) | Where {$_.DisplayName -eq $FolderName}
if ($ContactsFolderSearch)
{
    # Empty if found
    $service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
    $ContactsFolder = [Microsoft.Exchange.WebServices.Data.ContactsFolder]::Bind($service,$ContactsFolderSearch.Id);
    $ContactsFolder.Empty([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete, $true)
} else {

    # Create new contacts folder
    $service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
    $ContactsFolder = New-Object Microsoft.Exchange.WebServices.Data.ContactsFolder($service);
    $ContactsFolder.DisplayName = $FolderName
    $ContactsFolder.Save([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot)
    # Search for the new folder instance
    $RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot)
    $RootFolder.Load()
    $FolderView = new-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)
    $ContactsFolderSearch = $RootFolder.FindFolders($FolderView) | Where {$_.DisplayName -eq $FolderName}
    $ContactsFolder = [Microsoft.Exchange.WebServices.Data.ContactsFolder]::Bind($service,$ContactsFolderSearch.Id);
}

# Add contacts
$Users = get-user -Filter {WindowsEmailAddress -ne $null -and (MobilePhone -ne $null -or Phone -ne $null) -and WindowsEmailAddress -ne $EmailAddress} 
$Users = $Users | select DisplayName,FirstName,LastName,Title,Company,Department,WindowsEmailAddress,Phone,MobilePhone

foreach ($ContactItem in $Users)
{
    $service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);

    $ExchangeContact = New-Object Microsoft.Exchange.WebServices.Data.Contact($service);
    if ($ContactItem.FirstName -and $ContactItem.LastName)
    {
        $ExchangeContact.NickName = $ContactItem.FirstName + " " + $ContactItem.LastName;
    }
    elseif ($ContactItem.FirstName -and !$ContactItem.LastName)
    {
        $ExchangeContact.NickName = $ContactItem.FirstName;
    }
    elseif (!$ContactItem.FirstName -and $ContactItem.LastName)
    {
        $ExchangeContact.NickName = $ContactItem.LastName;
    }
    elseif (!$ContactItem.FirstName -and !$ContactItem.LastName)
    {
        $ExchangeContact.NickName = $ContactItem.DisplayName;
        $ContactItem.FirstName = $ContactItem.DisplayName;
    }

    $ExchangeContact.DisplayName = $ExchangeContact.NickName;
    $ExchangeContact.FileAs = $ExchangeContact.NickName;

    # This uses the Contact Mapping above to save coding each and every field, one by one. Instead we look for a mapping and perform an action on
    # what maps across. As some methods need more "code" a fake multi-dimensional array (seperated by :'s) is used where needed.
    foreach ($Key in $ContactMapping.Keys)
    {
        # Only do something if the key exists
        if ($ContactItem.$Key)
        {
            # Will this call a more complicated mapping?
            if ($ContactMapping[$Key] -like "*:*")
            {
                # Make an array using the : to split items.
                $MappingArray = $ContactMapping[$Key].Split(":")
                # Do action
                switch ($MappingArray[0])
                {
                    "Email"
                    {
                        $ExchangeContact.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::($MappingArray[1])] = $ContactItem.$Key.ToString();
                    }
                    "Phone"
                    {
                        $ExchangeContact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::($MappingArray[1])] = $ContactItem.$Key;
                    }
                }                
            } else {
                $ExchangeContact.($ContactMapping[$Key]) = $ContactItem.$Key;            
            }

        }    
    }
    # Save the contact    
    $ExchangeContact.Save($ContactsFolder.Id);

    # Provide output that can be used on the pipeline
    $Output_Object = New-Object Object;
    $Output_Object | Add-Member NoteProperty FileAs $ExchangeContact.FileAs;
    $Output_Object | Add-Member NoteProperty GivenName $ExchangeContact.GivenName;
    $Output_Object | Add-Member NoteProperty Surname $ExchangeContact.Surname;
    $Output_Object | Add-Member NoteProperty EmailAddress1 $ExchangeContact.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::EmailAddress1]
    $Output_Object;
}

然后我制作了一个foreach Loop 文件并命名为CopyGalToALLusers.ps1 需要将其保存在与我刚刚制作的上一个文件相同的文件夹中。在这个 foreach 循环中,我正在查找我的整个域。您可以选择不同的 OU 或其他任何内容。只需查看此处解释的 Get-Mailbox https://technet.microsoft.com/en-us/library/Bb123685(v=EXCHG.150).aspx。您也可以更改保存联系人的文件夹名称。只需用不同的名称代替 OrgContacts。

   $mailboxes = Get-Mailbox -OrganizationalUnit "indy.int/Esco Users"
foreach ($mailbox in $mailboxes)
{
. "C:\Program Files\Microsoft\Scripts\Copy Global Contact to Users\Copy-OrgContactsToUserContacts.ps1" -Mailbox $mailbox.alias -FolderName OrgContacts
}

最后一步是手动 运行 该文件是我用 foreach 循环创建的 Exchange PowerShell 或在任务计划程序中安排它。

.\CopyGalToALLusers.ps1

为了安排它并使其成为正确的 powershell 用户,我在每天凌晨 2 点进入任务计划程序并将其设置为 运行。在 运行 框中的程序中,我输入了以下内容。这是 server2012r2 上的 exchange 2010。第一部分不仅使它打开 powershell,而且连接到 exchange,以便它理解你的命令。我找到合适的字符串放在这里的方法是查看我的 exchange powershell 图标的属性并复制整个东西。最后一行是 forloop 脚本的位置。只需添加一个分号和 ". 'location of forloop file'" 。您也可以将其粘贴到命令行中以在执行计划任务之前对其进行测试。

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -command ". 'C:\Program Files\Microsoft\Exchange Server\V15\bin\RemoteExchange.ps1'; Connect-ExchangeServer -auto -ClientApplication:ManagementShell;  ". 'C:\Program Files\Microsoft\Scripts\Copy Global Contact to Users\CopyGALtoAllUsers.ps1'"