Loading...

How to perform operations if a command is not covered by the CLI for Microsoft 365

How to perform operations if a command is not covered by the CLI for Microsoft 365

One of the most powerful tools a Microsoft 365 user has is the CLI for Microsoft 365. The command line allows any user to get a lot of things done in a fast way. There is no boundary to the number of things a seasoned user can do by merely using the CLI for Microsoft 365.

This script shows how to perform operations if a command is not covered by the CLI for Microsoft 365.

Right now, AttachmentFiles property associated with a SharePoint list item is not available in CLI for Microsoft 365, so we need to execute a separate query to /_api/web/lists/getByTitle('list-title')/items(item-id)/AttachmentFiles endpoint to get the item attachments.

To call AttachmentFiles endpoint, we must acquire an access token from the Microsoft identity platform. To do this we can use m365 util accesstoken get command and attach the access token with AttachmentFiles endpoint as shown in this script.

Prerequisites

PowerShell Script

Function Get-ListAttachments() {
    param
    (
        [Parameter(Mandatory = $true)] [string] $AccessToken,
        [Parameter(Mandatory = $true)] [string] $SiteURL,
        [Parameter(Mandatory = $true)] [string] $ListTitle,
        [Parameter(Mandatory = $true)] [int] $ItemId
    )   
    Try {
        $ListItemAttachmentsEndPoint = "$($SiteURL)/_api/web/lists/getbytitle('$($ListTitle)')/items($($ItemId))/AttachmentFiles"
        $Header = @{
            "Authorization" = "Bearer $($AccessToken)"
            "Accept"        = "application/json; odata=verbose" 
            "Content-Type"  = "application/json "
        }
        $ListItemAttachments = Invoke-RestMethod -Uri $ListItemAttachmentsEndPoint -Headers $Header -Method Get  
        return $ListItemAttachments.d.results
    }
    Catch {
        throw "Error Getting List Item Attachments! $($_.Exception.Message)" 
    }
}
Function Download-ListAttachments() {
    param
    (
        [Parameter(Mandatory = $true)] [string] $TenantName,
        [Parameter(Mandatory = $true)] [string] $SiteURL,
        [Parameter(Mandatory = $true)] [string] $ListTitle,
        [Parameter(Mandatory = $true)] [string] $DownloadDirectory
    )   
    Try {

        #Get All Items from the List
        $ListItems = m365 spo listitem list --webUrl $SiteURL --title $ListTitle -o json | ConvertFrom-Json -AsHashtable
         
        #Iterate through each list item
        Foreach ($Item in $ListItems) {
            Try {
                Write-Output "Processing Item Id $($Item.Id)"

                # Right now AttachmentFiles property is not available in cli-microsoft365 so we need to execute a separate query to /_api/web/lists/getByTitle('list-title')/items(item-id)/AttachmentFiles to get the item attachments. 
                # AttachmentFiles endpoint requires access token 
                $AccessToken = m365 util accesstoken get --resource "https://$($TenantName).sharepoint.com" --new 

                #Get All attachments from the List Item
                $Attachments = Get-ListAttachments -AccessToken $AccessToken -SiteURL $SiteURL -ListTitle $ListTitle -ItemId $Item.Id

                If ($Attachments.Length -gt 0) {
                    #Create directory for each list item if it doesn't exist
                    $TargetDownloadDirectory = "$($DownloadDirectory)/$($Item.Id)"
                    If (!(Test-Path -path $TargetDownloadDirectory)) { New-Item $TargetDownloadDirectory -type Directory | Out-Null }

                    foreach ($Attachment in $Attachments) {
                        Try {
                            Write-Output "Downloading $($Attachment.FileName)"
                            $TargetFilePath = "$($TargetDownloadDirectory)/$($Attachment.FileName)"
                            #Download attachment
                            m365 spo file get --webUrl $SiteURL --url $Attachment.ServerRelativeUrl --asFile --path $TargetFilePath
                        }
                        Catch {
                            Write-Error "Error Downloading This Attachment! $($_.Exception.Message)" 
                        }
                    }
                }
                else {
                    Write-Warning "Attachments Not Found For This List Item!"
                }
            }
            Catch {
                Write-Error "Error Downloading This List Item Attachments! $($_.Exception.Message)"
            }
        }
    }
    Catch {
        Write-Error "Error Downloading List Attachments! $($_.Exception.Message)"
    }
}

#Set Parameters
$TenantName = "tenant-name"
$SiteRelativePath = "site-relative-path"
$ListTitle = "list-title"

$DownloadDirectory = "$($PSScriptRoot)/$($ListTitle)"
$SiteURL = "https://$($TenantName).sharepoint.com/$($SiteRelativePath)"

#Call the function to download list items attachments
Download-ListAttachments -TenantName $TenantName -SiteURL $SiteURL -ListTitle $ListTitle -DownloadDirectory $DownloadDirectory

Published on:

Learn more
Need help with this product?

We can help you with How to perform operations if a command is not covered by the CLI for Microsoft 365

If you want help implementing, troubleshooting, or improving this product, contact us and we’ll point you in the right direction.

Home | Joseph Velliah
Home | Joseph Velliah

Fulfilling God’s purpose for my life

Share post:

Related posts

Jev Does Not Paint: Putting TypeSafe in Front of Gemini

I wanted a reason to use TypeSafe that was not another chatbot wrapper. Most of the AI I wire into software still wants to talk. I needed some...

5 days ago

I Asked God to Hold My Hand

Almost 20 years ago, I was waiting outside my company to collect my documents and start my first IT job. I sat under a banyan tree. That day ...

13 days ago

Identity-Aware SRE Agents with kagent on Akamai LKE

I wanted a reason to put an AI agent in front of a real Kubernetes cluster and watch what happens when two different people ask it to fix the ...

26 days ago

Vasanam Studio: How I Built a Bible Verse Video Generator for My Church as a Hobby Project

Every morning at 5 AM, the women of my church gather for prayer. At the end of the session, our pastor’s wife shares a Bible verse and sends a...

3 months ago

The demo worked. That was the problem.

Over a weekend I built a small Kubernetes demo to play with zero trust. Three little services calling each other in a chain, a login page in f...

4 months ago

Notes from building an agent on AgentCore end to end

I wanted a reason to use AgentCore end to end. Runtime, memory, guardrails, identity, the whole thing. A Bible Q&A agent felt like a good ...

5 months ago

Building a Rust gRPC AI Security Gateway for LLM Traffic

I wanted a small, honest implementation of the GenAI governance shape in code: a component on every LLM call that applies policy first, option...

6 months ago

Claude Code Security: The Smart Way to Integrate AI

Anthropic just dropped Claude Code Security, and if you’re anywhere near AppSec or DevSecOps, you’ve probably already seen the debate lighting...

7 months ago

How I Built a Semantic Cache Using Only AWS Services

LLM calls are expensive and slow, but here’s the thing - users ask the same questions in different ways all the time. “What’s your refund poli...

8 months ago

How to Build Better AI Agent Tools: Cut Costs by 70% (MCP Server Case Study)

Building tools for AI agents isn’t the same as building regular APIs. This guide shows you how to design tools that reduce token costs by 60-7...

8 months ago

Newsletter

Get the latest Dynamics 365 and Power Platform content in your inbox

A curated digest of community blogs, product news, videos, and podcasts — delivered without the noise.

Weekly updates Unsubscribe anytime Fresh community picks
We use your email only for the newsletter and you can unsubscribe at any time.
By subscribing, you agree to the privacy policy.