0

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;
}
Fixitrod
  • 125
  • 1
  • 2
  • 10

2 Answers2

0

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.

Are you just trying to feed in the names of the Mailboxes in your Exchange Org? if that the case then just use Get-Mailbox eg

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

 }

Put you other code into Function (or cmdlet) and call that function for each Mailbox

If you want to use Exchange Management shell cmdlets in the Scheduled Task then you need to do something like http://mikepfeiffer.net/2010/02/creating-scheduled-tasks-for-exchange-2010-powershell-scripts/

Cheers Glen

Glen Scales
  • 20,495
  • 1
  • 20
  • 23
  • @"Glen Scales" I am very new to this. I'm a network engineer and learner Java, Java Script, just coding here and there so I apologize for some of my ignorant questions. I took your bit of code and replace .... $UserMailbox = Get-Mailbox $Mailbox (I tried to hit enter a do a code block but it must not be allowed here). It doesn't fail but it doesn't run either. I get no response. How do I implement this code please? – Fixitrod Aug 14 '15 at 12:01
  • If you don't understand powershell I don't really know how to give you answer that you will understand ?? . Get-Mailbox is a cmdlet that must be run from with the Exchange Management Shell or a Remote powershell connection so you first need to establish one of these eg https://technet.microsoft.com/en-us/library/bb123778(v=exchg.150).aspx. Next step is just try running Get-Mailbox by default it should return something else give an error. If you do get a response all Get-Mailbox does is returns a collection of Mailboxes. – Glen Scales Aug 17 '15 at 05:35
  • I appologize. I understand powershell, I just don't know a lot of the commands. I can run the Get-Mailbox. I don't know how to incorporate it into the above code I posted so I can automate it. I know how to schedule it once I can make it do all the work. In the script above I have to enter a name one at a time manually when I run it. I would like to incorporate this Get-Mailbox somehow so it loops through each mailbox and and does the same commands (adding the GAL as a separate address book) for each of those users. This way it can be scheduled and if we get a new user it just works. – Fixitrod Aug 19 '15 at 14:08
  • It should be really simple all you need to do is take the code you have in Copy-OrgContactsToUserContacts.ps1 and put that into a function http://www.computerperformance.co.uk/powershell/powershell_functions.htm and then call the function from within the Get-Mailbox loop. If you try something and it doesn't work post the modified code so somebody can help you work out what is wrong and you can learn from that. – Glen Scales Aug 20 '15 at 03:10
  • Will do. I've gotten pulled to 10 other things for the moment but will come back to this. in a day or two. Thank you for posting links as well. It's very helpful!!! – Fixitrod Aug 20 '15 at 18:18
0

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'"
Fixitrod
  • 125
  • 1
  • 2
  • 10
  • I just wanted to put a note we only have about 100 users but this has worked out excellent. I made a separate OU in AD that doesn't get looked at by this for when people are disabled. This allows the users that leave the company or temp/summer help/interns to be removed from everyone's address book on all devices. It has been great. – Fixitrod Jul 30 '16 at 03:59