My final goal is to have the below code find and run against all users in the domain without manually entering each one but I don't know how. I would then put the script in task scheduler.
Here is the story and information behind this in case is helps someone else.
My COO would like our Global Address Book to show up in everyone's device (ie Android, IOS, Windows, etc...) offline. I have found a way to do it one user at a time in Exchange Powershell using Steve Goodmans method/code --> http://www.stevieg.org/2012/02/importing-global-address-list-entries-into-a-users-contacts-folder . After doing this to my own username I was able to see the GAL information on my phone offline. And, if anyone calls me from their phone it shows up on my phone with their name now by pulling it from the imported GAL.
Basically, with his code, I'm copying the global address book to each person as a separate address book. If you look in Outlook you'll see your contacts and you'll see the new address book called OrgContacts (in this case). When your device syncs to exchange next time, this address book syncs as well and you have the whole company with you. We have a couple hundred users so it's not a big deal.
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.
Thank you very much for your time,
To run the code for each user I use this...
# example (Billy Smith network username is basmith)
.\Copy-OrgContactsToUserContacts.ps1 -Mailbox basmith -FolderName OrgContacts
Here is the Exchange power shell code...
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\1.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;
}
First, Glen, thank you for giving me links and making me think this through. You probably could have spelled it all out but I learned some things in the process.
Use at your own risk! I'm not a programmer. I wouldn't just do this on a production machine but it's up to you. This will copy all Contacts to All users. The next time their phone or devices syncs they will have all the contacts available when offline. It will also be available in their contacts in Outlook offline. We only have 150 people. I don't think I'd suggest this for much more. It takes over an hour for this to run just for us. There's probably a way to build the GAL then just copy it to each user instead of building a copy of the GAL over and over again like this does. But, I don't know enough about this to do that.
I got it to work. It's not the elegant way but it works.
I'm going to put the step by step here in case anyone wants to build this. The only thing you need to change is the the domain name or OU in the ForEach loop below (second code block).
First I saved this code (first code block) as Copy-OrgContactsToUserContacts.ps1 from 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\1.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;
}
Then I made a foreach Loop file and called it CopyGalToALLusers.ps1 that needs to be saved in the same folder as the previous file I just made. In this foreach loop I'm looking up my whole domain. You can pick different OU's or whatever. Just look up Get-Mailbox explained here https://technet.microsoft.com/en-us/library/Bb123685(v=EXCHG.150).aspx. You can change the folder name it saves the contacts as well. Just put a different name in place of 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
}
The last step is to manually run the file is Exchange PowerShell I made with the foreach loop or schedule it in task scheduler.
.\CopyGalToALLusers.ps1
To schedule it and make it user the right powershell I went to task scheduler and set it to run at 2am daily. In the program to run box I entered the following. This is exchange 2010 on server2012r2. The first part not only makes it open powershell but connects to exchange so it understands your commands. The way I found the proper string to put here was I looked at the properties of my exchange powershell icon and copied the whole thing. The last line is the location of your forloop script. Just add a semi-colon and ". 'location of forloop file'" . You can paste this in a command line to test it before making your scheduled task as well.
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'"