Public/Send-EmailMSGraph.ps1


Function Send-EmailMSGraph {

    <#
        .SYNOPSIS
        This function send email message using MSGraphAPI.
     
        .DESCRIPTION
        This function send email message using MSGraphAPI.
             
    
        .PARAMETER To
        Mail receipient address.
     
        .PARAMETER To
        Mail Cc receipient address.
     
        .PARAMETER From
        Email sender address.
     
        .PARAMETER Body
        Email message content in HTML format string format.
     
        .PARAMETER Subject
        Email Subject text.
     
     
        .PARAMETER AttachmentFile
        Email attachment file path.
     
     
     
             
        .EXAMPLE
     
         
        .NOTES
        Created By - Manukumar KM
        last updated - 30/March/2023
         
    #>

    
        [CmdletBinding()]
        param(
                [Parameter(Mandatory = $True, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false,
                HelpMessage = 'Email receipient address',
                Position = 1)]
                [string[]]$To,
    
                [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false,
                HelpMessage = 'Email Cc receipient address',
                Position = 2)]
                [string[]]$Cc,
            
                [Parameter(Mandatory = $True, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false,
                HelpMessage = 'Email sender address',
                Position = 3)]
                [string]$From,
                
                
                [Parameter(Mandatory = $True, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false,
                HelpMessage = 'Email Subject',
                Position = 4)]
                [string]$Subject,
    
                [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false,
                HelpMessage = 'Email body',
                Position = 5)]
                [string]$Body,
                
                [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false,
                HelpMessage = 'Attachment file(s)',
                Position = 6)]
                [ValidateScript({Test-Path $_ -PathType Leaf})]
                [string[]]$Attachments,
                
                [Parameter(Mandatory = $True, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false,
                HelpMessage = 'Azure Tenant ID',
                Position = 7)]
                [string]$TenantID,
    
                [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false,
                HelpMessage = 'Azure AD application ID',
                Position = 8)]
                [string]$AppId,
    
                [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false,
                HelpMessage = 'Certificate thumbprint which is already updated AzureAD and installed on the Server where the script is running',
                Position = 9)]
                [string]$CertThumbprint
    
        )
    
    
    
    ##Define Functions##
    
    function New-FileAttachment {
        [CmdletBinding()]
        Param(
            [Parameter(Mandatory = $true)]
            [ValidateScript({Test-Path $_ -PathType Leaf})]
            [string[]]$FileNames
        )
    
        $fileAttachments = @()
        for ($i = 0; $i -lt $FileNames.Length; $i++) {
            
            $FileName=(Get-Item -Path $FileNames[$i]).name
            $base64string = [Convert]::ToBase64String([IO.File]::ReadAllBytes($FileNames[$i]))
            
            $fileAttachment = @{
                "@odata.type" = "#microsoft.graph.fileAttachment"
                "name" = $FileName
                "contentType" = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
                "contentBytes" = $base64string
            }
            $fileAttachments += $fileAttachment
        }
    
        return $fileAttachments
    }
    
    function Format-Address {
        [CmdletBinding()]
        Param(
            [Parameter(Mandatory = $true)]
            [string[]]$Address
        )
    
        $AddressLists = @()
        for ($i = 0; $i -lt $Address.Length; $i++) {
            
                   
            $AddressList = @{
                "emailAddress" = @{"address" = $Address[$i] }
            }
    
            $AddressLists += $AddressList
        }
    
        return $AddressLists
    }
    
    Function Get-AccessTonkenCERT{
    
    <#
        .SYNOPSIS
        This function is for generating AzureAD access token using Certificate and application ID
     
        .DESCRIPTION
        This function is for generating AzureAD access token using Certificate and application ID
     
         
        .PARAMETER ApplicationId
        Application ID, which is already registered in the azure AD
     
        .PARAMETER CertThumbprint
        Certificate thumbprint for authentication and generating the access token
     
        .PARAMETER EnvironmentUrl
        Cloud or API url for which the access token to be generated for
     
     
        .EXAMPLE
        
        .NOTES
         
        #>

    
        [CmdletBinding()]
        param (
                [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false,
                HelpMessage = 'Azure AD application ID',
                Position = 0)]
                [string]$ApplicationId,
    
                [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false,
                HelpMessage = 'Certificate thumbprint which is already updated AzureAD and installed on the Server where the script is running',
                Position = 1)]
                [string]$CertThumbprint,
    
                [Parameter(Mandatory = $False, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false,
                HelpMessage = 'Azure Environment url to generate access token for',
                Position = 2)]
                [string]$EnvironmentUrl = "https://graph.microsoft.com",
    
                [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false,
                HelpMessage = 'Azure AD tenant ID',
                Position = 3)]
                [string]$TenantID,
    
                [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false,
                HelpMessage = 'Azure AD OAuth Token endpoint url',
                Position = 4)]
                [string]$oAuthTokenEndpoint = "https://login.microsoftonline.com/$($TenantID)/oauth2/v2.0/token"
                
    
                     
              )
    
         #Read more - https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-certificate-credentials
         Write-Verbose "Initiated Access Token request using function Get-AccessTonkenCERT"
    
         $Certificate = Get-Item Cert:\localMachine\My\$CertThumbprint
         $Scope = "$($EnvironmentUrl)/.default"  # Example: "https://graph.microsoft.com/.default"
    
    
         #Create base64 hash of certificate
         $CertificateBase64Hash = [System.Convert]::ToBase64String($Certificate.GetCertHash())
        
         # Create JWT timestamp for expiration
         $StartDate = (Get-Date "1970-01-01T00:00:00Z").ToUniversalTime()
         $JWTExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End (Get-Date).ToUniversalTime().AddMinutes(5)).TotalSeconds # Token Expiry set to 5 mins
         $NotAfter = [math]::Round($JWTExpirationTimeSpan,0)
        
         # Create JWT validity start timestamp
         $NotBeforeExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End ((Get-Date).ToUniversalTime())).TotalSeconds
         $NotBefore = [math]::Round($NotBeforeExpirationTimeSpan,0)
        
         # Create JWT header
         $JWTHeader = @{
             alg = "RS256"
             typ = "JWT"
             x5t = $CertificateBase64Hash -replace '\+','-' -replace '/','_' -replace '=' #Use the CertificateBase64Hash and replace/strip to match web encoding of base64
         }
        
         # Create JWT payload
         $JWTPayLoad = @{
         
                aud = $oAuthTokenEndpoint # What endpoint is allowed to use this JWT
                exp = $NotAfter  # Expiration timestamp
                iss = $ApplicationId  # Issuer = your application
                jti = [guid]::NewGuid() # JWT ID: random guid
                nbf = $NotBefore # Not to be used before
                sub = $ApplicationId # JWT Subject
         }
        
         # Convert header and payload to base64
         $JWTHeaderToByte = [System.Text.Encoding]::UTF8.GetBytes(($JWTHeader | ConvertTo-Json))
         $EncodedHeader = [System.Convert]::ToBase64String($JWTHeaderToByte)
        
         $JWTPayLoadToByte =  [System.Text.Encoding]::UTF8.GetBytes(($JWTPayload | ConvertTo-Json))
         $EncodedPayload = [System.Convert]::ToBase64String($JWTPayLoadToByte)
        
         # Join header and Payload with "." to create a valid (unsigned) JWT
         $JWT = $EncodedHeader + "." + $EncodedPayload
        
         # Get the private key object of your certificate
         $PrivateKey = ([System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($Certificate))
        
         # Define RSA signature and hashing algorithm
         $RSAPadding = [Security.Cryptography.RSASignaturePadding]::Pkcs1
         $HashAlgorithm = [Security.Cryptography.HashAlgorithmName]::SHA256
        
        
         # Create a signature of the JWT
         $Signature = [Convert]::ToBase64String(
             $PrivateKey.SignData([System.Text.Encoding]::UTF8.GetBytes($JWT),$HashAlgorithm,$RSAPadding)
         ) -replace '\+','-' -replace '/','_' -replace '='
        
         # Join the signature to the JWT with "."
         $JWT = $JWT + "." + $Signature
        
         # Create a hash with body parameters
         $Body = @{
             client_id = $ApplicationId
             client_assertion = $JWT
             client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
             scope = $Scope
             grant_type = "client_credentials"
        
         }
        
    
        
         # Use the self-generated JWT as Authorization
         $Header = @{
             Authorization = "Bearer $JWT"
         }
        
         # Splat the parameters for Invoke-Restmethod for cleaner code
         $Params = @{
             ContentType = 'application/x-www-form-urlencoded'
             Method = 'POST'
             Body = $Body
             Uri = $oAuthTokenEndpoint
             Headers = $Header
         }
     
    Try{   
             $authResponseObjectcert = Invoke-RestMethod @Params -ErrorAction Stop
             Write-Verbose "Access token generated Successfully"
             Return $authResponseObjectcert
           }
     Catch{
    
             Write-Error "ERROR :: $($_.Exception.Message)"
             Return $false
           }
    
    } #End of function Get-AccessTonkenCERT
    
    
        #Enforcing tls 1.2 as microsoft Azure only support TLS1.2
        $TLS12Protocol = [System.Net.SecurityProtocolType] 'Ssl3 , Tls12'
        [System.Net.ServicePointManager]::SecurityProtocol = $TLS12Protocol
        
        
        $MsGraphTokenParams =@{
            
                                ApplicationId = $AppId
                                CertThumbprint = $CertThumBprint
                                TenantID = $TenantID
                              }
        Try{
                Write-Verbose "Generating access token"
                $msGraphAccessTokenResponse = Get-AccessTonkenCERT @MsGraphTokenParams -ErrorAction stop
        }
        Catch{
    
                Write-Error "An error occure while generating the access token"
                Break
        }
    
    
    $MessageParams = @{
                        "URI"         = "https://graph.microsoft.com/v1.0/users/$From/sendMail"
                        "Headers"     = @{
                                            'Content-Type'  = "application/json"
                                            'Authorization' = "Bearer $($msGraphAccessTokenResponse.access_token)" 
                                          }
                        "Method"      = "POST"
                        "ContentType" = 'application/json'
    }
    
    $message = @{
                    "subject"      = $Subject
                    "body"         = @{
                                        "contentType" = 'HTML' 
                                        "content"     = $Body 
                                      }
                    "toRecipients" = @($(Format-Address -Address $To))
    }
    
    if($null -ne $Cc){
        $message["ccRecipients"] = @($(Format-Address -Address $Cc))
    }
    
    if($null -ne $Attachments){
        $message["attachments"] = @($(New-FileAttachment -FileNames $Attachments))
    }
    
    $MessageParams["Body"] = ConvertTo-JSON @{
                                "message" = $message
                                }  -Depth 10
    
        
        
    If(tnc graph.microsoft.com -Port 443 -InformationLevel Quiet){
        
        Try{
                Write-Verbose "Starting email message to - $($To.GetEnumerator())"
                Invoke-RestMethod @Messageparams -ErrorAction Stop
                Write-Host "Email message sent successfuly" -f Green
            
            }
        Catch{
                Write-Error  "An error occured while sending email - ERROR :: $($_.exception.message)"
        }
    }
    Else{
        Write-Error "Communication over 443 to graph.microsoft.com - failed"
    }
    
    
} #End of function Send-EmailMSGraph