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

Parameters for ModernAuth #46

Closed BastiGit closed 1 year ago

BastiGit commented 2 years ago

Hello,

can someone explain, how to correctly use ModernAuth with this script? We would like to keep BasicAuth disabled for our O365-Tenant, because Microsoft will disable it in 2022.

Any suggestion will be appreciated. Thank you very much in advance.

grahamr975 commented 2 years ago

Hello @BastiGit,

Have you tried using the -ModernAuth parameter? For a list of all parameters, see https://github.com/grahamr975/EWS-Office365-Contact-Sync/blob/master/EWSContactSync.ps1

Follow the Get Started guide in the README file. This shows how to set up the script with modern authentication.

I'd recommend you test this on an account with basic authentication disabled to verify the script will function properly in your environment using only modern authentication. The guide below shows you how to set up a security group with no basic authentication using PowerShell. It's working well in mine.

https://docs.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/disable-basic-authentication-in-exchange-online

BastiGit commented 2 years ago

Hello @grahamr975,

thanks for your quick reply.

Yes, I have used the -ModernAuth parameter as described in the README. However, then the following error appears:

[outlook.office365.com] Connecting to remote server outlook.office365.com failed with the following error message : Access is denied.

Is there anything else to do for modern authentication?

I have saved the credentials by Export-Clixml, but they are only relevant for BasicAuth, right?

grahamr975 commented 2 years ago

Did you unblock all of the DLL files referenced in step 4 of the README?

You do still need credentials for Modern Auth since we use those to generate the OAuth access token. This account should have permissions as specified in the README.

BastiGit commented 2 years ago

Yes, I have unblocked all DLL files from step 4.

At Export-Clixml I enter my Office 365 account data. This account is an administrator and also has the Application Impersonation permissions.

Unfortunately, the access denied error message comes up again.

grahamr975 commented 2 years ago

This issue is from the ExchangeOnline Powershell Module and occurs for one of the following reasons:

Note that the ExchangeOnline Powershell Module in this script still uses basic authentication. It's on my to-do list to migrate this over to OAuth just as I have done for the EWS portions.

We only use ExchangeOnline to retrieve the contacts, so if you're up to the challenge you could follow a guide like the one here to set up an AzureAD App. After that, you'd need to modify the function here: https://github.com/grahamr975/EWS-Office365-Contact-Sync/blob/master/EWSContacts/Module/functions/contacts/Get-GALContacts.ps1

$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri $ConnectionUri -Credential $Credentials -Authentication Basic -AllowRedirection

BastiGit commented 2 years ago

Thank you very much for your tips and time.

I have created an Azure-AD app using the tutorial and have customized the function in the Get-GALContacts.ps1 script. Now everything works perfectly and the contacts are being synced.

The script now uses the following function: Connect-ExchangeOnline -CertificateThumbPrint “<CertThumbprint>” -AppID “<MyAppId>” -Organization “<MyTenant.OnMicrosoft.com>”

and instead of Remove-PSSession $Session there is now Disconnect-ExchangeOnline -Confirm:$false at the end of the GALContact script.

We disabled BasicAuth on our tenant for all services, so I'm glad we don't have to enable it again.

Thanks @grahamr975!

grahamr975 commented 2 years ago

@BastiGit Thanks for this information. I'll work on incorporating this piece into the script as time allows since basic authentication will be fully depreciated next year, as you point out.

nldenic commented 2 years ago

Thank you very much for your tips and time.

I have created an Azure-AD app using the tutorial and have customized the function in the Get-GALContacts.ps1 script. Now everything works perfectly and the contacts are being synced.

The script now uses the following function: Connect-ExchangeOnline -CertificateThumbPrint “<CertThumbprint>” -AppID “<MyAppId>” -Organization “<MyTenant.OnMicrosoft.com>”

and instead of Remove-PSSession $Session there is now Disconnect-ExchangeOnline -Confirm:$false at the end of the GALContact script.

We disabled BasicAuth on our tenant for all services, so I'm glad we don't have to enable it again.

Thanks @grahamr975! Can you send me the PS1 you have use or using ?

We have activate mfa for admin accounts. My exchange admin account have already all permistions but getting error :: ERROR Failed to Sync-ContactList for icttest@domainname.nl A constructor was not found. Cannot find an appropriate constructor for type Microsoft.Exchange.WebServices.Data.OAuthCredentials.

BastiGit commented 2 years ago

Hello @nldenic,

I'm sorry, but I can't send you the original .ps1 file, because it contains confidential data.

I have replaced the "PSSession" sections with "ExchangeOnline" in both the Get-Mailboxes.ps1 and Get-GALContacts.ps1 files, as described above.

Have you created an Azure AD app for the script (as described here)?

nldenic commented 2 years ago

Thank you for your reply. Than i will create first the Application in azure and follow this steps. Than will change the ps1 files and see if that works.

The commands to will not change still same am i right so :

cd "%~dp0EWS-Office365-Contact-Sync"

PowerShell.exe -ExecutionPolicy Bypass ^ -File "%CD%\EWSContactSync.ps1" ^ -CredentialPath "C:\Encrypted Credentials\SecureCredential.cred" ^ -FolderName "Directory Contacts" ^ -LogPath "%~dp0Logs" ^ -MailboxList john.doe@mycompany.com ^ -ModernAuth pause

I have write this in an bat file. This is no issue am i right?

nldenic commented 2 years ago

@BastiGit @grahamr975

Can you maybe just send the ps1 without your credentials and appid.

i have follow every step but still getting an error. in this case its now : New-PSSession : A parameter cannot be found that matches parameter name 'AppID'

My Galcontacts.ps1 is now like :

process {
    try {
        # Connect to Office 365 Exchange Server using a Remote Session
    $Session = New-PSSession Connect-ExchangeOnline -CertificateThumbPrint “Thumbprint" -AppID "appid" -Organization “organisation.onmicrosoft.com”
    Import-PSSession $Session -DisableNameChecking -AllowClobber

        # Import Global Address List into Powershell from Office 365 Exchange as an array
        $ContactList = Get-User -ResultSize unlimited 

        # If the ExcludeSharedMailboxContacts switch is enabled, exclude contacts that are a shared mailbox or mailbox with no liscense
        if ($ExcludeSharedMailboxContacts) {
            $DirectoryList = $(Get-Mailbox -ResultSize unlimited | Where-Object {$_.HiddenFromAddressListsEnabled -Match "False"})
            $EmailAddressList = $DirectoryList.PrimarySMTPAddress
            $ContactList = $ContactList | Select-Object DisplayName,FirstName,LastName,Title,Company,Department,WindowsEmailAddress,Phone,MobilePhone | Where-Object {$EmailAddressList.Contains($_.WindowsEmailAddress)}
        } else {
            $ContactList = $ContactList | Select-Object DisplayName,FirstName,LastName,Title,Company,Department,WindowsEmailAddress,Phone,MobilePhone
        }

        # If the IncludeNonUserContacts switch is enabled, also include contacts that aren't actual users in the directory
        if ($IncludeNonUserContacts) {
            $ContactList += Get-Contact -ResultSize unlimited | Select-Object DisplayName,FirstName,LastName,Title,Company,Department,WindowsEmailAddress,Phone,MobilePhone
        }

        # If the ExcludeContactsWithoutPhoneNumber switch is enabled, exclude contacts that don't have a phone or mobile number
        if ($ExcludeContactsWithoutPhoneNumber) {
            $ContactList = $ContactList | Where-Object {$_.Phone -or $_.MobilePhone}
        }

    # Only return contacts with email addresses
    return $ContactList | Where-Object {$null -ne $_.WindowsEmailAddress -and "" -ne $_.WindowsEmailAddress}
    } catch {
        Write-Log -Level "FATAL" -Message "Failed to fetch Global Address List Contacts from Office 365 Directory" -exception $_.Exception.Message
    }
}
}Disconnect-ExchangeOnline -Confirm:$false

Can you please check this and confirm its set good ?

Its in this case changed for both Get-Mailboxes.ps1 and Get-GALContacts.ps1 but still same error message.

When i run empty powershell with. $Session = New-PSSession Connect-ExchangeOnline -CertificateThumbPrint “Thumbprint" -AppID "appid" -Organization “organisation.onmicrosoft.com”

Its working and exchange shell starts. So I don't get it how to write this correct in the ps1 files.

Csarly commented 2 years ago

dear all, I've changed Get-GALContacts.ps1's lines starting here:

...

process {
    try {
        # Connect to Office 365 Exchange Server using a Remote Session
#   $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri $ConnectionUri -Credential $Credentials -Authentication Basic -AllowRedirection
#   Import-PSSession $Session -DisableNameChecking -AllowClobber
$cert = Get-ChildItem Cert:\CurrentUser\My\<self-signed-cert-thumbprint>
$appId = 'my-azure-app-id'
$exchangeOrgId = 'myOrgID.onmicrosoft.com'
$paramConnectExchangeOnline = @{
    CertificateThumbprint = $cert.ThumbPrint
    AppId                 = $appId
    Organization          = $exchangeOrgId
}
Connect-ExchangeOnline @paramConnectExchangeOnline

        # Import Global Address List into Powershell from Office 365 Exchange as an array
        $ContactList = Get-User -ResultSize unlimited -RecipientTypeDetails 'Usermailbox'

... end of change

... and it works like a charm.

I hope you find it useful!

Cheers, Csarly

nldenic commented 2 years ago

@Csarly

I have try you Galsync changes. But still same error message :( Which file do you select here : $cert = Get-ChildItem Cert:\CurrentUser\My\

is it the Cer or pfx file ?

i have also already click allow public client flows in the application settings in azure : image

Are you able to share me your ps1 ? Is there Basicauth active for your account which you use ?

Csarly commented 2 years ago

@nldenic , what is the error message the script throws? Sorry, there's a missing back-slash, this line is correct: $cert = Get-ChildItem Cert:\CurrentUser\My\

Have you created a self-signed certificate? Take it's thumbprint and replace "" with it. Connect-ExchangeOnline uses modern authentication in combination with the certificate for automation capabilities.

I'm new to GitHub, so please forgive me, I just don't find the button to add a file... :(

nldenic commented 2 years ago

@Csarly No problem we can share here the script together.

Please see my error message : image

My script is now as following:

image

Is this correct or do i need to fill this different ?

Csarly commented 2 years ago

Get-GALContacts.txt Hi, I'm sorry, I was trying to create a branch, but it doesn't finish uploading... I finaly found the obvious thing: drag&drop the script here... facepalm You need to modify the parameters commented in capital letters! That should do it.. You have to provide your cert thumbprint, otherwise it won't work. Your version is a mixture of certificate store and desktop. That will probably cause the error...

I hope it helps! KR, Csarly

benj288 commented 2 years ago

Hello Csarly,

have you updated the Sync-ContactList or the Connect-EXCExchange Modules in your environment?

Csarly commented 2 years ago

Hi benj288, no, sadly I was pulled away from this and had no time to invest further. :( best, Csarly

benj288 commented 2 years ago

Hi Csarly, thank you for your reply! You don't use it anymore or has the problem resolved itself? Kind regards

Csarly commented 2 years ago

Hi ben, both, due to other topics and lacking time, I could not finish the adjustments. If the need arises again, and the 3rd party forces me again, I will post my version here. Sorry for not being that helpful at the moment... Cheers, Csarly

JBW-Imthorn commented 2 years ago

Hello,

Does anyone have this script working without ANY basic authentication? So without the $creds based on a (Global Admin) useraccount?

In several functions we see that the $creds variable is used. When leaving the $creds empty the script does not work, also not with the -ModernAuth parameter.

On this moment we have the script working with the -Modernauth parameter, but the script still requires credentials as described above. As we use Azure Privileged Identity Management, we cannot automate the script on this moment.

Does anyone else had this problem and managed to solve it?

Kind regards, Sebastiaan

Csarly commented 2 years ago

@JBW-Imthorn , hi Sebastiaan, nope, EWS doesn't provide cert based auth, e.g., but I managed to run the script - kind of automated - with user & pw stored in an encrypted xml in my tests a few months ago. I think there is a Azure Key Vault you maybe can leverage for storing your credentials. (I'm not familiar with it)

In my case OAuth2 was successfully commited after I changed line 99 in Connect-EXCExchange.ps1 from: $service.Credentials = New-Object Microsoft.Exchange.WebServices.Data.OAuthCredentials($token.AccessToken) to: $service.Credentials = [Microsoft.Exchange.WebServices.Data.OAuthCredentials]$token.AccessToken

I actually don't know why exactly... might be because I have installed the EWS Package ( -Name 'Exchange.WebServices.Managed.Api').... I replaced line 55 against: $EWSDLL = (Get-ChildItem -Path "$(split-path $((Get-Package -Name 'Exchange.WebServices.Managed.Api').Source) -Parent)\Microsoft.Exchange.WebServices.dll" -Recurse).FullName

I hope this helps a little bit though.. kr, Csarly

JBW-Imthorn commented 2 years ago

Hi @Csarly,

Thank you for your reply, I came across this item in the Microsoft docs. So that was triggering me with the question if anyone already tried.

Yes the encrypted XML is what we use too to run the script, and as you described we also changed the content of Connect-EXCExchange.ps1 and this is fully working.

The problem is more that we use Azure Privileged Identity Management. This means we need to activate a role in the Azure environment to gain access rights on the useraccount, it's more secure but also preventing us to automate the contactsync script as a privileged user account is needed.

We will lookin further for what the possibilities are, when we found a solution I will definitely post it here so others can use it also.

Kind regards, Sebastiaan

grahamr975 commented 1 year ago

All,

The latest version of the app moves to Azure-app-based certificate authentication which removes the need for basic auth & fixes MFA issues. See commit 9fcfe1f06192848882564615dd9aa05e71d69970.