Problems saving attachments to the local drive with Get-MgUserMessageAttachment

I’m trying to automate saving reports to a local server and adding the data to a SQL database.
I’ve got most of the script up and running, but I’m having issues with the actual saving files part.

Right now I’ve got the following code:

$MessageList = Get-MgUserMessage -UserId $MailBox -Filter "isRead eq false"

$ArchiveFolder = Get-MgUserMailFolder -UserId $MailBox -Filter "displayName eq 'Archive'"

foreach ($message in $MessageList) {
    $AttachmentList = Get-MgUserMessageAttachment -UserId $MailBox -MessageId $message.Id
    foreach ($attachment in $AttachmentList) {
        if ($attachment.AdditionalProperties.'@odata.type' -eq '#microsoft.graph.fileAttachment') {
            Write-Output "Saving attachment: $($attachment.Name)"
            Write-Output "Content: $($attachment.AdditionalProperties.ContentBytes)"
            $filePath = "C:\Temp\" + $attachment.Name
            $ContentBytes = $attachment.AdditionalProperties.ContentBytes
            if ($ContentBytes) {
              [System.IO.File]::WriteAllBytes($filePath, [System.Convert]::FromBase64String($ContentBytes))
            }
            else {
              Write-Output "No content found in: $($attachment.Name)"
            }
        }
    }
    # Update-MgUserMessage -UserId $MailBox -MessageId $message.Id -BodyParameter @{isRead = $true}

    # $destinationFolderId = $ArchiveFolder.Id
    # Move-MgUserMessage -UserId $MailBox -MessageId $message.Id -DestinationId $destinationFolderId
}

As you can tell the final steps are supposed to be marking the messages as read and moving them to the Archive folder so they won’t be processed again. However, as the script looks right now I’m just getting the “Saving attachment: AttachmentName” and “No content found in: AttachmentName” output.

Initiall I didn’t have the if ($ContentBytes) block and then it actually created files in C:\Temp\ with the correct file names, but they were all empty.

If run this manually on one message/attachment it works:

$at = Get-MgUserMessageAttachment -UserId $MailBox -MessageId $MessageList[0].Id
$at | select * 

ContentType          : application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
Id                   : [AttachmentId]
IsInline             : False
LastModifiedDateTime : 2025-04-08 08:06:57
Name                 : [AttachmentName]
Size                 : 18249
AdditionalProperties : {[@odata.type, #microsoft.graph.fileAttachment], [@odata.mediaContentType,
                       application/vnd.openxmlformats-officedocument.spreadsheetml.sheet], [contentId,
                       [ID].PROD.OUTLOOK.COM], [contentBytes, [AttachmentContent]]

$fPath = 'C:\Temp\' + $at.Name
[System.IO.File]::WriteAllBytes($fPath, [System.Convert]::FromBase64String($at.AdditionalProperties.contentBytes))

This gives me a working 17,3 kB Excel-file in the local C:\Temp\ folder.

I’m probably missing something simple here, but I’ve been going over this a number of times and it seems to me that the $attachment in the inner loop should be the same as the $at when I’m doing it manually.

Laage,

Stepping through your code I ran into the same problem as you. I found this is due to case sensitivity when accessing the keys of the “AdditionalProperties” dictionary. You should be good to update the following line:

$ContentBytes = $attachment.AdditionalProperties.ContentBytes

To

$ContentBytes = $attachment.AdditionalProperties.contentBytes
2 Likes

@LearnLaughOps
Thank you! Sometimes you need another pair of eyes.

Interesting that it switches from PascalCase to camelCase on the second part of the dot notation.

1 Like