ZIZHUOffice365MailboxOauthConnectivity.psm1
<#
.Synopsis PowerShell module to test EXO mailbox Oauth connectivity for Admin or dev .Description PowerShell module to test EXO mailbox Oauth connectivity for Admin or dev. Referce articles below: https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth https://techcommunity.microsoft.com/t5/exchange-team-blog/announcing-oauth-2-0-client-credentials-flow-support-for-pop-and/ba-p/3562963 https://github.com/DanijelkMSFT/ThisandThat/blob/main/Get-IMAPAccessToken.ps1 https://github.com/singhbrijraj/SamplePowerShellScripts/blob/main/Get-POP3AccessToken.ps1 .Example # Installl and import this PowerShell Module https://www.powershellgallery.com/packages/ZIZHUOffice365MailboxOauthConnectivity/1.0 https://github.com/APACMW/ZIZHUOffice365MailboxOauthConnectivity Install-Module -Name ZIZHUOffice365MailboxOauthConnectivity Import-Module -Name ZIZHUOffice365MailboxOauthConnectivity # Customers have the accesstoken $token = 'YourAccesstoken'; $mailbox= 'freeman@vjqg8.onmicrosoft.com'; Set-MailProtocol -protocol SMTP; Test-MailOauthConnectivityWithToken -accessToken $token -targetMailbox $mailbox; # Connect to ZIZHUOffice365MailboxOauthConnectivity via user sign-in $clientID = '06087bc1-286d-47f5-b487-5f0b15a0180d'; $tenantId = 'cff343b2-f0ff-416a-802b-28595997daa2'; $redirectUri='https://localhost'; $loginHint = 'freeman@vjqg8.onmicrosoft.com'; Connect-Office365MailboxOauthConnectivity -tenantID $tenantId -clientID $clientID -loginHint $loginHint -redirectUri $redirectUri -Protocol SMTP; Test-MailOauthConnectivity; Set-MailProtocol -protocol pop3; Test-MailOauthConnectivity; Set-MailProtocol -protocol imap; Test-MailOauthConnectivity; Disconnect-Office365MailboxOauthConnectivity; # Connect to ZIZHUOffice365MailboxOauthConnectivity via client secret $clientID = '7bc50456-263a-475d-9fd3-58a50e4e8cf8'; $tenantId = 'cff343b2-f0ff-416a-802b-28595997daa2'; $clientsecret=''; $targetMailbox = 'freeman@vjqg8.onmicrosoft.com'; Connect-Office365MailboxOauthConnectivity -tenantID $tenantId -clientID $clientID -clientsecret $clientsecret -targetMailbox $targetMailbox -Protocol SMTP; Test-MailOauthConnectivity; Set-MailProtocol -protocol pop3; Test-MailOauthConnectivity; Set-MailProtocol -protocol imap; Test-MailOauthConnectivity; Disconnect-Office365MailboxOauthConnectivity; # Connect to ZIZHUOffice365MailboxOauthConnectivity via client secret $clientID = '7bc50456-263a-475d-9fd3-58a50e4e8cf8'; $tenantId = 'cff343b2-f0ff-416a-802b-28595997daa2'; $thumbprint = '323A5285D82FECC277F1D972148DD3975E061E12'; $clientcertificate= get-item "cert:\localmachine\my\$thumbprint"; $targetMailbox = 'freeman@vjqg8.onmicrosoft.com'; Connect-Office365MailboxOauthConnectivity -tenantID $tenantId -clientID $clientID -clientcertificate $clientcertificate -targetMailbox $targetMailbox -Protocol SMTP; Test-MailOauthConnectivity; Set-MailProtocol -protocol pop3; Test-MailOauthConnectivity; Set-MailProtocol -protocol imap; Test-MailOauthConnectivity; Disconnect-Office365MailboxOauthConnectivity; #> enum MailProtocol { SMTP IMAP POP3 Unknown } [string]$script:tenantID = $null; [string]$script:clientId = $null; [string]$script:clientsecret = $null; [string]$script:redirectUri = $null; [string]$script:loginHint = $null; [X509Certificate]$script:clientcertificate = $null; $script:AuthResult = $null; [string]$script:office365Server = 'outlook.office365.com'; [string]$script:scope = "https://$($script:office365Server)/.default"; [string]$script:userMailbox = $null; [string]$script:SASLXOAUTH2 = $null; [int]$script:timeout = 8000; $script:mailProtocol = [MailProtocol]::Unknown; function Show-VerboseMessage { param( [Parameter(Mandatory = $true)][string]$message ) Write-Verbose "[$((Get-Date).ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss"))]: $message"; return; } function Show-InformationalMessage { param( [Parameter(Mandatory = $true)][string]$message, [Parameter(Mandatory = $false)][System.ConsoleColor]$consoleColor = [System.ConsoleColor]::Gray ) $defaultConsoleColor = $host.UI.RawUI.ForegroundColor; $host.UI.RawUI.ForegroundColor = $consoleColor; Write-Information -InformationAction Continue -MessageData "[$((Get-Date).ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss"))]: $message"; $host.UI.RawUI.ForegroundColor = $defaultConsoleColor; return; } function Show-HttpErrorResponse { param( [Parameter(Mandatory = $true)][object]$httpErrorResponse ) $httpError = $httpErrorResponse | Format-List | Out-String; Show-InformationalMessage -message $httpError -consoleColor Red; } function Show-LastErrorDetails { param( [Parameter(Mandatory = $false)]$lastError = $Error[0] ) $lastError | Format-List -Property * -Force; $lastError.InvocationInfo | Format-List -Property *; $exception = $lastError.Exception; for ($depth = 0; $null -ne $exception; $depth++) { Show-InformationalMessage -message "$depth" * 80 -consoleColor Green; $exception | Format-List -Property * -Force; $exception = $exception.InnerException; } } function Show-AppPermissions { <# .SYNOPSIS Show the API permissions in the access token .DESCRIPTION Show the API permissions in the access token .PARAMETER jwtToken The accesstoken string .EXAMPLE Show-AppPermissions $accesstoken .NOTES Just show the API permissions. Not enforce to must have the specific permissions #> [cmdletbinding()] param( [Parameter(Mandatory = $true)][string]$jwtToken ) $decodedToken = Read-JWTtoken -token $jwtToken; if ($null -ne $decodedToken -and $null -ne $decodedToken.scp) { $permissions = $decodedToken.scp; } elseif ($null -ne $decodedToken -and $null -ne $decodedToken.roles) { $permissions = $decodedToken.roles; } else { $permissions = $null; } Show-InformationalMessage -message "API permissions in the AccessToke: $($permissions)" -consoleColor Yellow; } function Read-JWTtoken { <# .SYNOPSIS Parse the access token/ID token based on https://datatracker.ietf.org/doc/html/rfc7519 .DESCRIPTION Parse the access token/ID token based on https://datatracker.ietf.org/doc/html/rfc7519 .PARAMETER token The accesstoken/ID token string .EXAMPLE Read-JWTtoken -token $jwtToken .NOTES https://datatracker.ietf.org/doc/html/rfc7519 #> [cmdletbinding()] param( [Parameter(Mandatory = $true)][string]$token ) # Validate Access and ID tokens per RFC 7519 if (!$token.Contains(".") -or !$token.StartsWith("eyJ")) { Show-InformationalMessage -message "Invalid token" -consoleColor Red; return; } # Parse the Header $tokenheader = $token.Split(".")[0].Replace('-', '+').Replace('_', '/'); # Fix padding as needed; keep adding "=" until string length modulus 4 reaches 0 while ($tokenheader.Length % 4) { Show-VerboseMessage -message "Invalid length for a Base-64 char array or string, adding ="; $tokenheader += "="; } Show-VerboseMessage -message "Base64 encoded (padded) header:" Show-VerboseMessage -message $tokenheader; # Convert from Base64 encoded string to PSObject Show-VerboseMessage -message "Decoded header:" $headers = [System.Text.Encoding]::ASCII.GetString([system.convert]::FromBase64String($tokenheader)) | ConvertFrom-Json | Format-List | Out-String; Show-VerboseMessage -message $headers; # Payload $tokenPayload = $token.Split(".")[1].Replace('-', '+').Replace('_', '/'); # Fix padding as needed; keep adding "=" until string length modulus 4 reaches 0 while ($tokenPayload.Length % 4) { Show-VerboseMessage -message "Invalid length for a Base-64 char array or string, adding ="; $tokenPayload += "="; } Show-VerboseMessage -message "Base64 encoded (padded) payload:"; Show-VerboseMessage -message $tokenPayload; # Convert to Byte array $tokenByteArray = [System.Convert]::FromBase64String($tokenPayload); # Convert to string array $tokenArray = [System.Text.Encoding]::ASCII.GetString($tokenByteArray); Show-VerboseMessage -message "Decoded array in JSON format:" Show-VerboseMessage -message $tokenArray # Convert from JSON to PSObject $tokenObj = $tokenArray | ConvertFrom-Json; Show-VerboseMessage -message "Decoded Payload:" Write-Output $tokenObj; return; } function Set-SASLXOAUTH2 { if ($null -eq $script:userMailbox) { Write-Error "Not supply the user mailbox. Exit." -ErrorAction Stop; } Get-OauthToken; if (($null -eq $script:AuthResult) -or ($null -eq $script:AuthResult.AccessToken)) { Write-Error "The authentication failure. Can not do Connect-Office365MailboxOauthConnectivity. Please check your app registration in AAD." -ErrorAction Stop; } $accessToken = $script:AuthResult.AccessToken; $saslXoauthstring = "user=" + $script:userMailbox + "$([char]0x01)auth=Bearer " + $accessToken + "$([char]0x01)$([char]0x01)"; $saslXoauthbytes = [System.Text.Encoding]::ASCII.GetBytes($saslXoauthstring); $script:SASLXOAUTH2 = [Convert]::ToBase64String($saslXoauthbytes); Show-VerboseMessage -message "SASL XOAUTH2 login string $script:SASLXOAUTH2"; } function Connect-Office365MailboxOauthConnectivity { <# .SYNOPSIS Initilize the script varibles to prepare for using mail protocols .DESCRIPTION Initilize the script varibles to prepare for using mail protocols .PARAMETER tenantID tenant id .PARAMETER clientId Azure AD application Id .PARAMETER protocol The mail protocol: imap, pop3 and smtp .PARAMETER redirectUri The redirectUri used for implicit auth flow .PARAMETER loginHint The loginHint (user's UPN) used for implicit auth flow .PARAMETER sharedMailbox The shared mailbox used in delegated access scenario .PARAMETER clientsecret The clientsecret used for client credential auth flow .PARAMETER clientcertificate The clientcertificate used for client credential auth flow .PARAMETER targetMailbox The target mailbox used in application permission scenario .EXAMPLE Connect-Office365MailboxOauthConnectivity -tenantID $tenantId -clientID $clientID -clientsecret $clientsecret -targetMailbox $targetMailbox -Protocol SMTP; .NOTES General notes #> param ( [Parameter(Mandatory = $true)][string]$tenantID, [Parameter(Mandatory = $true)][String]$clientId, [Parameter(Mandatory = $true)][MailProtocol]$protocol, [Parameter(Mandatory = $true, ParameterSetName = "authorizationcode")][String]$redirectUri, [Parameter(Mandatory = $true, ParameterSetName = "authorizationcode")][String]$loginHint, [Parameter(Mandatory = $false, ParameterSetName = "authorizationcode")][String]$sharedMailbox, [Parameter(Mandatory = $true, ParameterSetName = "clientcredentialsSecret")][String]$clientsecret, [Parameter(Mandatory = $true, ParameterSetName = "clientcredentialsCertificate")][X509Certificate]$clientcertificate, [Parameter(Mandatory = $true, ParameterSetName = "clientcredentialsSecret")] [Parameter(Mandatory = $true, ParameterSetName = "clientcredentialsCertificate")][String]$targetMailbox ) Import-Module -name 'msal.ps'; $msalModule = Get-Module msal.ps; #check for needed msal.ps module if ( $null -eq $msalModule) { Write-Error -message "[$((Get-Date).ToString("yyyy/MM/dd HH:mm:ss.fff"))] MSAL.PS module not installed, please check it out here https://www.powershellgallery.com/packages/MSAL.PS/" -ErrorAction Stop; } $script:tenantID = $tenantID; $script:clientId = $clientId; $script:mailProtocol = $protocol; if (-not [string]::IsNullOrWhiteSpace($clientsecret)) { $script:clientsecret = $clientsecret; } elseif ($PSBoundParameters.ContainsKey('clientcertificate') -and ($null -ne $clientcertificate)) { $script:clientcertificate = $clientcertificate; } elseif (-not [string]::IsNullOrWhiteSpace($redirectUri)) { $script:loginHint = $loginHint; $script:redirectUri = $redirectUri; } else { Write-Error "Not implement." -ErrorAction Stop; } if ($script:mailProtocol -eq [MailProtocol]::Unknown) { Write-Error "Not specify the mail protocol. Exit." -ErrorAction Stop; } Get-OauthToken; if ($null -eq $script:AuthResult) { Write-Error "The authentication failure. Can not do Connect-Office365MailboxOauthConnectivity. Please check your app registration in AAD." -ErrorAction Stop; } if ($PSBoundParameters.ContainsKey('targetMailbox') -and (-not [string]::IsNullOrWhiteSpace($targetMailbox))) { $script:userMailbox = $targetMailbox } elseif ($PSBoundParameters.ContainsKey('sharedMailbox') -and (-not [string]::IsNullOrWhiteSpace($sharedMailbox))) { $script:userMailbox = $sharedMailbox } elseif ($null -ne $script:AuthResult.Account -and $null -ne $script:AuthResult.Account.Username) { $script:userMailbox = $script:AuthResult.Account.Username; } else { Write-Error "Not supply the user mailbox. Exit." -ErrorAction Stop; } Show-AppPermissions $script:AuthResult.accesstoken; Show-InformationalMessage -message "The authentication succeeds. You can test the mail protocol $mailProtocol" -consoleColor Green; } function Get-OauthToken { <# .SYNOPSIS Use the Msal.ps module to get the access token. Support client credential, Implicit auth flow .DESCRIPTION Use the Msal.ps module to get the access token. Support client credential, Implicit auth flow .NOTES Use the variables from script scope #> Show-VerboseMessage "Start to invoke Get-OauthToken"; # If the access token is valid, then use an existing token $utcNow = (get-date).ToUniversalTime().AddMinutes(1); if ($null -ne $script:AuthResult -and ($utcNow -lt $script:AuthResult.ExpiresOn.UtcDateTime)) { Show-VerboseMessage "Current accesstoken is valid before $($script:AuthResult.ExpiresOn.UtcDateTime)"; return; } # Implicit auth flow (delegated API permissions). Will try to get the access token silently. If fail, then interactive sign-in if (-not [string]::IsNullOrWhiteSpace($script:redirectUri)) { try { Show-VerboseMessage "Get-MsalToken via user sign-in"; $script:AuthResult = Get-MsalToken -ClientId $script:clientId -TenantId $script:tenantID -Silent -LoginHint $script:loginHint -RedirectUri $script:redirectUri -Scopes $script:scope; } Catch [Microsoft.Identity.Client.MsalUiRequiredException] { $script:AuthResult = Get-MsalToken -ClientId $script:clientId -TenantId $script:tenantID -Interactive -LoginHint $script:loginHint -RedirectUri $script:redirectUri -Scopes $script:scope; } Catch { Show-LastErrorDetails; Write-Error -Message "Can not get the access token, exit." -ErrorAction Stop; } } # Client credential auth flow. Can use the client secret or certificate else { try { if (-not [string]::IsNullOrWhiteSpace($script:clientsecret)) { Show-VerboseMessage "Get-MsalToken via client crendential auth flow"; $securedclientSecret = ConvertTo-SecureString $script:clientsecret -AsPlainText -Force $script:AuthResult = Get-MsalToken -clientID $script:clientId -ClientSecret $securedclientSecret -tenantID $script:tenantID -Scopes $script:scope; } elseif ($null -ne $script:clientcertificate) { $script:AuthResult = Get-MsalToken -clientID $script:clientId -ClientCertificate $script:clientcertificate -tenantID $script:tenantID -Scopes $script:scope; } } catch { Show-LastErrorDetails; Write-Error -Message "Can not get the access token, stop." -ErrorAction Stop; } } Show-VerboseMessage "Succeed to invoke Get-OauthToken"; } function Test-SMTPXOAuth2Connectivity { <# .SYNOPSIS Test the smtp connectivity using XOAUTH2 under PS5 .DESCRIPTION Test the smtp connectivity using XOAUTH2 under PS5 .NOTES The script doesn't work correctly in PS7 #> # connecting to Office 365 IMAP Service Show-InformationalMessage -message "Connect to Office 365 SMTP Service." -consoleColor DarkGreen; $smtpServer = $script:office365Server; $smtpPort = '587'; try { # Create a TCP client and connect to the SMTP server $tcpClient = New-Object System.Net.Sockets.TcpClient($smtpServer, $smtpPort); $stream = $tcpClient.GetStream(); $stream.ReadTimeout = $script:timeout; $stream.WriteTimeout = $script:timeout; $streamWriter = new-object System.IO.StreamWriter($stream); $streamReader = new-object System.IO.StreamReader($stream); $streamWriter.AutoFlush = $true; $sslStream = New-Object System.Net.Security.SslStream($stream) $sslStream.ReadTimeout = $script:timeout $sslStream.WriteTimeout = $script:timeout $response = $streamReader.ReadLine(); Show-InformationalMessage "Server: $response" -consoleColor Yellow; if (!$response.StartsWith("220")) { Write-Error "Error connecting to the SMTP Server" -ErrorAction Stop; } Show-InformationalMessage -message "Client: EHLO" -consoleColor Green; $streamWriter.WriteLine("EHLO"); $response = $streamReader.ReadLine(); Show-InformationalMessage -message "Server: $response" -consoleColor Yellow; if (!$response.StartsWith("250")) { Write-Error "Error in EHLO Response" -ErrorAction Stop; } while ($streamReader.Peek() -ne -1) { $response = $streamReader.ReadLine(); Show-InformationalMessage -message "Server: $response" -consoleColor Yellow; } Show-InformationalMessage -message "Client: STARTTLS" -consoleColor Green; $streamWriter.WriteLine("STARTTLS"); $response = $streamReader.ReadLine(); Show-InformationalMessage -message "Server: $response" -consoleColor Yellow; $CheckCertRevocationStatus = $true; $sslStream.AuthenticateAsClient($smtpServer, $null, [System.Security.Authentication.SslProtocols]::Tls12, $CheckCertRevocationStatus); $sslstreamReader = new-object System.IO.StreamReader($sslStream) $SSLstreamWriter = new-object System.IO.StreamWriter($sslStream) $SSLstreamWriter.AutoFlush = $true; $SSLstreamWriter.WriteLine("EHLO"); $response = $sslstreamReader.ReadLine(); Show-InformationalMessage -message "Server: $response" -consoleColor Yellow; if (!$response.StartsWith("250")) { Write-Error "Error in EHLO Response" -ErrorAction Stop; } while ($sslstreamReader.Peek() -ne -1) { $response = $sslstreamReader.ReadLine(); Show-InformationalMessage -message "Server: $response" -consoleColor Yellow; } Show-InformationalMessage -message "Authenticate using XOAuth2" -consoleColor DarkGreen; # authenticate and check for results $command = "auth xoauth2" Show-InformationalMessage -message "Client: $command" -consoleColor Green; $SSLstreamWriter.WriteLine($command); $response = $sslstreamReader.ReadLine(); Show-InformationalMessage -message "Server: $response" -consoleColor Yellow; $command = $script:SASLXOAUTH2; Show-InformationalMessage -message "Client: $command" -consoleColor Green; $SSLstreamWriter.WriteLine($command); $response = $sslstreamReader.ReadLine(); Show-InformationalMessage -message "Server: $response" -consoleColor Yellow; $SendingAddress = $script:userMailbox; $To = $script:userMailbox; if ($response.StartsWith("235 2.7.0 Authentication successful")) { $command = "MAIL FROM: <" + $SendingAddress + ">"; Show-InformationalMessage -message "Client: $command" -consoleColor Green; $SSLstreamWriter.WriteLine($command) $response = $sslstreamReader.ReadLine(); Show-InformationalMessage -message "Server: $response" -consoleColor Yellow; $command = "RCPT TO: <" + $To + ">"; Show-InformationalMessage -message "Client: $command" -consoleColor Green; $SSLstreamWriter.WriteLine($command); $response = $sslstreamReader.ReadLine(); Show-InformationalMessage -message "Server: $response" -consoleColor Yellow; $command = "DATA"; Show-InformationalMessage -message "Client: $command" -consoleColor Green; $SSLstreamWriter.WriteLine($command); $response = $sslstreamReader.ReadLine() Show-InformationalMessage -message "Server: $response" -consoleColor Yellow; $SSLstreamWriter.WriteLine("Subject:test"); $SSLstreamWriter.WriteLine([string]::Empty); $SSLstreamWriter.WriteLine("This is a test message"); $SSLstreamWriter.WriteLine('.'); $response = $sslstreamReader.ReadLine(); Show-InformationalMessage -message "Server: $response" -consoleColor Yellow; $command = "QUIT"; Show-InformationalMessage -message "Client: $command" -consoleColor Green; $SSLstreamWriter.WriteLine($command); $response = $sslstreamReader.ReadLine(); Show-InformationalMessage -message "Server: $response" -consoleColor Yellow; } else { Show-InformationalMessage -message "ERROR during authentication $ResponseStr" -consoleColor Red; } # Session cleanup @($SSLstreamWriter, $sslstreamReader, $sslStream, $streamWriter, $streamReader, $stream, $tcpClient) | ForEach-Object { if ($null -ne $psitem) { $psitem.Dispose(); } } } catch { Show-LastErrorDetails; } } function Test-IMAPXOAuth2Connectivity { <# .SYNOPSIS Test the imap connectivity using XOAUTH2 .DESCRIPTION Test the imap connectivity using XOAUTH2 .NOTES Reuse the existing script from https://github.com/DanijelkMSFT/ThisandThat/blob/main/Get-IMAPAccessToken.ps1 #> # connecting to Office 365 IMAP Service Show-InformationalMessage -message "Connect to Office 365 IMAP Service." -consoleColor DarkGreen; $ComputerName = $script:office365Server; $Port = '993'; try { $tcpClient = New-Object System.Net.Sockets.Tcpclient($($ComputerName), $Port); $tcpStream = $tcpClient.GetStream(); try { $sslStream = New-Object System.Net.Security.SslStream($tcpStream); $sslStream.ReadTimeout = $script:timeout; $sslStream.WriteTimeout = $script:timeout; $CheckCertRevocationStatus = $true; $sslStream.AuthenticateAsClient($ComputerName, $null, [System.Security.Authentication.SslProtocols]::Tls12, $CheckCertRevocationStatus) } catch { Show-LastErrorDetails; Write-Error "Ran into an exception while negotating SSL connection. Exiting." -ErrorAction Stop; } } catch { Show-LastErrorDetails; Write-Error "Ran into an exception while opening TCP connection. Exiting." -ErrorAction Stop; } # continue if connection was successfully established $sslstreamReader = new-object System.IO.StreamReader($sslStream); $SSLstreamWriter = new-object System.IO.StreamWriter($sslStream); $SSLstreamWriter.AutoFlush = $true; $SSLstreamWriter.Newline = "`r`n"; $sslstreamReader.ReadLine(); Show-InformationalMessage -message "Authenticate using XOAuth2." -consoleColor DarkGreen; # authenticate and check for results $command = "A01 AUTHENTICATE XOAUTH2 {0}" -f $script:SASLXOAUTH2; Show-InformationalMessage -message "Client: $command" -consoleColor Green; $SSLstreamWriter.WriteLine($command); #respose might take longer sometimes while ($null -eq $responseStr) { try { $responseStr = $sslstreamReader.ReadLine(); Show-InformationalMessage -message "Server: $responseStr" -consoleColor Yellow; } catch { Show-LastErrorDetails; } } if ( $responseStr -like "*OK AUTHENTICATE completed.") { Show-InformationalMessage -message "Getting mailbox folder list as authentication was successfull." -consoleColor DarkGreen; $command = 'A01 LIST "" *'; Show-InformationalMessage -message "Client: $command" -consoleColor Green; $SSLstreamWriter.WriteLine($command); $done = $false; $str = $null; while (!$done ) { $str = $sslstreamReader.ReadLine(); if ($str -like "* OK LIST completed.") { $done = $true; } elseif ($str -like "* BAD User is authenticated but not connected.") { $str = $str + " Causing Error: IMAP protocol access to mailbox is disabled or permission not granted for client credential flow. Please enable IMAP protcol access or grant fullaccess to service principal."; $done = $true; } Show-InformationalMessage -message "Server: $str" -consoleColor Yellow; } Show-InformationalMessage -message "Logout and cleanup sessions." -consoleColor DarkGreen; $command = 'A01 Logout'; Show-InformationalMessage -message "Client: $command" -consoleColor Green; $SSLstreamWriter.WriteLine($command); $responseStr = $sslstreamReader.ReadLine(); Show-InformationalMessage -message "Server: $responseStr" -consoleColor Yellow; } else { Show-InformationalMessage -message "ERROR during authentication $responseStr" -consoleColor Red; } # Session cleanup @($SSLstreamWriter, $sslstreamReader, $sslStream, $tcpStream, $tcpClient) | ForEach-Object { if ($null -ne $psitem) { $psitem.Dispose(); } } } function Test-POP3XOAuth2Connectivity { <# .SYNOPSIS Test the pop3 connectivity using XOAUTH2 .DESCRIPTION Test the imap connectivity using XOAUTH2 .NOTES Reuse the existing script from https://github.com/singhbrijraj/SamplePowerShellScripts/blob/main/Get-POP3AccessToken.ps1 #> # connecting to Office 365 POP3 Service Show-InformationalMessage -message "Connect to Office 365 POP3 Service." -consoleColor DarkGreen; $ComputerName = $script:office365Server; $Port = '995'; try { $tcpClient = New-Object System.Net.Sockets.Tcpclient($($ComputerName), $Port); $tcpStream = $tcpClient.GetStream(); try { $SSLStream = New-Object System.Net.Security.SslStream($tcpStream); $SSLStream.ReadTimeout = $script:timeout; $SSLStream.WriteTimeout = $script:timeout; $CheckCertRevocationStatus = $true; $SSLStream.AuthenticateAsClient($ComputerName, $null, [System.Security.Authentication.SslProtocols]::Tls12, $CheckCertRevocationStatus); } catch { Show-LastErrorDetails; Write-Error "Ran into an exception while negotating SSL connection. Exiting." -ErrorAction Stop; } } catch { Show-LastErrorDetails; Write-Error "Ran into an exception while opening TCP connection. Exiting." -ErrorAction Stop; } # continue if connection was successfully established $sslstreamReader = new-object System.IO.StreamReader($sslStream); $sslstreamWriter = new-object System.IO.StreamWriter($sslStream); $sslstreamWriter.AutoFlush = $true; $sslstreamReader.ReadLine(); Show-InformationalMessage -message "Authenticate using XOAuth2." -consoleColor DarkGreen; # authenticate and check for results $command = "AUTH XOAUTH2"; Show-InformationalMessage -message "Client: $command" -consoleColor Green; $sslstreamWriter.WriteLine($command); $responseStr = $null; #respose might take longer sometimes while ($null -eq $responseStr) { try { $responseStr = $sslstreamReader.ReadLine(); Show-InformationalMessage -message "Server: $responseStr" -consoleColor Yellow; } catch { Show-LastErrorDetails; } } if ( -not ($responseStr -like "*+*")) { Write-Error "Encounter the authenticaiton failure. Exiting." -ErrorAction Stop; } Show-InformationalMessage -message "Passing XOAUTH2 formatted token" -consoleColor DarkGreen; $sslstreamWriter.WriteLine($script:SASLXOAUTH2); #respose might take longer sometimes $responseStr = $null; while ($null -eq $responseStr) { try { $responseStr = $sslstreamReader.ReadLine(); } catch { Show-LastErrorDetails; } } if ($responseStr -like "*+OK*") { Show-InformationalMessage -message "Getting list of messages as authentication was successfull." -consoleColor DarkGreen; $command = 'LIST'; Show-InformationalMessage -message "Client: $command" -consoleColor Green; $sslstreamWriter.WriteLine($command); $done = $false; $str = $null; while (!$done ) { $str = $sslstreamReader.ReadLine(); if ($str -like "*.") { $done = $true; } elseif ($str -like "* BAD User is authenticated but not connected.") { $str = $str + " Causing Error: POP3 protocol access to mailbox is disabled or permission not granted for client credential flow. Please enable IMAP protcol access or grant fullaccess to service principal."; $done = $true; } Show-InformationalMessage -message "Server: $str" -consoleColor Yellow; } Show-InformationalMessage -message "Logout and cleanup sessions." -consoleColor DarkGreen; $command = 'QUIT'; Show-InformationalMessage -message "Client: $command" -consoleColor Green; $sslstreamWriter.WriteLine($command); $responseStr = $sslstreamReader.ReadLine(); Show-InformationalMessage -message "Server: $responseStr" -consoleColor Yellow; } else { Show-InformationalMessage -message "ERROR during authentication $responseStr" -consoleColor Red; } @($sslstreamWriter, $sslstreamReader, $sslStream, $tcpStream, $tcpClient) | ForEach-Object { if ($null -ne $psitem) { $psitem.Dispose(); } } } function Set-SASLXOAUTH2WithToken { [cmdletbinding()] param( [Parameter(Mandatory = $true)][string]$accessToken, [Parameter(Mandatory = $true)][string]$targetMailbox ) $saslXoauthstring = "user=" + $targetMailbox + "$([char]0x01)auth=Bearer " + $accessToken + "$([char]0x01)$([char]0x01)"; $saslXoauthbytes = [System.Text.Encoding]::ASCII.GetBytes($saslXoauthstring); $script:SASLXOAUTH2 = [Convert]::ToBase64String($saslXoauthbytes); Show-VerboseMessage -message "SASL XOAUTH2 login string $script:SASLXOAUTH2"; } function Test-MailOauthConnectivityWithToken { [cmdletbinding()] param( [Parameter(Mandatory = $true)][string]$accessToken, [Parameter(Mandatory = $true)][string]$targetMailbox ) $script:userMailbox = $targetMailbox; Set-SASLXOAUTH2WithToken -accessToken $accessToken -targetMailbox $targetMailbox; switch ($script:mailProtocol) { SMTP { Test-SMTPXOAuth2Connectivity; } IMAP { Test-IMAPXOAuth2Connectivity; } POP3 { Test-POP3XOAuth2Connectivity; } Default { Write-Error "Not implement." -ErrorAction Stop; } } } function Test-MailOauthConnectivity { Set-SASLXOAUTH2; switch ($script:mailProtocol) { SMTP { Test-SMTPXOAuth2Connectivity; } IMAP { Test-IMAPXOAuth2Connectivity; } POP3 { Test-POP3XOAuth2Connectivity; } Default { Write-Error "Not implement." -ErrorAction Stop; } } } function Set-MailProtocol { param ( [Parameter(Mandatory = $true)][MailProtocol]$protocol ) $script:mailProtocol = $protocol; } function Disconnect-Office365MailboxOauthConnectivity { [string]$script:tenantID = $null; [string]$script:clientId = $null; [string]$script:clientsecret = $null; [string]$script:redirectUri = $null; [string]$script:loginHint = $null; [X509Certificate]$script:clientcertificate = $null; $script:AuthResult = $null; [string]$script:userMailbox = $null; [string]$script:SASLXOAUTH2 = $null; $script:mailProtocol = [MailProtocol]::Unknown; Show-InformationalMessage -message "Successfuly disconnect from outlook.office365.com" -consoleColor Green; } Export-ModuleMember Connect-Office365MailboxOauthConnectivity, Test-MailOauthConnectivity, Set-MailProtocol, Disconnect-Office365MailboxOauthConnectivity, Test-MailOauthConnectivityWithToken; |