ModernMailTools.psm1
function Register-ModernMailMessageEntraIDApp { <# .SYNOPSIS Registers an Entra ID App. .DESCRIPTION Registers an app in Entra ID, assigning necessary permissions and outputting relevant information. .PARAMETER ApplicationName The name of the application to register. .EXAMPLE Register-ModernMailMessageEntraIDApp -ApplicationName "M365 ModernMailTools" .INPUTS None .OUTPUTS .NOTES #> param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$ApplicationName #[string]$Tenant, #[string]$TenantID, #[string]$TenantName ) # Reference: # https://pnp.github.io/powershell/cmdlets/Register-PnPAzureADApp.html #Import-Module Microsoft.Graph.Authentication #Import-Module Microsoft.Entra.Authentication #$ApplicationName = "Test M365 ModernMailTools" ##$ApplicationName = "Test PS ModernMailTools" ##$ApplicationName = "Test M365 SendMail" ##$ApplicationName = "Test PS SendMail" #Connect-MgGraph Connect-Entra -Scopes 'Application.ReadWrite.All' -NoWelcome #-TenantId #$application = Get-EntraApplication -Filter "DisplayName eq 'My new application'" $application = Get-EntraApplication -Filter "DisplayName eq '$ApplicationName'" Write-Verbose "VAR: $($application.DisplayName)" -Verbose if(!$application) { #$application = New-EntraApplication -DisplayName 'My new application' $application = New-EntraApplication -DisplayName $ApplicationName #New-EntraServicePrincipal -AppId $myApp.AppId -DisplayName 'My new service principal' Write-Verbose "APP: $($application.DisplayName)" -Verbose } $requiredResourceAccess = @( @{resourceAppId = '00000003-0000-0000-c000-000000000000' # Microsoft Graph resourceAccess = @( #@{ # id = 'c79f8feb-a9db-4090-85f9-90d820caa0eb' # Application.Read.All (Delegate) - Read applications # type = 'Scope' #} #@{ # id = '9a5d68dd-52b0-4cc2-bd40-abcf44ac3a30' # Application.Read.All (Application) - Read all applications # type = 'Role' #} @{ id = 'e383f46e-2787-4529-855e-0e479a3ffac0' # Mail.Send (Delegate) - Send mail as a user type = 'Scope' # Role - AADSTS650051: Claim is invalid: Mail.Send does not exist in client application's RequiredResourceAccess. } #@{ # id = 'a367ab51-6b49-43bf-a716-a1fb06d2a174' # Mail.Send.Shared (Delegate) - Send mail on behalf of others # type = 'Scope' #} @{ id = 'b633e1c5-b582-4048-a93e-9f11b44c7e96' # Mail.Send (Application) - Send mail as any user type = 'Role' } @{ id = '258f6531-6087-4cc4-bb90-092c5fb3ed3f' # SMTP.Send (Delegate) - Send emails from mailboxes using SMTP AUTH type = 'Scope' # Role - AADSTS650051: Claim is invalid: SMTP.Send does not exist on resource application 00000003-0000-0000-c000-000000000000. } ) } @{resourceAppId = '00000002-0000-0ff1-ce00-000000000000' # Office 365 Exchange Online resourceAccess = @( @{ id = 'b633e1c5-b582-4048-a93e-9f11b44c7e96' # Mail.Send (Application) - Send mail as any user type = 'Role' } ) } ) $applicationEdited = Set-EntraApplication -ApplicationId $application.Id -RequiredResourceAccess $requiredResourceAccess # AADSTS500113: No reply address is registered for the application. #Set-EntraApplication -ApplicationId $application.Id -ReplyUrls "" # N/A # https://login.microsoftonline.com/common/oauth2/nativeclient + http://localhost # Ensure you have a 'Mobile and desktop applications' platform with redirect to 'http://localhost' configured (and not a 'Web' Platform). # Grant-EntraAdminConsent -AppId "your-application-id" # N/A # https://login.microsoftonline.com/{organization}/adminconsent?client_id={client-id} if($applicationEdited){ $context = Get-EntraContext $clientId = $application.AppId $tenantId = $context.TenantId $consentUrl = "https://login.microsoftonline.com/$tenantId/adminconsent?client_id=$clientId" #Write-host -ForegroundColor Gray "Opening: $consentUrl" Write-Output "Opening: $consentUrl" #Write-Information "Opening: $consentUrl" #Write-Verbose "Opening: $consentUrl" Start-Process $consentUrl } Write-Verbose "EDIT: $($application.DisplayName)" -Verbose } function Send-ModernMailMessage { <# .SYNOPSIS Sends an email message. .DESCRIPTION The Send-ModernMailMessage cmdlet sends an email message from within PowerShell. .EXAMPLE Send-ModernMailMessage -From "user01@fabrikam.com" -To "user02@fabrikam.com" -Subject "Test mail" Send-ModernMailMessage -From "user01@fabrikam.com" -To "user02@fabrikam.com" -Subject "Test mail" -SmtpServer smtp.contoso.com -UseSsl -Port 587 .INPUTS None .OUTPUTS .NOTES Use "Enable-MailMessageAlias" to enable the command "Send-MailMessage". #> param ( [Parameter(Position = 0)] # The path and file names of files to be attached to the email message. [alias("Attachments")] [String[]]$Attachment, [Parameter(Position = 1)] # Email addresses that receive a copy of the mail but are not listed as recipients of the message. #[Array] $Bcc, [String[]]$Bcc, [Parameter(Position = 2)] # The body (content) of the email message. [alias("Message")] #[string[]] $Text, [String]$Body, [Parameter(Position = 3)] # Indicates that the value of the Body parameter contains HTML. #[alias("Body")] #[string[]] $HTML, [Switch]$BodyAsHtml, [Parameter(Position = 4)] # The encoding used for the body and subject. # Explicitly reference System.Text.Encoding [System.Text.Encoding]$Encoding, [Parameter(Position = 5)] # Email addresses to which a carbon copy (CC) of the email message is sent. #[Array] $Cc, [String[]]$Cc, [Parameter(Position = 6)] # Delivery notifications (if accepted by the recipient) [alias("Dno")] #[System.Net.Mail.DeliveryNotificationOptions]$DeliveryNotificationOption, # Explicitly reference System.Net.Mail [ValidateSet('None', 'OnSuccess', 'OnFailure', 'Delay', 'Never')] # Delivery notification options with validation [String[]]$DeliveryNotificationOption, [Parameter(Position = 7, Mandatory = $true)] # The address from which the mail is sent. #[Parameter(Mandatory = $true)] #[object] $From, [alias("UserId")] [ValidateNotNullOrEmpty()] [String]$From, [Parameter(Position = 8)] # Deprecated?? [String]$SmtpServer, [Parameter(Position = 9)] # The priority of the email message. #[System.Net.Mail.MailPriority]$Priority, # Explicitly reference System.Net.Mail.MailPriority [alias('Importance')] [ValidateSet('Low', 'Normal', 'High')] [string] $Priority, [Parameter(Position = 10)] # Specifies additional email addresses (other than the From address) to use to reply to this message #[string] $ReplyTo, [String[]]$ReplyTo, [Parameter(Position = 11)] # The subject of the email message. #[string] $Subject, [String]$Subject, [Parameter(Position = 12, Mandatory = $true)] # The addresses to which the mail is sent #[Array] $To, #[Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String[]]$To, [Parameter(Position = 13)] # Deprecated? [PSCredential]$Credential, [Parameter(Position = 14)] # Deprecated? [Switch]$UseSsl, [Parameter(Position = 15, Mandatory = $false)] # Deprecated? #[Parameter(Position = 15)] #[Parameter(Mandatory = $false)] [Int32]$Port, # Indicates whether to save the message in Sent Items. [Switch]$SaveToSentItems, # Indicates whether a read receipt is requested for the message. #ToDevelop:IsReadReceiptRequested [Switch]$RequestReadReceipt, # Indicates whether a delivery receipt is requested for the message. #ToDevelop: IsDeliveryReceiptRequested [Switch]$RequestDeliveryReceipt #ToDevelop: Message #ToDevelop: BodyParameter ) <# Reference: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/send-mailmessage?view=powershell-7.5 https://ss64.com/ps/send-mailmessage.html https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.users.actions/send-mgusermail?view=graph-powershell-1.0 Send-MailMessage -From "User01 <user01@fabrikam.com>"" -To "User02 <user02@fabrikam.com>" -Subject "Test mail" Send-MailMessage -To "User01 <user01@contoso.com>" -From "User02 <user02@contoso.com>" -Subject "Test mail" -SmtpServer smtp.contoso.com -UseSsl -Port 587 #> # Deprecated values if ($SmtpServer) { Write-Verbose "Deprecated: $($SmtpServer)" } if ($Credential) { Write-Verbose "Deprecated: $($Credential)" } if ($UseSsl) { Write-Verbose "Deprecated: $($UseSsl)" } if ($Port) { Write-Verbose "Deprecated: $($Port)" } # Development values #if ($Attachments) { Write-Verbose "Development: $($Attachments)" } if ($Bcc) { Write-Verbose "Development: $($Bcc)" } if ($Encoding) { Write-Verbose "Development: $($Encoding)" } if ($Cc) { Write-Verbose "Development: $($Cc)" } if ($DeliveryNotificationOption) { Write-Verbose "Development: $($DeliveryNotificationOption)" } # DNO if ($Priority) { Write-Verbose "Development: $($Priority)" } if ($ReplyTo) { Write-Verbose "Development: $($ReplyTo)" } if ($SaveToSentItems) { Write-Verbose "Development: $($SaveToSentItems)" } if ($RequestReadReceipt) { Write-Verbose "Development: $($RequestReadReceipt)" } # DNO if ($RequestDeliveryReceipt) { Write-Verbose "Development: $($RequestDeliveryReceipt)" } # DNO # -- Authentication #Get-MgContext #Get-EntraContext if ($clientId -and $tenantId -and $certificateThumbprint) { # Graph - Application Connect-MgGraph ` -ClientId $clientId ` -TenantId $tenantId ` -CertificateThumbprint $certificateThumbprint } else { # Graph - Delegated Connect-MgGraph -Scopes "Mail.Send" -NoWelcome #Find-MgGraphCommand Send-MgUserMail $IsDelegated = $true } <# } else { # SMTP Import-Module EntraAuth # During the Connectiong $clientId = $application.AppId $tenantId = $context.TenantId $token = Connect-EntraService -ClientID $clientId -TenantID $tenantId -Service GraphBeta -PassThru $token.Scopes # After already being connected $token = Get-EntraToken -Service GraphBeta #$token | fl * $xauth2 = $token.AccessToken $secure_xauth2 = ConvertTo-SecureString -AsPlainText $xauth2 -Force # Token $secure_xauth2 = ConvertTo-SecureString -AsPlainText "[Password]" -Force # Pw (with Enabled MFA) #[pscredential]$credential = New-Object System.Management.Automation.PSCredential("AutomateB@contoso.onmicrosoft.com", $secure_xauth2) } #> # -- Settings $From = if ($IsDelegated) {(Get-MgContext).Account} else {$From} #$To = if ($To.Count -gt 1) {} else { $To[0] } # Handle Array $MessageBody = @{ contentType = if ($BodyAsHtml) { "HTML" } else { "Text" } #content = if ($Body) { $Body -join [System.Environment]::NewLine } else { "" } content = if ($Body) { $Body -join [System.Environment]::NewLine } else { "This email is sent via Microsoft Graph." } } $Subject = if ($Subject) { $Subject } else { "Test message from ModernMailTools" } if ($Attachment){ try { #$Attachment = "..\_readme.md" #Test-Path $Attachment # True #(Get-Item -Path $Attachment).Length -lt 3000000 # 3191 | True if ((Test-Path $Attachment) -and ((Get-Item -Path $Attachment).Length -lt 3000000)) { # Attachments are under 4MB or empty #Get File Name and Base64 string $FileName = (Get-Item -Path $Attachment).Name $FileBytes = [Convert]::ToBase64String([IO.File]::ReadAllBytes($Attachment)) Write-Verbose "Name: $($FileName)" Write-Verbose "Length: $($FileBytes.Length)" } else { <#Do this if attachments are over 4MB#> } } catch { Write-Verbose $_ #Write-Error $_.Exception.Message } } # -- Send if ($From) { Write-Verbose "Send v1: $($From)"} if ($MessageBody) { Write-Verbose "Send v1: $($MessageBody | Out-String)"} if ($Subject) { Write-Verbose "Send v1: $($Subject)"} if ($To) { Write-Verbose "Send v1: $($To)"} #If ($false) { If ($true) { # Graph $params = [ordered] @{ # https://docs.microsoft.com/en-us/graph/api/resources/message?view=graph-rest-1.0 message = [ordered] @{ subject = $Subject body = $MessageBody #from = $From toRecipients = @( @{ emailAddress = @{ address = $To[0] } } ) #toRecipients = @( # @{ # emailAddress = @{ # address = "meganb@contoso.com" # } # } #) #ccRecipients = @() #bccRecipients = @() #replyTo = @() #importance = $Priority #isReadReceiptRequested = $RequestReadReceipt.IsPresent #isDeliveryReceiptRequested = $RequestDeliveryReceipt.IsPresent } #saveToSentItems = -not $DoNotSaveToSentItems.IsPresent #saveToSentItems = $false saveToSentItems = $SaveToSentItems.IsPresent } Write-Debug ($params.Values | Out-String) #Send-MgUserMail -UserId $From -Message $params $result = Send-MgUserMail -UserId $From -BodyParameter $params; $result } else { # --- SMTP #Send-MKMailMessage -To "admin@contoso.onmicrosoft.com" -From "AutomateB@contoso.onmicrosoft.com" -Subject "Test" -SmtpServer "smtp.office365.com" -Credential $credential -Port 25 # > Send-MKMailMessage: as8758.net SMTPBLOCKER ESMTP Service not available #Send-MKMailMessage -To "admin@contoso.onmicrosoft.com" -From "AutomateB@contoso.onmicrosoft.com" -Subject "Test" -SmtpServer "smtp.office365.com" -Credential $credential -Port 587 # > Send-MKMailMessage: 535: 5.7.139 Authentication unsuccessful, the request did not meet the criteria to be authenticated successfully # > Send-MKMailMessage: 535: 5.7.139 Authentication unsuccessful, the request did not meet the criteria to be authenticated successfully. } } function Enable-MailMessageAlias { <# .SYNOPSIS Enables alias for Send-MailMessage command. .DESCRIPTION Enables Send-MailMessage command alias in the current PowerShell session. .EXAMPLE Enable-MailMessageAlias .INPUTS None .OUTPUTS .NOTES #> #Reference: # https://learn.microsoft.com/en-us/powershell/module/microsoft.entra/enable-entraazureadalias?view=entra-powershell Set-Alias -Name Send-MailMessage -Value Send-ModernMailMessage -Scope Global -Force Set-Alias -Name Register-MailMessageEntraIDApp -Value Register-ModernMailMessageEntraIDApp -Scope Global -Force Set-Alias -Name Register-MailMessageApp -Value Register-ModernMailMessageEntraIDApp -Scope Global -Force } Export-ModuleMember -Function @("Send-ModernMailMessage", "Register-ModernMailMessageEntraIDApp","Enable-MailMessageAlias") |