├── .github
└── workflows
│ └── PublishModule.yml
├── MSGraphMail.code-workspace
├── MSGraphMail.psd1
├── MSGraphMail.psm1
├── MSGraphMailSummary.Format.ps1xml
├── Private
├── Get-TokenExpiry.ps1
├── Invoke-EmailObjectParser.ps1
├── Invoke-EmailStringParser.ps1
├── Invoke-MSGraphHTTPClientRequest.ps1
├── Invoke-MSGraphWebRequest.ps1
├── New-MSGraphError.ps1
├── New-MSGraphErrorRecord.ps1
├── New-MSGraphMailAttachment.ps1
├── New-MSGraphMailBody.ps1
├── New-MSGraphMailDELETERequest.ps1
├── New-MSGraphMailGETRequest.ps1
├── New-MSGraphMailPOSTRequest.ps1
├── New-MSGraphMailPUTRequest.ps1
└── Write-CustomMessage.ps1
├── Public
├── Connect-MSGraphMail.ps1
├── Get-MSGraphMail.ps1
├── Move-MSGraphMail.ps1
├── New-MSGraphMail.ps1
├── Remove-MSGraphMail.ps1
└── Send-MSGraphMail.ps1
└── README.md
/.github/workflows/PublishModule.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | tags:
4 | - "v*"
5 | name: Build and Publish Module
6 | jobs:
7 | build:
8 | name: Build PowerShell Module
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v2
12 | - name: Set required Powershell modules
13 | id: psmodulecache
14 | uses: potatoqualitee/psmodulecache@v1
15 | with:
16 | modules-to-cache: Pester, PSSCriptAnalyzer, InvokeBuild, platyPS
17 | publish-module:
18 | name: Publish PowerShell Module
19 | runs-on: ubuntu-latest
20 | steps:
21 | - uses: pcgeek86/publish-powershell-module-action@v20
22 | with:
23 | # The NuGet API Key for PowerShell Gallery, with permission to push this module.
24 | NuGetApiKey: ${{ secrets.PS_GALLERY_KEY }}
25 |
--------------------------------------------------------------------------------
/MSGraphMail.code-workspace:
--------------------------------------------------------------------------------
1 | {
2 | "folders": [
3 | {
4 | "path": "."
5 | }
6 | ],
7 | "settings": {}
8 | }
--------------------------------------------------------------------------------
/MSGraphMail.psd1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/homotechsual/MSGraphMail/74e5125ca15975577b1bf8fa80bcc16a5aa37be6/MSGraphMail.psd1
--------------------------------------------------------------------------------
/MSGraphMail.psm1:
--------------------------------------------------------------------------------
1 | # MsGraphMail.psm1
2 | $FilesToImport = @(Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -ErrorAction SilentlyContinue) + @(Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 -ErrorAction SilentlyContinue)
3 |
4 | foreach ($ImportedFile in @($FilesToImport)){
5 | try
6 | {
7 | . $ImportedFile.FullName
8 | }
9 | catch
10 | {
11 | Write-Error -Message "Failed to import function $($ImportedFile.FullName): $_"
12 | }
13 | }
14 | Export-ModuleMember -Function $FilesToImport.BaseName -Alias *
--------------------------------------------------------------------------------
/MSGraphMailSummary.Format.ps1xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MSGraphMailSummary
6 |
7 | MSGraphMailSummary
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | subject
16 |
17 |
18 |
19 | id
20 |
21 |
22 |
23 | fromString
24 |
25 |
26 |
27 | toString
28 |
29 |
30 |
31 | ccString
32 |
33 |
34 |
35 | receivedDateTime
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/Private/Get-TokenExpiry.ps1:
--------------------------------------------------------------------------------
1 | #Requires -Version 7
2 | function Get-TokenExpiry {
3 | <#
4 | .SYNOPSIS
5 | Calculates and returns the expiry date/time of an auth token.
6 | .DESCRIPTION
7 | Takes the expires in time for an auth token and returns a PowerShell date/time object containing the expiry date/time of the token.
8 | .OUTPUTS
9 | A powershell date/time object representing the token expiry.
10 | #>
11 | [CmdletBinding()]
12 | [OutputType([DateTime])]
13 | param (
14 | # Timestamp value for token expiry. e.g 3600
15 | [Parameter(
16 | Mandatory = $True
17 | )]
18 | [int64]$ExpiresIn
19 | )
20 | $Now = Get-Date
21 | $TimeZone = Get-TimeZone
22 | $UTCTime = $Now.AddMilliseconds($ExpiresIn)
23 | $UTCOffset = $TimeZone.GetUtcOffset($(Get-Date)).TotalMinutes
24 | $ExpiryDateTime = $UTCTime.AddMinutes($UTCOffset)
25 | Write-Verbose "Calcuated token expiry as $($ExpiryDateTime.ToString())"
26 | Return $ExpiryDateTime
27 | }
--------------------------------------------------------------------------------
/Private/Invoke-EmailObjectParser.ps1:
--------------------------------------------------------------------------------
1 | function Invoke-EmailObjectParser {
2 | [CmdletBinding()]
3 | Param (
4 | [Parameter(Mandatory = $True)]
5 | [Object[]]$Objects
6 | )
7 | # Loop over each email object and add it to a string.
8 | Write-Debug "Email object parser received $($Objects | ConvertTo-Json)"
9 | $EmailAddresses = foreach ($EmailObject in $Objects) {
10 | $Name = $EmailObject.emailAddress.Name
11 | $Address = $EmailObject.emailAddress.Address
12 | Write-Debug "Got name $($Name) and email $($Address) from object $($EmailObject.emailAddress | Out-String)"
13 | # Turn the email into an output string.
14 | $EmailAddress = "$($Name) <$($Address)>"
15 | $EmailAddress
16 | }
17 | return $EmailAddresses -Join ';'
18 | }
--------------------------------------------------------------------------------
/Private/Invoke-EmailStringParser.ps1:
--------------------------------------------------------------------------------
1 | function Invoke-EmailStringParser {
2 | [CmdletBinding()]
3 | Param (
4 | [Parameter(Mandatory = $True)]
5 | [String[]]$Strings
6 | )
7 | # Split input string on ";" character.
8 | if ($Strings.Length -ge 2) {
9 | $EmailStrings = $Strings
10 | } else {
11 | $EmailStrings = $Strings.Split(';')
12 | }
13 | # Loop over each email string and add it to a hashtable in the expected format for an IMicrosoftGraphRecipient[] object.
14 | $EmailAddresses = foreach ($EmailString in $EmailStrings) {
15 | $ParsedEmailString = [regex]::Matches($EmailString, '\s?"?((?.*?)"?\s*<)?(?.*?[^>]*)')
16 | $Name = $ParsedEmailString[0].Groups['name'].value
17 | $Address = $ParsedEmailString[0].Groups['email'].value
18 | Write-Debug "Got name $($Name) and email $($Address) from string $($EmailString)"
19 | # Add the email address in the expected format for an IMicrosoftGraphEmailAddress object.
20 | $EmailAddress = @{
21 | 'emailAddress' = @{
22 | name = $Name
23 | address = $Address
24 | }
25 | }
26 | $EmailAddress
27 | }
28 | Write-Verbose "Got $($EmailAddresses.Length) email addresses"
29 | Write-Verbose ($EmailAddresses | ConvertTo-Json)
30 | return $EmailAddresses
31 | }
--------------------------------------------------------------------------------
/Private/Invoke-MSGraphHTTPClientRequest.ps1:
--------------------------------------------------------------------------------
1 | using namespace System.Net.Http
2 | using namespace System.Net.Http.Headers
3 | #Requires -Version 7
4 | function Invoke-MSGraphHTTPClientRequest {
5 | <#
6 | .SYNOPSIS
7 | Sends a request to the Microsoft Graph API using HTTPClient.
8 | .DESCRIPTION
9 | Wrapper function to send web requests to the Microsoft Graph API using the .NET HTTP client implementation.
10 | .OUTPUTS
11 | Outputs an object containing the response from the web request.
12 | #>
13 | [Cmdletbinding()]
14 | [OutputType([Object])]
15 | param (
16 | # The request URI.
17 | [Parameter(Mandatory = $True)]
18 | [uri]$URI,
19 | [Parameter(Mandatory = $True)]
20 | [string]$Method,
21 | [object]$Body,
22 | # The content type for the request.
23 | [string]$ContentType
24 | )
25 | $ProgressPreference = 'SilentlyContinue'
26 | if ([DateTime]::Now -ge $Script:MSGMAuthenticationInformation.Expires) {
27 | Write-Verbose 'The auth token has expired, renewing.'
28 | $ReconnectParameters = @{
29 | Reconnect = $True
30 | }
31 | Connect-MSGraphMail @ReconnectParameters
32 | }
33 | if (($null -ne $Script:MSGMAuthenticationInformation) -and ($Method -ne 'PUT')) {
34 | $AuthHeader = [AuthenticationHeaderValue]::New($Script:MSGMAuthenticationInformation.Type, $Script:MSGMAuthenticationInformation.Token)
35 | }
36 | try {
37 | Write-Verbose "Making a $($Method) request to $($URI)"
38 | Write-Debug "Authentication headers: $($AuthHeader.ToString())"
39 | $HTTPClient = [HttpClient]::new()
40 | $HTTPClient.DefaultRequestHeaders.Authorization = $AuthHeader
41 | $HTTPClient.DefaultRequestHeaders.Add('Prefer', 'IdType%3D%22ImmutableId%22')
42 | if ($Method = 'GET') {
43 | $Request = $HTTPClient.GetAsync($URI)
44 | } elseif ($Method = 'PUT') {
45 | if (-Not $Body) {
46 | Throw 'Body is missing on PUT request.'
47 | }
48 | $Request = $HTTPClient.PutAsync($URI, $Body)
49 | }
50 | $Request.Wait()
51 | $Result = $Request.Result
52 | if ($Result.isFaulted) {
53 | Throw $Result.Exception
54 | }
55 | $Response = $Result.Content.ReadAsStringAsync().Result
56 | Write-Debug "Response headers: $($Result.Headers | Out-String)"
57 | Write-Debug "Raw response: $($Result | Out-String)"
58 | return $Response
59 | } catch {
60 | $ErrorRecord = @{
61 | ExceptionType = 'System.Net.Http.HttpRequestException'
62 | ErrorMessage = "Microsoft Graph API request $($_.TargetObject.Method) $($_.TargetObject.RequestUri) failed."
63 | InnerException = $_.Exception
64 | ErrorID = 'MicrosoftGraphRequestFailed'
65 | ErrorCategory = 'ProtocolError'
66 | TargetObject = $_.TargetObject
67 | ErrorDetails = $_.ErrorDetails
68 | BubbleUpDetails = $True
69 | }
70 | $RequestError = New-MSGraphErrorRecord @ErrorRecord
71 | $PSCmdlet.ThrowTerminatingError($RequestError)
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Private/Invoke-MSGraphWebRequest.ps1:
--------------------------------------------------------------------------------
1 | #Requires -Version 7
2 | function Invoke-MSGraphWebRequest {
3 | <#
4 | .SYNOPSIS
5 | Sends a request to the Microsoft Graph API using Invoke-WebRequest.
6 | .DESCRIPTION
7 | Wrapper function to send web requests to the Microsoft Graph API using the Invoke-WebRequest commandlet.
8 | .OUTPUTS
9 | Outputs an object containing the response from the web request.
10 | #>
11 | [Cmdletbinding()]
12 | [OutputType([Object])]
13 | param (
14 | # The request URI.
15 | [Parameter(Mandatory = $True)]
16 | [uri]$URI,
17 | # The request method.
18 | [Parameter(Mandatory = $True)]
19 | [string]$Method,
20 | # Don't authenticate.
21 | [switch]$Anonymous,
22 | # The content type for the request.
23 | [string]$ContentType,
24 | # The body content of the request.
25 | [object]$Body,
26 | # Additional headers.
27 | [hashtable]$AdditionalHeaders
28 | )
29 | $ProgressPreference = 'SilentlyContinue'
30 | if ([DateTime]::Now -ge $Script:MSGMAuthenticationInformation.Expires) {
31 | Write-Verbose 'The auth token has expired, renewing.'
32 | $ReconnectParameters = @{
33 | Reconnect = $True
34 | }
35 | Connect-MSGraphMail @ReconnectParameters
36 | }
37 | if ($null -ne $Script:MSGMAuthenticationInformation -and (-not $Anonymous)) {
38 | $AuthHeader = @{
39 | Authorization = "$($Script:MSGMAuthenticationInformation.Type) $($Script:MSGMAuthenticationInformation.Token)"
40 | }
41 | }
42 | if ($null -ne $AdditionalHeaders) {
43 | $RequestHeaders = $AuthHeader + $AdditionalHeaders
44 | } else {
45 | $RequestHeaders = $AuthHeader
46 | }
47 | if ($Method -eq 'PUT') {
48 | $SkipHeaderValidation = $True
49 | } else {
50 | $SkipHeaderValidation = $False
51 | }
52 | try {
53 | Write-Verbose "Making a $($Method) request to $($URI)"
54 | Write-Debug "Request headers: $($RequestHeaders | Out-String -Width 5000)"
55 | $WebRequestParams = @{
56 | URI = $URI
57 | Method = $Method
58 | ContentType = $ContentType
59 | Headers = $RequestHeaders
60 | SkipHeaderValidation = $SkipHeaderValidation
61 | }
62 | Write-Debug "Request parameters: $($WebRequestParams | Out-String -Width 5000)"
63 | if ($Body -and (($Method -eq 'POST') -or ($Method -eq 'PUT'))) {
64 | $WebRequestParams.Body = $Body
65 | }
66 | $Response = Invoke-WebRequest @WebRequestParams
67 | Write-Debug "Response headers: $($Response.Headers | Out-String)"
68 | Write-Debug "Raw response: $($Response | Out-String -Width 5000)"
69 | return $Response
70 | } catch {
71 | New-MSGraphError $_
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Private/New-MSGraphError.ps1:
--------------------------------------------------------------------------------
1 | using namespace System.Collections.Generic
2 | using namespace System.Management.Automation
3 | function New-MSGraphError {
4 | [CmdletBinding()]
5 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Private function - no need to support.')]
6 | param (
7 | [Parameter(Mandatory = $true)]
8 | [errorrecord]$ErrorRecord,
9 | [Parameter()]
10 | [switch]$HasResponse
11 |
12 | )
13 | Write-Verbose 'Generating Microsoft Graph error output.'
14 | $ExceptionMessage = [Hashset[String]]::New()
15 | $APIResultMatchString = '*The Microsoft Graph API said*'
16 | $HTTPResponseMatchString = '*The API returned the following HTTP*'
17 | if ($ErrorRecord.ErrorDetails) {
18 | Write-Verbose 'ErrorDetails contained in error record.'
19 | $ErrorDetailsIsJson = Test-Json -Json $ErrorRecord.ErrorDetails -ErrorAction SilentlyContinue
20 | if ($ErrorDetailsIsJson) {
21 | Write-Verbose 'ErrorDetails is JSON.'
22 | $ErrorDetails = $ErrorRecord.ErrorDetails | ConvertFrom-Json
23 | Write-Debug "Raw error details: $($ErrorDetails | Out-String)"
24 | if ($null -ne $ErrorDetails) {
25 | if (($null -ne $ErrorDetails.error.code) -and ($null -ne $ErrorDetails.error.message)) {
26 | Write-Verbose 'ErrorDetails contains code and message.'
27 | $ExceptionMessage.Add("The Microsoft Graph API said $($ErrorDetails.error.code): $($ErrorDetails.error.message).") | Out-Null
28 | } elseif ($null -ne $ErrorDetails.error.code) {
29 | Write-Verbose 'ErrorDetails contains code.'
30 | $ExceptionMessage.Add("The Microsoft Graph API said $($ErrorDetails.code).") | Out-Null
31 | } elseif ($null -ne $ErrorDetails.error) {
32 | Write-Verbose 'ErrorDetails contains error.'
33 | $ExceptionMessage.Add("The Microsoft Graph API said $($ErrorDetails.error).") | Out-Null
34 | } elseif ($null -ne $ErrorDetails) {
35 | Write-Verbose 'ErrorDetails is not null.'
36 | $ExceptionMessage.Add("The Microsoft Graph API said $($ErrorRecord.ErrorDetails).") | Out-Null
37 | } else {
38 | Write-Verbose 'ErrorDetails is null.'
39 | $ExceptionMessage.Add('The Microsoft Graph API returned an error.') | Out-Null
40 | }
41 | }
42 | } elseif ($ErrorRecord.ErrorDetails -like $APIResultMatchString -and $ErrorRecord.ErrorDetails -like $HTTPResponseMatchString) {
43 | $Errors = $ErrorRecord.ErrorDetails -Split "`r`n"
44 | if ($Errors -is [array]) {
45 | ForEach-Object -InputObject $Errors {
46 | $ExceptionMessage.Add($_) | Out-Null
47 | }
48 | } elseif ($Errors -is [string]) {
49 | $ExceptionMessage.Add($_)
50 | }
51 | }
52 | } else {
53 | $ExceptionMessage.Add('The Microsoft Graph API returned an error but did not provide a result code or error message.') | Out-Null
54 | }
55 | if (($ErrorRecord.Exception.Response -and $HasResponse) -or $ExceptionMessage -notlike $HTTPResponseMatchString) {
56 | $Response = $ErrorRecord.Exception.Response
57 | Write-Debug "Raw HTTP response: $($Response | Out-String)"
58 | if ($Response.StatusCode.value__ -and $Response.ReasonPhrase) {
59 | $ExceptionMessage.Add("The API returned the following HTTP error response: $($Response.StatusCode.value__) $($Response.ReasonPhrase)") | Out-Null
60 | } else {
61 | $ExceptionMessage.Add('The API returned an HTTP error response but did not provide a status code or reason phrase.')
62 | }
63 | } else {
64 | $ExceptionMessage.Add('The API did not provide a response code or status.') | Out-Null
65 | }
66 | $Exception = [System.Exception]::New(
67 | $ExceptionMessage,
68 | $ErrorRecord.Exception
69 | )
70 | $MSGraphError = [ErrorRecord]::New(
71 | $ErrorRecord,
72 | $Exception
73 | )
74 | $UniqueExceptions = $ExceptionMessage | Get-Unique
75 | $MSGraphError.ErrorDetails = [String]::Join("`r`n", $UniqueExceptions)
76 | $PSCmdlet.ThrowTerminatingError($MSGraphError)
77 | }
--------------------------------------------------------------------------------
/Private/New-MSGraphErrorRecord.ps1:
--------------------------------------------------------------------------------
1 | using namespace System.Collections.Generic
2 | function New-MSGraphErrorRecord {
3 | [CmdletBinding()]
4 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Private function - no need to support.')]
5 | param (
6 | [Parameter(Mandatory = $true)]
7 | [type]$ExceptionType,
8 | [Parameter(Mandatory = $true)]
9 | [string]$ErrorMessage,
10 | [exception]$InnerException = $null,
11 | [Parameter(Mandatory = $true)]
12 | [string]$ErrorID,
13 | [Parameter(Mandatory = $true)]
14 | [errorcategory]$ErrorCategory,
15 | [object]$TargetObject = $null,
16 | [object]$ErrorDetails = $null,
17 | [switch]$BubbleUpDetails
18 | )
19 | $ExceptionMessage = [list[string]]::New()
20 | $ExceptionMessage.Add($ErrorMessage)
21 | if ($ErrorDetails.Message) {
22 | $MSGraphError = $_.ErrorDetails.Message | ConvertFrom-Json
23 | if ($MSGraphError.Message) {
24 | $ExceptionMessage.Add("The Microsoft Graph API said $($MSGraphError.ClassName): $($MSGraphError.Message).")
25 | }
26 | }
27 | if ($InnerException.Response) {
28 | $Response = $InnerException.Response
29 | }
30 | if ($InnerException.InnerException.Response) {
31 | $Response = $InnerException.InnerException.Response
32 | }
33 | if ($InnerException.InnerException.InnerException.Response) {
34 | $Response = $InnerException.InnerException.InnerException.Response
35 | }
36 | if ($Response) {
37 | $ExceptionMessage.Add("The Microsoft Graph API provided the status code $($Response.StatusCode.Value__): $($Response.ReasonPhrase).")
38 | }
39 | $Exception = $ExceptionType::New(
40 | $ExceptionMessage,
41 | $InnerException
42 | )
43 | #if (($Exception -and ($ExceptionType -is 'Microsoft.PowerShell.Commands.HttpResponseException')) -and $Response) {
44 | # $Exception.Response = $Response
45 | #}
46 | $ExceptionMessage.Add('You can use "Get-Error" for detailed error information.')
47 | $MSGraphError = [ErrorRecord]::New(
48 | $Exception,
49 | $ErrorID,
50 | $ErrorCategory,
51 | $TargetObject
52 | )
53 | if ($BubbleUpDetails) {
54 | $MSGraphError.ErrorDetails = $ErrorDetails
55 | }
56 | Return $MSGraphError
57 | }
--------------------------------------------------------------------------------
/Private/New-MSGraphMailAttachment.ps1:
--------------------------------------------------------------------------------
1 | function New-MSGraphMailAttachment {
2 | [CmdletBinding()]
3 | param (
4 | [Parameter(Mandatory = $True)]
5 | [string]$Mailbox,
6 | [Parameter(Mandatory = $True)]
7 | [string]$MessageID,
8 | [string]$Folder,
9 | [Parameter(Mandatory = $True)]
10 | [string[]]$Attachments,
11 | [switch]$InlineAttachments
12 | )
13 | Write-Debug "Got attachments $($Attachments -join ', ')"
14 | foreach ($AttachmentItem in $Attachments) {
15 | if ($InlineAttachments) {
16 | $IAParts = $AttachmentItem.Split(';')
17 | $CID = $IAParts[0]
18 | Write-Verbose ("Content ID: $CID")
19 | $Attachment = $IAParts[1]
20 | Write-Verbose ("Attachment: $Attachment")
21 | } else {
22 | $Attachment = $AttachmentItem
23 | }
24 | Test-Path -Path $Attachment -ErrorAction Stop | Out-Null
25 | $AttachmentFile = Get-Item -Path $Attachment -ErrorAction Stop
26 | $Bytes = Get-Content -Path $AttachmentFile.FullName -AsByteStream -Raw
27 | if ($Bytes.Length -le 2999999) {
28 | Write-Debug "Attachment $($AttachmentFile.Fullname) size is $($Bytes.Length) which is less than 3MB - using direct upload"
29 | $UploadSession = $False
30 | } else {
31 | Write-Debug "Attachment $($AttachmentFile.Fullname) size is $($Bytes.Length) which is greater than 3MB - using streaming upload"
32 | $UploadSession = $True
33 | }
34 | $AttachmentItem = @{
35 | "@odata.type" = "#microsoft.graph.fileAttachment"
36 | attachmentType = 'file'
37 | name = $AttachmentFile.Name
38 | }
39 | if ($CID) {
40 | $AttachmentItem.contentId = $CID
41 | $AttachmentItem.isInline = $True
42 | if ($AttachmentFile.Extension -eq '.png') {
43 | $AttachmentItem.contentType = 'image/png'
44 | } elseif (($AttachmentFile.Extension -eq '.jpg') -or ($AttachmentFile.Extension -eq '.jpeg')) {
45 | $AttachmentItem.contentType = 'image/jpeg'
46 | } elseif ($AttachmentFile.Extension -eq '.gif') {
47 | $AttachmentItem.contentType = 'image/gif'
48 | }
49 | } else {
50 | $AttachmentItem.size = $($Bytes.Length)
51 | }
52 | Write-Debug "Generated attachment item $($AttachmentItem | ConvertTo-JSON)"
53 | $RequestURI = [System.UriBuilder]::New('https', 'graph.microsoft.com')
54 | if ($UploadSession) {
55 | $UploadTry = 0
56 | do {
57 | if ($Folder) {
58 | $RequestURI.Path = "v1.0/users/$($Mailbox)/mailFolders/$($Folder)/messages/$($MessageID)/attachments/createUploadSession"
59 | } else {
60 | $RequestURI.Path = "v1.0/users/$($Mailbox)/messages/$($MessageID)/attachments/createUploadSession"
61 | }
62 | $AttachmentItem.Remove('@odata.type')
63 | $SessionAttachmentItem = @{
64 | AttachmentItem = $AttachmentItem
65 | }
66 | $UploadSessionParams = @{
67 | URI = $RequestURI.ToString()
68 | Body = $SessionAttachmentItem
69 | ContentType = 'application/json'
70 | Raw = $False
71 | }
72 | try {
73 | $UploadTry++
74 | $InternalServerError = $False
75 | Write-CustomMessage "Attempting to upload $($AttachmentFile.FullName) attempt number $($UploadTry)" -Type 'Information'
76 | $AttachmentSession = New-MSGraphMailPOSTRequest @UploadSessionParams
77 | Write-Debug "Got upload session details $($AttachmentSession)"
78 | $AttachmentSessionURI = $AttachmentSession.uploadurl
79 | } catch {
80 | $ErrorRecord = @{
81 | ExceptionType = 'System.Net.Http.HttpRequestException'
82 | ErrorMessage = 'Creating session for attachment upload to the Microsoft Graph API failed.'
83 | InnerException = $_.Exception
84 | ErrorID = 'MSGraphMailFailedToGetAttachmentUploadSession'
85 | ErrorCategory = 'ProtocolError'
86 | TargetObject = $_.TargetObject
87 | ErrorDetails = $_.ErrorDetails
88 | BubbleUpDetails = $True
89 | }
90 | $RequestError = New-MSGraphErrorRecord @ErrorRecord
91 | $PSCmdlet.ThrowTerminatingError($RequestError)
92 | }
93 | if ($AttachmentSession) {
94 | $AdditionalHeaders = @{
95 | "Content-Range" = "bytes 0-$($Bytes.Length -1)/$($Bytes.Length)"
96 | }
97 | $AttachmentUploadParams =@{
98 | URI = $AttachmentSessionURI
99 | Body = $Bytes
100 | Anonymous = $True
101 | AdditionalHeaders = $AdditionalHeaders
102 | Raw = $False
103 | }
104 | try {
105 | $AttachmentUpload = New-MSGraphMailPUTRequest @AttachmentUploadParams
106 | if ($AttachmentUpload) {
107 | $InternalServerError = $False
108 | Write-CustomMessage -Message "Attached file '$($AttachmentFile.FullName)' to message $($MessageID)" -Type 'Success'
109 | }
110 | } catch {
111 | if ($_.Exception.InnerException.InnerException.Response.StatusCode.value__ -eq 500) {
112 | Write-Warning "Attempt to upload '$($AttachmentFile.FullName)' failed. Retrying."
113 | $InternalServerError = $True
114 | } else {
115 | $ErrorRecord = @{
116 | ExceptionType = 'System.Net.Http.HttpRequestException'
117 | ErrorMessage = "Sending attachment '$($AttachmentFile.Name)' to the Microsoft Graph API failed."
118 | InnerException = $_.Exception
119 | ErrorID = 'MSGraphMailAttachmentUploadFailed'
120 | ErrorCategory = 'ProtocolError'
121 | TargetObject = $_.TargetObject
122 | ErrorDetails = $_.ErrorDetails
123 | BubbleUpDetails = $True
124 | }
125 | $RequestError = New-MSGraphErrorRecord @ErrorRecord
126 | $PSCmdlet.ThrowTerminatingError($RequestError)
127 | }
128 | }
129 | }
130 | } while (($InternalServerError) -and ($UploadTry -le 5))
131 | } else {
132 | if ($Folder) {
133 | $RequestURI.Path = "v1.0/users/$($Mailbox)/mailFolders/$($Folder)/messages/$($MessageID)/attachments"
134 | } else {
135 | $RequestURI.Path = "v1.0/users/$($Mailbox)/messages/$($MessageID)/attachments"
136 | }
137 | $AttachmentItem.contentBytes = [convert]::ToBase64String($Bytes)
138 | $SimpleAttachmentParams = @{
139 | URI = $RequestURI.ToString()
140 | Body = $($AttachmentItem)
141 | ContentType = 'application/json'
142 | Raw = $False
143 | }
144 | try {
145 | $AttachmentUpload = New-MSGraphMailPOSTRequest @SimpleAttachmentParams
146 | if ($AttachmentUpload) {
147 | Write-CustomMessage -Message "Attached file '$($AttachmentFile.Name)' to message $($MessageID)" -Type 'Success'
148 | }
149 | } catch {
150 | $ErrorRecord = @{
151 | ExceptionType = 'System.Net.Http.HttpRequestException'
152 | ErrorMessage = "Sending attachment '$($AttachmentFile.Name)' to the Microsoft Graph API failed."
153 | InnerException = $_.Exception
154 | ErrorID = 'MSGraphMailAttachmentUploadFailed'
155 | ErrorCategory = 'ProtocolError'
156 | TargetObject = $_.TargetObject
157 | ErrorDetails = $_.ErrorDetails
158 | BubbleUpDetails = $True
159 | }
160 | $RequestError = New-MSGraphErrorRecord @ErrorRecord
161 | $PSCmdlet.ThrowTerminatingError($RequestError)
162 | }
163 | }
164 | }
165 | }
--------------------------------------------------------------------------------
/Private/New-MSGraphMailBody.ps1:
--------------------------------------------------------------------------------
1 | function New-MSGraphMailBody {
2 | [CmdletBinding()]
3 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Private function - no need to support.')]
4 | Param (
5 | [Parameter(Mandatory = $True)]
6 | [ValidateSet('HTML', 'text')]
7 | [string]$BodyFormat,
8 | [Parameter(Mandatory = $True)]
9 | [string]$BodyContent,
10 | [string]$FooterContent
11 | )
12 | if (Test-Path $BodyContent) {
13 | $MailContent = (Get-Content $BodyContent -Raw)
14 | Write-Verbose "Using file $BodyContent as body content."
15 | Write-Debug "Body content: `r`n$MailContent"
16 | } else {
17 | $MailContent = $BodyContent
18 | Write-Verbose "Using string as body content."
19 | Write-Debug "Body content: `r`n$MailContent"
20 | }
21 | if (Test-Path $FooterContent) {
22 | $MailFooter = (Get-Content $FooterContent -Raw)
23 | Write-Verbose "Using file $FooterContent as footer content."
24 | Write-Debug "Footer content: `r`n$MailFooter"
25 | } else {
26 | $MailFooter = $FooterContent
27 | Write-Verbose "Using string as footer content."
28 | Write-Debug "Footer content: `r`n$MailFooter"
29 | }
30 | $MailBody = @{
31 | content = "$($MailContent)$([System.Environment]::NewLine)$($MailFooter)"
32 | contentType = $BodyFormat
33 | }
34 | Return $MailBody
35 | }
36 |
--------------------------------------------------------------------------------
/Private/New-MSGraphMailDELETERequest.ps1:
--------------------------------------------------------------------------------
1 | function New-MSGraphMailDELETERequest {
2 | <#
3 | .SYNOPSIS
4 | Builds a DELETE request for the Microsoft Graph API.
5 | .DESCRIPTION
6 | Wrapper function to build web requests for the Microsoft Graph API.
7 | .OUTPUTS
8 | Outputs an object containing the response from the web request.
9 | #>
10 | [CmdletBinding()]
11 | [OutputType([Object])]
12 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Private function - no need to support.')]
13 | param (
14 | # The request URI.
15 | [uri]$URI,
16 | # The content type for the request.
17 | [string]$ContentType
18 | )
19 | if ($null -eq $Script:MSGMConnectionInformation) {
20 | Throw "Missing Microsoft Graph connection information, please run 'Connect-MSGraphMail' first."
21 | }
22 | if ($null -eq $Script:MSGMAuthenticationInformation) {
23 | Throw "Missing Microsoft Graph authentication tokens, please run 'Connect-MSGraphMail' first."
24 | }
25 | try {
26 | $WebRequestParams = @{
27 | Method = 'DELETE'
28 | URI = $URI
29 | ContentType = $ContentType
30 | }
31 | Write-Debug "Building new Microsoft Graph DELETE request with params: $($WebRequestParams | Out-String)"
32 | $Result = Invoke-MSGraphWebRequest @WebRequestParams
33 | if ($Result) {
34 | Write-Debug "Microsoft Graph request returned $($Result | Out-String)"
35 | Return $Result
36 | } else {
37 | Throw 'Failed to process DELETE request.'
38 | }
39 | } catch {
40 | $ErrorRecord = @{
41 | ExceptionType = 'System.Net.Http.HttpRequestException'
42 | ErrorMessage = 'DELETE request sent to the Microsoft Graph API failed.'
43 | InnerException = $_.Exception
44 | ErrorID = 'MSGraphMailDeleteRequestFailed'
45 | ErrorCategory = 'ProtocolError'
46 | TargetObject = $_.TargetObject
47 | ErrorDetails = $_.ErrorDetails
48 | BubbleUpDetails = $True
49 | }
50 | $RequestError = New-MSGraphErrorRecord @ErrorRecord
51 | $PSCmdlet.ThrowTerminatingError($RequestError)
52 | }
53 | }
--------------------------------------------------------------------------------
/Private/New-MSGraphMailGETRequest.ps1:
--------------------------------------------------------------------------------
1 | function New-MSGraphMailGETRequest {
2 | <#
3 | .SYNOPSIS
4 | Builds a GET request for the Microsoft Graph API.
5 | .DESCRIPTION
6 | Wrapper function to build web requests for the Microsoft Graph API.
7 | .OUTPUTS
8 | Outputs an object containing the response from the web request.
9 | #>
10 | [CmdletBinding()]
11 | [OutputType([Object])]
12 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Private function - no need to support.')]
13 | param (
14 | # The request URI.
15 | [uri]$URI,
16 | # The content type for the request.
17 | [string]$ContentType,
18 | # Use HTTPClient instead of Invoke-WebRequest.
19 | [switch]$UseHTTPClient
20 | )
21 | if ($null -eq $Script:MSGMConnectionInformation) {
22 | Throw "Missing Microsoft Graph connection information, please run 'Connect-MSGraphMail' first."
23 | }
24 | if ($null -eq $Script:MSGMAuthenticationInformation) {
25 | Throw "Missing Microsoft Graph authentication tokens, please run 'Connect-MSGraphMail' first."
26 | }
27 | try {
28 | if ($UseHTTPClient) {
29 | $WebRequestParams = @{
30 | Method = 'GET'
31 | URI = $URI
32 | ContentType = $ContentType
33 | }
34 | Write-Debug "Building new Microsoft Graph GET request with params: $($WebRequestParams | Out-String)"
35 | $Result = Invoke-MSGraphHTTPClientRequest @WebRequestParams
36 | } else {
37 | $WebRequestParams = @{
38 | Method = 'GET'
39 | URI = $URI
40 | ContentType = $ContentType
41 | }
42 | Write-Debug "Building new Microsoft Graph GET request with params: $($WebRequestParams | Out-String)"
43 | $Result = Invoke-MSGraphWebRequest @WebRequestParams
44 | }
45 | if ($Result) {
46 | Write-Debug "Microsoft Graph request returned $($Result | Out-String)"
47 | Return $Result
48 | } else {
49 | Throw 'Failed to process GET request.'
50 | }
51 | } catch {
52 | $ErrorRecord = @{
53 | ExceptionType = 'System.Net.Http.HttpRequestException'
54 | ErrorMessage = 'GET request sent to the Microsoft Graph API failed.'
55 | InnerException = $_.Exception
56 | ErrorID = 'MSGraphMailGetRequestFailed'
57 | ErrorCategory = 'ProtocolError'
58 | TargetObject = $_.TargetObject
59 | ErrorDetails = $_.ErrorDetails
60 | BubbleUpDetails = $True
61 | }
62 | $RequestError = New-MSGraphErrorRecord @ErrorRecord
63 | $PSCmdlet.ThrowTerminatingError($RequestError)
64 | }
65 | }
--------------------------------------------------------------------------------
/Private/New-MSGraphMailPOSTRequest.ps1:
--------------------------------------------------------------------------------
1 | function New-MSGraphMailPOSTRequest {
2 | <#
3 | .SYNOPSIS
4 | Builds a POST request for the Microsoft Graph API.
5 | .DESCRIPTION
6 | Wrapper function to build web requests for the Microsoft Graph API.
7 | .OUTPUTS
8 | Outputs an object containing the response from the web request.
9 | #>
10 | [CmdletBinding()]
11 | [OutputType([Object])]
12 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Private function - no need to support.')]
13 | param (
14 | # The request URI.
15 | [uri]$URI,
16 | # The content type for the request.
17 | [string]$ContentType,
18 | # The request body.
19 | [object]$Body,
20 | # Don't authenticate.
21 | [switch]$Anonymous,
22 | # Additional headers.
23 | [hashtable]$AdditionalHeaders = $null,
24 | # Return raw result?
25 | [switch]$Raw
26 | )
27 | if ($null -eq $Script:MSGMConnectionInformation) {
28 | Throw "Missing Microsoft Graph connection information, please run 'Connect-MSGraphMail' first."
29 | }
30 | if ($null -eq $Script:MSGMAuthenticationInformation) {
31 | Throw "Missing Microsoft Graph authentication tokens, please run 'Connect-MSGraphMail' first."
32 | }
33 | try {
34 | $WebRequestParams = @{
35 | Method = 'POST'
36 | Uri = $URI
37 | ContentType = $ContentType
38 | Anonymous = $Anonymous
39 | AdditionalHeaders = $AdditionalHeaders
40 | }
41 | if ($ContentType -like 'application/json*' -and $Body) {
42 | $WebRequestParams.Body = ConvertTo-Json -InputObject $Body -Depth 5
43 | }
44 | if ($ContentType -eq 'text/plain' -and $Body) {
45 | $WebRequestParams.Body = $Body
46 | }
47 | Write-Debug "Building new Microsoft Graph POST request with body: $($WebRequestParams | Out-String -Width 5000)"
48 | Write-Verbose "Using Content-Type: $($WebRequestParams.ContentType)"
49 | $Result = Invoke-MSGraphWebRequest @WebRequestParams
50 | if ($Result) {
51 | if ($Raw) {
52 | Return $Result
53 | } else {
54 | Return $Result.content | ConvertFrom-Json -Depth 5
55 | }
56 | } else {
57 | Throw 'No response to POST request'
58 | }
59 | } catch {
60 | New-MSGraphError $_
61 | }
62 | }
--------------------------------------------------------------------------------
/Private/New-MSGraphMailPUTRequest.ps1:
--------------------------------------------------------------------------------
1 | function New-MSGraphMailPUTRequest {
2 | <#
3 | .SYNOPSIS
4 | Builds a PUT request for the Microsoft Graph API.
5 | .DESCRIPTION
6 | Wrapper function to build web requests for the Microsoft Graph API.
7 | .OUTPUTS
8 | Outputs an object containing the response from the web request.
9 | #>
10 | [CmdletBinding()]
11 | [OutputType([Object])]
12 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Private function - no need to support.')]
13 | param (
14 | # The request URI.
15 | [uri]$URI,
16 | # The content type for the request.
17 | [string]$ContentType,
18 | # The request body.
19 | [object]$Body,
20 | # Don't authenticate.
21 | [switch]$Anonymous,
22 | # Additional headers.
23 | [hashtable]$AdditionalHeaders = $null,
24 | # Return raw result?
25 | [switch]$Raw
26 | )
27 | if ($null -eq $Script:MSGMConnectionInformation) {
28 | Throw "Missing Microsoft Graph connection information, please run 'Connect-MSGraphMail' first."
29 | }
30 | if ($null -eq $Script:MSGMAuthenticationInformation) {
31 | Throw "Missing Microsoft Graph authentication tokens, please run 'Connect-MSGraphMail' first."
32 | }
33 | try {
34 | $WebRequestParams = @{
35 | Method = 'PUT'
36 | Uri = $URI
37 | ContentType = $ContentType
38 | Anonymous = $Anonymous
39 | Body = ($Body)
40 | AdditionalHeaders = $AdditionalHeaders
41 | }
42 | #Write-Debug "Building new Microsoft Graph PUT request with body: $($WebRequestParams.Body | ConvertTo-Json | Out-String)"
43 | $Result = Invoke-MSGraphWebRequest @WebRequestParams
44 | if ($Result) {
45 | Write-Debug "Microsoft Graph request returned $($Result | Out-String)"
46 | Return $Result
47 | } else {
48 | Throw 'Failed to process PUT request.'
49 | }
50 | } catch {
51 | $ErrorRecord = @{
52 | ExceptionType = 'System.Net.Http.HttpRequestException'
53 | ErrorMessage = 'PUT request sent to the Microsoft Graph API failed.'
54 | InnerException = $_.Exception
55 | ErrorID = 'MSGraphMailPutRequestFailed'
56 | ErrorCategory = 'ProtocolError'
57 | TargetObject = $_.TargetObject
58 | ErrorDetails = $_.ErrorDetails
59 | BubbleUpDetails = $True
60 | }
61 | $RequestError = New-MSGraphErrorRecord @ErrorRecord
62 | $PSCmdlet.ThrowTerminatingError($RequestError)
63 | }
64 | }
--------------------------------------------------------------------------------
/Private/Write-CustomMessage.ps1:
--------------------------------------------------------------------------------
1 | using namespace System.Management.Automation
2 | #Requires -Version 7
3 | function Write-CustomMessage {
4 | [CmdletBinding()]
5 | param (
6 | [Parameter(Mandatory = $True)]
7 | [string]$Message,
8 | [Parameter(Mandatory = $True)]
9 | [string]$Type
10 | )
11 | Switch ($Type) {
12 | 'Success' {
13 | $ForegroundColour = 'Green'
14 | $Prefix = 'SUCCESS: '
15 | }
16 | 'Information' {
17 | $ForegroundColour = 'Blue'
18 | $Prefix = 'INFO: '
19 | }
20 | }
21 | $MessageData = [HostInformationMessage]@{
22 | Message = "$($Prefix)$($Message)"
23 | ForegroundColor = $ForegroundColour
24 | }
25 | Write-Information -MessageData $MessageData -InformationAction Continue
26 | }
--------------------------------------------------------------------------------
/Public/Connect-MSGraphMail.ps1:
--------------------------------------------------------------------------------
1 | function Connect-MSGraphMail {
2 | [CmdletBinding()]
3 | Param(
4 | # Azure AD application id.
5 | [Parameter(Mandatory = $True, ParameterSetName = 'Connect')]
6 | [string]$ApplicationID,
7 | # Azure AD application secret.
8 | [Parameter(Mandatory = $True, ParameterSetName = 'Connect')]
9 | [string]$ApplicationSecret,
10 | # Graph permission scope.
11 | [Parameter(ParameterSetName = 'Connect')]
12 | [uri]$Scope = [uri]'https://graph.microsoft.com/.default',
13 | # Tenant ID.
14 | [Parameter(ParameterSetName = 'Connect')]
15 | [string]$TenantID,
16 | # Reconnect mode
17 | [Parameter(Mandatory = $True, ParameterSetName = 'Reconnect')]
18 | [switch]$Reconnect
19 | )
20 | if ((-not $Script:MSGMAuthenticationInformation.Token) -or ([DateTime]::Now -ge $Script:MSGMAuthenticationInformation.Expires)) {
21 | if (([DateTime]::Now -ge $Script:MSGMAuthenticationInformation.Expiry)) {
22 | try {
23 | if ((-not $Script:MSGMConnectionInformation) -and (-not $Reconnect)) {
24 | $ConnectionInformation = @{
25 | ClientID = $ApplicationID
26 | ClientSecret = $ApplicationSecret
27 | Scope = $Scope
28 | URI = "https://login.microsoftonline.com/$($TenantID)/oauth2/v2.0/token"
29 | TenantID = $TenantID
30 | }
31 | New-Variable -Name 'MSGMConnectionInformation' -Value $ConnectionInformation -Scope 'Script'
32 | }
33 | $AuthenticationBody = @{
34 | client_id = $Script:MSGMConnectionInformation.ClientID
35 | client_secret = $Script:MSGMConnectionInformation.ClientSecret
36 | scope = $Script:MSGMConnectionInformation.Scope
37 | grant_type = 'client_credentials'
38 | }
39 | $AuthenticationParameters = @{
40 | URI = $Script:MSGMConnectionInformation.URI
41 | Method = 'POST'
42 | ContentType = 'application/x-www-form-urlencoded'
43 | Body = $AuthenticationBody
44 | }
45 | $TokenResponse = Invoke-WebRequest @AuthenticationParameters
46 | $TokenPayload = ($TokenResponse.Content | ConvertFrom-Json)
47 | $AuthenticationInformation = @{
48 | Token = $TokenPayload.access_token
49 | Expires = Get-TokenExpiry -ExpiresIn $TokenPayload.expires_in
50 | Type = $TokenPayload.token_type
51 | }
52 | if (-Not $Script:MSGMAuthenticationInformation) {
53 | New-Variable -Name 'MSGMAuthenticationInformation' -Value $AuthenticationInformation -Scope 'Script'
54 | } else {
55 | Set-Variable -Name 'MSGMAuthenticationInformation' -Value $AuthenticationInformation -Scope 'Script'
56 | }
57 | Write-CustomMessage -Message 'Connected to the Microsoft Graph API' -Type 'Success'
58 | } catch {
59 | $ErrorRecord = @{
60 | ExceptionType = 'System.Net.Http.HttpRequestException'
61 | ErrorMessage = "Graph API request $($_.TargetObject.Method) $($_.TargetObject.RequestUri) failed."
62 | InnerException = $_.Exception
63 | ErrorID = 'GraphAuthenticationFailed'
64 | ErrorCategory = 'ProtocolError'
65 | TargetObject = $_.TargetObject
66 | ErrorDetails = $_.ErrorDetails
67 | BubbleUpDetails = $True
68 | }
69 | $RequestError = New-MSGraphErrorRecord @ErrorRecord
70 | $PSCmdlet.ThrowTerminatingError($RequestError)
71 | }
72 | }
73 | } else {
74 | Write-CustomMessage -Message "Already connected to Microsoft Graph API." -Type 'Information'
75 | }
76 | }
--------------------------------------------------------------------------------
/Public/Get-MSGraphMail.ps1:
--------------------------------------------------------------------------------
1 | using namespace System.Management.Automation
2 | function Get-MSGraphMail {
3 | [CmdletBinding()]
4 | param (
5 | # Specify the mailbox (or UPN) to retrieve emails for.
6 | [Parameter(Mandatory = $true, ParameterSetName = 'Multi')]
7 | [Parameter(Mandatory = $true, ParameterSetName = 'Single', ValueFromPipelineByPropertyName)]
8 | [string]$Mailbox,
9 | # Retrieve a single message using a message ID.
10 | [Parameter(Mandatory = $true, ParameterSetName = 'Single', ValueFromPipelineByPropertyName)]
11 | [Alias('id')]
12 | [string[]]$MessageID,
13 | # Retrieve from folder.
14 | [Parameter(ParameterSetName = 'Multi')]
15 | [Parameter(ParameterSetName = 'Single', ValueFromPipelineByPropertyName)]
16 | [Alias('parentFolderId')]
17 | [string]$Folder,
18 | # Retrieve headers only.
19 | [Parameter(ParameterSetName = 'Multi')]
20 | [switch]$HeadersOnly,
21 | # Retrieve the message in MIME format.
22 | [Parameter(ParameterSetName = 'Single')]
23 | [switch]$MIME,
24 | # Search for emails based on a string.
25 | [Parameter(ParameterSetName = 'Multi')]
26 | [string]$Search,
27 | # Selects the specified properties.
28 | [Parameter(ParameterSetName = 'Multi')]
29 | [Parameter(ParameterSetName = 'Single')]
30 | [string[]]$Select,
31 | # Return this number of results.
32 | [Parameter(ParameterSetName = 'Multi')]
33 | [int]$PageSize = 500,
34 | # Transform the output into an object suitable for piping to other commands.
35 | [Parameter(ParameterSetName = 'Multi')]
36 | [Parameter(ParameterSetName = 'Single')]
37 | [switch]$Pipeline,
38 | # Transform the output into a summary format.
39 | [Parameter(ParameterSetName = 'Multi')]
40 | [switch]$Summary
41 | )
42 | try {
43 | $QueryStringCollection = [system.web.httputility]::ParseQueryString([string]::Empty)
44 | if ($HeadersOnly) {
45 | $QueryStringCollection.Add('$select', 'internetMessageHeaders')
46 | }
47 | if ($Search) {
48 | $QueryStringCollection.Add('$search', $Search)
49 | }
50 | if (($PageSize) -and ($PSCmdlet.ParameterSetName -ne 'Single')) {
51 | $QueryStringCollection.Add('$top', $PageSize)
52 | }
53 | if ($Select) {
54 | if ($Select.Length -gt 1) {
55 | $Select = $Select -join ','
56 | }
57 | $QueryStringCollection.Add('$select', $Select)
58 | }
59 | $RequestURI = [System.UriBuilder]::New('https', 'graph.microsoft.com')
60 | if ($MessageID) {
61 | $RequestURI.Path = "v1.0/users/$($Mailbox)/messages/$($MessageID)"
62 | $ContentType = 'application/json'
63 | if ($MIME) {
64 | $RequestURI.Path = "v1.0/users/$($Mailbox)/messages/$($MessageID)/`$value"
65 | $ContentType = 'text/plain'
66 | }
67 | } elseif ($Folder) {
68 | $RequestURI.Path = "v1.0/users/$($Mailbox)/mailfolders/$($Folder)/messages"
69 | } else {
70 | $RequestURI.Path = "v1.0/users/$($Mailbox)/messages"
71 | $ContentType = 'application/json'
72 | }
73 | if ($QueryStringCollection.Count -gt 0) {
74 | $RequestURI.Query = $QueryStringCollection.toString()
75 | }
76 | $GETRequestParameters = @{
77 | URI = $RequestURI.ToString()
78 | ContentType = $ContentType
79 | UseHTTPClient = $True
80 | }
81 | $Content = New-MSGraphMailGETRequest @GETRequestParameters
82 | if ($Content) {
83 | if (-not $MIME) {
84 | $Content = $Content | ConvertFrom-Json
85 | } else {
86 | $Result = $Content
87 | Return $Result
88 | }
89 | }
90 | if ($Content.value) {
91 | if ($Pipeline) {
92 | $Result = [PSCustomObject]@{
93 | id = $($Content).value.id
94 | mailbox = $($Content).value.toRecipients.emailAddress.address
95 | folder = $($Content).value.parentFolderId
96 | }
97 | Return $Result
98 | } elseif ($Summary) {
99 | $Content.value | ForEach-Object {
100 | $_.PSTypeNames.Insert(0, 'MSGraphMailSummary')
101 | if ($_.from) {
102 | $fromValue = Invoke-EmailObjectParser $_.from
103 | $_.PSObject.Properties.Add(
104 | [PSNoteProperty]::New('fromString', $fromValue)
105 | )
106 | }
107 | if ($_.toRecipients) {
108 | $toValue = Invoke-EmailObjectParser $_.toRecipients
109 | $_.PSObject.Properties.Add(
110 | [PSNoteProperty]::New('toString', $toValue)
111 | )
112 | }
113 | if ($_.ccRecipients) {
114 | $ccValue = Invoke-EmailObjectParser $_.ccRecipients
115 | $_.PSObject.Properties.Add(
116 | [PSNoteProperty]::New('ccString', $ccValue)
117 | )
118 | }
119 | }
120 | Return $Content.value
121 | } elseif ($Content.value) {
122 | Return $Content.value
123 | }
124 | } elseif ($Content) {
125 | if ($Pipeline) {
126 | $Result = [PSCustomObject]@{
127 | id = $($Content).id
128 | mailbox = $($Content).toRecipients.emailAddress.address
129 | }
130 | Return $Result
131 | } elseif ($Summary) {
132 | $Content | ForEach-Object {
133 | $_.PSTypeNames.Insert(0, 'MSGraphMailSummary')
134 | if ($_.from) {
135 | $fromValue = Invoke-EmailObjectParser $_.from
136 | $_.PSObject.Properties.Add(
137 | [PSNoteProperty]::New('fromString', $fromValue)
138 | )
139 | }
140 | if ($_.toRecipients) {
141 | $toValue = Invoke-EmailObjectParser $_.toRecipients
142 | $_.PSObject.Properties.Add(
143 | [PSNoteProperty]::New('toString', $toValue)
144 | )
145 | }
146 | if ($_.ccRecipients) {
147 | $ccValue = Invoke-EmailObjectParser $_.ccRecipients
148 | $_.PSObject.Properties.Add(
149 | [PSNoteProperty]::New('ccString', $ccValue)
150 | )
151 | }
152 | }
153 | Return $Content
154 | } elseif ($Content) {
155 | Return $Content
156 | }
157 | }
158 | } catch {
159 | $ErrorRecord = @{
160 | ExceptionType = 'System.Exception'
161 | ErrorMessage = "Microsoft Graph API request $($_.TargetObject.Method) $($_.TargetObject.RequestUri) failed."
162 | InnerException = $_.Exception
163 | ErrorID = 'MicrosoftGraphRequestFailed'
164 | ErrorCategory = 'ProtocolError'
165 | TargetObject = $_.TargetObject
166 | ErrorDetails = $_.ErrorDetails
167 | BubbleUpDetails = $True
168 | }
169 | $RequestError = New-MSGraphErrorRecord @ErrorRecord
170 | $PSCmdlet.ThrowTerminatingError($RequestError)
171 | }
172 | }
--------------------------------------------------------------------------------
/Public/Move-MSGraphMail.ps1:
--------------------------------------------------------------------------------
1 | function Move-MSGraphMail {
2 | [CmdletBinding()]
3 | param (
4 | # Specify the mailbox (or UPN) to move emails for.
5 | [Parameter(Mandatory = $true, ParameterSetName = 'Single', ValueFromPipelineByPropertyName)]
6 | [string]$Mailbox,
7 | # Retrieve a single message using a message ID.
8 | [Parameter(Mandatory = $true, ParameterSetName = 'Single', ValueFromPipelineByPropertyName)]
9 | [Alias('id')]
10 | [string[]]$MessageID,
11 | # Retrieve from folder.
12 | [Parameter(ParameterSetName = 'Single', ValueFromPipelineByPropertyName)]
13 | [Alias('parentFolderId')]
14 | [string]$Folder,
15 | # Destination.
16 | [string]$Destination = 'deleteditems'
17 | )
18 | try {
19 | $CommandName = $MyInvocation.InvocationName
20 | $MoveParams = @{
21 | destinationId = $Destination
22 | }
23 | $RequestURI = [System.UriBuilder]::New('https', 'graph.microsoft.com')
24 | if ($Folder) {
25 | $RequestURI.Path = "v1.0/users/$($Mailbox)/mailfolders/$($Folder)/messages/$($MessageID)/move"
26 | } else {
27 | $RequestURI.Path = "v1.0/users/$($Mailbox)/messages/$($MessageID)/move"
28 | }
29 | $POSTRequestParams = @{
30 | URI = $RequestURI.ToString()
31 | ContentType = 'application/json'
32 | Body = $MoveParams
33 | }
34 | $Message = New-MSGraphMailPOSTRequest @POSTRequestParams
35 | Write-Debug "Microsoft Graph returned $($Message)"
36 | if ($Message) {
37 | Write-CustomMessage -Message "Moved message '$($Message.subject)' with ID $($Message.id) to folder $($Message.parentFolderId)" -Type 'Success'
38 | }
39 | } catch {
40 | $Command = $CommandName -Replace '-', ''
41 | $ErrorRecord = @{
42 | ExceptionType = 'System.Exception'
43 | ErrorMessage = "$($CommandName) failed."
44 | InnerException = $_.Exception
45 | ErrorID = "MicrosoftGraph$($Command)CommandFailed"
46 | ErrorCategory = 'ReadError'
47 | TargetObject = $_.TargetObject
48 | ErrorDetails = $_.ErrorDetails
49 | BubbleUpDetails = $True
50 | }
51 | $CommandError = New-MSGraphErrorRecord @ErrorRecord
52 | $PSCmdlet.ThrowTerminatingError($CommandError)
53 | }
54 | }
--------------------------------------------------------------------------------
/Public/New-MSGraphMail.ps1:
--------------------------------------------------------------------------------
1 | function New-MSGraphMail {
2 | [CmdletBinding()]
3 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Does not change system state.')]
4 | param (
5 | [Parameter(Mandatory = $true, ParameterSetName = 'MIME')]
6 | [String]$Mailbox,
7 | [Parameter(Mandatory = $true, ParameterSetName = 'Standard')]
8 | [String[]]$From,
9 | [Parameter(Mandatory = $true, ParameterSetName = 'Standard')]
10 | [String[]]$To,
11 | [Parameter(ParameterSetName = 'Standard')]
12 | [String[]]$CC,
13 | [Parameter(ParameterSetName = 'Standard')]
14 | [String[]]$BCC,
15 | [Parameter(ParameterSetName = 'Standard')]
16 | [String]$Subject,
17 | [Parameter(Mandatory = $true, ParameterSetName = 'Standard')]
18 | [String]$BodyContent,
19 | [Parameter(ParameterSetName = 'Standard')]
20 | [String]$FooterContent,
21 | [Parameter(Mandatory = $true, ParameterSetName = 'Standard')]
22 | [ValidateSet('HTML', 'text')]
23 | [string]$BodyFormat,
24 | [Parameter(Mandatory = $true, ParameterSetName = 'MIME')]
25 | [String]$MIMEMessage,
26 | [Parameter(ParameterSetName = 'Standard')]
27 | [Parameter(ParameterSetName = 'MIME')]
28 | [String]$Folder,
29 | [Parameter(ParameterSetName = 'Standard')]
30 | [String[]]$Attachments,
31 | [Parameter(ParameterSetName = 'Standard')]
32 | [String[]]$InlineAttachments,
33 | [Parameter(ParameterSetName = 'Standard')]
34 | [Switch]$Draft,
35 | [Parameter(ParameterSetName = 'Standard')]
36 | [Switch]$RequestDeliveryReceipt,
37 | [Parameter(ParameterSetName = 'Standard')]
38 | [Switch]$RequestReadReceipt,
39 | [Parameter(ParameterSetName = 'Standard')]
40 | [Parameter(ParameterSetName = 'MIME')]
41 | [Switch]$Pipeline,
42 | [Parameter(ParameterSetName = 'Standard')]
43 | [Parameter(ParameterSetName = 'MIME')]
44 | [Switch]$Send,
45 | [Parameter(ParameterSetName = 'Standard')]
46 | [Parameter(ParameterSetName = 'MIME')]
47 | [Switch]$SaveandSend
48 | )
49 | try {
50 | Write-Verbose "Using parameter set $($PSCmdlet.ParameterSetName)."
51 | if ($PSCmdlet.ParameterSetName -eq 'Standard') {
52 | $MailFrom = Invoke-EmailStringParser -Strings $From
53 | $MailTo = Invoke-EmailStringParser -Strings @($To)
54 | if ($CC) {
55 | $MailCC = Invoke-EmailStringParser -Strings @($CC)
56 | } else {
57 | $MailCC = @()
58 | }
59 | if ($BCC) {
60 | $MailBCC = Invoke-EmailStringParser -Strings @($BCC)
61 | } else {
62 | $MailBCC = @()
63 | }
64 | if ($Draft) {
65 | $MailParams.isDraft = $true
66 | }
67 | if ($RequestDeliveryReceipt) {
68 | $MailParams.isDeliveryReceiptRequested = $true
69 | }
70 | if ($RequestReadReceipt) {
71 | $MailParams.isReadReceiptRequested = $true
72 | }
73 | $MailBody = New-MSGraphMailBody -BodyFormat $BodyFormat -BodyContent $BodyContent -FooterContent $FooterContent
74 | $MailParams = @{
75 | toRecipients = @($MailTo)
76 | from = $MailFrom
77 | subject = $Subject
78 | body = $MailBody
79 | ccRecipients = @($MailCC)
80 | bccRecipients = @($MailBCC)
81 | }
82 | $ContentType = 'application/json; charset=utf-8'
83 | } elseif ($PSCmdlet.ParameterSetName -eq 'MIME') {
84 | $MailParams = $MIMEMessage
85 | $ContentType = 'text/plain'
86 | }
87 | $RequestURI = [System.UriBuilder]::New('https', 'graph.microsoft.com')
88 | if ($Folder) {
89 | $MessageBody = $MailParams
90 | if ($PSCmdlet.ParameterSetName -eq 'Standard') {
91 | $RequestURI.Path = "v1.0/users/$($MailFrom.EmailAddress.Address)/mailfolders/$($Folder)/messages"
92 | } elseif ($PSCmdlet.ParameterSetName -eq 'MIME') {
93 | $RequestURI.Path = "v1.0/users/$($Mailbox)/mailfolders/$($Folder)/messages"
94 | }
95 | } elseif ($Send) {
96 | if ($PSCmdlet.ParameterSetName -eq 'Standard') {
97 | $MessageBody = @{
98 | message = $MailParams
99 | saveToSentItems = $true
100 | }
101 | $RequestURI.Path = "v1.0/users/$($MailFrom.EmailAddress.Address)/sendmail"
102 | } elseif ($PSCmdlet.ParameterSetName -eq 'MIME') {
103 | $MessageBody = $MailParams
104 | $RequestURI.Path = "v1.0/users/$($Mailbox)/sendmail"
105 | }
106 |
107 | } else {
108 | $MessageBody = $MailParams
109 | if ($PSCmdlet.ParameterSetName -eq 'Standard') {
110 | $RequestURI.Path = "v1.0/users/$($MailFrom.EmailAddress.Address)/messages"
111 | } elseif ($PSCmdlet.ParameterSetName -eq 'MIME') {
112 | $RequestURI.Path = "v1.0/users/$($Mailbox)/messages"
113 | }
114 | }
115 | $POSTRequestParams = @{
116 | URI = $RequestURI.ToString()
117 | ContentType = $ContentType
118 | Body = $MessageBody
119 | }
120 | $Message = New-MSGraphMailPOSTRequest @POSTRequestParams
121 | Write-Debug "Microsoft Graph returned $($Message)"
122 | if ($Message) {
123 | Write-CustomMessage -Message "Created message '$($Message.subject)' with ID $($Message.id)" -Type 'Success'
124 | }
125 | if ($Attachments) {
126 | $AttachmentParams = @{
127 | Mailbox = $MailFrom.EmailAddress.Address
128 | MessageID = $Message.id
129 | Attachments = $Attachments
130 | }
131 | New-MSGraphMailAttachment @AttachmentParams | Out-Null
132 | }
133 | if ($InlineAttachments) {
134 | $InlineAttachmentParams = @{
135 | Mailbox = $MailFrom.EmailAddress.Address
136 | MessageID = $Message.id
137 | Attachments = $InlineAttachments
138 | InlineAttachments = $True
139 | }
140 | New-MSGraphMailAttachment @InlineAttachmentParams | Out-Null
141 | }
142 | if ($Pipeline -and $Message) {
143 | $Result = [PSCustomObject]@{
144 | id = $($Message).id
145 | mailbox = $MailFrom.EmailAddress.Address
146 | folder = $($Message).parentFolderId
147 | }
148 | Return $Result
149 | } elseif ($SaveandSend) {
150 | $SendParams = @{
151 | MessageID = $($Message).id
152 | Mailbox = $MailFrom.EmailAddress.Address
153 | Folder = $($Message).parentFolderId
154 | }
155 | Send-MSGraphMail @SendParams
156 | } elseif ($Message) {
157 | Return $Message
158 | }
159 | } catch {
160 | New-MSGraphError $_
161 | }
162 | }
--------------------------------------------------------------------------------
/Public/Remove-MSGraphMail.ps1:
--------------------------------------------------------------------------------
1 | function Remove-MSGraphMail {
2 | [CmdletBinding( SupportsShouldProcess = $True, ConfirmImpact = 'High' )]
3 | param (
4 | # Specify the mailbox (or UPN) to remove an email for.
5 | [Parameter(Mandatory = $true, ParameterSetName = 'Single', ValueFromPipelineByPropertyName)]
6 | [string]$Mailbox,
7 | # The ID of the message to remove.
8 | [Parameter(Mandatory = $true, ParameterSetName = 'Single', ValueFromPipelineByPropertyName)]
9 | [Alias('id')]
10 | [string[]]$MessageID,
11 | # Retrieve from folder.
12 | [Parameter(ParameterSetName = 'Single', ValueFromPipelineByPropertyName)]
13 | [Alias('parentFolderId')]
14 | [string]$Folder
15 | )
16 | try {
17 | $CommandName = $MyInvocation.InvocationName
18 | $RequestURI = [System.UriBuilder]::New('https', 'graph.microsoft.com')
19 | if ($Folder) {
20 | $RequestURI.Path = "v1.0/users/$($Mailbox)/mailfolders/$($Folder)/messages$($MessageID)"
21 | } else {
22 | $RequestURI.Path = "v1.0/users/$($Mailbox)/messages/$($MessageID)"
23 | }
24 | $DELETERequestParams = @{
25 | URI = $RequestURI.ToString()
26 | ContentType = 'application/json'
27 | }
28 | if ($PSCmdlet.ShouldProcess("Message $($MessageID)", 'Delete')) {
29 | $Result = New-MSGraphMailDELETERequest @DELETERequestParams
30 | if ($Result.StatusCode -eq 204) {
31 | Write-CustomMessage -Message "Removed message with ID $($MessageID)" -Type 'Success'
32 | }
33 | }
34 | } catch {
35 | $Command = $CommandName -Replace '-', ''
36 | $ErrorRecord = @{
37 | ExceptionType = 'System.Exception'
38 | ErrorMessage = "$($CommandName) failed."
39 | InnerException = $_.Exception
40 | ErrorID = "MicrosoftGraph$($Command)CommandFailed"
41 | ErrorCategory = 'ReadError'
42 | TargetObject = $_.TargetObject
43 | ErrorDetails = $_.ErrorDetails
44 | BubbleUpDetails = $True
45 | }
46 | $CommandError = New-MSGraphErrorRecord @ErrorRecord
47 | $PSCmdlet.ThrowTerminatingError($CommandError)
48 | }
49 | }
--------------------------------------------------------------------------------
/Public/Send-MSGraphMail.ps1:
--------------------------------------------------------------------------------
1 | function Send-MSGraphMail {
2 | [CmdletBinding()]
3 | param (
4 | # Specify the mailbox (or UPN) to move emails for.
5 | [Parameter(Mandatory = $true, ParameterSetName = 'Single', ValueFromPipelineByPropertyName)]
6 | [string]$Mailbox,
7 | # Retrieve a single message using a message ID.
8 | [Parameter(Mandatory = $true, ParameterSetName = 'Single', ValueFromPipelineByPropertyName)]
9 | [Alias('id')]
10 | [string[]]$MessageID,
11 | # Retrieve from folder.
12 | [Parameter(ParameterSetName = 'Single', ValueFromPipelineByPropertyName)]
13 | [Alias('parentFolderId')]
14 | [string]$Folder
15 | )
16 | try {
17 | $RequestURI = [System.UriBuilder]::New('https', 'graph.microsoft.com')
18 | if ($Folder) {
19 | $RequestURI.Path = "v1.0/users/$($Mailbox)/mailfolders/$($Folder)/messages/$($MessageID)/send"
20 | } else {
21 | $RequestURI.Path = "v1.0/users/$($Mailbox)/messages/$($MessageID)/send"
22 | }
23 | $POSTRequestParams = @{
24 | URI = $RequestURI.ToString()
25 | ContentType = 'application/json; charset=utf-8'
26 | }
27 | $Message = New-MSGraphMailPOSTRequest @POSTRequestParams
28 | Write-Debug "Microsoft Graph returned $($Message)"
29 | if ($Message) {
30 | Write-CustomMessage -Message "Sent message '$($Message.subject)' with ID $($Message.id)" -Type 'Success'
31 | }
32 | } catch {
33 | New-MSGraphError $_
34 | }
35 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MS Graph Mail - A pure-PowerShell Graph API mail client
2 |
3 |  
4 |
5 | ## Preparations
6 |
7 | You will need the following:
8 |
9 | 1. An [**Azure AD Application ID**](https://aad.portal.azure.com)
10 | 2. An **Azure AD Application Secret**
11 | 3. An **Azure AD Tenant ID**
12 | 4. [**PowerShell 7**](https://aka.ms/powershell-release?tag=stable) installed on your Windows, Linux or MacOS device.
13 | 5. The code from this module in your PowerShell modules folder find this by running `$env:PSModulePath` in your PowerShell 7 session. Install from PSGallery with `Install-Module MSGraphMail`
14 |
15 | ## Import the Module
16 |
17 | Run `Import-Module 'MSGraphMail'` to load the module into your current session.
18 |
19 | ## Connecting to the Microsoft Graph API
20 |
21 | Connecting to the Microsoft Graph API uses the Azure AD Application information and the Connect-MSGraphMail client application.
22 |
23 | Using the **Splatting** technique:
24 |
25 | Splatting is a system in PowerShell that lets us put our parameters in a nicely formatted easy to read object (a HashTable to be specific!) and then "splat" them at the command. To do this, first things first setup a PowerShell object to hold your credentials. For example:
26 |
27 | ```powershell
28 | $MSGraphMailConnectionParameters = @{
29 | ApplicationID = ''
30 | ApplicationSecret = ''
31 | TenantID = ''
32 | }
33 | Connect-MSGraphMail @MSGraphMailConnectionParameters
34 | ```
35 |
36 | Using the **Traditional** technique:
37 |
38 | If you don't want to - or can't "splat" - we can fall back on a more traditional route:
39 |
40 | ```powershell
41 | Connect-MSGraphMail -ApplicationID '' -ApplicationSecret '' -TenantID ''
42 | ```
43 |
44 | ## Getting Emails
45 |
46 | Getting emails hinges around a single command `Get-MSGraphMail` at it's most basic this looks like this.
47 |
48 | Using the **Splatting** technique:
49 |
50 | ```powershell
51 | $MailParameters = @{
52 | Mailbox = 'you@example.uk'
53 | }
54 | Get-MSGraphMail @MailParameters
55 | ```
56 |
57 | Using the **Traditional** technique:
58 |
59 | ```powershell
60 | Get-MSGraphMail -Mailbox 'you@example.uk'
61 | ```
62 |
63 | You can get more specific with the following parameters:
64 |
65 | * **MessageID** - Retrieves a single message by ID.
66 | * **Folder** - Retrieves messages (or a single message) from a specific folder.
67 | * **HeadersOnly** - Retrieves only the message headers.
68 | * **MIME** - Retrieves a single message in MIME format (Requires **MessageID**).
69 | * **Search** - Searches emails based on a string.
70 | * **PageSize** - Retrieves only the given number of results.
71 | * **Pipeline** - Formats the output for Pipelining to other commands - like `Move-MSGraphMail` or `Delete-MSGraphMail`.
72 | * **Select** - Retrieves only the specified fields from the Graph API.
73 | * **Summary** - Displays a summary of the message(s) retrieved. See #1 for details.
74 |
75 | ## Creating an E-Mail
76 |
77 | Creating an email requires passing parameters to the `New-MSGraphMail` commandlet like so:
78 |
79 | Using the **Splatting** technique:
80 |
81 | ```powershell
82 | $MailParameters = @{
83 | From = 'You '
84 | To = 'Them ', 'Someone '
85 | Subject = 'Your invoice #1234 is ready.'
86 | BodyContent = 'X:\Emails\BodyContent.txt'
87 | FooterContent = 'X:\Emails\FooterContent.txt'
88 | Attachments = 'X:\Files\SendtoExample.docx','X:\Files\SendToExample.zip'
89 | BodyFormat = 'text'
90 | }
91 | New-MSGraphMail @MailParameters
92 | ```
93 |
94 | Using the **Traditional** technique:
95 |
96 | ```powershell
97 | New-MSGraphMail -From 'You ' -To 'Them ', 'Someone ' -Subject 'Your invoice #1234 is ready.' -BodyContent 'X:\Emails\BodyContent.txt' -FooterContent 'X:\Emails\FooterContent.txt' -Attachments 'X:\Files\SendtoExample.docx','X:\Files\SendToExample.zip' -BodyFormat 'text'
98 | ```
99 |
100 | If this works we'll see:
101 |
102 | > SUCCESS: Created message 'Your invoice #1234 is ready.' with ID AAMkADg0MTI1YTY5LTZhNTAtNGY2Ni1iYmFmLTYyNTIxNmQ3ZTAyMQBGAAAAAADcjV4oGXn1Sb6mQOgHYL6tBwAynr9oS8bwR42_Ec20-qUkAAAAAAEQAAAynr9oS8bwR42_Ec20-qUkAAcuZgfeAAA=
103 |
104 | A draft email will have appeared in the account provided to `From`. Unless you specify the `-Send` parameter which immediately sends the email bypassing the draft creation.
105 |
106 | You can use inline attachments by using `-InlineAttachments` and specifying attachments in the format `'cid;filepath'` e.g:
107 |
108 | ```powershell
109 | New-MSGraphMail -From 'You ' -To 'Them ', 'Someone ' -Subject 'Your invoice #1234 is ready.' -BodyContent 'X:\Emails\BodyContent.html' -FooterContent 'X:\Emails\FooterContent.html' -Attachments 'X:\Files\SendtoExample.docx','X:\Files\SendToExample.zip' -BodyFormat 'html' -InlineAttachments 'signaturelogo;X\Common\EmailSignatureLogo.png', 'productlogo;X:\Products\Widgetiser\WidgetiserLogoEmail.png'
110 | ```
111 |
112 | The two inline attachments would map to:
113 |
114 | ```html
115 |
116 | ```
117 |
118 | and
119 |
120 | ```html
121 |
122 | ```
123 |
124 | respectively.
125 |
126 | ## Sending an E-Mail
127 |
128 | Sending an email requires one small alteration of the above command - adding:
129 |
130 | ```powershell
131 | Pipeline = $True
132 | ```
133 |
134 | if splatting or
135 |
136 | ```powershell
137 | -Pipeline
138 | ```
139 |
140 | if using traditional parameter passing.
141 |
142 | This tells the command that we're going to pipeline the output - specifically that we're going to send it to another command. In our case we'd end up doing:
143 |
144 | ```powershell
145 | New-MSGraphMail @MailParameters | Send-MSGraphMail
146 | ```
147 |
148 | The important part here is `| Send-MSGraphMail` quite literally `|` or **Pipe** and then the next command.
149 |
150 | ## Moving an E-Mail
151 |
152 | Moving an email requires one small alteration of the Get or New command - adding:
153 |
154 | ```powershell
155 | Pipeline = $True
156 | ```
157 |
158 | if splatting or
159 |
160 | ```powershell
161 | -Pipeline
162 | ```
163 |
164 | if using traditional parameter passing.
165 |
166 | This tells the command that we're going to pipeline the output - specifically that we're going to send it to another command. In our case we'd end up doing:
167 |
168 | ```powershell
169 | New-MSGraphMail @MailParameters | Move-MSGraphMail -Destination 'deleteditems'
170 | ```
171 |
172 | The `-Destination` parameter for `Move-MSGraphMail` accepts a "well known folder name" e.g: "deleteditems" or "drafts" or "inbox" or a Folder ID.
173 |
174 | The important part here is `| Move-MSGraphMail` quite literally **Pipe (|)** and then the next command.
175 |
176 | ## Deleting an E-Mail
177 |
178 | If you want to "permanently" delete an email you can pipe the email to the `Remove-MSGraphMail` command. Similar to moving an email this works as so:
179 |
180 | ```powershell
181 | Get-MSGraphMail @MailParameters | Remove-MSGraphMail -Confirm:$False
182 | ```
183 |
184 | Disecting this - we're getting the mail and then passing it down the pipeline an telling `Remove-MSGraphMail` not to prompt us for permission by setting `-Confirm:$False`
185 |
--------------------------------------------------------------------------------