Tag: SharePoint Online

  • Assign Sensitivity Labels In SharePoint with MSGraph (PowerShell)

    What are Sensitvity Labels?

    Organizations have the need to tag their documents regarding sensitivity. This need is arising among ISO 27001. Sensitivity labels in Microsoft 365 enable organizations to tag documents regarding their sensitivity. In general the sensitivity of a document can be classified roughly in three categories: public, confidential, highly confidential. Each organization might have a different understanding when to use which sensitivity label.

    How to assign Sensitivity Labels in SharePoint with PowerShell?

    In this article, I will describe following four steps to assign sensitivity labels with PowerShell:

    1. Make sure that sensitivity Labels are enabled
    2. Create Azure AD App Registration with the permissions to assign sensitivity labels for SharePoint Sites
    3. Enable metered APIs and Service in Microsoft Graph.
    4. Run the script, which is provided in this article

    How to enable Sensitivtiy Labels for SharePoint? (Short Summary)

    In order to enable sensitivity labels, you need to enable sensitivity labels in Microsofts Purview (Compliance Center) as a global administrator.

    1. Sign in to Purview Portal as a global administrator
    2. Click on Information protection -> Labels
      screenshot of Azure purview portal showing the protection selection
    3. Click on Turn on now
      Screenshot of button to turn on sensitivity labels in SharePoint

    Now you can set Sensitivity Labels in SharePoint.

    Assign sensitivity Label in SharePoint Online - Browser view

    Details about about supported file types, limitations etc. can be obtained here: Enable sensitivity labels for Office files | Microsoft Learn

    How to configure the Azure App Registration to set Sensitivity Labels in SharePoint

    Make sure that you have configured following permissions for your Azure App Registration if you want to authenticate via client secret or certificate. This permissions allow you to update documents in all SharePoint Sites:


    Sites.ReadWrite.All
    ApplicationRead and write items in all site collections

    If you want to do it only on one Site, use Sites.Selected permission and add the permission as per following documentation:

    If you want to get familiar on how to create an Azure App registration, check out following article, where I describe in detail, what an Azure App Registration is and how you can configure it in order to use MS Graph.
    How to configure Azure App registration for MS Graph | SPO Scripts

    I have configured the app with the Sites.ReadWrite.All permission and authenticate with a certificate.

    Screenshot of Azure App registration permissions

    Enable Metered Graph API services for MS Graph

    The API to assigning sensitivity labels for SharePoint files involve additional cost for metered API services. In order to set up metered Graph API services, I have created an resource by following the article: Enable metered APIs and services in Microsoft Graph. The pricing is based on the ammount of API calls (currently $0.00185 (USD) per API call as per 09th October 2023). Metered APIs are currently only available in Microsoft Global environment.

    You can create the resource for metered APIs by running following cmdlet in the Azure cloud shell:

    az resource create --resource-group myRG --name myGraphAppBilling --resource-type Microsoft.GraphServices/accounts --properties  "{`"appId`": `"myAppGUID`"}" --location Global --subscription mySubscriptionGUID

    In my case it looks like this:

    az resource create --resource-group MeteredAPI --name myGraphAppBilling --resource-type Microsoft.GraphServices/accounts --properties  "{`"appId`": `"90747925-550c-4658-850f-b19d26ed1a43`"}" --location Global --subscription "Azure subscription 1"

    Running it in Azure cloud shell, I received following output:

    PS /home/mod> az resource create --resource-group MeteredAPI --name myGraphAppBilling --resource-type Microsoft.GraphServices/accounts --properties  "{`"appId`": `"90747925-550c-4658-850f-b19d26ed1a43`"}" --location Global --subscription "Azure subscription 1"
    Resource provider 'Microsoft.GraphServices' used by this operation is not registered. We are registering for you.
    
    Registration succeeded.
    {
      "extendedLocation": null,
      "id": "/subscriptions/ff5e7ad9-f695-45b4-9a68-e0bb85c02731/resourceGroups/MeteredAPI/providers/Microsoft.GraphServices/accounts/myGraphAppBilling",
      "identity": null,
      "kind": null,
      "location": "Global",
      "managedBy": null,
      "name": "myGraphAppBilling",
      "plan": null,
      "properties": {
        "appId": "90747925-550c-4658-850f-b19d26ed1a43",
        "billingPlanId": "e81f3875-03e9-47ee-b1db-5da2697468bb",
        "provisioningState": "Succeeded"
      },
      "resourceGroup": "MeteredAPI",
      "sku": null,
      "systemData": {
        "createdAt": "2023-09-23T20:04:50.6961011Z",
        "createdByType": "User",
        "lastModifiedAt": "2023-09-23T20:04:50.6961011Z",
        "lastModifiedByType": "User"
      },
      "tags": null,
      "type": "microsoft.graphservices/accounts"
    }
    PS /home/mod>                                              

    If you try it without the registration, you will receive following error message

    Set-MgDriveItemSensitivityLabel_AssignExpanded: Payment Required. Ensure that your application is able to call this premium API.For details see https://aka.ms/graph-metered-overview
    
    Status: 402 (PaymentRequired)
    ErrorCode: paymentRequired
    Date: 2023-09-23T19:27:33
    
    Headers:
    Cache-Control                 : no-store, no-cache
    Transfer-Encoding             : chunked
    Vary                          : Accept-Encoding
    Strict-Transport-Security     : max-age=31536000
    request-id                    : bb607711-f87b-4974-87a4-632a70d5985a
    client-request-id             : 152c36fa-971c-47c1-aa00-6342fe6d9c3b
    x-ms-ags-diagnostic           : {"ServerInfo":{"DataCenter":"Germany West Central","Slice":"E","Ring":"5","ScaleUnit":"000","RoleInstance":"FR1PEPF00000B9E"}}
    Date                          : Sat, 23 Sep 2023 19:27:33 GMT

    How to assign a Sensitivity Label for a SharePoint File with PowerShell

    Let’s assume, we want to change the sensitivity label for this file to confidential:

    Screenshot of a document, which obtained the confidential sensitivity label in SharePoint

    If you want to assign a sensitivity label for one file, you first need to identify the DriveItem ID and Drive ID for the SharePoint file, where you want to set the sensitivity label. You can get the DriveItem ID by executing the script in following article:

    In order to assign a sensitivity label, I wrote following function:

    function Assign-SensitivityLabel
    {
        [CmdletBinding()]
        param (
            [Parameter(Mandatory = $true)]
            $DriveId,
            
            [Parameter(Mandatory = $true)]
            $DriveItemId,
            
            [Parameter(Mandatory = $false)]
            $AssignmentMethod = "privileged", # The assignment method of the label on the document. Can be obtained here: https://learn.microsoft.com/en-us/dotnet/api/microsoft.informationprotection.assignmentmethod?view=mipsdk-dotnet-1.10
            
            [Parameter(Mandatory = $false)]
            $JustificationText = "No justification provIded", # Justification why this sensitivity label is applied.
    
            [Parameter(Mandatory = $true)]
            $SensitivityLabelId # Id of the sensitivitylabel. Can be obtained here: https://compliance.microsoft.com/informationprotection/labels
        )
    
        $Body = @{
            Any                = $null
            AssignmentMethod   = $AssignmentMethod
            JustificationText  = $JustificationText
            SensitivityLabelId = $SensitivityLabelId #Trusted People https://compliance.microsoft.com/informationprotection/labels
        }
        $Uri = "https://graph.microsoft.com/v1.0/drives/$($DriveId)/items/$($DriveItemId)/assignSensitivityLabel"
        
        try
        {
    
            $Response = Invoke-MgGraphRequest -Method "POST" -Uri $Uri  -Body $Body -OutputType PSObject
            Return $Response
        }
        catch
        {
            $Error[0].Exception
        }
    }
    

    I recommend to export the certificate password as an encrypted file, which will be used in the upcomming scripts. A hardcoded password in a script is not recommended.

    Get-credential("CertificateName") | Export-Clixml -Path "C:\Users\Serka\OneDrive\Desktop\PS\SPSitesReadAll.key"

    With the execution of the script below, the sensitivity of the Subfoldderdocument.docx becomes a confidential. If you want to use the script, replace the parameters to fit your scenario.

    Param (
        $AppId = "90747925-550c-4658-850f-b19d26ed1a43", # Replace with the App Id of your App registration
        $Tenant = "1f795e95-c06b-4109-924e-3569fddf499f", # Replace with the Tenant prefix
        $CertificatePath = "C:\Users\Serka\OneDrive\Desktop\PS\SPSitesReadAll.pfx", # Replace with the path to your certificate,
        $CertificatePasswordPath = "C:\Users\Serka\OneDrive\Desktop\PS\SPSitesReadAll.key", # Replace with the password of your certificate,
        $SensitivityLabelId = "defa4170-0d19-0005-0005-bc88714345d2",  # Id of the sensitivitylabel. Can be obtained here: https://compliance.microsoft.com/informationprotection/labels
        $Justificationtext = "Labelled by Serkar", #Here you can justify your labelling
        $AssignmentMethod = "Auto", # The assignment method of the label on the document. Can be obtained here: https://learn.microsoft.com/en-us/dotnet/api/microsoft.informationprotection.assignmentmethod?view=mipsdk-dotnet-1.10,
        $DriveId = "b!kJV0CniHeUeAiju7m8Gl4ZmfCOoRAXJNrYB9wjbkfZ-Vmuw3EELGQ7bZlNIfSaf4", #ID of the drive, which is typically the SharePoint Library
        $DriveitemId = "01NLC4VWO4XBKMOKUMUVH3Z6GINE5TGTRT" #ID of the SharePoint file, for which the Sensitivitylabel shall be adjusted
    )
    
    
    function Assign-SensitivityLabel
    {
        [CmdletBinding()]
        param (
            [Parameter(Mandatory = $true)]
            $DriveId,
            
            [Parameter(Mandatory = $true)]
            $DriveItemId,
            
            [Parameter(Mandatory = $false)]
            $AssignmentMethod = "privileged", # The assignment method of the label on the document. Can be obtained here: https://learn.microsoft.com/en-us/dotnet/api/microsoft.informationprotection.assignmentmethod?view=mipsdk-dotnet-1.10
            
            [Parameter(Mandatory = $false)]
            $JustificationText = "No justification provIded", # Justification why this sensitivity label is applied.
    
            [Parameter(Mandatory = $true)]
            $SensitivityLabelId # Id of the sensitivitylabel. Can be obtained here: https://compliance.microsoft.com/informationprotection/labels
        )
    
        $Body = @{
            Any                = $null
            AssignmentMethod   = $AssignmentMethod
            JustificationText  = $JustificationText
            SensitivityLabelId = $SensitivityLabelId #Trusted People https://compliance.microsoft.com/informationprotection/labels
        }
        $Uri = "https://graph.microsoft.com/v1.0/drives/$($DriveId)/items/$($DriveItemId)/assignSensitivityLabel"
        
        try
        {
    
            $Response = Invoke-MgGraphRequest -Method "POST" -Uri $Uri  -Body $Body -OutputType PSObject
            Return $Response
        }
        catch
        {
            $Error[0].Exception
        }
    }
    
    
    # Load required Modules
    Import-Module Microsoft.Graph.Authentication, Microsoft.Graph.Files
    
    # Load the certificate from file
    $CertificatePassword = (Import-Clixml -Path $CertificatePasswordPath).getnetworkcredential().Password
    $Certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($CertificatePath, $CertificatePassword)
    
    # Authenticate
    Connect-MgGraph -ClientId $AppId -TenantId $Tenant -CertificateThumbprint $Certificate.Thumbprint -NoWelcome
     
    #region prepare export
    
    # Get drive
    $Drive = Get-MgSite -SiteId $SiteId -Property Drives -ExpandProperty Drives | Select-Object Drives -ExpandProperty Drives | Where-Object { $_.Name -eq $LibraryName }
    
    # Get drive item
    $DriveItems = Invoke-MgGraphRequest -Uri "v1.0/drives/$($Drive.Id)/root/children"
    
    $ItemExportRaw = New-Object System.Collections.Generic.List[object]
    
    foreach ($DriveItem in $DriveItems.Value)
    {
        #Get All drivitems below root
        $ItemExportRaw.Add( $(List-MgDriveItem -Drive $Drive -DriveItem $DriveItem))
    }
    
    $ItemExport = New-Object System.Collections.Generic.List[object]
    
    
    $ItemExportRaw |ForEach-Object {
    
            $_ | ForEach-Object {$ItemExport.add($_)}    
    }
    #endregion
    
    #region assign sensitivity label
    
    $SensitivyLabelParams  =$null
    $SensitivyLabelParams = @{
        DriveId            = $DriveId
        DriveitemId        = $DriveitemId
        AssignmentMethod   = $AssignmentMethod
        Justificationtext  = $Justificationtext
        SensitivityLabelId = $SensitivityLabelId
    }
    
    Write-Output "Assigining sensitivity label to $($Item.DriveItemName)"
    Assign-SensitivityLabel @SensitivyLabelParams
    
    
    #endregon

    I oberserved that it took a few moments until the sensitivity label was updated in SharePoint as it happens asychronously.

    Assigned confidential sensitivity label to SharePoint file

    How to assign Sensitivity Labels for a SharePoint Library with PowerShell

    In order to assign sensitivity labels a whole SharePoint library with PowerShell, you need to obtain first the DriveItemIDs for each SharePoint file and then proceed with assigning the sensitivity labels.

    To showcase the functionality, I have prepared following SharePoint Library with various sensitivity labels. My aim is to assign the confidential sensitivity label for each file. This script recurses subfolders, so that child items of folders are assigned with the sensitivity label of choice.

    SharePoint Library to assign sensitivity labels

    Param (
        $AppId = "90747925-550c-4658-850f-b19d26ed1a43", # Replace with the App Id of your App registration
        $Tenant = "1f795e95-c06b-4109-924e-3569fddf499f", # Replace with the Tenant prefix
        $SiteId = "0a749590-8778-4779-808a-3bbb9bc1a5e1", # Replace it with the SiteId of your SharePoint Site https://m365x04995906.sharepoint.com/sites/Remoteliving/_api/site/Id
        $LibraryName = "Documents", # Replace with the list name,
        $CertificatePath = "C:\Users\Serka\OneDrive\Desktop\PS\SPSitesReadAll.pfx", # Replace with the path to your certificate,
        $CertificatePasswordPath = "C:\Users\Serka\OneDrive\Desktop\PS\SPSitesReadAll.key", # Replace with the password of your certificate,
        $SensitivityLabelId = "defa4170-0d19-0005-0005-bc88714345d2",  # Id of the sensitivitylabel. Can be obtained here: https://compliance.microsoft.com/informationprotection/labels
        $Justificationtext = "Labelled by Serkar", #Here you can justify your labelling
        $AssignmentMethod = "Auto" # The assignment method of the label on the document. Can be obtained here: https://learn.microsoft.com/en-us/dotnet/api/microsoft.informationprotection.assignmentmethod?view=mipsdk-dotnet-1.10
            
    )
    
    function List-MgDriveItem
    {
        [CmdletBinding()]
        param (
            [Parameter()]
            $Drive,
            [Parameter()]
            $DriveItem
        )
    
        Try
        {
            if ($DriveItem.folder)
            {
                # Recurse in the folder to get the childitems of the folder
                $ChildItems = (Get-MgDriveItem -DriveId $Drive.Id -DriveItemId $DriveItem.Id  -ExpandProperty Children ).Children
    
                if ($ChildItems.Count -gt 0)
                {
                    <# Action to perform if the condition is true #>
                    $ChildItems | ForEach-Object { 
                        List-MgDriveItem -Drive $Drive -DriveItem (Get-MgDriveItem -DriveId $Drive.Id -DriveItemId $($_.Id) ) 
                    }
                }
    
            }
    
                $Result = New-Object psobject -Property @{
                DriveItemName = $DriveItem.name;
                DriveitemId   = $DriveItem.Id;
                DriveId       = $Drive.Id;
                DriveItemURL  = $DriveItem.webUrl
            }
            return $Result
        }
        catch
        {
            Return $Error[0]
        }
    
    }
    function Assign-SensitivityLabel
    {
        [CmdletBinding()]
        param (
            [Parameter(Mandatory = $true)]
            $DriveId,
            
            [Parameter(Mandatory = $true)]
            $DriveItemId,
            
            [Parameter(Mandatory = $false)]
            $AssignmentMethod = "privileged", # The assignment method of the label on the document. Can be obtained here: https://learn.microsoft.com/en-us/dotnet/api/microsoft.informationprotection.assignmentmethod?view=mipsdk-dotnet-1.10
            
            [Parameter(Mandatory = $false)]
            $JustificationText = "No justification provIded", # Justification why this sensitivity label is applied.
    
            [Parameter(Mandatory = $true)]
            $SensitivityLabelId # Id of the sensitivitylabel. Can be obtained here: https://compliance.microsoft.com/informationprotection/labels
        )
    
        $Body = @{
            Any                = $null
            AssignmentMethod   = $AssignmentMethod
            JustificationText  = $JustificationText
            SensitivityLabelId = $SensitivityLabelId #Trusted People https://compliance.microsoft.com/informationprotection/labels
        }
        $Uri = "https://graph.microsoft.com/v1.0/drives/$($DriveId)/items/$($DriveItemId)/assignSensitivityLabel"
        
        try
        {
    
            $Response = Invoke-MgGraphRequest -Method "POST" -Uri $Uri  -Body $Body -OutputType PSObject
            Return $Response
        }
        catch
        {
            $Error[0].Exception
        }
    }
    
    
    # Load required Modules
    Import-Module Microsoft.Graph.Authentication, Microsoft.Graph.Files
    
    # Load the certificate from file
    $CertificatePassword = (Import-Clixml -Path $CertificatePasswordPath).getnetworkcredential().Password
    $Certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($CertificatePath, $CertificatePassword)
    
    # Authenticate
    Connect-MgGraph -ClientId $AppId -TenantId $Tenant -CertificateThumbprint $Certificate.Thumbprint -NoWelcome
     
    #region prepare export
    
    # Get drive
    $Drive = Get-MgSite -SiteId $SiteId -Property Drives -ExpandProperty Drives | Select-Object Drives -ExpandProperty Drives | Where-Object { $_.Name -eq $LibraryName }
    
    # Get drive item
    $DriveItems = Invoke-MgGraphRequest -Uri "v1.0/drives/$($Drive.Id)/root/children"
    
    $ItemExportRaw = New-Object System.Collections.Generic.List[object]
    
    foreach ($DriveItem in $DriveItems.Value)
    {
        #Get All drivitems below root
        $ItemExportRaw.Add( $(List-MgDriveItem -Drive $Drive -DriveItem $DriveItem))
    }
    
    $ItemExport = New-Object System.Collections.Generic.List[object]
    
    
    $ItemExportRaw |ForEach-Object {
    
            $_ | ForEach-Object {$ItemExport.add($_)}    
    }
    
    #endregion
    
    #region assign sensitivity label
    
    foreach ($Item in $ItemExport)
    {
        <# $Item is the current item #>
    
        $SensitivyLabelParams  =$null
        $SensitivyLabelParams = @{
            DriveId            = $Item.DriveId
            DriveitemId        = $Item.DriveitemId
            AssignmentMethod   = $AssignmentMethod
            Justificationtext  = $Justificationtext
            SensitivityLabelId = $SensitivityLabelId
        }
    
        Write-Output "Assigining sensitivity label to $($Item.DriveItemName)"
        Assign-SensitivityLabel @SensitivyLabelParams
    }
    
    #endregon

    The output of the script shows, that not every file type is supported for assigining sensitivity labels to it. Currently only Word, Excel and PowerPoint file types support sensitivity label per default.

    Screenshot of PowerShell script highlighting that SharePoint files received the sensitivity labels

    Further Reference

    If you are new to Graph API, consider reading following article, which will help you to configure the Azure Application correctly:

    How to configure Azure App registration for MS Graph | SPO Scripts

  • How to Get SharePoint Files with MSGraph with PowerShell

    With the newest MSGraph module one has the capability to get SharePoint files with MSGraph. I recently figured out, how to fetch them in a recursed manner. In this article I want to share with you how to get SharePoint files with MSGraph. You will need to get the files with MSGraph, if you are thinking about to do following things:

    • Read/ Write/ Delete Metadata of SharePoint Files
    • Obtain DriveItem IDs, if you want to upload files to specific folders
    • Download specific SharePoint files
    • Assess the storage of your SharePoint libraries

    What do you need to get SharePoint Files with MSGraph

    You need to fulfil two requirements, so that you can get SharePoint files with MSGraph recursedly.

    1. You need to configure an Azure App registration with read permission to the SharePoint sites, where you want to obtain the files from. If you have never done this, follow my article, where I explain how to configure your Azure App registration for MSGraph access.
      How to configure Azure App registration for MS Graph | SPO Scripts
    2. Install the MSGraph module for PowerShell
      Install the Microsoft Graph PowerShell SDK | Microsoft Learn

    Demo Setup

    In my demo setup I have a SharePoint Library, where I have stored some files and folders. I am showcasing you, so that you can understand what you can expect from the solution. I have also some files in a folder, which will show that the solution is able to fetch folder items.

    Get SharePoint Files with MSGraph
    SharePoint Subfolder

    I have created an Azure App registration with the Sites Read All permission. The authentication will be done with a self signed certificate.

    Azure App Registration with MSGraph permission

    Azure App Registration with certificate for authentication

    How to Get all SharePoint Files with MSGraph in PowerShell (recursively)

    Below you can find the PowerShell script to get all SharePoint files. I have created the function List-MgDriveItem in order to list the drive items of a drive. A drive item is either a file or a folder within a SharePoint Library You can adjust the function, if you want to have different attributes.

    function List-MgDriveItem
    {
        [CmdletBinding()]
        param (
            [Parameter()]
            $Drive,
            [Parameter()]
            $DriveItem
        )
    
        Try
        {
            if ($DriveItem.folder)
            {
                # Recurse in the folder to get the childitems of the folder
                $ChildItems = (Get-MgDriveItem -DriveId $Drive.Id -DriveItemId $DriveItem.Id  -ExpandProperty Children ).Children
    
                if ($ChildItems.Count -gt 0)
                {
                    <# Action to perform if the condition is true #>
                    $ChildItems | ForEach-Object { 
                        List-MgDriveItem -Drive $Drive -DriveItem (Get-MgDriveItem -DriveId $Drive.Id -DriveItemId $($_.Id) ) 
                    }
                }
    
            }
    
                $Result = New-Object psobject -Property @{
                DriveItemName = $DriveItem.name;
                DriveitemSize   = $DriveItem.Size
            }
            return $Result
        }
        catch
        {
            Return $Error[0]
        }
    
    }

    A sample output of a folder driveitem looks like this:

    PS C:\Users\Serka\OneDrive\Desktop\PS> $driveitem | Select-Object *
    
    webUrl               : https://m365x04995906.sharepoint.com/sites/Remoteliving/Shared%20Documents/General
    id                   : 01NLC4VWP4YR6WIO6O6JFIFTARZGEQZMAL
    createdBy            : {[user, System.Collections.Hashtable], [application, System.Collections.Hashtable]}
    lastModifiedBy       : {[user, System.Collections.Hashtable], [application, System.Collections.Hashtable]}
    lastModifiedDateTime : 31.07.2023 12:17:50
    name                 : General
    folder               : {[childCount, 3]}
    size                 : 4505145
    fileSystemInfo       : {[lastModifiedDateTime, 31.07.2023 12:17:50], [createdDateTime, 31.07.2023 12:17:50]}
    createdDateTime      : 31.07.2023 12:17:50
    parentReference      : {[id, 01NLC4VWN6Y2GOVW7725BZO354PWSELRRZ], [path, /drives/b!kJV0CniHeUeAiju7m8Gl4ZmfCOoRAXJNrYB9wjbkfZ-Vmuw3EELGQ7bZlNIfSaf4/root:], [driveType, documentLibrary], [driveId, b!kJV0CniHeUeAiju7m8Gl4ZmfCOoRAXJNrYB9wjbkfZ-Vmuw3EELGQ7bZlNIfSaf4]…}
    eTag                 : "{647DC4FC-CE3B-4AF2-82CC-11C9890CB00B},2"
    cTag                 : "c:{647DC4FC-CE3B-4AF2-82CC-11C9890CB00B},0"
    shared               : {[scope, users]}

    A sample output of a file driveitem looks like this:

    PS C:\Users\Serka\OneDrive\Desktop\PS> $driveitem | select-object * |Format-list
    
    createdDateTime              : 19.08.2023 20:56:39
    cTag                         : "c:{6C722880-DCC4-45F4-B64C-96805B32D473},2"
    lastModifiedBy               : {[user, System.Collections.Hashtable]}
    @microsoft.graph.downloadUrl : https://m365x04995906.sharepoint.com/sites/Remoteliving/_layouts/15/download.aspx?UniqueId=6c722880-dcc4-45f4-b64c-96805b32d473&Translate=false&tempauth=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIwMDAwMDAwMy0wMDAwLTBmZjEtY2UwMC0wMDAwMDAwMDAwMDAvbTM2NXgwNDk5NTkwNi5zaGFyZXBvaW50LmNvbUAxZjc5NWU5NS1jMDZiLTQxMDktOTI0ZS0zNTY5ZmRkZjQ5OWYiLCJpc3MiOiIwMDAwMDAwMy0wMDAwLTBmZjEtY2UwMC0wMDAwMDAwMDAwMDAiLCJuYmYiOiIxNjk0OTc0NTMzIiwiZXhwIjoiMTY5NDk3ODEzMyIsImVuZHBvaW50dXJsIjoib0dEUzI3bnFpOEtUSHd5NHRaQ1NYVzE1Mk1KVGFMVkpZZmh0VUhBTVUrUT0iLCJlbmRwb2ludHVybExlbmd0aCI6IjE0MyIsImlzbG9vcGJhY2siOiJUcnVlIiwiY2lkIjoiVWxYbUJaczF1MHU4d3ZpSTFwTGhudz09IiwidmVyIjoiaGFzaGVkcHJvb2Z0b2tlbiIsInNpdGVpZCI6Ik1HRTNORGsxT1RBdE9EYzNPQzAwTnpjNUxUZ3dPR0V0TTJKaVlqbGlZekZoTldVeCIsImFwcF9kaXNwbGF5bmFtZSI6IlNQX1NlbnNpdGl2aXR5X0xhYmVscyIsIm5hbWVpZCI6IjkwNzQ3OTI1LTU1MGMtNDY1OC04NTBmLWIxOWQyNmVkMWE0M0AxZjc5NWU5NS1jMDZiLTQxMDktOTI0ZS0zNTY5ZmRkZjQ5OWYiLCJyb2xlcyI6ImFsbHNpdGVzLndyaXRlIiwidHQiOiIxIiwiaXBhZGRyIjoiMjAuMTkwLjE5MC4xMDMifQ.knKrUliPCxeMF4NQ2-3_FSAkEYRBD5nlTOvAVurT_gQ&ApiVersion=2.0
    file                         : {[mimeType, image/png], [hashes, System.Collections.Hashtable]}
    photo                        : {}
    name                         : 2023-06-25 00_18_51-Alfahosting.de® • Meine Rechnungen and 2 more pages - Personal - Microsoft​ Edge.png
    fileSystemInfo               : {[lastModifiedDateTime, 19.08.2023 20:56:39], [createdDateTime, 19.08.2023 20:56:39]}
    id                           : 01NLC4VWMAFBZGZRG46RC3MTEWQBNTFVDT
    shared                       : {[scope, users]}
    image                        : {[width, 868], [height, 406]}
    createdBy                    : {[user, System.Collections.Hashtable]}
    size                         : 23175
    eTag                         : "{6C722880-DCC4-45F4-B64C-96805B32D473},1"
    lastModifiedDateTime         : 19.08.2023 20:56:39
    webUrl                       : https://m365x04995906.sharepoint.com/sites/Remoteliving/Shared%20Documents/2023-06-25%2000_18_51-Alfahosting.de%C2%AE%20%E2%80%A2%20Meine%20Rechnungen%20and%202%20more%20pages%20-%20Personal%20-%20Microsoft%E2%80%8B%20Edge.png
    parentReference              : {[id, 01NLC4VWN6Y2GOVW7725BZO354PWSELRRZ], [path, /drives/b!kJV0CniHeUeAiju7m8Gl4ZmfCOoRAXJNrYB9wjbkfZ-Vmuw3EELGQ7bZlNIfSaf4/root:], [driveType, documentLibrary], [driveId, b!kJV0CniHeUeAiju7m8Gl4ZmfCOoRAXJNrYB9wjbkfZ-Vmuw3EELGQ7bZlNIfSaf4]…}

    Here you can find the PowerShell script to get all SharePoint files with MSGraph. Keep in mind that you replace the param block with the values from your tenant, app etc.

    Param (
        $AppID = "90747925-550c-4658-850f-b19d26ed1a43", # Replace with the App ID of your App registration
        $Tenant = "1f795e95-c06b-4109-924e-3569fddf499f", # Replace with the Tenant ID
        $SiteID = "0a749590-8778-4779-808a-3bbb9bc1a5e1", # Replace it with the SiteID of your SharePoint Site https://m365x04995906.sharepoint.com/sites/Remoteliving/_api/site/id
        $LibraryName = "Documents", # Replace with the list name,
        $CertificatePath = "C:\Users\Serka\OneDrive\Desktop\PS\SPSitesReadAll.pfx", # Replace with the path to your certificate,
        $CertificatePassword = "XXX" # Replace with the password of your certificate
    )
    
    function List-MgDriveItem {
        [CmdletBinding()]
        param (
            [Parameter()]
            $Drive,
            [Parameter()]
            $DriveItem
        )
        
        if ($DriveItem.folder) {
            <# Action to perform if the condition is true #>
            Write-Output "Folder $($DriveItem.Name), size $($DriveItem.Size) `n"
    
            # Recurse in the folder to get the childitems of the folder
            $ChildItems = $null
            $ChildItems = (Get-MgDriveItem -DriveId $Drive.id -DriveItemId $DriveItem.id  -ExpandProperty Children ).Children
    
            if ($ChildItems.Count -gt 0) {
                <# Action to perform if the condition is true #>
                $ChildItems | ForEach-Object { 
                    List-MgDriveItem -Drive $Drive -DriveItem (Get-MgDriveItem -DriveId $Drive.ID -DriveItemId $($_.Id) ) 
                }
            }
            
        }   
        elseif ($null -ne $DriveItem.file) {
            <# Action when this condition is true #>
            Write-Output "File $($DriveItem.Name), size $($DriveItem.Size) `n"
        }
    }
    
    #region prerequistes
    Import-Module microsoft.graph.authentication
    
    # Load the certificate from file
    $Certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($CertificatePath, $CertificatePassword)
    
    # Authenticate
    Connect-MgGraph -ClientID $AppID -TenantId $Tenant -CertificateThumbprint $Certificate.Thumbprint
    #endregion
    
    #region main
    # Get drive
    $Drive = Get-MgSite -SiteId $SiteID -Property Drives -ExpandProperty Drives | Select-Object Drives -ExpandProperty Drives | Where-Object { $_.Name -eq $LibraryName }
    
    # Get drive item
    $DriveItems = Invoke-MgGraphRequest -Uri "v1.0/drives/$($Drive.ID)/root/children"
    
    foreach ($DriveItem in $DriveItems.Value) {
    
        #Get All drivitems below root
        List-MgDriveItem -Drive $Drive -DriveItem $DriveItem
    }
    
    #
    #endregion

    As you can see below, the script populates the space ocupation for each SharePoint file/ folder.

    Output of get SharePoint files for MSGraph

    How to get DriveItemIDs for SharePoint Files?

    I have modified the script, so that you can get all driveItemIDs for all SharePoint Files in your library. Below you can find the script with certificate based authentication.

    Param 
    (
        $AppId = "90747925-550c-4658-850f-b19d26ed1a43", # Replace with the App Id of your App registration
        $Tenant = "1f795e95-c06b-4109-924e-3569fddf499f", # Replace with the Tenant prefix
        $SiteId = "0a749590-8778-4779-808a-3bbb9bc1a5e1", # Replace it with the SiteId of your SharePoint Site https://m365x04995906.sharepoint.com/sites/Remoteliving/_api/site/Id
        $LibraryName = "Documents", # Replace with the list name,
        $CertificatePath = "C:\Users\Serka\OneDrive\Desktop\PS\SPSitesReadAll.pfx", # Replace with the path to your certificate,
        $CertificatePasswordPath = "C:\Users\Serka\OneDrive\Desktop\PS\SPSitesReadAll.key" # Replace with the path to the password of your certificate,
    )
    
    function List-MgDriveItem
    {
        [CmdletBinding()]
        param (
            [Parameter()]
            $Drive,
            [Parameter()]
            $DriveItem
        )
    
        Try
        {
            if ($DriveItem.folder)
            {
                # Recurse in the folder to get the childitems of the folder
                $ChildItems = (Get-MgDriveItem -DriveId $Drive.Id -DriveItemId $DriveItem.Id  -ExpandProperty Children ).Children
    
                if ($ChildItems.Count -gt 0)
                {
                    <# Action to perform if the condition is true #>
                    $ChildItems | ForEach-Object { 
                        List-MgDriveItem -Drive $Drive -DriveItem (Get-MgDriveItem -DriveId $Drive.Id -DriveItemId $($_.Id) ) 
                    }
                }
    
            }
    
                $Result = New-Object psobject -Property @{
                DriveItemName = $DriveItem.name;
                DriveitemId   = $DriveItem.Id;
                DriveId       = $Drive.Id;
                DriveItemURL  = $DriveItem.webUrl
            }
            return $Result
        }
        catch
        {
            Return $Error[0]
        }
    
    }
    
    
    # Load required Modules
    Import-Module Microsoft.Graph.Authentication, Microsoft.Graph.Files
    
    # Load the certificate from file
    $CertificatePassword = (Import-Clixml -Path $CertificatePasswordPath).getnetworkcredential().Password
    $Certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($CertificatePath, $CertificatePassword)
    
    # Authenticate
    Connect-MgGraph -ClientId $AppId -TenantId $Tenant -CertificateThumbprint $Certificate.Thumbprint -NoWelcome
     
    #region prepare export
    
    # Get drive
    $Drive = Get-MgSite -SiteId $SiteId -Property Drives -ExpandProperty Drives | Select-Object Drives -ExpandProperty Drives | Where-Object { $_.Name -eq $LibraryName }
    
    # Get drive item
    $DriveItems = Invoke-MgGraphRequest -Uri "v1.0/drives/$($Drive.Id)/root/children"
    
    $ItemExportRaw = New-Object System.Collections.Generic.List[object]
    
    foreach ($DriveItem in $DriveItems.Value)
    {
        #Get All drivitems below root
        $ItemExportRaw.Add( $(List-MgDriveItem -Drive $Drive -DriveItem $DriveItem))
    }
    
    $ItemExport = New-Object System.Collections.Generic.List[object]
    
    
    $ItemExportRaw |ForEach-Object {
    
            $_ | ForEach-Object {$ItemExport.add($_)}    
    }
    
    $ItemExport
    #endregion

    If you want to use app secrets instead of certificate based authentication, you can use following script

    Param 
    (
        $AppId = "13b20636-5165-402e-aedc-5e05eeb4a57f", # Replace with the App Id of your App registration
        $Tenant = "b7b0953d-d866-4606-9199-7642f06a0b2e", # Replace with the Tenant prefix
        $SiteId = "2dfd508f-98ab-46ad-839b-f0435730c79a", # Replace it with the SiteId of your SharePoint Site https://m365x04995906.sharepoint.com/sites/Remoteliving/_api/site/Id
        $LibraryName = "Documenten" # Replace with the list name,
    )
    
    function List-MgDriveItem
    {
        [CmdletBinding()]
        param (
            [Parameter()]
            $Drive,
            [Parameter()]
            $DriveItem
        )
    
        Try
        {
            if ($DriveItem.folder)
            {
                # Recurse in the folder to get the childitems of the folder
                $ChildItems = (Get-MgDriveItem -DriveId $Drive.Id -DriveItemId $DriveItem.Id  -ExpandProperty Children ).Children
    
                if ($ChildItems.Count -gt 0)
                {
                    <# Action to perform if the condition is true #>
                    $ChildItems | ForEach-Object { 
                        List-MgDriveItem -Drive $Drive -DriveItem (Get-MgDriveItem -DriveId $Drive.Id -DriveItemId $($_.Id) ) 
                    }
                }
    
            }
    
                $Result = New-Object psobject -Property @{
                DriveItemName = $DriveItem.name;
                DriveitemId   = $DriveItem.Id;
                DriveId       = $Drive.Id;
                DriveItemURL  = $DriveItem.webUrl
            }
            return $Result
        }
        catch
        {
            Return $Error[0]
        }
    
    }
    
    
    # Load required Modules
    Import-Module Microsoft.Graph.Authentication, Microsoft.Graph.Files
    
    # Complie Client Credential
    $ClientSecretCredential = Get-Credential($AppID)
    
    # Authenticate
    Connect-MgGraph -TenantId $Tenant -ClientSecretCredential $ClientSecretCredential -NoWelcome
    
    #region prepare export
    
    # Get drive
    $Drive = Get-MgSite -SiteId $SiteId -Property Drives -ExpandProperty Drives | Select-Object Drives -ExpandProperty Drives | Where-Object { $_.Name -eq $LibraryName }
    
    # Get drive item
    $DriveItems = Invoke-MgGraphRequest -Uri "v1.0/drives/$($Drive.Id)/root/children"
    
    $ItemExportRaw = New-Object System.Collections.Generic.List[object]
    
    foreach ($DriveItem in $DriveItems.Value)
    {
        #Get All drivitems below root
        $ItemExportRaw.Add( $(List-MgDriveItem -Drive $Drive -DriveItem $DriveItem))
    }
    
    $ItemExport = New-Object System.Collections.Generic.List[object]
    
    
    $ItemExportRaw |ForEach-Object {
    
            $_ | ForEach-Object {$ItemExport.add($_)}    
    }
    
    $ItemExport
    #endregion

    Once the script is exectued, all driveitems are shown like this:

    Output of all driveitems

    Further Reference

    Here you can obtain the permission levels, which are required for your Azure App Registration:
    Microsoft Graph permissions reference – Microsoft Graph | Microsoft Learn

    Here you can find the cmdlet map for MS Graph module

    Find Azure AD and MSOnline cmdlets in Microsoft Graph PowerShell | Microsoft Learn

  • How To Download a SharePoint Library Using Graph API (PowerShell)

    SharePoint is a powerful platform for managing and collaborating on documents, but downloading files from SharePoint libraries can be a tricky, especially when it comes to deal with large files or a large amount of documents. In this post, I want to show you a straightforward guide to download a SharePoint Library using PowerShell with Microsoft’s Graph API for SharePoint administrators, developers or system integrators. This tutorial will help you streamline your automation in terms of downloading SharePoint libraries. At the end, you’ll find a ready to use PowerShell script, which you can use to download the SharePoint Library using Graph API.

    If you want to download a SharePoint library using Graph API, there are certain prerequisites you must fulfil. To successfully download files from SharePoint using Graph API, you need to ensure the following:

    What do I need to download files to SharePoint using Graph API?

    • You have the sites.selected permission for an Azure Enterprise Application. Your global administrator in your organization can consent this permission.
    • You have installed the PNP PowerShell module to allow the Enterprise Application permission to download files from the specific SharePoint Site. Without this module, the Enterprise Application will not be able to access and download the files. Learn here how you can install the PNP PowerShell module:
      Connect to SharePoint with PowerShell | SharePoint Online (workplace-automation.com/)

    By fulfilling these prerequisites, you can easily download SharePoint libraries using Graph API and streamline your document management process.

    How to create the Enterprise Application to Download a SharePoint Library using Graph?

    1. Go to the Azure Active Directory Portal to create an App Registration with Sites.Selected permissions. This allows the Enterprise Application to access and download files from SharePoint.
    2. Create a credential object for the App registration.
    3. Once you have created the credentials for the App Registration, make sure to note down the credentials for future use. These credentials will be required when you are running the PowerShell script to download a SharePoint Library using Graph.

    By following these simple steps, you can create an Enterprise Application with the necessary permissions to download files from SharePoint using Graph API. You will get to know in the upcoming passage how to create the Enterprise Application step-by-step.

    Browse to Azure Active Directory Portal

    Open https://portal.azure.com/#view/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade in your browser.

    If you have the global admin rights, I recommend authenticating with that account, so that you can directly grant your enterprise application the permission. Otherwise, you need to reauthenticate or ask your administrator to grant your enterprise application the permissions.

    Create an App Registration with Sites.Selected permissions

    Browse to the App Registration blade in Azure Active Directory Portal.

    Screenshot of the App registration Azure Active Directory Tenant overview

    Click on “New registration” to create a new App Registration.

    Screenshot of new enterprise app registration

    Define a meaningful name for the App that follows your organization’s standards, so that different administrators can easily recognize the purpose of the App. In this case I am using the name SP_Sites.Selected_Retail name, so that Azure Active directory administrators can recognize that the App Registration will have the permission Sites.Selected for the SharePoint Site Retail

    Browse to the “API Permissions” to grant your App Registraion the permission to download the SharePoint Library using Graph API.

    Screenshot of API Permissions link

    Click on “Add a permission”

    Screenshot of API Permissions overview to add a permission

    As we want to go with Microsoft Graph, please choose “Microsoft Graph”.

    Screenshot of the selection for APIs

    To download a SharePoint library using Graph API, it’s crucial to choose the right permission level. If you want your application to run in the background without requiring user authentication, you’ll need to select the Application permission option. In this tutorial on how to download a SharePoint library using Graph API, we’ll focus on the application permission method to automate the process of downloading files from SharePoint.

    Screenshot of API permission types

    Now add the sites.selected permission, which allows you to access contents of a single SharePoint site.

    Screenshot of Microsoft Graph Permissions for SharePoint Sites

    To grant admin consent for your tenant, you must be signed in with a user account that has global administrator privileges. However, if you don’t have these privileges ask your administrator with the global administrator role to consent permissions for the App Registration.

    Screenshot of granting of admin consent for current tenant

    Create a secret for the App Registration

    Now as you have configured the App permissions, you have to ensure, that you can authenticate with your App Registration. To configure the authentication, click on “Certificates & secrets”.

    Screenshot of certificates & secrets of App Registrations

    Now you can either upload certificates, create client secrets or create federated credentials. In this tutorial I will show you how to work with client secrets.

    Screenshot of Selection for App Registration Authentication

    Now you need to define a credential name. I choose the client and the IP Address to recognize, which Server/ Application will use the client secret.

    Screenshot of Add a client secret

    As you have added a client secret, make sure that you store the value for the secret as you are only able to see it, when you create it.

    Screenshot of exposed client secret value

    How to grant the App Registration Permissions for a SharePoint Site?

    To grant permission to your app registration, it’s essential to ensure that the PNP Module is installed on your client and that you have permission to use it. If you haven’t already installed the PNP Module, check out the documentation: Connect to SharePoint with PowerShell | SharePoint Online (workplace-automation.com/)

    Once you have confirmed that both conditions are met, you can use the following code to grant your app registration the necessary permissions to read from the site. You can find your app ID on the overview page of your app registration.

    Take the App ID from the overview page of your App registration and run the code below.

    Screenshot of App ID
    $AppID = "333d169e-7f2d-417c-b349-8498b2248802"
    $AppRegistrationName = "SP_Sites.Selected_Retail"
    $SiteURL = "https://m365x69801090.sharepoint.com/sites/Retail"
    
    Import-Module PnP.PowerShell
    
    $DisplayNameofSitePermission = "Enterprise Application $AppRegistrationName"
    
    
    Connect-PnPOnline -Url $SiteURL -Interactive
    Grant-PnPAzureADAppSitePermission -AppId $AppID -DisplayName $DisplayNameofSitePermission -Site $SiteURL -Permissions Read

    Now login with your account.

    Screenshot of interactive authentication prompt

    This is how it looks like, when the permission was granted successfully for the App Registration.

    Screenshot of granted site permission

    How to Download Files from SharePoint using Graph API?

    Now that we’ve created an app registration and granted it permission to write to a selected site, we can use it to download a SharePoint Library using Graph API. In this example, we’ll download the SharePoint Library Documents from the Retail SharePoint site. Before running the code, make sure you have adjusted the parameters and have the client secret ready, which we created in the previous steps.

    You can get the SiteID by browsing to the siteID page

    <siteurl>/_api/site/id

    For my example:

    https://m365x69801090.sharepoint.com/sites/Retail/_api/site/id

    When you run the code, you’ll be prompted to enter the client secret for your app registration.

    Screenshot of SharePoint Site ID

    Once you have replaced the parameters with your actual values, you can download aSharePoint Library with Graph using PowerShell. You will get an authentication prompt, where you have to enter the client secret for the App Registration.

    Screenshot of authentication promt for app registration

    # Script to download a SharePoint Library using Graph
    # Author: Serkar Aydin - Serkar@workplace-automation.com
    # Accept input parameters
    Param (
        $Tenant = "m365x69801090",
        $AppID = "333d169e-7f2d-417c-b349-8498b2248802",
        $SiteID = "74667e94-9fcf-41ab-8e2f-0dfaf0294de8",
        $LibraryURL = "https://m365x69801090.sharepoint.com/sites/Retail/Shared%20Documents",
        $Path = "C:\Users\Serkar\Desktop\Retail"
    )
    
    Function DownloadDriveItem {
    
        param(
            $DriveItem,
            $URL,
            $Header,
            $Path
            
        )
        
        #if there is no downloadurl, it is a folder
        If (!$DriveItem. '@microsoft.graph.downloadUrl') {
        
            Write-Output "Downloading the folder $($DriveItem.weburl)"
        
            #Create a folder for the SharePoint folder
            $FolderPath = "$Path\$($DriveItem.name)"
            New-Item -ItemType Directory -Path $FolderPath | Out-Null
    
            $Url  = "https://graph.microsoft.com/v1.0/drives/$DriveID/items/$($DriveItem.ID)/children"
            $Response =  Invoke-RestMethod -Uri $Url -Headers $Header -Method Get -ContentType 'multipart/form-data' 
    
            $Response.value | ForEach-Object {
    
                DownloadDriveItem -DriveItem $_ -URL $Url -Header $Header -Path $FolderPath
    
            }
    
        }
    
        #Else it is a file
        Else{
        
            Write-Output "Downloading the file $($DriveItem.weburl)"
            Invoke-WebRequest -Uri $DriveItem.'@microsoft.graph.downloadUrl' -OutFile "$Path\$($DriveItem.name)"
        }
    }
    
    
    # Prompt for application credentials
    $AppCredential = Get-Credential($AppID)
    
    #region authorize
    
    # Set the scope for the authorization request
    $Scope = "https://graph.microsoft.com/.default"
    
    # Build the body of the authorization request
    $Body = @{
        client_id = $AppCredential.UserName
        client_secret = $AppCredential.GetNetworkCredential().password
        scope = $Scope
        grant_type = 'client_credentials'
    }
    
    # Build the URL for the authorization request
    $GraphUrl = "https://login.microsoftonline.com/$($Tenant).onmicrosoft.com/oauth2/v2.0/token"
    
    # Send the authorization request and retrieve the access token
    $AuthorizationRequest = Invoke-RestMethod -Uri $GraphUrl -Method "Post" -Body $Body
    $Access_token = $AuthorizationRequest.Access_token
    
    # Build the header for API requests
    $Header = @{
        Authorization = $AuthorizationRequest.access_token
        "Content-Type"= "application/json"
    }
    
    #endregion
    
    #region get drives
    
    # Build the URL to retrieve the list of drives in the SharePoint site
    $GraphUrl = "https://graph.microsoft.com/v1.0/sites/$SiteID/drives"
    
    # Convert the body of the authorization request to JSON and send the API request
    $BodyJSON = $Body | ConvertTo-Json -Compress
    $Result = Invoke-RestMethod -Uri $GraphUrl -Method 'GET' -Headers $Header -ContentType "application/json"
    
    # Find the ID of the specified SharePoint library
    $DriveID = $Result.value| Where-Object {$_.webURL -eq $LibraryURL } | Select-Object id -ExpandProperty id
    
    # If the SharePoint library cannot be found, throw an error
    If ($DriveID -eq $null){
        Throw "SharePoint Library under $LibraryURL could not be found."
    }
    
    #endregion
    
    #region create folder. If there is already one, replace it with the new folder
    
    Try {
    
        New-Item -ItemType Directory -Path $Path -ErrorAction Stop | Out-Null
    }
    Catch {
    
            Remove-Item $Path -Force -Recurse
            New-Item -ItemType Directory -Path $Path -Force | Out-Null
    }
    #endregion
    
    #region download library
    
    $Url  = "https://graph.microsoft.com/v1.0/drives/$DriveID/root/children"
    $Response =  Invoke-RestMethod -Uri $Url -Headers $Header -Method Get -ContentType 'multipart/form-data' 
    
    $Response.value | ForEach-Object {
    
        DownloadDriveItem -DriveItem $_ -URL $Url -Header $Header -Path $Path
    
    }
    
    #endregion
    Screenshot of the process "Download a SharePoint Library using Graph"

    As you can see, I was able to download a SharePoint Library using Graph API. All folders and subfolders are created on my local C drive. Result of Download a SharePoint Library using Graph 1

    Result of Download a SharePoint Library using Graph 1
    Result of Download a SharePoint Library using Graph File share 1
    Result of Download a SharePoint Library using Graph 2
    Result of Download a SharePoint Library using Graph File share 2
    Result of Download a SharePoint Library using Graph 3
    Result of Download a SharePoint Library using Graph File share 3

    Further Reference

    You might want to download single files from SharePoint with Graph API? Check this out:
    How to download files from SharePoint using Graph API (PowerShell) (workplace-automation.com/)

    Learn how to access SharePoint via Graph in PowerShell: Access SharePoint via Graph API in PowerShell

    Learn how to upload files to SharePoint using Graph (PowerShell):
    How to Upload Files to SharePoint using Graph API PowerShell (workplace-automation.com/)

  • How to download files from SharePoint using Graph API (PowerShell)

    How to download files from SharePoint using Graph API (PowerShell)

    Downloading files from SharePoint is a common use case, when we are integrating 3rd party systems with SharePoint. In my previous articles, I have been explaining how you can upload files to SharePoint using PNP module. In this article, I want to show you how you can achieve download files from SharePoint using Graph API.

    In the beginning, we will create an Entra ID Enterprise Application in Entra ID, grant the created Enterprise Application the permission to interact with selected sites. At the end, I will share a PowerShell script to download files from the SharePoint using Graph API.

    What do I need to download files to SharePoint using Graph API?

    To downlaod a file to SharePoint using Graph API, you need the following prerequisites fulfilled:

    Your Sites.Selected App registration shall have following permission (at least):

    Screenshot of app registration with sites selected permissions

    How to Download Files from SharePoint using Graph API?

    As we have created an app registration and gave it the permission to write to a selected site, we can use to download files from SharePoint using Graph API. In my example, I want to download an Excel file from the SharePoint Library Shared Documents. Make sure, that you have adjusted the parameters and have the client secret handy, which we have created in the previous steps.

    Before you run the code, change the value of there parameters:

    $Tenant = “m365x323732” -> Name of the tenant. You can fetch it from the URL of your SharePoint: https://m365x16735261.sharepoint.com

    $AppID = “e0b8aefa-cb52-4bda-93a0-7d87120bcdbb” -> App ID, which you previously created in Entra ID

    $SiteID = “e35cee33-6d10-4e2c-a83b-496a26062ad3” -> ID of the Site, where you want to download the file from. In my example it would be https://m365x323732.sharepoint.com/sites/SalesAndMarketing/_api/site/id

    $LibraryURL = “https://m365x323732.sharepoint.com/sites/SalesAndMarketing/Shared%20Documents” -> URL to the SharePoint Library, where the file is located, which you want to download.

    $Path = “C:\Users\Serkar\Desktop\DG-2000 Product Specification.docx” – Path to where the file should be downloaded to

    $FileName = “DG-2000 Product Specification.docx” -> Name of the file

    When you run the code, you will be asked to provide the client secret for your app registration.

    Screenshot of credential prompt
    Param (
        $Tenant = "m365x323732",
        $AppID = "e0b8aefa-cb52-4bda-93a0-7d87120bcdbb",
        $SiteID = "e35cee33-6d10-4e2c-a83b-496a26062ad3",
        $LibraryURL = "https://m365x323732.sharepoint.com/sites/SalesAndMarketing/Shared%20Documents",
        $Path = "C:\Users\Serkar\Desktop\DG-2000 Product Specification.docx",
        $FileName = "DG-2000 Product Specification.docx"
    )
    
    $AppCredential = Get-Credential($AppID)
    
    #region authorize
    $Scope = "https://graph.microsoft.com/.default"
    
    $Body = @{
        client_id = $AppCredential.UserName
        client_secret = $AppCredential.GetNetworkCredential().password
        scope = $Scope
        grant_type = 'client_credentials'
    }
    
    $GraphUrl = "https://login.microsoftonline.com/$($Tenant).onmicrosoft.com/oauth2/v2.0/token"
    $AuthorizationRequest = Invoke-RestMethod -Uri $GraphUrl -Method "Post" -Body $Body
    $Access_token = $AuthorizationRequest.Access_token
    
    $Header = @{
        Authorization = $AuthorizationRequest.access_token
        "Content-Type"= "application/json"
    }
    #endregion
    
    #region get drives
    
    $GraphUrl = "https://graph.microsoft.com/v1.0/sites/$SiteID/drives"
    
    $BodyJSON = $Body | ConvertTo-Json -Compress
    $Result = Invoke-RestMethod -Uri $GraphUrl -Method 'GET' -Headers $Header -ContentType "application/json" 
    $DriveID = $Result.value| Where-Object {$_.webURL -eq $LibraryURL } | Select-Object id -ExpandProperty id
    
    If ($DriveID -eq $null){
    
        Throw "SharePoint Library under $LibraryURL could not be found."
    }
    
    #endregion
    
    #region download file
    
    $Url  = "https://graph.microsoft.com/v1.0/drives/$DriveID/items/root:/$($FileName)"
    
    $Response =  Invoke-RestMethod -Uri $Url -Headers $Header -Method Get -ContentType 'multipart/form-data' 
    
    Invoke-WebRequest -Uri $Response.'@microsoft.graph.downloadUrl' -OutFile $Path
    
    #endregion

    At the end you will receive the following response as example

    As you can see, the file was downloaded successfully from the SharePoint Library

    Screenshot of the downloaded file from SharePoint using Graph API
    Result of download files from SharePoint using Graph

    Further Reference

    You might be also interested in following articles, which are related to MS Graph API:

    Security of app registration in Entra ID | SPO Scripts

    Create SharePoint list items using Graph API (PowerShell) (workplace-automation.com/)

    How to get SharePoint List Items with Graph API (PowerShell) | SPO Scripts

  • How to upload files to SharePoint using Graph API (PowerShell)

    How to upload files to SharePoint using Graph API (PowerShell)

    Uploading files to SharePoint is a common use case, when we are integrating 3rd party systems with SharePoint. In my previous articles, I have been explaining how you can upload files to SharePoint using PNP module. In this article, I want to show you how you can achieve upload files to SharePoint using Graph API.

    Note: This method works for files, where the size is maximum 4 MB.

    In the beginning, we will create an Azure Enterprise Application in Azure Active Directory. Then we will grant the created Enterprise Application the permission to interact with selected sites. At the end, I will share a PowerShell script to upload files to the SharePoint using Graph API.

    What do I need to upload files to SharePoint using Graph API?

    To upload a file to SharePoint using Graph API, you need the following prerequisites fulfilled:

    • You need your global administrator in your organization to grant the sites.selected permission for an Azure Enterprise Application
    • You need to install PNP PowerShell module, to grant the Enterprise Application the permission to upload files to the specific SharePoint Site

    How to create the Enterprise Application to upload Files to SharePoint?

    In the beginning it is import to understand, which permission the Enterprise Application needs so that we can upload files to SharePoint using Graph API.

    1. Browse to Azure Active Directory Portal
    2. Create an App Registration with Sites.Selected permissions
    3. Create and note down credentials for the App Registration


    I will explain how to do it step-by-step.

    Browse to Azure Active Directory Portal

    Open https://portal.azure.com/#view/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade in your browser.

    If you have the global admin rights, I recommend authenticating with that account, so that you can directly grant your enterprise application the permission. Otherwise, you need to reauthenticate or ask your administrator to grant your enterprise application the permissions.

    Create an App Registration with Sites.Selected permissions

    Browse to the App Registration blade

    Screenshot of the App registrations shortcut

    Click on new registration

    Screenshot of new enterprise app registration

    Define for the App a meaningful name, which follows your organization standards (Different admins should recognize what the purpose for this app is) and register it.

    Screenshot on how to create the App registration in MS Azure

    Browse to API permissions to grant your app registration the permission to upload files to SharePoint using Graph API.

    Screenshot of configuration blade for App API permissions

    Click on Add a permission

    Screenshot of how to add the API permission to the App registration

    Now choose Microsoft Graph

    Chose of Microsoft Graph Permission

    If you want your application to work in the background (Without any user authentication), you need to choose Application permission. As my intention is to show you how to automate the process, I am sharing with you the application permission way.

    Screenshot of Application permission path of API permission

    Now search for Sites.Selected and click on add permissions.

    If you are signed in with a user account with global administrator privileges, you can grant administrator admin consent for your tenant. In other case, you either need to sign in with the global administrator account or you have to ask your administrator to grant your app registration the permission.

    Screenshot of grant admin consent for tenant of the app registration

    Create Secret for the App Registration

    To authenticate with your enterprise application, you need to either upload a certificate or create a secret for your enterprise application. In this case I am creating a secret.

    Browse to Certificates & secrets

    Screenshot of Certificates & secrets blade

    Screenshot on how to create a client secret

    For the description, I think it makes sense to define which application is going to use your client secret. For the duration, I would go with the recommendation of Microsoft, as you might have lost this application out of sight in 24 months, which is the maximum duration for a client secret.

    Screenshot of creation of app secret

    Now note down, what you see under value, you can only see it now.

    Screenshot of app secret

    With that last step, your Enterprise application has the right permissions on Azure Active Directory. In the next step you need to grant your enterprise Application the permission to write into the specific SharePoint site.

    How to grant the App Registration Write Permissions for a Selected SharePoint Site?

    In order to grant the app registration the permission, you need to ensure that PNP Module is installed on your client and that you are allowed to use it.

    If you have not yet installed it, check the following documentation:

    Connect to SharePoint with PowerShell | SharePoint Online (workplace-automation.com/)

    If both conditions are applying, you can use this code to grant your App registration right permission to write in the site.

    You can get your App ID by browsing to the overview page of your app registration.

    Screenshot of Application (client) ID

    Import-Module PnP.PowerShell
    
    $AppID = "e0b8aefa-cb52-4bda-93a0-7d87120bcdbb"
    $AppRegistrationName = "SP_Sites.Selected_SalesandMarketing"
    
    $DisplayNameofSitePermission = "Enterprise Application $AppRegistrationName"
    $SiteURL = "https://m365x323732.sharepoint.com/sites/SalesAndMarketing"
    
    
    Connect-PnPOnline -Url $SiteURL -Interactive
    Grant-PnPAzureADAppSitePermission -AppId $AppID -DisplayName $DisplayNameofSitePermission -Site $SiteURL -Permissions Write

    You will be asked to authenticate.

    Screenshot of authentication prompt

    At the end, your app registration has write permissions.

    Screenshot of App registration to write permissions to SharePoint Site

    How to Upload Files to SharePoint using Graph API?

    As we have created an app registration and gave it the permission to write to a selected site, we can use to upload files to SharePoint using Graph API. In my example, I want to upload a picture to the SharePoint Library Shared Documents. Make sure, that you have adjusted the parameters and have the client secret handy, which we have created in the previous steps.

    When you run the code, you will be asked to provide the client secret for your app registration.

    Screenshot of credential prompt
    Param (
        $Tenant = "m365x323732",
        $AppID = "e0b8aefa-cb52-4bda-93a0-7d87120bcdbb",
        $SiteID = "e35cee33-6d10-4e2c-a83b-496a26062ad3",
        $LibraryURL = "https://m365x323732.sharepoint.com/sites/SalesAndMarketing/Shared%20Documents",
        $Path = "C:\Users\Serkar\Desktop\security.png"
    )
    
    $AppCredential = Get-Credential($AppID)
    
    #region authorize
    $Scope = "https://graph.microsoft.com/.default"
    
    $Body = @{
        client_id = $AppCredential.UserName
        client_secret = $AppCredential.GetNetworkCredential().password
        scope = $Scope
        grant_type = 'client_credentials'
    }
    
    $GraphUrl = "https://login.microsoftonline.com/$($Tenant).onmicrosoft.com/oauth2/v2.0/token"
    $AuthorizationRequest = Invoke-RestMethod -Uri $GraphUrl -Method "Post" -Body $Body
    $Access_token = $AuthorizationRequest.Access_token
    
    $Header = @{
        Authorization = $AuthorizationRequest.access_token
        "Content-Type"= "application/json"
    }
    #endregion
    
    #region get drives
    
    
    $GraphUrl = "https://graph.microsoft.com/v1.0/sites/$SiteID/drives"
    
    $BodyJSON = $Body | ConvertTo-Json -Compress
    $Result = Invoke-RestMethod -Uri $GraphUrl -Method 'GET' -Headers $Header -ContentType "application/json" 
    $DriveID = $Result.value| Where-Object {$_.webURL -eq $LibraryURL } | Select-Object id -ExpandProperty id
    
    If ($DriveID -eq $null){
    
        Throw "SharePoint Library under $LibraryURL could not be found."
    }
    
    #endregion
    
    #region upload file
    
    $FileName = $Path.Split("\")[-1]
    $Url  = "https://graph.microsoft.com/v1.0/drives/$DriveID/items/root:/$($FileName):/content"
    
    Invoke-RestMethod -Uri $Url -Headers $Header -Method Put -InFile $Path -ContentType 'multipart/form-data' -Verbose
    #endregion 

    At the end you will receive the following response as example

    VERBOSE: PUT with -1-byte payload
    VERBOSE: received -1-byte response of content type application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8
    
    
    @odata.context               : https://graph.microsoft.com/v1.0/$metadata#drives('b%21M-5c4xBtLE6oO0lqJgYq04f6JqJ_iTlEhBdXmkuqxRI4cWqlVo8-QKlAJO6KoBgT')/items/$entity
    @microsoft.graph.downloadUrl : https://m365x323732.sharepoint.com/sites/SalesAndMarketing/_layouts/15/download.aspx?UniqueId=9f900d6c-d023-41cc-8839-61f079916c03&Translate=fals
                                   e&tempauth=eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJhdWQiOiIwMDAwMDAwMy0wMDAwLTBmZjEtY2UwMC0wMDAwMDAwMDAwMDAvbTM2NXgzMjM3MzIuc2hhcmVwb2ludC5jb21AOG
                                   I5Y2ZmMzQtNjg4YS00YTkzLThhZDMtMjNjYTQ3ZWU2ZTcyIiwiaXNzIjoiMDAwMDAwMDMtMDAwMC0wZmYxLWNlMDAtMDAwMDAwMDAwMDAwIiwibmJmIjoiMTY3MjgyMjg4MSIsImV4cCI6IjE
                                   2NzI4MjY0ODEiLCJlbmRwb2ludHVybCI6IjYrUUtpd1NldjBJdksyUFkwS2wyV0YveWNLSnYxQkdPc1RFb1pWclRyems9IiwiZW5kcG9pbnR1cmxMZW5ndGgiOiIxNDYiLCJpc2xvb3BiYWNr
                                   IjoiVHJ1ZSIsImNpZCI6IlpUSTFaakE0WTJJdE5XUTRPUzAwTldRd0xXSmtPVEV0WlRnMFpEUmhZVFJrTW1VMyIsInZlciI6Imhhc2hlZHByb29mdG9rZW4iLCJzaXRlaWQiOiJaVE0xWTJWb
                                   E16TXRObVF4TUMwMFpUSmpMV0U0TTJJdE5EazJZVEkyTURZeVlXUXoiLCJhcHBfZGlzcGxheW5hbWUiOiJTUF9TaXRlcy5TZWxlY3RlZF9TYWxlc2FuZE1hcmtldGluZyIsIm5hbWVpZCI6Im
                                   UwYjhhZWZhLWNiNTItNGJkYS05M2EwLTdkODcxMjBiY2RiYkA4YjljZmYzNC02ODhhLTRhOTMtOGFkMy0yM2NhNDdlZTZlNzIiLCJyb2xlcyI6InNlbGVjdGVkc2l0ZXMiLCJ0dCI6IjEiLCJ
                                   1c2VQZXJzaXN0ZW50Q29va2llIjpudWxsLCJpcGFkZHIiOiIyMC4xOTAuMTkwLjEwMSJ9.c0kydmY4eUFWS2lvWmJkTG1yTjJGbmV0SkVHVHRtdjNZWHppbm1SKytTRT0&ApiVersion=2.0
    createdDateTime              : 2023-01-04T09:01:21Z
    eTag                         : "{9F900D6C-D023-41CC-8839-61F079916C03},1"
    id                           : 01P5GC6MTMBWIJ6I6QZRAYQOLB6B4ZC3AD
    lastModifiedDateTime         : 2023-01-04T09:01:21Z
    name                         : security.png
    webUrl                       : https://m365x323732.sharepoint.com/sites/SalesAndMarketing/Shared%20Documents/security.png
    cTag                         : "c:{9F900D6C-D023-41CC-8839-61F079916C03},2"
    size                         : 129678
    createdBy                    : @{application=; user=}
    lastModifiedBy               : @{application=; user=}
    parentReference              : @{driveType=documentLibrary; driveId=b!M-5c4xBtLE6oO0lqJgYq04f6JqJ_iTlEhBdXmkuqxRI4cWqlVo8-QKlAJO6KoBgT; id=01P5GC6MV6Y2GOVW7725BZO354PWSELRRZ; 
                                   path=/drives/b!M-5c4xBtLE6oO0lqJgYq04f6JqJ_iTlEhBdXmkuqxRI4cWqlVo8-QKlAJO6KoBgT/root:}
    file                         : @{mimeType=image/png; hashes=}
    fileSystemInfo               : @{createdDateTime=2023-01-04T09:01:21Z; lastModifiedDateTime=2023-01-04T09:01:21Z}
    image                        : 
    shared                       : @{scope=users}
    
    
    
    Screenshot of the response

    As you can see, the file was uploaded successfully to the SharePoint Library

    Screenshot of the uploaded files to SharePoint using Graph API
     Screenshot of the picture in the SharePoint Library

    How to Upload Files to a Folder in SharePoint using Graph API?

    In order to upload files to folders, you just need to make sure that your URI contains the folder structure after root:/.
    In my example below, I uploaded apicture to the Subfolder folder.

    $Url = "https://graph.microsoft.com/v1.0/drives/$DriveID/items/root:/General/Subfolder/$($FileName):/content"
    
    Param (
        $Tenant = "m365x04995906",
        $AppID = "3669592a-9085-4f09-8c03-2b2223aa002c",
        $SiteID = "0a749590-8778-4779-808a-3bbb9bc1a5e1",
        $LibraryURL = "https://m365x04995906.sharepoint.com/sites/Remoteliving/Shared%20Documents",
        $Path = "C:\Users\Serka\OneDrive\Desktop\pngs\00003.jpg",
        $Folder = "General/Subfolder"
    )
    
    #$AppCredential = Get-Credential($AppID)
    
    #region authorize
    $Scope = "https://graph.microsoft.com/.default"
    
    $Body = @{
        client_id = $AppCredential.UserName
        client_secret = $AppCredential.GetNetworkCredential().password
        scope = $Scope
        grant_type = 'client_credentials'
    }
    
    $GraphUrl = "https://login.microsoftonline.com/$($Tenant).onmicrosoft.com/oauth2/v2.0/token"
    $AuthorizationRequest = Invoke-RestMethod -Uri $GraphUrl -Method "Post" -Body $Body
    $Access_token = $AuthorizationRequest.Access_token
    
    $Header = @{
        Authorization = $AuthorizationRequest.access_token
        "Content-Type"= "application/json"
    }
    #endregion
    
    #region get drives
    
    
    $GraphUrl = "https://graph.microsoft.com/v1.0/sites/$SiteID/drives"
    
    $BodyJSON = $Body | ConvertTo-Json -Compress
    $Result = Invoke-RestMethod -Uri $GraphUrl -Method 'GET' -Headers $Header -ContentType "application/json" 
    $DriveID = $Result.value| Where-Object {$_.webURL -eq $LibraryURL } | Select-Object id -ExpandProperty id
    
    If ($DriveID -eq $null){
    
        Throw "SharePoint Library under $LibraryURL could not be found."
    }
    
    #endregion
    
    #region upload file
    
    $FileName = $Path.Split("\")[-1]
    $Url = "https://graph.microsoft.com/v1.0/drives/$DriveID/items/root:/$Folder/$($FileName):/content"
    
    Invoke-RestMethod -Uri $Url -Headers $Header -Method Put -InFile $Path -ContentType 'multipart/form-data' -Verbose
    #endregion 

    Once executed, you’ll get back following response:

    VERBOSE: HTTP/1.1 PUT with 232877-byte payload
    VERBOSE: received -byte response of content type application/json
    VERBOSE: Content encoding: utf-8
    
    @odata.context               : https://graph.microsoft.com/v1.0/$metadata#drives('b%21kJV0CniHeUeAiju7m8Gl4ZmfCOoRAXJNrYB9wjbkfZ-Vmuw3EELGQ7bZlNIfSaf4')/items/$entity
    @microsoft.graph.downloadUrl : https://m365x04995906.sharepoint.com/sites/Remoteliving/_layouts/15/download.aspx?UniqueId=2c3c2e98-5be4-4ce2-8a81-2c0d4bab00b4&Translate=false&tempauth=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIwMDAwMDAwMy0wMDAwLTBmZjEtY2UwMC0wMDAwMDAwMDAwMDAvbTM2NXgwNDk5NTkwNi5zaGFyZXBvaW50LmNv 
                                   bUAxZjc5NWU5NS1jMDZiLTQxMDktOTI0ZS0zNTY5ZmRkZjQ5OWYiLCJpc3MiOiIwMDAwMDAwMy0wMDAwLTBmZjEtY2UwMC0wMDAwMDAwMDAwMDAiLCJuYmYiOiIxNjk3NDQ0MDk5IiwiZXhwIjoiMTY5NzQ0NzY5OSIsImVuZHBvaW50dXJsIjoiWjA4ZkI5NTdNQklCckgzRUg4VHp1N0RuT3lTWVhCMHQ4VE5zZTNvNmMvMD0iLCJlbmRwb2ludHVybExlbmd0aCI6IjE0MyIsImlzbG 
                                   9vcGJhY2siOiJUcnVlIiwiY2lkIjoiWktRTFZjNHhUMEt2RlF2YSsxMjZJZz09IiwidmVyIjoiaGFzaGVkcHJvb2Z0b2tlbiIsInNpdGVpZCI6Ik1HRTNORGsxT1RBdE9EYzNPQzAwTnpjNUxUZ3dPR0V0TTJKaVlqbGlZekZoTldVeCIsImFwcF9kaXNwbGF5bmFtZSI6IlNQU2l0ZXNfUmVhZFdyaXRlQWxsIiwibmFtZWlkIjoiMzY2OTU5MmEtOTA4NS00ZjA5LThjMDMtMmIyMjIz 
                                   YWEwMDJjQDFmNzk1ZTk1LWMwNmItNDEwOS05MjRlLTM1NjlmZGRmNDk5ZiIsInJvbGVzIjoiYWxsc2l0ZXMud3JpdGUiLCJ0dCI6IjEiLCJpcGFkZHIiOiIyMC4xOTAuMTkwLjk5In0.2EMXEM184-nAFJnbOJy_PM8q5YKvOK66IoqggTX0g_E&ApiVersion=2.0
    createdDateTime              : 16.10.2023 08:14:59
    eTag                         : "{2C3C2E98-5BE4-4CE2-8A81-2C0D4BAB00B4},1"
    id                           : 01NLC4VWMYFY6CZZC34JGIVAJMBVF2WAFU
    lastModifiedDateTime         : 16.10.2023 08:14:59
    name                         : 00003.jpg
    webUrl                       : https://m365x04995906.sharepoint.com/sites/Remoteliving/Shared%20Documents/General/Subfolder/00003.jpg
    cTag                         : "c:{2C3C2E98-5BE4-4CE2-8A81-2C0D4BAB00B4},2"
    size                         : 232877
    createdBy                    : @{application=; user=}
    lastModifiedBy               : @{application=; user=}
    parentReference              : @{driveType=documentLibrary; driveId=b!kJV0CniHeUeAiju7m8Gl4ZmfCOoRAXJNrYB9wjbkfZ-Vmuw3EELGQ7bZlNIfSaf4; id=01NLC4VWOFX4E7TTHLCNB256DE3BDS7OML; name=Subfolder; path=/drives/b!kJV0CniHeUeAiju7m8Gl4ZmfCOoRAXJNrYB9wjbkfZ-Vmuw3EELGQ7bZlNIfSaf4/root:/General/Subfolder;
                                   siteId=0a749590-8778-4779-808a-3bbb9bc1a5e1}
    file                         : @{mimeType=image/jpeg; hashes=}
    fileSystemInfo               : @{createdDateTime=16.10.2023 08:14:59; lastModifiedDateTime=16.10.2023 08:14:59}
    image                        : 
    shared                       : @{scope=users}

    Further Reference

    You might be also interested in following articles, which are related to MS Graph API:

    Security of app registration in Azure Active Directory | SPO Scripts

    Create SharePoint list items using Graph API (PowerShell) (workplace-automation.com/)

    How to get SharePoint List Items with Graph API (PowerShell) | SPO Scripts

  • How to get SharePoint List Items with Graph API (PowerShell)

    How to get SharePoint List Items with Graph API (PowerShell)

    In this article, I want to show you how to get SharePoint List Items with Graph API. Sometimes we are limited to the tools we can use, so we need to think about other approaches, to get our aim delivered. In this article, I want to show you how you can get SharePoint List items with the Microsoft Graph API by using PowerShell.

    To get SharePoint List Items with Graph API, you can follow the next three steps:

    1. Configure the Azure Enterprise Application
    2. Grant the Enterprise Application the Permission
    3. Get SharePoint List Items with Graph API in PowerShell

    Prerequistes

    Step 1: Configure the Azure Enterprise Application

    I am following the least privilege approach and grant only the necessary permission for the app registration.

    Hence I have created an App registration with following permissions:

    Permission NameType
    Sites.Selected Application
    User.ReadDelegated
    Application Registration in Microsoft Azure for reading SharePoint List Items

    If you don’t know how to create it, follow my next steps, otherwise if you are familiar with it, you can also skip to the Step 2 – Grant the Enterprise Application the Permission.

    1. Browse to Azure Portal and Search for Application Registrations and click on New registration

    Create a new Azure App registration Screenshot

    2. Give the App a meaningful name, which follows your organization standards (Differnt admins should recognize what the purpose for this app is) and Register it

    Registration of new App registration in Azure Portal

    3. Note down the Application ID and go to Certificates & Secrets

    Display of Application Registration in Azure Portal

    4. Create a new client secret or upload a certificate (I will show the secret approach)

    Creation of new client secret in Azure Portal

    5. Also here a meaningful name is supportive for other colleauges. For the duration it makes sense to go with a reasonable duration. I would go with the recommendation of Microsoft as you might have lost this application out of sight in 24 months, which is the maximum duration for a client secret.
    Creation of client secret for an App registration
    6. Now you will have ONE chance to note down the client secret. Treat it like a password. Depending on your App Permission your App might be powerful. Hence you should save for instance in a Password Manager.
    Client Secret Exposure for App registration

    7. Now Click on API permissions on the left navigation pane and add a permission for Microsoft Graph

    Adding Permission for Microsoft Graph
    8. Add the Application Permission Sites.Selected if you want the code run in the background without a signed-in user.
    API Permission for Application Permissions
    Sites.Selected Graph Permissions
    9. Once you added that, you will need to consent the permission from a global administrator.
    Not granted permissions for sites.selected
    Granted permissions look like this:
    Granted app registration permissions

    That’s it. You created an Azure App registration with Sites.Selected permission, where you need now to grant the permissions for the specific site.

    Step 2: Grant the Enterprise Application the Permission

    For this step you need to ensure that PNP Module is installed on your client and that you are allowed to use it.

    If both conditions are appliying, you can use this code to grant Enterprise App, created in Step 1 the right permission for the site. In this case I am granting a read role.

    Import-Module PnP.PowerShell
    
    $AppID = "9ea2120f-288c-47b6-8895-31e0fb4d9211"
    
    $DisplayNameofSitePermission = "Enterprise Application SP_Access_SharePoint_List_SalesAndMarketing"
    $SiteURL = "https://m365x323732.sharepoint.com/sites/SalesAndMarketing"
    
    
    Connect-PnPOnline -Url $SiteURL -Interactive
    Grant-PnPAzureADAppSitePermission -AppId $AppID -DisplayName $DisplayNameofSitePermission -Site $SiteURL -Permissions Read 

    You will need to log in with an account, which has access to the site.

    After that you will see, that the Enterprise Application has now read access to the Site.

    Screenshot of PowerShell, which indicates that Site Access is now working

    Step 3: Get SharePoint List Items with Graph API in PowerShell

    As the enterprise application has now the permission to read contents from the designated SharePoint Site, you are able to read the contents of the SharePoint Site.

    For this we need the app credentials and the site id of the site for which you want to read out the content.

    <siteurl>/_api/site/id

    For my example it is:

    https://m365x323732.sharepoint.com/sites/SalesAndMarketing/_api/site/id

    Screenshot of the SharePoint site Id

    Once you adjusted the parameters, you can run the script

    Param (
        $AppID = "9ea2120f-288c-47b6-8895-31e0fb4d9211",
        $Scope = "https://graph.microsoft.com/.default",
        $Tenant = "m365x323732",
        $SiteID = "e35cee33-6d10-4e2c-a83b-496a26062ad3",
        $ListTitle = "Product%20List"
    )
    
    Import-Module PnP.PowerShell
    
    
    $AppCredential = Get-Credential($AppID)
    
    #region authorize
    
    $Body = @{
        client_id = $AppCredential.UserName
        client_secret = $AppCredential.GetNetworkCredential().password
        scope = $Scope
        grant_type = 'client_credentials'
    }
    
    $GraphUrl = "https://login.microsoftonline.com/$($Tenant).onmicrosoft.com/oauth2/v2.0/token"
    $AuthorizationRequest = Invoke-RestMethod -Uri $GraphUrl -Method "Post" -Body $Body
    $Access_token = $AuthorizationRequest.Access_token
    
    $Header = @{
    
        Authorization = $AuthorizationRequest.access_token
    }
    
    #endregion
    
    #region get items
    
    $GraphUrl = "https://graph.microsoft.com/v1.0/sites/$SiteID/lists/$ListTitle/items?expand=fields"
    
    $Items  = Invoke-RestMethod -Uri $GraphUrl -Method 'GET' -Body $Body -Headers $Header
    $Items.value.fields
    #endregion 
    get SharePoint List Items with Graph API

    Further reference

    To understand how Microsoft Graph API works, check out this article from Microsoft:
    Use the Microsoft Graph API – Microsoft Graph | Microsoft Learn

    You might also be interested in getting all SharePoint lists with PowerShell
    SharePoint Online: How to get all lists and libraries (workplace-automation.com/)

  • SharePoint PowerShell add site collection administrator

    SharePoint PowerShell add site collection administrator

    In this article, I am describing how to add site collection administrator to your SharePoint sites. I use this script, when I need to supply an application service for a new customer. I recommend running this script periodically (every day), so it is ensured, that you have access to new sites later also.

    You will learn how to add a user and an AD group to the site collection administrators.

    Prerequisites

    Before you can add a site collection administrator to a site collection, you need to ensure the following:

    Scheduled runs

    When you run the scripts below, it is only for the current existing sites, but since the departments won’t stop create sites, e.g. by creating a new team in Microsoft Teams, it makes sense to schedule the scripts. If you want to schedule the scripts, ensure that the connection to the tenant is not interactive, by exporting the credentials locally with the service user, which you will run to schedule the script and import the credential in your script. Only the user who exported the credentials object, can read the credential object on the same machine.

    Before you schedule the script, export your service user credential with this cmdlets (change the path before running):

    Get-Credential |Export-Clixml "C:\Users\Serkar\Desktop\devmod.key"

    You will be prompted for credentials and after this, a file will be created.

    Use this credential object before you connect to SharePoint like this:

    $Credential = Import-Clixml "C:\Users\Serkar\Desktop\devmod.key"
    

    And connect to SharePoint Online like this:

    Connect-SPOService -Url  $TenantUrl -Credential $Credential

    I wrote a detailed article about this. If you want to deepen your knowledge, check it out: Use credentials in PowerShell | SPO Scripts

    In the end of this article, I have also provided a script, which shows you how the script has to look, if you want to schedule it periodically.

    Add a user as a site colllection adminstrator

    If you want to add a user as a site collection administrator, you can do it like this:

    Please change the values of the variables.

    Connect-SPOService -Url  "https://devmodernworkplace-admin.sharepoint.com/"
    $User = "Serkar@devmodernworkplace.onmicrosoft.com"
    $SiteUrl = "https://devmodernworkplace.sharepoint.com/sites/Sales"
    
    Set-SPOUser -Site $SiteUrl -LoginName $User -IsSiteCollectionAdmin $true

    Add an AD group as a site collection administrators

    If you want to add the AD group to the site collection adminstrators, you need the Object ID of the group. You can look it up like this:

    Visit https://portal.azure.com

    Azure Portal home
    Azure active directory groups

    For this purpose I want to add the IT AD security group to the site collection administrators.

    Screenshot of an ad group, which I want to add as a site collection administrator

    Take a note of the Object Id

    Object ID of the ad group, which will be added as site collection administrator

    With the Object ID, we can add a group as a site collection administrator:

    Connect-SPOService -Url  "https://devmodernworkplace-admin.sharepoint.com/"
    $Group = "C:0t.c|tenant|df8e0958-7882-4835-b6a4-cc877922a1f8"
    $SiteUrl = "https://devmodernworkplace.sharepoint.com/sites/Sales"
    
    Set-SPOUser -Site $SiteUrl -LoginName $Group -IsSiteCollectionAdmin $true

    Add a user as a site collection administrator to all site collections

    If you want to add a single user as a site collection administrator, you can use this script:

    $TenantUrl = "https://devmodernworkplace-admin.sharepoint.com/"
    $User = "Serkar@devmodernworkplace.onmicrosoft.com"
    
    Connect-SPOService -Url  $TenantUrl 
    
    $SPOSites = Get-SPOSite 
    
    foreach ($SPOSite in $SPOSites)
    {
        Set-SPOUser -Site $SPOSite.Url -LoginName $User -IsSiteCollectionAdmin $true
    }

    Add an AD group as a site collection administrator to all site collections

    $TenantUrl = "https://devmodernworkplace-admin.sharepoint.com/"
    $Group = "C:0t.c|tenant|df8e0958-7882-4835-b6a4-cc877922a1f8"
    
    Connect-SPOService -Url  $TenantUrl 
    
    $SPOSites = Get-SPOSite 
    
    foreach ($SPOSite in $SPOSites)
    {
        Set-SPOUser -Site $SPOSite.Url -LoginName $Group -IsSiteCollectionAdmin $true
    }
    

    Scheduled runs: Add an AD group as a site collection administrator to all site collections

    If you run this script, you won’t get any prompt from PowerShell, but you have to ensure, that you have exported the credential object before, otherwise the script will not work.

    $TenantUrl = "https://devmodernworkplace-admin.sharepoint.com/"
    $Group = "C:0t.c|tenant|df8e0958-7882-4835-b6a4-cc877922a1f8"
    
    $Credential = Import-Clixml -Path "C:\Users\Serkar\Desktop\devmod.key"
    
    Connect-SPOService -Url  $TenantUrl -Credential $Credential
    
    $SPOSites = Get-SPOSite 
    
    foreach ($SPOSite in $SPOSites)
    {
        Set-SPOUser -Site $SPOSite.Url -LoginName $Group -IsSiteCollectionAdmin $true
    }

    Website vector created by stories – www.freepik.com

  • 3 of the most important SharePoint PowerShell Modules and Snappins

    In my role as an automation consultant, I encounter several challenges, where you have to think out of the box. Thinking out of the box also means, to reconsider the tools, which you are using. If you start with SharePoint/ M365 automation, you will notice that there are at least three SharePoint PowerShell modules and snappins, with which you can automate your SharePoint environment. All of the SharePoint PowerShell modules/ snappins have indeed a reason for existence. In this article, I will show you where the focus of each module is, so you can get an idea, when to use which module. You will also get a overview, how to use all of them and what pros and cons eacht SharePoint PowerShell module/ snappin has.

    Think out of the box when designing an automation solution

    PNP PowerShell

    The beginning of PNP PowerShell was a community project of several developers, which have developed many client side object model (CSOM) wrappers for SharePoint 2013, 2016 and 2019. In fact PNP stands for Patterns and Practices. The modules are known as SharePointPnPPowerShell20xx or SharePointPnPPowerShellOnline. About the turn of the year all consolidated to PNP.PowerShell. Also the main focus from “catchall” have changed to the cloud version of SharePoint.

    When to use PNP PowerShell

    PNP.PowerShell is developing to one of the overreaching modules in Microsoft 365 cosmos. I would recommend to try everything first with PNP.PowerShell before you try doing it with SharePointOnlinePowerShell, since the development of the module goes on and on and you can do way more things with PNP PowerShell, than with SharePointOnlinePowerShell in terms of business process automation.

    Topics covered by PNP PowerShell

    Currently PNP PowerShell covers following topics and services:

    • SharePoint Online
    • SharePoint Server (on premises)
    • M365 Groups
    • Power Automate
    • Azure AD
    • SharePoint Syntex
    • Microsoft Teams
    • Microsoft Planner

    Pros and Cons of PNP PowerShell

    ProsCons
    ✅ You can use it for on-premises and cloud services❌ Some cmdlets are not working as expected e.g. Set-PNPSite -SitecollectionAdmin
    ✅ You can use it on all servers/ clients, which have access to the tenant/ farm (online or on-premises)❌ With constant development, cmdlets might change, so you have to monitor the changes, when updating the module
    ✅ Constant development of module❌ You have to consent the access with the global admin role
    ✅ Supports many branches of M365 (SharePoint, Teams, Planner etc.)❌ Microsoft Support won’t support if there is a bug in the modules. You have to open a request in github
    ✅ You don’t have to make breaking changes to your scripts if you move from SharePoint Server to SharePoint Online
    ✅ You can change a wide range of content in SharePoint (Sites, Lists, Items)
    ✅ You don’t need SharePoint Admin permission to connect to a site

    How to make use of PNP PowerShell?

    If you want to make use of PNP PowerShell, check out this blog post, which describes every step of it in detail: Connect to SharePoint with PowerShell | SharePoint Online (workplace-automation.com/)

    What can I change with PNP PowerShell?

    In this section, I want to give you an overview of what you can change with PNP Powershell. Indeed there are more objects in SharePoint. I focus in the first place on objects, which I think are mostly changed with PowerShell for business process automation.

    ObjectChanging
    possible
    with
    PNP PowerShell?
    CmdletPrequisitesReference
    TenantSet-PNPTenantSharePoint Admin RoleSet-PnPTenant (PnP.Powershell) | Microsoft Docs
    Hub SiteSet-PNPHubSite Access to SiteSet-PnPHubSite (PnP.Powershell) | Microsoft Docs
    SiteSet-PNPSiteAccess to Site Set-PnPSite (PnP.Powershell) | Microsoft Docs
    Sub SiteSet-PNPWeb Access to Site and SubsiteSet-PnPWeb (PnP.Powershell) | Microsoft Docs
    ListSet-PNPList Access to Site & ListSet-PnPList (PnP.Powershell) | Microsoft Docs
    LibrarySet-PNPSite Access to Site & Library Set-PnPList (PnP.Powershell) | Microsoft Docs
    ItemSet-PNPListItemAccess to Site & List & ItemSet-PnPListItem (PnP.Powershell) | Microsoft Docs
    FileSet-PNPListItem Access to Site & Library & FileSet-PnPListItem (PnP.Powershell) | Microsoft Docs
    PageSet-PNPage Access to Site & Library & Page Set-PnPPage (PnP.Powershell) | Microsoft Docs

    Who is maintaing PNP PowerShell?

    The module is maintained by the PNP Developer Community. At this stage I have to thank you guys for your hard and good work. It was really a game changer, when I didn’t had to use scripts on the SharePoint Server.

    Since it is all open source, you can also check the source code here: Microsoft 365 Community (github.com)

    You can find more about the community here: SharePoint Developer Community (SharePoint PnP) resources | Microsoft Docs

    What if I find a bug in PNP Powershell?

    Be sure to follow their rules, when you open an issue, so they can help you faster. In the first step check the discussions. You have to be patient, since it is a community developed module.

    If you find a bug, you have to open a github issue here: pnp/powershell: PnP PowerShell (github.com)

    SharePointOnlinePowerShell

    SharePointOnlinePowerShell is the official Module, which is published by Microsoft. In comparison to PNP Powershell, SharePointOnlinePowerShell focuses only on the administration of SharePoint Online.

    When to use SharePointOnlinePowerShell

    My strategy is to use SharePointOnlinePowerShell, when I experience a buggy behaviour by PNP.PowerShell. Altough it is the official SharePoint PowerShell module, you cannot change e.g. list items.

    Topics covered by SharePointOnlinePowerShell

    SharePointOnlinePowerShell is only focussing on SharePoint Online. You cannot connect to another service or to SharePoint Server (on premises).

    Pros and Cons of SharePointOnlinePowerShell

    ProsCons
    ✅ I have experienced more stability on this module – It tends to have less bugs❌ You can only administer SharePoint. Business process automation will be hard to cover with this module, because you cannot change webs, lists or items
    ✅ It is supported by Microsoft❌ You can use it only for SharePoint Online
    ✅ Constant development of module – I have not experienced any breaking changes❌ You have to have SharePoint admin role

    How to make use of SharePointOnlinePowerShell?

    I have described how to install and connect with this SharePoint PowerShell Module here: SharePointOnlinePowerShell: How to connect to SharePoint Online (workplace-automation.com/)

    What can I change with SharePointOnlinePowerShell ?

    In this section, I want to give you a overview of what you can change with PNP Powershell.

    ObjectChanging
    possible
    with
    SharePointOnlinePowerShell?
    CmdletPrequisitesReference
    TenantSet-SPOTenantSharePoint Admin RoleSet-SPOTenant (SharePointOnlinePowerShell) | Microsoft Docs
    Hub SiteSet-PNPHubSite SharePoint Admin RoleSet-SPOHubSite (SharePointOnlinePowerShell) | Microsoft Docs
    SiteSet-SPOSiteSharePoint Admin RoleSet-SPOSite (SharePointOnlinePowerShell) | Microsoft Docs
    Sub Site
    List
    Library
    Item
    File
    Page

    Who is maintaing SharePointOnlinePowerShell?

    The module is developed and maintained by Microsoft. You can find the source code is here: Microsoft 365 Community (github.com)

    What if I find a bug in SharePointOnlinePowerShell ?

    You can contact Microsoft Support or try to “Contact Owners” in PowerShellGallery: https://www.powershellgallery.com/packages/Microsoft.Online.SharePoint.PowerShell

    SharePointOnlinePowerShell PowerShell Gallery

    Microsoft.SharePoint.PowerShell

    Microsoft.SharePoint PowerShell is the only type of cmdlets, which is not a SharePoint PowerShell Module, but a PSSnappin. Snapins are considered as the old way to add cmdlets and other resources. It’s focus is SharePoint Server.

    When to use Microsoft.SharePoint.PowerShell

    I recommend to use Microsoft.SharePoint.PowerShell in following situations:

    • You don’t plan to migrate the automation solution to SharePoint Online
    • You have access to the SharePoint Servers
    • You want to administer SharePoint Server

    Topics covered by Microsoft.SharePoint.PowerShell

    With Microsoft.SharePoint.PowerShell you can only connect to SharePoint Server (on premises). You can see all cmdlets here from the Microsoft reference: SharePointServer Module | Microsoft Docs

    Pros and Cons of Microsoft.SharePoint.PowerShell

    ProsCons
    ✅ Microsoft.SharePoint.PowerShell is a established PSSNappin, which tends to have very few bugs❌ You have to readapt the scripts, if you want to migrate to SharePoint Online
    ✅ It is supported by Microsoft❌ You can use it only for SharePoint Server
    ✅ You can change nearly everything with this SharePoint PowerShell Module on your SharePoint Server❌ You need SPShellAdmin rights to access SharePoint Server with this SharePoint PowerShell Module

    How to make use of Microsoft.SharePoint.PowerShell?

    1. Connect to SharePoint Server with a user, which is SPShellAdmin
    2. Start Windows PowerShell
    3. Type Add-PSSnapin Microsoft.SharePoint.Powershell
    4. You are connected!

    What can I change with Microsoft.SharePoint.PowerShell?

    If you work with this SharePoint PowerShell snappin, you will notice, that you have to use mostly the SpWeb classes to change everything below a SpWeb. Check out the Microsoft docs to see, which classes are below SPWeb: SPWeb Class (Microsoft.SharePoint) | Microsoft Docs. If you need help, don’t hesitate to contact me: Serkar@workplace-automation.com

    ObjectChanging
    possible
    with
    PNP PowerShell?
    CmdletPrequisitesReference
    FarmSet-SPFarmConfigSPShellAdminAcessSet-SPFarmConfig (SharePointServer) | Microsoft Docs
    SiteSet-SPSite SPShellAdminAcess Set-SPSite (SharePointServer) | Microsoft Docs
    Sub SiteSet-SPWeb SPShellAdminAcess Set-SPWeb (SharePointServer) | Microsoft Docs
    ListNo dedicated cmdlet. You have to call lists from web object
    (get-spweb "url").lists
    SPShellAdminAcess
    Library No dedicated cmdlet. You have to call libraries from web object

    (get-spweb "url").lists
    SPShellAdminAcess
    ItemNo dedicated cmdlet. You have to call libraries from list object

    (get-spweb "url").lists.items
    SPShellAdminAcess
    FileNo dedicated cmdlet -You have to call the file from the item object

    (get-spweb "url").lists.items[0].file
    SPShellAdminAcess
    PageNo dedicated cmdlet – You have to call it from the sitepages library. SPShellAdminAcess

    Who is maintaing Microsoft.SharePoint.PowerShell?

    The module is maintained by Microsoft.

    What if I find a bug in Microsoft.SharePoint.PowerShell?

    You can contact Microsoft Support and report the bug there.

    Conclusio

    As you saw each of the SharePoint PowerShell module / snappin got its pros and cons. PNP PowerShell is the number one choice, if you want to automate processes in Microsoft Cloud. Beside this SharePointOnlinePowerShell is the second tool recommended, If you only seek to administer SharePoint Online. As a final point Microsoft.SharePoint.PowerShell should be mentioned as a powerful tool if you want to administer SharePoint Server (on-premises). If I missed a point, I would appreciate, if you contact me: serkar@workplace-automation.com.

    Further Documentation

    This is the direct link to PNP documentation: http://aka.ms/m365pnp

    PNP provide code samples, which you can find here: PnP | Microsoft 365

    You can check out the PNP repro on github here. GitHub – pnp/powershell: PnP PowerShell

  • How I easily add Webparts to SharePoint Pages by PowerShell

    How I easily add Webparts to SharePoint Pages by PowerShell

    A page consists of multiple webparts. With the webparts you can refer to other lists, libraries and apps within a page. You can add webparts SharePoint Pages manually and programmatically to a page. Adding webparts to single sites, can be done straight forwards in the edit section of the pages. If you want to do it in multiple sites and want to ensure, that the pages do look identical in terms of the structure, add webparts to SharePoint Pages with PowerShell! In this post I will show you how to do add webparts to pages manually and with PowerShell.

    Add Webparts to SharePoint Pages manually

    If you want to add webparts to SharePoint pages manually, you have to edit the page by clicking on edit.

    webpart page edit

    Now you can add the webpart to areas, where you get displayed a red line with a plus:

    Like here

    Add webpart to homepage

    or here:

    Add webpart to homepage

    After clicing on the red cross, you have to choose the webpart and can add. In my example, I want to display a user.

    Add people webpart
    Add the webpart name and set a person.
    set webpart properties

    If you want to display the changes to all users, click on publish (1) , otherwise click save as draft (2), so only you can see the changes.

    save or publish the page

    I have published the page, so every user can now see my change:

    published webpart page

    Add Webparts to SharePoint pages with PowerShell

    Recommendations

    • Use a code editor, which can format JSON properly – otherwise your code will look like a mess. I would recommend Visual Studio Code
    • Refresh the $Page variable fore each change you make on your page – Otherwise you will experience, that the homepage will be messed up

    Building webpart Components

    In the first step, I would recommend to design the webpart in the worbench page. You can find the workbench page of your site, by calling following URL:

    https://DOMAIN.sharepoint.com/sites/SITE/_layouts/15/workbench.aspx

    For my demo page it is:

    https://devmodernworkplace.sharepoint.com/sites/Sales/_layouts/15/workbench.aspx

    You can ignore the warning:

    warning, which you can ignore in workbench

    Now add the webpart, which you want manually.

    demo webpart in workbench

    Now click on “Web part data” and copy the yellow marked contents

    webpart code from web part data

    Put it in an editor like visual studio code and I would recommend changing to language to JSON.

    visual studio code changing language

    After doing that, make a right click and click on format document.

    Formatting document in visual studio code

    Now copy the content of WebPartData into a new tab (CTRL + N).

    Add { as the first character and } as the last character. After doing this, format the document.

    formatting document in visual studio code

    Your JSON should look like this:

    {
        "webPartData": {
            "id": "7f718435-ee4d-431c-bdbf-9c4ff326f46e",
            "instanceId": "ad75d0d7-81be-4271-b809-405a30d161d2",
            "title": "People",
            "description": "Display selected people and their profiles",
            "audiences": [],
            "serverProcessedContent": {
                "htmlStrings": {},
                "searchablePlainTexts": {
                    "title": "Added by PowerShell",
                    "persons[0].name": "Serkar Aydin",
                    "persons[0].email": "Serkar@devmodernworkplace.onmicrosoft.com"
                },
                "imageSources": {},
                "links": {}
            },
            "dataVersion": "1.3",
            "properties": {
                "layout": 1,
                "persons": [
                    {
                        "id": "Serkar@devmodernworkplace.onmicrosoft.com",
                        "upn": "",
                        "role": "",
                        "department": "",
                        "phone": "",
                        "sip": ""
                    }
                ]
            }
        }
    }

    Adding Webpart to Page with PowerShell

    In order to add webparts to SharePoint pages with PowerShell, we have to connect to SharePoint. If you are doing this the first time, check out the post: Connect to SharePoint Online with PowerShell (workplace-automation.com/)

    $Credential = Get-Credential
    $SiteUrl = "https://devmodernworkplace.sharepoint.com/sites/sales"
    Connect-PnPOnline -Url $SiteUrl -Credential $Credential

    Get the page, where you want to add the webparts to:

    $Page = Get-PnPPage -Identity "Home"

    After connecting, you can add the webpart by the previously created JSON content:

    $PersonJSON = @"
    {
        "webPartData": {
            "id": "7f718435-ee4d-431c-bdbf-9c4ff326f46e",
            "instanceId": "ad75d0d7-81be-4271-b809-405a30d161d2",
            "title": "People",
            "description": "Display selected people and their profiles",
            "audiences": [],
            "serverProcessedContent": {
                "htmlStrings": {},
                "searchablePlainTexts": {
                    "title": "Added by PowerShell",
                    "persons[0].name": "Serkar Aydin",
                    "persons[0].email": "Serkar@devmodernworkplace.onmicrosoft.com"
                },
                "imageSources": {},
                "links": {}
            },
            "dataVersion": "1.3",
            "properties": {
                "layout": 1,
                "persons": [
                    {
                        "id": "Serkar@devmodernworkplace.onmicrosoft.com",
                        "upn": "",
                        "role": "",
                        "department": "",
                        "phone": "",
                        "sip": ""
                    }
                ]
            }
        }
    }
    "@
    
    Add-PnPPageWebPart -Page $Page -DefaultWebPartType People -WebPartProperties $PersonJSON -Section 1 -Column 1 -Order 3

    The result of the added webpart looks like this:

    added webpart with powershell

    Bonus: Ready-to-use script

    The ready to use script looks like this:

    $Credential = Get-Credential
    $SiteUrl = "https://devmodernworkplace.sharepoint.com/sites/sales"
    Connect-PnPOnline -Url $SiteUrl -Credential $Credential
    
    
    $Page = Get-PnPPage -Identity "Home"
    
    $PersonJSON = @"
    {
        "webPartData": {
            "id": "7f718435-ee4d-431c-bdbf-9c4ff326f46e",
            "instanceId": "ad75d0d7-81be-4271-b809-405a30d161d2",
            "title": "People",
            "description": "Display selected people and their profiles",
            "audiences": [],
            "serverProcessedContent": {
                "htmlStrings": {},
                "searchablePlainTexts": {
                    "title": "Added by PowerShell",
                    "persons[0].name": "Serkar Aydin",
                    "persons[0].email": "Serkar@devmodernworkplace.onmicrosoft.com"
                },
                "imageSources": {},
                "links": {}
            },
            "dataVersion": "1.3",
            "properties": {
                "layout": 1,
                "persons": [
                    {
                        "id": "Serkar@devmodernworkplace.onmicrosoft.com",
                        "upn": "",
                        "role": "",
                        "department": "",
                        "phone": "",
                        "sip": ""
                    }
                ]
            }
        }
    }
    "@
    
    Add-PnPPageWebPart -Page $Page -DefaultWebPartType People -WebPartProperties $PersonJSON -Section 1 -Column 1 -Order 3

    Further Documentation

    If you are curious about webpart development and the workbench page check: Build your first SharePoint client-side web part (Hello World part 1) | Microsoft Docs

    Alternative to Visual Studio Code, you can use following link: JSON Formatter & Validator (curiousconcept.com)