Mailozaurr.psm1
function Remove-EmptyValue { [alias('Remove-EmptyValues')] [CmdletBinding()] param([alias('Splat', 'IDictionary')][Parameter(Mandatory)][System.Collections.IDictionary] $Hashtable, [string[]] $ExcludeParameter, [switch] $Recursive, [int] $Rerun) foreach ($Key in [string[]] $Hashtable.Keys) { if ($Key -notin $ExcludeParameter) { if ($Recursive) { if ($Hashtable[$Key] -is [System.Collections.IDictionary]) { if ($Hashtable[$Key].Count -eq 0) { $Hashtable.Remove($Key) } else { Remove-EmptyValue -Hashtable $Hashtable[$Key] -Recursive:$Recursive } } else { if ($null -eq $Hashtable[$Key] -or ($Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') -or ($Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0)) { $Hashtable.Remove($Key) } } } else { if ($null -eq $Hashtable[$Key] -or ($Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') -or ($Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0)) { $Hashtable.Remove($Key) } } } } if ($Rerun) { for ($i = 0; $i -lt $Rerun; $i++) { Remove-EmptyValue -Hashtable $Hashtable -Recursive:$Recursive } } } class MySmtpClient : MailKit.Net.Smtp.SmtpClient { MySmtpClient() : base() {} [string[]] $DeliveryNotificationOption = @() [Nullable[MailKit.DeliveryStatusNotification]] GetDeliveryStatusNotifications([MimeKit.MimeMessage]$message, [MimeKit.MailboxAddress]$mailbox) { $Output = @( if ($this.DeliveryNotificationOption -contains 'OnSuccess') { [MailKit.DeliveryStatusNotification]::Success } if ($this.DeliveryNotificationOption -contains 'Delay') { [MailKit.DeliveryStatusNotification]::Delay } if ($this.DeliveryNotificationOption -contains 'OnFailure') { [MailKit.DeliveryStatusNotification]::Failure } if ($this.DeliveryNotificationOption -contains 'Never') { [MailKit.DeliveryStatusNotification]::Never } ) if ($Output.Count -gt 0) { return $Output } return $null } } function Connect-O365Graph { [cmdletBinding()] param( [string][alias('ClientID')] $ApplicationID, [string][alias('ClientSecret')] $ApplicationKey, [string] $TenantDomain, [ValidateSet('https://manage.office.com', 'https://graph.microsoft.com')] $Resource = 'https://manage.office.com' ) # https://dzone.com/articles/getting-access-token-for-microsoft-graph-using-oau-1 #$Scope = @( #'https://outlook.office.com/IMAP.AccessAsUser.All', # 'https://outlook.office.com/POP.AccessAsUser.All', # 'https://outlook.office.com/Mail.Send' # 'https://outlook.office.com/User.Read' #) $Body = @{ grant_type = 'client_credentials' resource = $Resource client_id = $ApplicationID client_secret = $ApplicationKey #scope = [System.Web.HttpUtility]::UrlEncode( $Scope) } try { $Authorization = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$($TenantDomain)/oauth2/token" -Body $body -ErrorAction Stop } catch { $ErrorMessage = $_.Exception.Message -replace "`n", ' ' -replace "`r", ' ' Write-Warning -Message "Connect-O365Graph - Error: $ErrorMessage" } if ($Authorization) { @{'Authorization' = "$($Authorization.token_type) $($Authorization.access_token)" } } else { $null } } function ConvertFrom-GraphCredential { [cmdletBinding()] param( [Parameter(Mandatory)][PSCredential] $Credential ) $Object = $Credential.UserName -split '@' if ($Object.Count -eq 2) { [PSCustomObject] @{ ClientID = $Object[0] DirectoryID = $Object[1] ClientSecret = $Credential.GetNetworkCredential().Password } } } function ConvertFrom-OAuth2Credential { [cmdletBinding()] param( [Parameter(Mandatory)][PSCredential] $Credential ) [PSCustomObject] @{ UserName = $Credential.UserName Token = $Credential.GetNetworkCredential().Password } } function ConvertTo-GraphAddress { [cmdletBinding()] param( [Array] $MailboxAddress, [object] $From, [switch] $LimitedFrom ) foreach ($_ in $MailboxAddress) { if ($_ -is [string]) { if ($_) { @{ emailAddress = @{ address = $_ } } } } elseif ($_ -is [System.Collections.IDictionary]) { if ($_.Email) { @{ emailAddress = @{ address = $_.Email } } } } elseif ($_ -is [MimeKit.MailboxAddress]) { if ($_.Address) { @{ emailAddress = @{ address = $_.Address } } } } else { if ($_.Name -and $_.Email) { @{ emailAddress = @{ address = $_.Email } } } elseif ($_.Email) { @{ emailAddress = @{ address = $_.Email } } } } } if ($From) { if ($From -is [string]) { if ($LimitedFrom) { $From } else { @{ emailAddress = @{ address = $From } } } } elseif ($From -is [System.Collections.IDictionary]) { if ($LimitedFrom) { $From.Email } else { @{ emailAddress = @{ address = $From.Name #name = $From.Name } } } } } } function ConvertTo-MailboxAddress { [cmdletBinding()] param( [Array] $MailboxAddress ) <# MimeKit.MailboxAddress new(System.Text.Encoding encoding, string name, System.Collections.Generic.IEnumerable[string] route, string address) MimeKit.MailboxAddress new(string name, System.Collections.Generic.IEnumerable[string] route, string address) MimeKit.MailboxAddress new(System.Collections.Generic.IEnumerable[string] route, string address) MimeKit.MailboxAddress new(System.Text.Encoding encoding, string name, string address) MimeKit.MailboxAddress new(string name, string address) MimeKit.MailboxAddress new(string address) #> foreach ($_ in $MailboxAddress) { if ($_ -is [string]) { $SmtpTo = [MimeKit.MailboxAddress]::new("$_") } elseif ($_ -is [System.Collections.IDictionary]) { $SmtpTo = [MimeKit.MailboxAddress]::new($_.Name, $_.Email) } elseif ($_ -is [MimeKit.MailboxAddress]) { $SmtpTo = $_ } else { if ($_.Name -and $_.Email) { $SmtpTo = [MimeKit.MailboxAddress]::new($_.Name, $_.Email) } elseif ($_.Email) { $SmtpTo = [MimeKit.MailboxAddress]::new($_.Email) } } $SmtpTo } } function Invoke-O365Graph { [cmdletBinding()] param( [uri] $PrimaryUri = 'https://graph.microsoft.com/v1.0', [uri] $Uri, [alias('Authorization')][System.Collections.IDictionary] $Headers, [validateset('GET', 'DELETE', 'POST')][string] $Method = 'GET', [string] $ContentType = 'application/json', [switch] $FullUri ) $RestSplat = @{ Headers = $Headers Method = $Method ContentType = $ContentType } if ($FullUri) { $RestSplat.Uri = $Uri } else { $RestSplat.Uri = -join ($PrimaryUri, $Uri) } try { $OutputQuery = Invoke-RestMethod @RestSplat -Verbose:$false if ($Method -eq 'GET') { if ($OutputQuery.value) { $OutputQuery.value } if ($OutputQuery.'@odata.nextLink') { $RestSplat.Uri = $OutputQuery.'@odata.nextLink' $MoreData = Invoke-O365Graph @RestSplat -FullUri if ($MoreData) { $MoreData } } } else { return $true } } catch { $RestError = $_.ErrorDetails.Message if ($RestError) { try { $ErrorMessage = ConvertFrom-Json -InputObject $RestError $ErrorMy = -join ('JSON Error:' , $ErrorMessage.error.code, ' ', $ErrorMessage.error.message, ' Additional Error: ', $_.Exception.Message) Write-Warning $ErrorMy } catch { Write-Warning $_.Exception.Message } } else { Write-Warning $_.Exception.Message } if ($Method -ne 'GET') { return $false } } } function Send-GraphMailMessage { [cmdletBinding(SupportsShouldProcess)] param( [object] $From, [Array] $To, [Array] $Cc, [Array] $Bcc, [string] $ReplyTo, [string] $Subject, [alias('Body')][string[]] $HTML, [string[]] $Text, [alias('Attachments')][string[]] $Attachment, [PSCredential] $Credential, [alias('Importance')][ValidateSet('Low', 'Normal', 'High')][string] $Priority, [switch] $DoNotSaveToSentItems ) if ($Credential) { $AuthorizationData = ConvertFrom-GraphCredential -Credential $Credential } else { return } $Authorization = Connect-O365Graph -ApplicationID $AuthorizationData.ClientID -ApplicationKey $AuthorizationData.ClientSecret -TenantDomain $AuthorizationData.DirectoryID -Resource https://graph.microsoft.com $Body = @{} if ($HTML) { $Body['contentType'] = 'HTML' $body['content'] = $HTML -join [System.Environment]::NewLine } elseif ($Text) { $Body['contentType'] = 'Text' $body['content'] = $Text -join [System.Environment]::NewLine } else { $Body['contentType'] = 'Text' $body['content'] = '' } $Message = [ordered] @{ # https://docs.microsoft.com/en-us/graph/api/resources/message?view=graph-rest-1.0 message = [ordered] @{ subject = $Subject body = $Body from = ConvertTo-GraphAddress -From $From toRecipients = @( ConvertTo-GraphAddress -MailboxAddress $To ) ccRecipients = @( ConvertTo-GraphAddress -MailboxAddress $CC ) bccRecipients = @( ConvertTo-GraphAddress -MailboxAddress $BCC ) #sender = @( # ConvertTo-GraphAddress -MailboxAddress $From #) replyTo = @( ConvertTo-GraphAddress -MailboxAddress $ReplyTo ) attachments = @( foreach ($A in $Attachment) { $ItemInformation = Get-Item -Path $FilePath if ($ItemInformation) { $File = [system.io.file]::ReadAllBytes($FilePath) $Bytes = [System.Convert]::ToBase64String($File) @{ '@odata.type' = '#microsoft.graph.fileAttachment' 'name' = $ItemInformation.Name #'contentType' = 'text/plain' 'contentBytes' = $Bytes } } } ) importance = $Priority #isReadReceiptRequested = $true #isDeliveryReceiptRequested = $true } saveToSentItems = -not $DoNotSaveToSentItems.IsPresent } $MailSentTo = -join ($To -join ',', $CC -join ', ', $Bcc -join ', ') Remove-EmptyValue -Hashtable $Message -Recursive -Rerun 2 $Body = $Message | ConvertTo-Json -Depth 5 $FromField = ConvertTo-GraphAddress -From $From -LimitedFrom Try { if ($PSCmdlet.ShouldProcess("$MailSentTo", 'Send-EmailMessage')) { $null = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/users/$FromField/sendMail" -Headers $Authorization -Method POST -Body $Body -ContentType 'application/json' -ErrorAction Stop if (-not $Suppress) { [PSCustomObject] @{ Status = $True Error = '' SentTo = $MailSentTo SentFrom = $FromField } } } } catch { if ($PSBoundParameters.ErrorAction -eq 'Stop') { Write-Error $_ return } else { $ErrorDetails = $_.ErrorDetails.Message | ConvertFrom-Json Write-Warning "Send-GraphMailMessage - Error: $($_.Exception.Message)" Write-Warning "Send-GraphMailMessage - Error details: $($ErrorDetails.Error.Message)" } if (-not $Suppress) { [PSCustomObject] @{ Status = $False Error = -join ( $($_.Exception.Message), ' details: ', $($ErrorDetails.Error.Message)) SentTo = $MailSentTo SentFrom = $FromField } } } if ($VerbosePreference) { if ($Message.message.attachments) { $Message.message.attachments | ForEach-Object { if ($_.contentBytes.Length -ge 10) { $_.contentBytes = -join ($_.contentBytes.Substring(0, 10), 'ContentIsTrimmed') } else { $_.contentBytes = -join ($_.contentBytes, 'ContentIsTrimmed') } } } If ($Message.message.body.content) { if ($Message.message.body.content.Length -gt 10) { $Message.message.body.content = -join ($Message.message.body.content.Substring(0, 10), 'ContentIsTrimmed') } else { $Message.message.body.content = -join ($Message.message.body.content, 'ContentIsTrimmed') } } $TrimmedBody = $Message | ConvertTo-Json -Depth 5 Write-Verbose "Message content: $TrimmedBody" } } function Wait-Task { # await replacement param ( [Parameter(ValueFromPipeline = $true, Mandatory = $true)] $Task ) # https://stackoverflow.com/questions/51218257/await-async-c-sharp-method-from-powershell process { while (-not $Task.AsyncWaitHandle.WaitOne(200)) { } $Task.GetAwaiter().GetResult() } } function Connect-IMAP { [cmdletBinding(DefaultParameterSetName = 'Credential')] param( [Parameter(ParameterSetName = 'oAuth2')] [Parameter(ParameterSetName = 'Credential')] [Parameter(ParameterSetName = 'ClearText')] [Parameter(Mandatory)][string] $Server, [Parameter(ParameterSetName = 'oAuth2')] [Parameter(ParameterSetName = 'Credential')] [Parameter(ParameterSetName = 'ClearText')] [int] $Port = '993', [Parameter(ParameterSetName = 'ClearText', Mandatory)][string] $UserName, [Parameter(ParameterSetName = 'ClearText', Mandatory)][string] $Password, [Parameter(ParameterSetName = 'oAuth2')] [Parameter(ParameterSetName = 'Credential')][System.Management.Automation.PSCredential] $Credential, [Parameter(ParameterSetName = 'oAuth2')] [Parameter(ParameterSetName = 'Credential')] [Parameter(ParameterSetName = 'ClearText')] [MailKit.Security.SecureSocketOptions] $Options = [MailKit.Security.SecureSocketOptions]::Auto, [Parameter(ParameterSetName = 'oAuth2')] [Parameter(ParameterSetName = 'Credential')] [Parameter(ParameterSetName = 'ClearText')] [int] $TimeOut = 120000, [Parameter(ParameterSetName = 'oAuth2')] [switch] $oAuth2 ) $Client = [MailKit.Net.Imap.ImapClient]::new() try { $Client.Connect($Server, $Port, $Options) } catch { Write-Warning "Connect-IMAP - Unable to connect $($_.Exception.Message)" return } <# void Connect(string host, int port, MailKit.Security.SecureSocketOptions options, System.Threading.CancellationToken cancellationToken) void Connect(System.Net.Sockets.Socket socket, string host, int port, MailKit.Security.SecureSocketOptions options, System.Threading.CancellationToken cancellationToken) void Connect(System.IO.Stream stream, string host, int port, MailKit.Security.SecureSocketOptions options, System.Threading.CancellationToken cancellationToken) void Connect(uri uri, System.Threading.CancellationToken cancellationToken) void Connect(string host, int port, bool useSsl, System.Threading.CancellationToken cancellationToken) void IMailService.Connect(string host, int port, bool useSsl, System.Threading.CancellationToken cancellationToken) void IMailService.Connect(string host, int port, MailKit.Security.SecureSocketOptions options, System.Threading.CancellationToken cancellationToken) void IMailService.Connect(System.Net.Sockets.Socket socket, string host, int port, MailKit.Security.SecureSocketOptions options, System.Threading.CancellationToken cancellationToken) void IMailService.Connect(System.IO.Stream stream, string host, int port, MailKit.Security.SecureSocketOptions options, System.Threading.CancellationToken cancellationToken) #> if ($Client.TimeOut -ne $TimeOut) { $Client.TimeOut = $Timeout } if ($Client.IsConnected) { if ($oAuth2.IsPresent) { $Authorization = ConvertFrom-OAuth2Credential -Credential $Credential $SaslMechanismOAuth2 = [MailKit.Security.SaslMechanismOAuth2]::new($Authorization.UserName, $Authorization.Token) try { $Client.Authenticate($SaslMechanismOAuth2) } catch { Write-Warning "Connect-POP - Unable to authenticate via oAuth $($_.Exception.Message)" return } } elseif ($UserName -and $Password) { try { $Client.Authenticate($UserName, $Password) } catch { Write-Warning "Connect-IMAP - Unable to authenticate $($_.Exception.Message)" return } } else { try { $Client.Authenticate($Credential) } catch { Write-Warning "Connect-IMAP - Unable to authenticate $($_.Exception.Message)" return } } } else { return } if ($Client.IsAuthenticated) { [ordered] @{ Uri = $Client.SyncRoot.Uri #: pops: / / pop.gmail.com:995 / AuthenticationMechanisms = $Client.SyncRoot.AuthenticationMechanisms #: { } Capabilities = $Client.SyncRoot.Capabilities #: Expire, LoginDelay, Pipelining, ResponseCodes, Top, UIDL, User Stream = $Client.SyncRoot.Stream #: MailKit.Net.Pop3.Pop3Stream State = $Client.SyncRoot.State #: Transaction IsConnected = $Client.SyncRoot.IsConnected #: True ApopToken = $Client.SyncRoot.ApopToken #: ExpirePolicy = $Client.SyncRoot.ExpirePolicy #: 0 Implementation = $Client.SyncRoot.Implementation #: LoginDelay = $Client.SyncRoot.LoginDelay #: 300 IsAuthenticated = $Client.IsAuthenticated IsSecure = $Client.IsSecure Data = $Client Count = $Client.Count } } <# void Authenticate(MailKit.Security.SaslMechanism mechanism, System.Threading.CancellationToken cancellationToken) void Authenticate(System.Text.Encoding encoding, System.Net.ICredentials credentials, System.Threading.CancellationToken cancellationToken) void Authenticate(System.Net.ICredentials credentials, System.Threading.CancellationToken cancellationToken) void Authenticate(System.Text.Encoding encoding, string userName, string password, System.Threading.CancellationToken cancellationToken) void Authenticate(string userName, string password, System.Threading.CancellationToken cancellationToken) void IMailService.Authenticate(System.Net.ICredentials credentials, System.Threading.CancellationToken cancellationToken) void IMailService.Authenticate(System.Text.Encoding encoding, System.Net.ICredentials credentials, System.Threading.CancellationToken cancellationToken) void IMailService.Authenticate(System.Text.Encoding encoding, string userName, string password, System.Threading.CancellationToken cancellationToken) void IMailService.Authenticate(string userName, string password, System.Threading.CancellationToken cancellationToken) void IMailService.Authenticate(MailKit.Security.SaslMechanism mechanism, System.Threading.CancellationToken cancellationToken) #> <# ------------------- System.Threading.Tasks.Task AuthenticateAsync(MailKit.Security.SaslMechanism mechanism, System.Threading.CancellationToken cancellationToken) System.Threading.Tasks.Task AuthenticateAsync(System.Text.Encoding encoding, System.Net.ICredentials credentials, System.Threading.CancellationToken cancellati onToken) System.Threading.Tasks.Task AuthenticateAsync(System.Net.ICredentials credentials, System.Threading.CancellationToken cancellationToken) System.Threading.Tasks.Task AuthenticateAsync(System.Text.Encoding encoding, string userName, string password, System.Threading.CancellationToken cancellationT oken) System.Threading.Tasks.Task AuthenticateAsync(string userName, string password, System.Threading.CancellationToken cancellationToken) System.Threading.Tasks.Task IMailService.AuthenticateAsync(System.Net.ICredentials credentials, System.Threading.CancellationToken cancellationToken) System.Threading.Tasks.Task IMailService.AuthenticateAsync(System.Text.Encoding encoding, System.Net.ICredentials credentials, System.Threading.CancellationTok en cancellationToken) System.Threading.Tasks.Task IMailService.AuthenticateAsync(System.Text.Encoding encoding, string userName, string password, System.Threading.CancellationToken cancellationToken) System.Threading.Tasks.Task IMailService.AuthenticateAsync(string userName, string password, System.Threading.CancellationToken cancellationToken) System.Threading.Tasks.Task IMailService.AuthenticateAsync(MailKit.Security.SaslMechanism mechanism, System.Threading.CancellationToken cancellationToken) #> #$Client.GetMessageSizes } function Connect-oAuthGoogle { [cmdletBinding()] param( [Parameter(Mandatory)][string] $GmailAccount, [Parameter(Mandatory)][string] $ClientID, [Parameter(Mandatory)][string] $ClientSecret, [ValidateSet("https://mail.google.com/")][string[]] $Scope = @("https://mail.google.com/") ) $ClientSecrets = [Google.Apis.Auth.OAuth2.ClientSecrets]::new() $ClientSecrets.ClientId = $ClientID $ClientSecrets.ClientSecret = $ClientSecret $Initializer = [Google.Apis.Auth.OAuth2.Flows.GoogleAuthorizationCodeFlow+Initializer]::new() $Initializer.DataStore = [Google.Apis.Util.Store.FileDataStore]::new("CredentialCacheFolder", $false) $Initializer.Scopes = $Scope $Initializer.ClientSecrets = $ClientSecrets $CodeFlow = [Google.Apis.Auth.OAuth2.Flows.GoogleAuthorizationCodeFlow]::new($Initializer) $codeReceiver = [Google.Apis.Auth.OAuth2.LocalServerCodeReceiver]::new() $AuthCode = [Google.Apis.Auth.OAuth2.AuthorizationCodeInstalledApp]::new($CodeFlow, $codeReceiver) $Credential = $AuthCode.AuthorizeAsync($GmailAccount, [System.Threading.CancellationToken]::None) | Wait-Task if ($Credential.Token.IsExpired([Google.Apis.Util.SystemClock]::Default)) { $credential.RefreshTokenAsync([System.Threading.CancellationToken]::None) | Wait-Task } #$oAuth2 = [MailKit.Security.SaslMechanismOAuth2]::new($credential.UserId, $credential.Token.AccessToken) #$oAuth2 #[PSCustomObject] @{ # UserName = $Credential.UserId # Token = $Credential.Token.AccessToken #} ConvertTo-OAuth2Credential -UserName $Credential.UserId -Token $Credential.Token.AccessToken } function Connect-oAuthO365 { [cmdletBinding()] param( [string] $Login, [Parameter(Mandatory)][string] $ClientID, [Parameter(Mandatory)][string] $TenantID, [uri] $RedirectUri = 'https://login.microsoftonline.com/common/oauth2/nativeclient', [ValidateSet( "email", "offline_access", "https://outlook.office.com/IMAP.AccessAsUser.All", "https://outlook.office.com/POP.AccessAsUser.All", "https://outlook.office.com/SMTP.Send" )][string[]] $Scopes = @( "email", "offline_access", "https://outlook.office.com/IMAP.AccessAsUser.All", "https://outlook.office.com/POP.AccessAsUser.All", "https://outlook.office.com/SMTP.Send" ) ) $Options = [Microsoft.Identity.Client.PublicClientApplicationOptions]::new() $Options.ClientId = $ClientID $Options.TenantId = $TenantID $Options.RedirectUri = $RedirectUri $PublicClientApplication = [Microsoft.Identity.Client.PublicClientApplicationBuilder]::CreateWithApplicationOptions($Options).Build() # https://www.powershellgallery.com/packages/MSAL.PS/4.2.1.1/Content/Get-MsalToken.ps1 # Here we should implement something for Silent Token # $Account = $Account # $AuthToken = $PublicClientApplication.AcquireTokenSilent($Scopes, $login).ExecuteAsync([System.Threading.CancellationToken]::None) | Wait-Task # $oAuth2 = [MailKit.Security.SaslMechanismOAuth2]::new($AuthToken.Account.Username, $AuthToken.AccessToken) # https://docs.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth try { if ($Login) { $AuthToken = $PublicClientApplication.AcquireTokenInteractive($Scopes).ExecuteAsync([System.Threading.CancellationToken]::None) | Wait-Task } else { $AuthToken = $PublicClientApplication.AcquireTokenInteractive($Scopes).WithLoginHint($Login).ExecuteAsync([System.Threading.CancellationToken]::None) | Wait-Task } # Here we should save the AuthToken.Account somehow, somewhere # $AuthToken.Account | Export-Clixml -Path $Env:USERPROFILE\Desktop\test.xml -Depth 2 #[PSCustomObject] @{ # UserName = $AuthToken.Account.UserName # Token = $AuthToken.AccessToken #} ConvertTo-OAuth2Credential -UserName $AuthToken.Account.UserName -Token $AuthToken.AccessToken #$oAuth2 = [MailKit.Security.SaslMechanismOAuth2]::new($AuthToken.Account.Username, $AuthToken.AccessToken) #$oAuth2 } catch { Write-Warning "Connect-oAuth - $_" } } function Connect-POP { [alias('Connect-POP3')] [cmdletBinding()] param( [Parameter(ParameterSetName = 'oAuth2')] [Parameter(ParameterSetName = 'Credential')] [Parameter(ParameterSetName = 'ClearText')] [Parameter(Mandatory)][string] $Server, [Parameter(ParameterSetName = 'oAuth2')] [Parameter(ParameterSetName = 'Credential')] [Parameter(ParameterSetName = 'ClearText')] [int] $Port = '995', [Parameter(ParameterSetName = 'ClearText', Mandatory)][string] $UserName, [Parameter(ParameterSetName = 'ClearText', Mandatory)][string] $Password, [Parameter(ParameterSetName = 'oAuth2', Mandatory)] [Parameter(ParameterSetName = 'Credential')][System.Management.Automation.PSCredential] $Credential, [Parameter(ParameterSetName = 'oAuth2')] [Parameter(ParameterSetName = 'Credential')] [Parameter(ParameterSetName = 'ClearText')] [MailKit.Security.SecureSocketOptions] $Options = [MailKit.Security.SecureSocketOptions]::Auto, [Parameter(ParameterSetName = 'oAuth2')] [Parameter(ParameterSetName = 'Credential')] [Parameter(ParameterSetName = 'ClearText')] [int] $TimeOut = 120000, [Parameter(ParameterSetName = 'oAuth2')] [switch] $oAuth2 ) $Client = [MailKit.Net.Pop3.Pop3Client]::new() try { $Client.Connect($Server, $Port, $Options) } catch { Write-Warning "Connect-POP - Unable to connect $($_.Exception.Message)" return } <# void Connect(string host, int port, MailKit.Security.SecureSocketOptions options, System.Threading.CancellationToken cancellationToken) void Connect(System.Net.Sockets.Socket socket, string host, int port, MailKit.Security.SecureSocketOptions options, System.Threading.CancellationToken cancellationToken) void Connect(System.IO.Stream stream, string host, int port, MailKit.Security.SecureSocketOptions options, System.Threading.CancellationToken cancellationToken) void Connect(uri uri, System.Threading.CancellationToken cancellationToken) void Connect(string host, int port, bool useSsl, System.Threading.CancellationToken cancellationToken) void IMailService.Connect(string host, int port, bool useSsl, System.Threading.CancellationToken cancellationToken) void IMailService.Connect(string host, int port, MailKit.Security.SecureSocketOptions options, System.Threading.CancellationToken cancellationToken) void IMailService.Connect(System.Net.Sockets.Socket socket, string host, int port, MailKit.Security.SecureSocketOptions options, System.Threading.CancellationToken cancellationToken) void IMailService.Connect(System.IO.Stream stream, string host, int port, MailKit.Security.SecureSocketOptions options, System.Threading.CancellationToken cancellationToken) #> if ($Client.TimeOut -ne $TimeOut) { $Client.TimeOut = $Timeout } if ($Client.IsConnected) { if ($oAuth2.IsPresent) { $Authorization = ConvertFrom-OAuth2Credential -Credential $Credential $SaslMechanismOAuth2 = [MailKit.Security.SaslMechanismOAuth2]::new($Authorization.UserName, $Authorization.Token) try { $Client.Authenticate($SaslMechanismOAuth2) } catch { Write-Warning "Connect-POP - Unable to authenticate via oAuth $($_.Exception.Message)" return } } elseif ($UserName -and $Password) { try { $Client.Authenticate($UserName, $Password) } catch { Write-Warning "Connect-POP - Unable to authenticate via UserName/Password $($_.Exception.Message)" return } } else { try { $Client.Authenticate($Credential) } catch { Write-Warning "Connect-POP - Unable to authenticate via Credentials $($_.Exception.Message)" return } } } else { return } if ($Client.IsAuthenticated) { [ordered] @{ Uri = $Client.SyncRoot.Uri #: pops: / / pop.gmail.com:995 / AuthenticationMechanisms = $Client.SyncRoot.AuthenticationMechanisms #: { } Capabilities = $Client.SyncRoot.Capabilities #: Expire, LoginDelay, Pipelining, ResponseCodes, Top, UIDL, User Stream = $Client.SyncRoot.Stream #: MailKit.Net.Pop3.Pop3Stream State = $Client.SyncRoot.State #: Transaction IsConnected = $Client.SyncRoot.IsConnected #: True ApopToken = $Client.SyncRoot.ApopToken #: ExpirePolicy = $Client.SyncRoot.ExpirePolicy #: 0 Implementation = $Client.SyncRoot.Implementation #: LoginDelay = $Client.SyncRoot.LoginDelay #: 300 IsAuthenticated = $Client.IsAuthenticated IsSecure = $Client.IsSecure Data = $Client Count = $Client.Count } } <# void Authenticate(MailKit.Security.SaslMechanism mechanism, System.Threading.CancellationToken cancellationToken) void Authenticate(System.Text.Encoding encoding, System.Net.ICredentials credentials, System.Threading.CancellationToken cancellationToken) void Authenticate(System.Net.ICredentials credentials, System.Threading.CancellationToken cancellationToken) void Authenticate(System.Text.Encoding encoding, string userName, string password, System.Threading.CancellationToken cancellationToken) void Authenticate(string userName, string password, System.Threading.CancellationToken cancellationToken) void IMailService.Authenticate(System.Net.ICredentials credentials, System.Threading.CancellationToken cancellationToken) void IMailService.Authenticate(System.Text.Encoding encoding, System.Net.ICredentials credentials, System.Threading.CancellationToken cancellationToken) void IMailService.Authenticate(System.Text.Encoding encoding, string userName, string password, System.Threading.CancellationToken cancellationToken) void IMailService.Authenticate(string userName, string password, System.Threading.CancellationToken cancellationToken) void IMailService.Authenticate(MailKit.Security.SaslMechanism mechanism, System.Threading.CancellationToken cancellationToken) #> <# ------------------- System.Threading.Tasks.Task AuthenticateAsync(MailKit.Security.SaslMechanism mechanism, System.Threading.CancellationToken cancellationToken) System.Threading.Tasks.Task AuthenticateAsync(System.Text.Encoding encoding, System.Net.ICredentials credentials, System.Threading.CancellationToken cancellati onToken) System.Threading.Tasks.Task AuthenticateAsync(System.Net.ICredentials credentials, System.Threading.CancellationToken cancellationToken) System.Threading.Tasks.Task AuthenticateAsync(System.Text.Encoding encoding, string userName, string password, System.Threading.CancellationToken cancellationT oken) System.Threading.Tasks.Task AuthenticateAsync(string userName, string password, System.Threading.CancellationToken cancellationToken) System.Threading.Tasks.Task IMailService.AuthenticateAsync(System.Net.ICredentials credentials, System.Threading.CancellationToken cancellationToken) System.Threading.Tasks.Task IMailService.AuthenticateAsync(System.Text.Encoding encoding, System.Net.ICredentials credentials, System.Threading.CancellationTok en cancellationToken) System.Threading.Tasks.Task IMailService.AuthenticateAsync(System.Text.Encoding encoding, string userName, string password, System.Threading.CancellationToken cancellationToken) System.Threading.Tasks.Task IMailService.AuthenticateAsync(string userName, string password, System.Threading.CancellationToken cancellationToken) System.Threading.Tasks.Task IMailService.AuthenticateAsync(MailKit.Security.SaslMechanism mechanism, System.Threading.CancellationToken cancellationToken) #> #$Client.GetMessageSizes } function ConvertTo-GraphCredential { [cmdletBinding()] param( [Parameter(Mandatory)][string] $ClientID, [Parameter(Mandatory)][string] $ClientSecret, [Parameter(Mandatory)][string] $DirectoryID ) # Convert to SecureString $EncryptedToken = ConvertTo-SecureString -String $ClientSecret -AsPlainText -Force $UserName = -join ($ClientID, '@', $DirectoryID) $EncryptedCredentials = [System.Management.Automation.PSCredential]::new($UserName, $EncryptedToken) $EncryptedCredentials } function ConvertTo-OAuth2Credential { [cmdletBinding()] param( [Parameter(Mandatory)][string] $UserName, [Parameter(Mandatory)][string] $Token ) # Convert to SecureString $EncryptedToken = ConvertTo-SecureString -String $Token -AsPlainText -Force $EncryptedCredentials = [System.Management.Automation.PSCredential]::new($UserName, $EncryptedToken) $EncryptedCredentials } function Disconnect-IMAP { [cmdletBinding()] param( [System.Collections.IDictionary] $Client ) if ($Client.Data) { try { $Client.Data.Disconnect($true) } catch { Write-Warning "Disconnect-IMAP - Unable to authenticate $($_.Exception.Message)" return } } } function Disconnect-POP { [alias('Disconnect-POP3')] [cmdletBinding()] param( [System.Collections.IDictionary] $Client ) if ($Client.Data) { try { $Client.Data.Disconnect($true) } catch { Write-Warning "Disconnect-POP - Unable to authenticate $($_.Exception.Message)" return } } } function Find-DKIMRecord { [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipelineByPropertyName, ValueFromPipeline, Position = 0)][Array] $DomainName, [string] $Selector = 'selector1', [System.Net.IPAddress] $DnsServer, [switch] $AsHashTable, [switch] $AsObject ) process { foreach ($Domain in $DomainName) { if ($Domain -is [string]) { $S = $Selector $D = $Domain } elseif ($Domain -is [System.Collections.IDictionary]) { $S = $Domain.Selector $D = $Domain.DomainName if (-not $S -and -not $D) { Write-Warning 'Find-DKIMRecord - properties DomainName and Selector are required when passing Array of Hashtables' } } $Splat = @{ Name = "$S._domainkey.$D" Type = 'TXT' ErrorAction = 'SilentlyContinue' } if ($DnsServer) { $Splat['Server'] = $DnsServer } $DNSRecord = Resolve-DnsQuery @Splat | Where-Object Text -Match 'DKIM1' if (-not $AsObject) { $MailRecord = [ordered] @{ Name = $D Count = $DNSRecord.Text.Count Selector = "$D`:$S" DKIM = $DNSRecord.Text -join '; ' } } else { $MailRecord = [ordered] @{ Name = $D Count = $DNSRecord.Text.Count Selector = "$D`:$S" DKIM = $DNSRecord.Text -join '; ' } } if ($AsHashTable) { $MailRecord } else { [PSCustomObject] $MailRecord } } } } function Find-DMARCRecord { [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipelineByPropertyName, ValueFromPipeline, Position = 0)][Array] $DomainName, [System.Net.IPAddress] $DnsServer, [switch] $AsHashTable, [switch] $AsObject ) process { foreach ($Domain in $DomainName) { if ($Domain -is [string]) { $D = $Domain } elseif ($Domain -is [System.Collections.IDictionary]) { $D = $Domain.DomainName if (-not $D) { Write-Warning 'Find-DMARCRecord - property DomainName is required when passing Array of Hashtables' } } $Splat = @{ Name = "_dmarc.$D" Type = 'TXT' ErrorAction = 'Stop' } if ($DnsServer) { $Splat['Server'] = $DnsServer } try { $DNSRecord = Resolve-DnsQuery @Splat | Where-Object Text -Match 'DMARC1' if (-not $AsObject) { $MailRecord = [ordered] @{ Name = $D Count = $DNSRecord.Count TimeToLive = $DnsRecord.TimeToLive -join '; ' DMARC = $DnsRecord.Text -join '; ' } } else { $MailRecord = [ordered] @{ Name = $D Count = $DNSRecord.Count TimeToLive = $DnsRecord.TimeToLive DMARC = $DnsRecord.Text } } } catch { $MailRecord = [ordered] @{ Name = $D Count = 0 TimeToLive = '' DMARC = '' } Write-Warning "Find-DMARCRecord - $_" } if ($AsHashTable) { $MailRecord } else { [PSCustomObject] $MailRecord } } } } function Find-MxRecord { [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipelineByPropertyName, ValueFromPipeline, Position = 0)][Array]$DomainName, [System.Net.IPAddress] $DnsServer, [switch] $ResolvePTR, [switch] $AsHashTable, [switch] $Separate, [switch] $AsObject ) process { foreach ($Domain in $DomainName) { if ($Domain -is [string]) { $D = $Domain } elseif ($Domain -is [System.Collections.IDictionary]) { $D = $Domain.DomainName if (-not $D) { Write-Warning 'Find-MxRecord - property DomainName is required when passing Array of Hashtables' } } $Splat = @{ Name = $D Type = 'MX' ErrorAction = 'SilentlyContinue' } if ($DnsServer) { $Splat['Server'] = $DnsServer } $MX = Resolve-DnsQuery @Splat [Array] $MXRecords = foreach ($MXRecord in $MX) { $MailRecord = [ordered] @{ Name = $D Preference = $MXRecord.Preference TimeToLive = $MXRecord.TimeToLive MX = ($MXRecord.Exchange) -replace '.$' } [Array] $IPAddresses = foreach ($Record in $MX.Exchange) { $Splat = @{ Name = $Record Type = 'A' ErrorAction = 'SilentlyContinue' } if ($DnsServer) { $Splat['Server'] = $DnsServer } (Resolve-DnsQuery @Splat) | ForEach-Object { $_.Address.IPAddressToString } } $MailRecord['IPAddress'] = $IPAddresses if ($ResolvePTR) { $MailRecord['PTR'] = foreach ($IP in $IPAddresses) { $Splat = @{ Name = $IP Type = 'PTR' ErrorAction = 'SilentlyContinue' } if ($DnsServer) { $Splat['Server'] = $DnsServer } (Resolve-DnsQuery @Splat) | ForEach-Object { $_.PtrDomainName -replace '.$' } } } $MailRecord } if ($Separate) { foreach ($MXRecord in $MXRecords) { if ($AsHashTable) { $MXRecord } else { [PSCustomObject] $MXRecord } } } else { if (-not $AsObject) { $MXRecord = [ordered] @{ Name = $D Count = $MXRecords.Count Preference = $MXRecords.Preference -join '; ' TimeToLive = $MXRecords.TimeToLive -join '; ' MX = $MXRecords.MX -join '; ' IPAddress = ($MXRecords.IPAddress | Sort-Object -Unique) -join '; ' } if ($ResolvePTR) { $MXRecord['PTR'] = ($MXRecords.PTR | Sort-Object -Unique) -join '; ' } } else { $MXRecord = [ordered] @{ Name = $D Count = $MXRecords.Count Preference = $MXRecords.Preference TimeToLive = $MXRecords.TimeToLive MX = $MXRecords.MX IPAddress = ($MXRecords.IPAddress | Sort-Object -Unique) } if ($ResolvePTR) { $MXRecord['PTR'] = ($MXRecords.PTR | Sort-Object -Unique) } } if ($AsHashTable) { $MXRecord } else { [PSCustomObject] $MXRecord } } } } } function Find-SPFRecord { [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipelineByPropertyName, ValueFromPipeline, Position = 0)][Array]$DomainName, [System.Net.IPAddress] $DnsServer, [switch] $AsHashTable, [switch] $AsObject ) process { foreach ($Domain in $DomainName) { if ($Domain -is [string]) { $D = $Domain } elseif ($Domain -is [System.Collections.IDictionary]) { $D = $Domain.DomainName if (-not $D) { Write-Warning 'Find-MxRecord - property DomainName is required when passing Array of Hashtables' } } $Splat = @{ Name = $D Type = 'txt' ErrorAction = 'Stop' } if ($DnsServer) { $Splat['Server'] = $DnsServer } try { $DNSRecord = Resolve-DnsQuery @Splat | Where-Object Text -Match 'spf1' if (-not $AsObject) { $MailRecord = [ordered] @{ Name = $D Count = $DNSRecord.Count TimeToLive = $DnsRecord.TimeToLive -join '; ' SPF = $DnsRecord.Text -join '; ' } } else { $MailRecord = [ordered] @{ Name = $D Count = $DNSRecord.Count TimeToLive = $DnsRecord.TimeToLive SPF = $DnsRecord.Text } } } catch { $MailRecord = [ordered] @{ Name = $D Count = 0 TimeToLive = '' SPF = '' } Write-Warning "Find-SPFRecord - $_" } if ($AsHashTable) { $MailRecord } else { [PSCustomObject] $MailRecord } } } } function Get-IMAPFolder { [cmdletBinding()] param( [System.Collections.IDictionary] $Client, [MailKit.FolderAccess] $FolderAccess = [MailKit.FolderAccess]::ReadOnly ) if ($Client) { $Folder = $Client.Data.Inbox $null = $Folder.Open($FolderAccess) Write-Verbose "Get-IMAPMessage - Total messages $($Folder.Count), Recent messages $($Folder.Recent)" $Client.Messages = $Folder $Client.Count = $Folder.Count $Client.Recent = $Folder.Recent $Client } else { Write-Verbose 'Get-IMAPMessage - Client not connected?' } } function Get-IMAPMessage { [cmdletBinding()] param( [Parameter()][System.Collections.IDictionary] $Client, [MailKit.FolderAccess] $FolderAccess = [MailKit.FolderAccess]::ReadOnly ) if ($Client) { $Folder = $Client.Data.Inbox $null = $Folder.Open($FolderAccess) Write-Verbose "Get-IMAPMessage - Total messages $($Folder.Count), Recent messages $($Folder.Recent)" $Client.Folder = $Folder } else { Write-Verbose 'Get-IMAPMessage - Client not connected?' } } function Get-MailFolder { [cmdletBinding()] param( [string] $UserPrincipalName, [PSCredential] $Credential ) if ($Credential) { $AuthorizationData = ConvertFrom-GraphCredential -Credential $Credential } else { return } $Authorization = Connect-O365Graph -ApplicationID $AuthorizationData.ClientID -ApplicationKey $AuthorizationData.ClientSecret -TenantDomain $AuthorizationData.DirectoryID -Resource https://graph.microsoft.com Invoke-O365Graph -Headers $Authorization -Uri "/users/$UserPrincipalName/mailFolders" -Method GET } function Get-MailMessage { [cmdletBinding()] param( [string] $UserPrincipalName, [PSCredential] $Credential, [switch] $All, [int] $Limit = 10, [ValidateSet( 'createdDateTime', 'lastModifiedDateTime', 'changeKey', 'categories', 'receivedDateTime', 'sentDateTime', 'hasAttachments', 'internetMessageId', 'subject', 'bodyPreview', 'importance', 'parentFolderId', 'conversationId', 'conversationIndex', 'isDeliveryReceiptRequested', 'isReadReceiptRequested', 'isRead', 'isDraft', 'webLink', 'inferenceClassification', 'body', 'sender', 'from', 'toRecipients', 'ccRecipients', 'bccRecipients', 'replyTo', 'flag') ][string[]] $Property, [string] $Filter ) if ($Credential) { $AuthorizationData = ConvertFrom-GraphCredential -Credential $Credential } else { return } $Authorization = Connect-O365Graph -ApplicationID $AuthorizationData.ClientID -ApplicationKey $AuthorizationData.ClientSecret -TenantDomain $AuthorizationData.DirectoryID -Resource https://graph.microsoft.com $Uri = "/users/$UserPrincipalName/messages" $Addon = '?' if ($Property) { $Poperties = $Property -join ',' $Addon = -join ($Addon, "`$Select=$Poperties") } if ($Filter) { $Addon = -join ($Addon, "&`$filter=$Filter") } #Write-Verbose $Addon #$Addon = [System.Web.HttpUtility]::UrlEncode($Addon) if ($Addon.Length -gt 1) { $Uri = -join ($Uri, $Addon) } Write-Verbose "Get-MailMessage - Executing $Uri" $Uri = [uri]::EscapeUriString($Uri) Write-Verbose "Get-MailMessage - Executing $Uri" if ($All) { Invoke-O365Graph -Headers $Authorization -Uri $Uri -Method GET } else { Invoke-O365Graph -Headers $Authorization -Uri $Uri -Method GET | Select-Object -First $Limit } } function Get-POPMessage { [alias('Get-POP3Message')] [cmdletBinding()] param( [Parameter()][System.Collections.IDictionary] $Client, [int] $Index, [int] $Count = 1, [switch] $All ) if ($Client -and $Client.Data) { if ($All) { $Client.Data.GetMessages($Index, $Count) } else { if ($Index -lt $Client.Data.Count) { $Client.Data.GetMessages($Index, $Count) } else { Write-Warning "Get-POP3Message - Index is out of range. Use index less than $($Client.Data.Count)." } } } else { Write-Warning 'Get-POP3Message - Is POP3 connected?' } <# $Client.Data.GetMessage MimeKit.MimeMessage GetMessage(int index, System.Threading.CancellationToken cancellationToken, MailKit.ITransferProgress progress) MimeKit.MimeMessage IMailSpool.GetMessage(int index, System.Threading.CancellationToken cancellationToken, MailKit.ITransferProgress progress) #> <# $Client.Data.GetMessages System.Collections.Generic.IList[MimeKit.MimeMessage] GetMessages(System.Collections.Generic.IList[int] indexes, System.Threading.CancellationToken cancellationToken, MailKit.ITransferProgress progress) System.Collections.Generic.IList[MimeKit.MimeMessage] GetMessages(int startIndex, int count, System.Threading.CancellationToken cancellationToken, MailKit.ITransferProgress progress) System.Collections.Generic.IList[MimeKit.MimeMessage] IMailSpool.GetMessages(System.Collections.Generic.IList[int] indexes, System.Threading.CancellationTokencancellationToken, MailKit.ITransferProgress progress) System.Collections.Generic.IList[MimeKit.MimeMessage] IMailSpool.GetMessages(int startIndex, int count, System.Threading.CancellationToken cancellationToken, MailKit.ITransferProgress progress) #> } function Resolve-DnsQuery { [cmdletBinding()] param( [alias('Query')][Parameter(Mandatory)][string] $Name, [Parameter(Mandatory)][DnsClient.QueryType] $Type, [switch] $All ) $Lookup = [DnsClient.LookupClient]::new() if ($Type -eq [DnsClient.QueryType]::PTR) { #$Lookup = [DnsClient.LookupClient]::new() $Results = $Lookup.QueryReverseAsync($Name) | Wait-Task $Name = $Results.Answers.DomainName.Original } $Results = $Lookup.Query($Name, $Type) if ($All) { $Results } else { $Results.Answers } } function Save-MailMessage { [cmdletBinding()] param( [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)][PSCustomObject[]] $Message, [string] $Path ) Begin { $ResolvedPath = Convert-Path -LiteralPath $Path } Process { if (-not $ResolvedPath) { return } foreach ($M in $Message) { if ($M) { if ($M.Body -and $M.Content) { Write-Verbose "Processing $($M.changekey)" $RandomFileName = [io.path]::GetRandomFileName() $RandomFileName = [io.path]::ChangeExtension($RandomFileName, 'html') $FilePath = [io.path]::Combine($ResolvedPath, $RandomFileName) try { $M.Body.Content | Out-File -FilePath $FilePath -ErrorAction Stop } catch { Write-Warning "Save-MailMessage - Coultn't save file to $FilePath. Error: $($_.Exception.Message)" } } else { Write-Warning "Save-MailMessage - Message doesn't contain Body property. Did you request it? (eTag: $($M.'@odata.etag')" } } } } End {} } function Save-POPMessage { [alias('Save-POP3Message')] [cmdletBinding()] param( [Parameter()][System.Collections.IDictionary] $Client, [Parameter(Mandatory)][int] $Index, [Parameter(Mandatory)][string] $Path #, # [int] $Count = 1, #[switch] $All ) if ($Client -and $Client.Data) { if ($All) { # $Client.Data.GetMessages($Index, $Count) } else { if ($Index -lt $Client.Data.Count) { $Client.Data.GetMessage($Index).WriteTo($Path) } else { Write-Warning "Save-POP3Message - Index is out of range. Use index less than $($Client.Data.Count)." } } } else { Write-Warning 'Save-POP3Message - Is POP3 connected?' } } function Send-EmailMessage { <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER Server Parameter description .PARAMETER Port Parameter description .PARAMETER From Parameter description .PARAMETER ReplyTo Parameter description .PARAMETER Cc Parameter description .PARAMETER Bcc Parameter description .PARAMETER To Parameter description .PARAMETER Subject Parameter description .PARAMETER Priority Parameter description .PARAMETER Encoding Parameter description .PARAMETER DeliveryNotificationOption Parameter description .PARAMETER DeliveryStatusNotificationType Parameter description .PARAMETER Credential Parameter description .PARAMETER Username Parameter description .PARAMETER Password Parameter description .PARAMETER SecureSocketOptions Parameter description .PARAMETER UseSsl Parameter description .PARAMETER HTML Parameter description .PARAMETER Text Parameter description .PARAMETER Attachment Parameter description .PARAMETER Timeout Parameter description .PARAMETER oAuth2 Parameter description .PARAMETER Graph Parameter description .PARAMETER DoNotSaveToSentItems Parameter description .PARAMETER Email Parameter description .PARAMETER Suppress Parameter description .EXAMPLE An example .NOTES General notes #> [cmdletBinding(DefaultParameterSetName = 'Compatibility', SupportsShouldProcess)] param( [Parameter(ParameterSetName = 'ClearText')] [Parameter(ParameterSetName = 'oAuth')] [Parameter(ParameterSetName = 'Compatibility')] [alias('SmtpServer')][string] $Server, [Parameter(ParameterSetName = 'ClearText')] [Parameter(ParameterSetName = 'oAuth')] [Parameter(ParameterSetName = 'Compatibility')] [int] $Port = 587, [Parameter(ParameterSetName = 'ClearText')] [Parameter(ParameterSetName = 'oAuth')] [Parameter(ParameterSetName = 'Graph')] [Parameter(ParameterSetName = 'Compatibility')] [object] $From, [Parameter(ParameterSetName = 'ClearText')] [Parameter(ParameterSetName = 'oAuth')] [Parameter(ParameterSetName = 'Graph')] [Parameter(ParameterSetName = 'Compatibility')] [string] $ReplyTo, [Parameter(ParameterSetName = 'ClearText')] [Parameter(ParameterSetName = 'oAuth')] [Parameter(ParameterSetName = 'Graph')] [Parameter(ParameterSetName = 'Compatibility')] [string[]] $Cc, [Parameter(ParameterSetName = 'ClearText')] [Parameter(ParameterSetName = 'oAuth')] [Parameter(ParameterSetName = 'Graph')] [Parameter(ParameterSetName = 'Compatibility')] [string[]] $Bcc, [Parameter(ParameterSetName = 'ClearText')] [Parameter(ParameterSetName = 'oAuth')] [Parameter(ParameterSetName = 'Graph')] [Parameter(ParameterSetName = 'Compatibility')] [string[]] $To, [Parameter(ParameterSetName = 'ClearText')] [Parameter(ParameterSetName = 'oAuth')] [Parameter(ParameterSetName = 'Graph')] [Parameter(ParameterSetName = 'Compatibility')] [string] $Subject, [Parameter(ParameterSetName = 'ClearText')] [Parameter(ParameterSetName = 'oAuth')] [Parameter(ParameterSetName = 'Graph')] [Parameter(ParameterSetName = 'Compatibility')] [alias('Importance')][ValidateSet('Low', 'Normal', 'High')][string] $Priority, [Parameter(ParameterSetName = 'ClearText')] [Parameter(ParameterSetName = 'oAuth')] [Parameter(ParameterSetName = 'Compatibility')] [ValidateSet('ASCII', 'BigEndianUnicode', 'Default', 'Unicode', 'UTF32', 'UTF7', 'UTF8')][string] $Encoding = 'Default', [Parameter(ParameterSetName = 'ClearText')] [Parameter(ParameterSetName = 'oAuth')] [Parameter(ParameterSetName = 'Compatibility')] [ValidateSet('None', 'OnSuccess', 'OnFailure', 'Delay', 'Never')][string[]] $DeliveryNotificationOption, [Parameter(ParameterSetName = 'ClearText')] [Parameter(ParameterSetName = 'oAuth')] [Parameter(ParameterSetName = 'Compatibility')] [MailKit.Net.Smtp.DeliveryStatusNotificationType] $DeliveryStatusNotificationType, [Parameter(ParameterSetName = 'oAuth')] [Parameter(ParameterSetName = 'Graph')] [Parameter(ParameterSetName = 'Compatibility')] [pscredential] $Credential, [Parameter(ParameterSetName = 'ClearText')] [string] $Username, [Parameter(ParameterSetName = 'ClearText')] [string] $Password, [Parameter(ParameterSetName = 'ClearText')] [Parameter(ParameterSetName = 'oAuth')] [Parameter(ParameterSetName = 'Compatibility')] [MailKit.Security.SecureSocketOptions] $SecureSocketOptions = [MailKit.Security.SecureSocketOptions]::Auto, [Parameter(ParameterSetName = 'ClearText')] [Parameter(ParameterSetName = 'oAuth')] [Parameter(ParameterSetName = 'Compatibility')] [switch] $UseSsl, [Parameter(ParameterSetName = 'ClearText')] [Parameter(ParameterSetName = 'oAuth')] [Parameter(ParameterSetName = 'Graph')] [Parameter(ParameterSetName = 'Compatibility')] [alias('Body')][string[]] $HTML, [Parameter(ParameterSetName = 'ClearText')] [Parameter(ParameterSetName = 'oAuth')] [Parameter(ParameterSetName = 'Graph')] [Parameter(ParameterSetName = 'Compatibility')] [string[]] $Text, [Parameter(ParameterSetName = 'ClearText')] [Parameter(ParameterSetName = 'oAuth')] [Parameter(ParameterSetName = 'Graph')] [Parameter(ParameterSetName = 'Compatibility')] [alias('Attachments')][string[]] $Attachment, [Parameter(ParameterSetName = 'ClearText')] [Parameter(ParameterSetName = 'oAuth')] [Parameter(ParameterSetName = 'Compatibility')] [int] $Timeout = 12000, [Parameter(ParameterSetName = 'oAuth')] [alias('oAuth')][switch] $oAuth2, [Parameter(ParameterSetName = 'Graph')] [switch] $Graph, [Parameter(ParameterSetName = 'Graph')] [switch] $DoNotSaveToSentItems, # Different feature set [Parameter(ParameterSetName = 'Grouped')] [alias('EmailParameters')][System.Collections.IDictionary] $Email, [Parameter(ParameterSetName = 'ClearText')] [Parameter(ParameterSetName = 'oAuth')] [Parameter(ParameterSetName = 'Compatibility')] [Parameter(ParameterSetName = 'Graph')] [Parameter(ParameterSetName = 'Grouped')] [switch] $Suppress ) if ($Email) { # Following code makes sure both formats are accepted. if ($Email.EmailTo) { $EmailParameters = $Email.Clone() } else { $EmailParameters = @{ EmailFrom = $Email.From EmailTo = $Email.To EmailCC = $Email.CC EmailBCC = $Email.BCC EmailReplyTo = $Email.ReplyTo EmailServer = $Email.Server EmailServerPassword = $Email.Password EmailServerPasswordAsSecure = $Email.PasswordAsSecure EmailServerPasswordFromFile = $Email.PasswordFromFile EmailServerPort = $Email.Port EmailServerLogin = $Email.Login EmailServerEnableSSL = $Email.EnableSsl EmailEncoding = $Email.Encoding EmailEncodingSubject = $Email.EncodingSubject EmailEncodingBody = $Email.EncodingBody EmailSubject = $Email.Subject EmailPriority = $Email.Priority EmailDeliveryNotifications = $Email.DeliveryNotifications EmailUseDefaultCredentials = $Email.UseDefaultCredentials } } $From = $EmailParameters.EmailFrom $To = $EmailParameters.EmailTo $Cc = $EmailParameters.EmailCC $Bcc = $EmailParameters.EmailBCC $ReplyTo = $EmailParameters.EmailReplyTo $Server = $EmailParameters.EmailServer $Password = $EmailParameters.EmailServerPassword # $EmailServerPasswordAsSecure = $EmailParameters.EmailServerPasswordAsSecure # $EmailServerPasswordFromFile = $EmailParameters.EmailServerPasswordFromFile $Port = $EmailParameters.EmailServerPort $Username = $EmailParameters.EmailServerLogin #$UseSsl = $EmailParameters.EmailServerEnableSSL $Encoding = $EmailParameters.EmailEncoding #$EncodingSubject = $EmailParameters.EmailEncodingSubject $Encoding = $EmailParameters.EmailEncodingBody $Subject = $EmailParameters.EmailSubject $Priority = $EmailParameters.EmailPriority $DeliveryNotificationOption = $EmailParameters.EmailDeliveryNotifications #$EmailUseDefaultCredentials = $EmailParameters.EmailUseDefaultCredentials } else { if ($null -eq $To -and $null -eq $Bcc -and $null -eq $Cc) { Write-Warning 'Send-EmailMessage - At least one To, CC or BCC is required.' return } } # lets define credentials early on, because if it's Graph we use different way to send emails if ($Credential) { if ($oAuth2.IsPresent) { $Authorization = ConvertFrom-OAuth2Credential -Credential $Credential $SaslMechanismOAuth2 = [MailKit.Security.SaslMechanismOAuth2]::new($Authorization.UserName, $Authorization.Token) } elseif ($Graph.IsPresent) { $sendGraphMailMessageSplat = @{ From = $From To = $To Cc = $CC Bcc = $Bcc Subject = $Subject HTML = $HTML Text = $Text Attachment = $Attachment Credential = $Credential Priority = $Priority ReplyTo = $ReplyTo DoNotSaveToSentItems = $DoNotSaveToSentItems } Remove-EmptyValue -Hashtable $sendGraphMailMessageSplat return Send-GraphMailMessage @sendGraphMailMessageSplat } else { $SmtpCredentials = $Credential } } elseif ($Username -and $Password) { #void Authenticate(string userName, string password, System.Threading.CancellationToken cancellationToken) } $Message = [MimeKit.MimeMessage]::new() # Doing translation for compatibility with Send-MailMessage if ($Priority -eq 'High') { $Message.Priority = [MimeKit.MessagePriority]::Urgent } elseif ($Priority -eq 'Low') { $Message.Priority = [MimeKit.MessagePriority]::NonUrgent } else { $Message.Priority = [MimeKit.MessagePriority]::Normal } [MimeKit.InternetAddress] $SmtpFrom = ConvertTo-MailboxAddress -MailboxAddress $From $Message.From.Add($SmtpFrom) if ($To) { [MimeKit.InternetAddress[]] $SmtpTo = ConvertTo-MailboxAddress -MailboxAddress $To $Message.To.AddRange($SmtpTo) } if ($Cc) { [MimeKit.InternetAddress[]] $SmtpCC = ConvertTo-MailboxAddress -MailboxAddress $Cc $Message.Cc.AddRange($SmtpCC) } if ($Bcc) { [MimeKit.InternetAddress[]] $SmtpBcc = ConvertTo-MailboxAddress -MailboxAddress $Bcc $Message.Bcc.AddRange($SmtpBcc) } if ($ReplyTo) { [MimeKit.InternetAddress] $SmtpReplyTo = ConvertTo-MailboxAddress -MailboxAddress $ReplyTo $Message.ReplyTo.Add($SmtpReplyTo) } $MailSentTo = -join ($To -join ',', $CC -join ', ', $Bcc -join ', ') if ($Subject) { $Message.Subject = $Subject } [System.Text.Encoding] $SmtpEncoding = [System.Text.Encoding]::$Encoding $BodyBuilder = [MimeKit.BodyBuilder]::new() if ($HTML) { $BodyBuilder.HtmlBody = $HTML } if ($Text) { $BodyBuilder.TextBody = $Text } if ($Attachment) { foreach ($A in $Attachment) { $null = $BodyBuilder.Attachments.Add($A) } } $Message.Body = $BodyBuilder.ToMessageBody() ### SMTP Part Below $SmtpClient = [MySmtpClient]::new() if ($DeliveryNotificationOption) { # This requires custom class MySmtpClient $SmtpClient.DeliveryNotificationOption = $DeliveryNotificationOption } if ($DeliveryStatusNotificationType) { $SmtpClient.DeliveryStatusNotificationType = $DeliveryStatusNotificationType } if ($UseSsl) { $SmtpClient.Connect($Server, $Port, [MailKit.Security.SecureSocketOptions]::StartTls) } else { $SmtpClient.Connect($Server, $Port, $SecureSocketOptions) } if ($Credential) { if ($oAuth2.IsPresent) { $SmtpClient.Authenticate($SaslMechanismOAuth2) } elseif ($Graph.IsPresent) { # This is not going to happen is graph is used } else { try { $SmtpClient.Authenticate($SmtpEncoding, $SmtpCredentials, [System.Threading.CancellationToken]::None) } catch { if ($PSBoundParameters.ErrorAction -eq 'Stop') { Write-Error $_ return } else { Write-Warning "Send-EmailMessage - Error: $($_.Exception.Message)" if (-not $Suppress) { return [PSCustomObject] @{ Status = $False Error = $($_.Exception.Message) SentTo = $MailSentTo } } } } } } elseif ($UserName -and $Password) { try { $SmtpClient.Authenticate($UserName, $Password, [System.Threading.CancellationToken]::None) } catch { if ($PSBoundParameters.ErrorAction -eq 'Stop') { Write-Error $_ return } else { Write-Warning "Send-EmailMessage - Error: $($_.Exception.Message)" if (-not $Suppress) { return [PSCustomObject] @{ Status = $False Error = $($_.Exception.Message) SentTo = $MailSentTo } } } } } $SmtpClient.Timeout = $Timeout try { if ($PSCmdlet.ShouldProcess("$MailSentTo", 'Send-EmailMessage')) { $SmtpClient.Send($Message) if (-not $Suppress) { [PSCustomObject] @{ Status = $True Error = '' SentTo = $MailSentTo } } } } catch { if ($PSBoundParameters.ErrorAction -eq 'Stop') { Write-Error $_ return } else { Write-Warning "Send-EmailMessage - Error: $($_.Exception.Message)" } if (-not $Suppress) { [PSCustomObject] @{ Status = $False Error = $($_.Exception.Message) SentTo = $MailSentTo } } } $SmtpClient.Disconnect($true) } function Test-EmailAddress { [cmdletBinding()] param( [Parameter(Mandatory, ValueFromPipelineByPropertyName, ValueFromPipeline, Position = 0)][string[]] $EmailAddress ) process { foreach ($Email in $EmailAddress) { [PSCustomObject] @{ EmailAddress = $Email IsValid = [EmailValidation.EmailValidator]::Validate($Email) } } } } Export-ModuleMember -Function @('Connect-IMAP', 'Connect-oAuthGoogle', 'Connect-oAuthO365', 'Connect-POP', 'ConvertTo-GraphCredential', 'ConvertTo-OAuth2Credential', 'Disconnect-IMAP', 'Disconnect-POP', 'Find-DKIMRecord', 'Find-DMARCRecord', 'Find-MxRecord', 'Find-SPFRecord', 'Get-IMAPFolder', 'Get-IMAPMessage', 'Get-MailFolder', 'Get-MailMessage', 'Get-POPMessage', 'Resolve-DnsQuery', 'Save-MailMessage', 'Save-POPMessage', 'Send-EmailMessage', 'Test-EmailAddress') -Alias @('Connect-POP3', 'Disconnect-POP3', 'Get-POP3Message', 'Save-POP3Message') # SIG # Begin signature block # MIIgQAYJKoZIhvcNAQcCoIIgMTCCIC0CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUHDE/QqFytFTME/zvsDIZYLnl # afGgghtvMIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0B # AQUFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD # VQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVk # IElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQsw # CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu # ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg # Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg # +XESpa7cJpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lT # XDGEKvYPmDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5 # a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g # 0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1 # roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf # GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0G # A1UdDgQWBBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLL # gjEtUYunpyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3 # cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmr # EthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+ # fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5Q # Z7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu # 838fYxAe+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw # 8jCCBTAwggQYoAMCAQICEAQJGBtf1btmdVNDtW+VUAgwDQYJKoZIhvcNAQELBQAw # ZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ # d3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBS # b290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowcjELMAkGA1UE # BhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2lj # ZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUg # U2lnbmluZyBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPjTsxx/ # DhGvZ3cH0wsxSRnP0PtFmbE620T1f+Wondsy13Hqdp0FLreP+pJDwKX5idQ3Gde2 # qvCchqXYJawOeSg6funRZ9PG+yknx9N7I5TkkSOWkHeC+aGEI2YSVDNQdLEoJrsk # acLCUvIUZ4qJRdQtoaPpiCwgla4cSocI3wz14k1gGL6qxLKucDFmM3E+rHCiq85/ # 6XzLkqHlOzEcz+ryCuRXu0q16XTmK/5sy350OTYNkO/ktU6kqepqCquE86xnTrXE # 94zRICUj6whkPlKWwfIPEvTFjg/BougsUfdzvL2FsWKDc0GCB+Q4i2pzINAPZHM8 # np+mM6n9Gd8lk9ECAwEAAaOCAc0wggHJMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYD # VR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHkGCCsGAQUFBwEBBG0w # azAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsGAQUF # BzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVk # SURSb290Q0EuY3J0MIGBBgNVHR8EejB4MDqgOKA2hjRodHRwOi8vY3JsNC5kaWdp # Y2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMDqgOKA2hjRodHRw # Oi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3Js # ME8GA1UdIARIMEYwOAYKYIZIAYb9bAACBDAqMCgGCCsGAQUFBwIBFhxodHRwczov # L3d3dy5kaWdpY2VydC5jb20vQ1BTMAoGCGCGSAGG/WwDMB0GA1UdDgQWBBRaxLl7 # KgqjpepxA8Bg+S32ZXUOWDAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823I # DzANBgkqhkiG9w0BAQsFAAOCAQEAPuwNWiSz8yLRFcgsfCUpdqgdXRwtOhrE7zBh # 134LYP3DPQ/Er4v97yrfIFU3sOH20ZJ1D1G0bqWOWuJeJIFOEKTuP3GOYw4TS63X # X0R58zYUBor3nEZOXP+QsRsHDpEV+7qvtVHCjSSuJMbHJyqhKSgaOnEoAjwukaPA # JRHinBRHoXpoaK+bp1wgXNlxsQyPu6j4xRJon89Ay0BEpRPw5mQMJQhCMrI2iiQC # /i9yfhzXSUWW6Fkd6fp0ZGuy62ZD2rOwjNXpDd32ASDOmTFjPQgaGLOBm0/GkxAG # /AeB+ova+YJJ92JuoVP6EpQYhS6SkepobEQysmah5xikmmRR7zCCBT0wggQloAMC # AQICEATV3B9I6snYUgC6zZqbKqcwDQYJKoZIhvcNAQELBQAwcjELMAkGA1UEBhMC # VVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0 # LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUgU2ln # bmluZyBDQTAeFw0yMDA2MjYwMDAwMDBaFw0yMzA3MDcxMjAwMDBaMHoxCzAJBgNV # BAYTAlBMMRIwEAYDVQQIDAnFmmzEhXNraWUxETAPBgNVBAcTCEthdG93aWNlMSEw # HwYDVQQKDBhQcnplbXlzxYJhdyBLxYJ5cyBFVk9URUMxITAfBgNVBAMMGFByemVt # eXPFgmF3IEvFgnlzIEVWT1RFQzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC # ggEBAL+ygd4sga4ZC1G2xXvasYSijwWKgwapZ69wLaWaZZIlY6YvXTGQnIUnk+Tg # 7EoT7mQiMSaeSPOrn/Im6N74tkvRfQJXxY1cnt3U8//U5grhh/CULdd6M3/Z4h3n # MCq7LQ1YVaa4MYub9F8WOdXO84DANoNVG/t7YotL4vzqZil3S9pHjaidp3kOXGJc # vxrCPAkRFBKvUmYo23QPFa0Rd0qA3bFhn97WWczup1p90y2CkOf28OVOOObv1fNE # EqMpLMx0Yr04/h+LPAAYn6K4YtIu+m3gOhGuNc3B+MybgKePAeFIY4EQzbqvCMy1 # iuHZb6q6ggRyqrJ6xegZga7/gV0CAwEAAaOCAcUwggHBMB8GA1UdIwQYMBaAFFrE # uXsqCqOl6nEDwGD5LfZldQ5YMB0GA1UdDgQWBBQYsTUn6BxQICZOCZA0CxS0TZSU # ZjAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwdwYDVR0fBHAw # bjA1oDOgMYYvaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC1j # cy1nMS5jcmwwNaAzoDGGL2h0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zaGEyLWFz # c3VyZWQtY3MtZzEuY3JsMEwGA1UdIARFMEMwNwYJYIZIAYb9bAMBMCowKAYIKwYB # BQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCAYGZ4EMAQQBMIGE # BggrBgEFBQcBAQR4MHYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0 # LmNvbTBOBggrBgEFBQcwAoZCaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0Rp # Z2lDZXJ0U0hBMkFzc3VyZWRJRENvZGVTaWduaW5nQ0EuY3J0MAwGA1UdEwEB/wQC # MAAwDQYJKoZIhvcNAQELBQADggEBAJq9bM+JbCwEYuMBtXoNAfH1SRaMLXnLe0py # VK6el0Z1BtPxiNcF4iyHqMNVD4iOrgzLEVzx1Bf/sYycPEnyG8Gr2tnl7u1KGSjY # enX4LIXCZqNEDQCeTyMstNv931421ERByDa0wrz1Wz5lepMeCqXeyiawqOxA9fB/ # 106liR12vL2tzGC62yXrV6WhD6W+s5PpfEY/chuIwVUYXp1AVFI9wi2lg0gaTgP/ # rMfP1wfVvaKWH2Bm/tU5mwpIVIO0wd4A+qOhEia3vn3J2Zz1QDxEprLcLE9e3Gmd # G5+8xEypTR23NavhJvZMgY2kEXBEKEEDaXs0LoPbn6hMcepR2A4wggZqMIIFUqAD # AgECAhADAZoCOv9YsWvW1ermF/BmMA0GCSqGSIb3DQEBBQUAMGIxCzAJBgNVBAYT # AlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2Vy # dC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IEFzc3VyZWQgSUQgQ0EtMTAeFw0xNDEw # MjIwMDAwMDBaFw0yNDEwMjIwMDAwMDBaMEcxCzAJBgNVBAYTAlVTMREwDwYDVQQK # EwhEaWdpQ2VydDElMCMGA1UEAxMcRGlnaUNlcnQgVGltZXN0YW1wIFJlc3BvbmRl # cjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKNkXfx8s+CCNeDg9sYq # 5kl1O8xu4FOpnx9kWeZ8a39rjJ1V+JLjntVaY1sCSVDZg85vZu7dy4XpX6X51Id0 # iEQ7Gcnl9ZGfxhQ5rCTqqEsskYnMXij0ZLZQt/USs3OWCmejvmGfrvP9Enh1DqZb # FP1FI46GRFV9GIYFjFWHeUhG98oOjafeTl/iqLYtWQJhiGFyGGi5uHzu5uc0LzF3 # gTAfuzYBje8n4/ea8EwxZI3j6/oZh6h+z+yMDDZbesF6uHjHyQYuRhDIjegEYNu8 # c3T6Ttj+qkDxss5wRoPp2kChWTrZFQlXmVYwk/PJYczQCMxr7GJCkawCwO+k8IkR # j3cCAwEAAaOCAzUwggMxMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBYG # A1UdJQEB/wQMMAoGCCsGAQUFBwMIMIIBvwYDVR0gBIIBtjCCAbIwggGhBglghkgB # hv1sBwEwggGSMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20v # Q1BTMIIBZAYIKwYBBQUHAgIwggFWHoIBUgBBAG4AeQAgAHUAcwBlACAAbwBmACAA # dABoAGkAcwAgAEMAZQByAHQAaQBmAGkAYwBhAHQAZQAgAGMAbwBuAHMAdABpAHQA # dQB0AGUAcwAgAGEAYwBjAGUAcAB0AGEAbgBjAGUAIABvAGYAIAB0AGgAZQAgAEQA # aQBnAGkAQwBlAHIAdAAgAEMAUAAvAEMAUABTACAAYQBuAGQAIAB0AGgAZQAgAFIA # ZQBsAHkAaQBuAGcAIABQAGEAcgB0AHkAIABBAGcAcgBlAGUAbQBlAG4AdAAgAHcA # aABpAGMAaAAgAGwAaQBtAGkAdAAgAGwAaQBhAGIAaQBsAGkAdAB5ACAAYQBuAGQA # IABhAHIAZQAgAGkAbgBjAG8AcgBwAG8AcgBhAHQAZQBkACAAaABlAHIAZQBpAG4A # IABiAHkAIAByAGUAZgBlAHIAZQBuAGMAZQAuMAsGCWCGSAGG/WwDFTAfBgNVHSME # GDAWgBQVABIrE5iymQftHt+ivlcNK2cCzTAdBgNVHQ4EFgQUYVpNJLZJMp1KKnka # g0v0HonByn0wfQYDVR0fBHYwdDA4oDagNIYyaHR0cDovL2NybDMuZGlnaWNlcnQu # Y29tL0RpZ2lDZXJ0QXNzdXJlZElEQ0EtMS5jcmwwOKA2oDSGMmh0dHA6Ly9jcmw0 # LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRENBLTEuY3JsMHcGCCsGAQUF # BwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEG # CCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRB # c3N1cmVkSURDQS0xLmNydDANBgkqhkiG9w0BAQUFAAOCAQEAnSV+GzNNsiaBXJuG # ziMgD4CH5Yj//7HUaiwx7ToXGXEXzakbvFoWOQCd42yE5FpA+94GAYw3+puxnSR+ # /iCkV61bt5qwYCbqaVchXTQvH3Gwg5QZBWs1kBCge5fH9j/n4hFBpr1i2fAnPTgd # KG86Ugnw7HBi02JLsOBzppLA044x2C/jbRcTBu7kA7YUq/OPQ6dxnSHdFMoVXZJB # 2vkPgdGZdA0mxA5/G7X1oPHGdwYoFenYk+VVFvC7Cqsc21xIJ2bIo4sKHOWV2q7E # LlmgYd3a822iYemKC23sEhi991VUQAOSK2vCUcIKSK+w1G7g9BQKOhvjjz3Kr2qN # e9zYRDCCBs0wggW1oAMCAQICEAb9+QOWA63qAArrPye7uhswDQYJKoZIhvcNAQEF # BQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UE # CxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJ # RCBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTIxMTExMDAwMDAwMFowYjELMAkG # A1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRp # Z2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgQXNzdXJlZCBJRCBDQS0xMIIB # IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6IItmfnKwkKVpYBzQHDSnlZU # XKnE0kEGj8kz/E1FkVyBn+0snPgWWd+etSQVwpi5tHdJ3InECtqvy15r7a2wcTHr # zzpADEZNk+yLejYIA6sMNP4YSYL+x8cxSIB8HqIPkg5QycaH6zY/2DDD/6b3+6LN # b3Mj/qxWBZDwMiEWicZwiPkFl32jx0PdAug7Pe2xQaPtP77blUjE7h6z8rwMK5nQ # xl0SQoHhg26Ccz8mSxSQrllmCsSNvtLOBq6thG9IhJtPQLnxTPKvmPv2zkBdXPao # 8S+v7Iki8msYZbHBc63X8djPHgp0XEK4aH631XcKJ1Z8D2KkPzIUYJX9BwSiCQID # AQABo4IDejCCA3YwDgYDVR0PAQH/BAQDAgGGMDsGA1UdJQQ0MDIGCCsGAQUFBwMB # BggrBgEFBQcDAgYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCDCCAdIGA1Ud # IASCAckwggHFMIIBtAYKYIZIAYb9bAABBDCCAaQwOgYIKwYBBQUHAgEWLmh0dHA6 # Ly93d3cuZGlnaWNlcnQuY29tL3NzbC1jcHMtcmVwb3NpdG9yeS5odG0wggFkBggr # BgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0AGgAaQBzACAA # QwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1AHQAZQBzACAA # YQBjAGMAZQBwAHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABpAGcAaQBDAGUA # cgB0ACAAQwBQAC8AQwBQAFMAIABhAG4AZAAgAHQAaABlACAAUgBlAGwAeQBpAG4A # ZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBoAGkAYwBoACAA # bABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAgAGEAcgBlACAA # aQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAgAGIAeQAgAHIA # ZQBmAGUAcgBlAG4AYwBlAC4wCwYJYIZIAYb9bAMVMBIGA1UdEwEB/wQIMAYBAf8C # AQAweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp # Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwgYEGA1UdHwR6MHgwOqA4oDaG # NGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RD # QS5jcmwwOqA4oDaGNGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFz # c3VyZWRJRFJvb3RDQS5jcmwwHQYDVR0OBBYEFBUAEisTmLKZB+0e36K+Vw0rZwLN # MB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA0GCSqGSIb3DQEBBQUA # A4IBAQBGUD7Jtygkpzgdtlspr1LPUukxR6tWXHvVDQtBs+/sdR90OPKyXGGinJXD # UOSCuSPRujqGcq04eKx1XRcXNHJHhZRW0eu7NoR3zCSl8wQZVann4+erYs37iy2Q # wsDStZS9Xk+xBdIOPRqpFFumhjFiqKgz5Js5p8T1zh14dpQlc+Qqq8+cdkvtX8JL # FuRLcEwAiR78xXm8TBJX/l/hHrwCXaj++wc4Tw3GXZG5D2dFzdaD7eeSDY2xaYxP # +1ngIw/Sqq4AfO6cQg7PkdcntxbuD8O9fAqg7iwIVYUiuOsYGk38KiGtSTGDR5V3 # cdyxG0tLHBCcdxTBnU8vWpUIKRAmMYIEOzCCBDcCAQEwgYYwcjELMAkGA1UEBhMC # VVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0 # LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUgU2ln # bmluZyBDQQIQBNXcH0jqydhSALrNmpsqpzAJBgUrDgMCGgUAoHgwGAYKKwYBBAGC # NwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor # BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQxFgQUnUDJpdNT # dtEmgx4+eRpRUHY6+sYwDQYJKoZIhvcNAQEBBQAEggEAqiGVvLb05JV05wziFIqx # ajLzYjQ9JIhD7FoM9K2UXN/u3KpZQMfGITq0ImmLgCmlvqWUahrWZIyGJmDkc4Fv # eHuDn40M1G6TQkm0/vohHB8hQ2Li1ZHtQsiXGQvEM7knItGDDFgiPyxfQI0nncAC # sb85q3Z3vVW0s5/25G7TrHzFx5lsZXbJsP9qhMLxbRwiVC7ka4kn8Uf8dq9k8vOq # ybEZaZI9Wx3M6EVQevYdbaAE3Yrppp7CpshyXv9T0YR1DWw0mpB/1lCncZXES/+7 # 1Tmw7rFgcK8bANbz/QYABdU5AJ+yXHgaGFWTBddXQax9Rn9lulpn8Wj06O6HdnJM # FKGCAg8wggILBgkqhkiG9w0BCQYxggH8MIIB+AIBATB2MGIxCzAJBgNVBAYTAlVT # MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j # b20xITAfBgNVBAMTGERpZ2lDZXJ0IEFzc3VyZWQgSUQgQ0EtMQIQAwGaAjr/WLFr # 1tXq5hfwZjAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAc # BgkqhkiG9w0BCQUxDxcNMjAwODA2MDgyNTA4WjAjBgkqhkiG9w0BCQQxFgQU/VGt # 0N8OreYKBcfJvt25M0BtGb0wDQYJKoZIhvcNAQEBBQAEggEAFmbhsckrVBNOlAkz # VRkhOIumU8Ij+4ebQ+yLDqLAtiywRaBmgSiOlO6uaoa5EakFDA5zXm4jx30WDht2 # RjKhwuoC5OweqOn0Y5Tf1/gbi5n9/Cz1XDzOoZV/WTWS+0kl4Kie7FRman4Jo9Oa # NaOUkPmVhkS3ObuEjty12pPGaWQTTjrqIuT4nlzTNbdv6aT6DefWDCj7QwB+q5TJ # gPJJCKuLcX9bICz4DZ+9dGuiexS2R4qX7FRQpIoi/1dnpz9NzI7y21bVs9XVKMqC # +hpoE+anPkg5G8yZ01ZhG3vFhyoHrOEh3PAZ6lzX8Cjs0zsdRgu042ZJWVmYaUSv # ckxXqg== # SIG # End signature block |