grahamr975 / EWS-Office365-Contact-Sync

Uses Exchange Web Services to synchronize a Global Address List in Office 365 to a user's mailbox
MIT License
96 stars 21 forks source link

Sync-ContactList doesn't remove field from contacts detination if source field is emptied. #31

Closed x-zempt closed 1 year ago

x-zempt commented 3 years ago

Hi there, I've found an issue, say if John Smith has a phone number in his GAL in 'Business Phone' field. I pull that data into an array and push to users Contacts folders. Later John Smith stops having a work phone number and we empty the 'Business Phone' field from the GAL and do another sync. The current processing of Sync-ContactList doesn't replicate the empty field to the end users contacts when doing a sync.

Hope that makes sense.

x-zempt commented 3 years ago

No one else experienced this? Or an I doing something wrong. I think the logic is causing the issue.

Source field (GAL) for contact is empty/null (ie. phone numer), so do not sync/copy field. It doesn't seem to take into account that the destination (User contacts list) has data that is no longer relevant and to blank/null it out.

grahamr975 commented 3 years ago

@x-zempt I see the issue you're talking about. In Set-EXCContactObject.ps1, contact fields are only updated if the data passed to each field isn't null/empty.

if ($FirstName -ne "" -And $FirstName -ne $null)
{
    $Contact.GivenName = $FirstName
}

I did this to prevent certain contact fields, such as notes, from being overwritten. This works in my environment since we've only changed cellphone numbers/names and not removed them, but I now see how this can cause problems in other environments.

To resolve this, I could set certain fields to allow null values, such as phone numbers, while leaving the notes field and others the same. What do you think?

x-zempt commented 3 years ago

Yea I can understand why you would do that. But it doesn't sync the 'Notes' field of the contact etc anyway, as it isn't defined in the PS script to sync. Isn't that correct?

I think what would be handy is if we define the fields we want to sync as true/false flag when running the script.

Also, would be handy to define a email domain for which email accounts to remove from the users email box (ie. the company/business domain) so that users can still keep their own, but the internal company contact are managed. ie. If user no longer exists in GAL and is part of @mydomain.com remove.

I'm trying to sync to the main 'Contacts' folder so that it passes through to Teams and Outlook mobile apps etc. As it doesn't do it for additional contacts folders.

Backmischung commented 3 years ago

Hey there, we´re experiencing the same. We wanted to assign all our employees phonenumbers (for signature reasons) but decided, that since they all are the same, we later wanted to remove those fields. Now were stuck with the phonenumbers and they wont get removed.

the attribute phoneNumber correlates most likely to this line: if ($BusinssPhone -ne "") { $Contact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::BusinessPhone] = $BusinssPhone

i was wondering if the querry if ($BusinssPhone -ne "" -Or NULL) would work too?

also just as a sidenote: the variable is missing an "e" in $BusinssPhone but thats just an optic improvement. It works nontheless.

grahamr975 commented 3 years ago

I did some digging into this. Unfortunately, deleting contact attributes using the EWS 2.0 API is an unnecessarily complicated process. You can't just set a property, like GivenName, to null. I'll need to do some further research in order to find the correct Property IDs for each contact property that needs to be deletable.

https://docs.microsoft.com/en-us/previous-versions/office/developer/exchange-server-2010/jj585538(v=exchg.80)

Backmischung commented 3 years ago

Hello again,

as a workaround, would it be possible to delete those NULL-attribute-contacts and simply sync them new?

My current plan is:

Iill get you an update on how that worked.

Backmischung commented 3 years ago

Update: That didnt work as great as hoped. The hidden contacts still got synced.

Atleast to me, this behaviour doesn´t make sense.

Greetings

nicolpas commented 2 years ago

@x-zempt I see the issue you're talking about. In Set-EXCContactObject.ps1, contact fields are only updated if the data passed to each field isn't null/empty.

if ($FirstName -ne "" -And $FirstName -ne $null)
{
  $Contact.GivenName = $FirstName
}

I did this to prevent certain contact fields, such as notes, from being overwritten. This works in my environment since we've only changed cellphone numbers/names and not removed them, but I now see how this can cause problems in other environments.

To resolve this, I could set certain fields to allow null values, such as phone numbers, while leaving the notes field and others the same. What do you think?

Hi @grahamr975 , to bypass this problem and allow (for my situation) replicaty empty Business Phone when I empty this field from GAL, I've try into file Set-EXCContactObject.ps1 to change these lines:

if ($BusinssPhone -ne "") { $Contact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::BusinessPhone] = $BusinssPhone }

in $Contact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::BusinessPhone] = $BusinssPhone

but still doesn't replicate Business Phone empty field.

Any workaround for this issue?

Many many thanks in advance

grahamr975 commented 2 years ago

@nicolpas

You can't set directly set a contact's property to null due to EWS silliness. Instead, you'd need to remove the extended property definition. If you're interested, there's documentation on this here.

Try out the below code and let me know if this helps.

if ($BusinssPhone -eq "" -Or $BusinssPhone -eq $null) {
    $PidTagBusinessTelephoneNumber = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x3A08,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);
    $Contact.RemoveExtendedProperty($PidTagBusinessTelephoneNumber)
} else {
    $Contact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::BusinessPhone] = $BusinssPhone
}
nicolpas commented 2 years ago

@nicolpas

You can't set directly set a contact's property to null due to EWS silliness. Instead, you'd need to remove the extended property definition. If you're interested, there's documentation on this here.

Try out the below code and let me know if this helps.

if ($BusinssPhone -eq "" -Or $BusinssPhone -eq $null) {
    $PidTagBusinessTelephoneNumber = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x3A08,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);
    $Contact.RemoveExtendedProperty($PidTagBusinessTelephoneNumber)
} else {
    $Contact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::BusinessPhone] = $BusinssPhone
}

Hi @grahamr975 and first of all many thanks for your reply.

I've try to modify only file Set-EXCContactObject.ps1 and I've replace: if ($BusinssPhone -ne "") { $Contact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::BusinessPhone] = $BusinssPhone }

with your code:

if ($BusinssPhone -eq "" -Or $BusinssPhone -eq $null) { $PidTagBusinessTelephoneNumber = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x3A08,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String); $Contact.RemoveExtendedProperty($PidTagBusinessTelephoneNumber) } else { $Contact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::BusinessPhone] = $BusinssPhone }

but Business phone still not emptied.

May be that:

paochiz commented 2 years ago

Hy Ryan any news on the topic :D

christianneeb commented 2 years ago

Any updates?

Dark345 commented 1 year ago

Update: That didnt work as great as hoped. The hidden contacts still got synced.

Atleast to me, this behaviour doesn´t make sense.

Greetings

@grahamr975 defintely we need to take care of this scenario

but, for the moment, workaround initially suggested by @Backmischung is working:

grahamr975 commented 1 year ago

With the latest commit, business and mobile phones can now be removed from contacts.

The missing piece is that you have to bind the extended properties to the contact at the time of the object's creation or they will fail to remove each time.

Fetching the contacts from the user's folder. Now it adds the extended properties for the Telephone & Mobile phone with the First Class properties list when each contact object is generated.

$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)
$psPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)

### Add the extended properties for the business and mobile phone so they can be removed later if needed
$PidTagBusinessTelephoneNumber = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x3A08,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);
$PidTagMobileTelephoneNumber = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x3A1C,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);
$psPropset.Add($PidTagBusinessTelephoneNumber)
$psPropset.Add($PidTagMobileTelephoneNumber)
###

Remove the Telephone & Mobile if they're not present

if ($BusinssPhone -and $BusinssPhone -ne "")
{
    $Contact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::BusinessPhone] = $BusinssPhone
} else {
    Write-Host "Removing Business Telephone..."
    # https://interoperability.blob.core.windows.net/files/MS-OXPROPS/%5bMS-OXPROPS%5d.pdf
    $PidTagBusinessTelephoneNumber = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x3A08,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);
    $Contact.RemoveExtendedProperty($PidTagBusinessTelephoneNumber)
}
if ($MobilePhone -and $MobilePhone -ne "")
{
    $Contact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::MobilePhone] = $MobilePhone
} else {
    Write-Host "Removing Mobile Telephone..."
    # https://interoperability.blob.core.windows.net/files/MS-OXPROPS/%5bMS-OXPROPS%5d.pdf
    $PidTagMobileTelephoneNumber = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x3A1C,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);
    $Contact.RemoveExtendedProperty($PidTagMobileTelephoneNumber)
}