SPO.ps1

# Functions for SharePoint Online


# Jul 17th 2019
function Get-SPOSiteGroups
{
<#
    .SYNOPSIS
    Gets list of groups of SharePoint Online site
 
    .DESCRIPTION
    Gets list of groups of SharePoint Online site the user has access to.
 
    .Parameter Site
    Url of the SharePoint site
 
    .Parameter AuthHeader
    SharePoint Online authentication header
     
    .Example
    PS C:\>$auth=Get-AADIntSPOAuthenticationHeader -Site https://company.sharepoint.com
    PS C:\>Get-AADIntSPOSiteGroups -Site https://company.sharepoint.com/sales -AuthHeader $auth
#>

    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$True)]
        [String]$Site,
        [Parameter(Mandatory=$False)]
        [String]$AuthHeader,
        [Parameter(Mandatory=$False)]
        [String]$AccessToken
    )
    Process
    {
        # Check the site url
        if($Site.EndsWith("/"))
        {
            $Site=$Site.Substring(0,$Site.Length-1)
        }

        $siteDomain=$Site.Split("/")[2]

        if(![string]::IsNullOrEmpty($AuthHeader))
        {
            # Create a WebSession object
            $siteSession = Create-WebSession -SetCookieHeader $AuthHeader -Domain $siteDomain
        }
        else
        {
            # Get from cache if not provided
            $AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource $site -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"
            $headers=@{
                "Authorization" = "Bearer $AccessToken"
            }
        }

        # Invoke the request
        $response=Invoke-WebRequest -UseBasicParsing -Uri "$Site/_api/web/sitegroups" -Method Get -WebSession $siteSession -ErrorAction SilentlyContinue -Headers $headers

        if($response.StatusCode -eq 200)
        {
            [xml]$response=$response.Content


            $users=New-Object System.Collections.ArrayList

            # Loop through the entries
            foreach($entry in $response.feed.entry)
            {
                $attributes = @{}
                $attributes["Id"] = $entry.content.properties.Id.'#Text'
                $attributes["IsHiddenInUI"] = $entry.content.properties.IsHiddenInUI.'#Text' -eq "true"
                $attributes["LoginName"] = $entry.content.properties.LoginName
                $attributes["Title"] = $entry.content.properties.Title
                $attributes["PrincipalType"] = $entry.content.properties.PrincipalType.'#Text'
                $attributes["OwnerTitle"] = $entry.content.properties.OwnerTitle
                $attributes["Description"] = $entry.content.properties.Description.'#Text'
                
                $attributes["AllowMembersEditMembership"] = $entry.content.properties.AllowMembersEditMembership.'#Text' -eq "true"
                $attributes["AllowRequestToJoinLeave"] = $entry.content.properties.AllowRequestToJoinLeave.'#Text' -eq "true"
                $attributes["AutoAcceptRequestToJoinLeave"] = $entry.content.properties.AutoAcceptRequestToJoinLeave.'#Text' -eq "true"
                $attributes["OnlyAllowMembersViewMembership"] = $entry.content.properties.OnlyAllowMembersViewMembership.'#Text' -eq "true"
           
                $users+=New-Object PSObject -Property $attributes
            }

            # Return
            return $users
        }
    }
}

# Jul 17th 2019
function Get-SPOSiteUsers
{
<#
    .SYNOPSIS
    Gets list of users of SharePoint Online site
 
    .DESCRIPTION
    Gets list of users of SharePoint Online site the user has access to.
 
    .Parameter Site
    Url of the SharePoint site
 
    .Parameter AuthHeader
    SharePoint Online authentication header
     
    .Example
    PS C:\>$auth=Get-AADIntSPOAuthenticationHeader -Site https://company.sharepoint.com
    PS C:\>Get-AADIntSPOSiteUsers -Site https://company.sharepoint.com/sales -AuthHeader $auth
#>

    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$True)]
        [String]$Site,
        [Parameter(Mandatory=$False)]
        [String]$AuthHeader,
        [Parameter(Mandatory=$False)]
        [String]$AccessToken
    )
    Process
    {
        $Site=$Site.TrimEnd("/")
        
        $tenant=$Site.Split("/")[2]

        if(![string]::IsNullOrEmpty($AuthHeader))
        {
            # Create a WebSession object
            $siteSession = Create-WebSession -SetCookieHeader $AuthHeader -Domain $siteDomain
        }
        else
        {
            # Get from cache if not provided
            $AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://$Tenant" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"
            $headers=@{
                "Authorization" = "Bearer $AccessToken"
            }
        }

        # Invoke the request
        $response=Invoke-WebRequest -UseBasicParsing -Uri "$Site/_api/web/siteusers" -Method Get -WebSession $siteSession -Headers $headers -ErrorAction SilentlyContinue

        if($response.StatusCode -eq 200)
        {
            [xml]$response=$response.Content


            $users=New-Object System.Collections.ArrayList

            # Loop through the entries
            foreach($entry in $response.feed.entry)
            {
                $attributes = @{}
                $attributes["Id"] = $entry.content.properties.Id.'#Text'
                $attributes["IsHiddenInUI"] = $entry.content.properties.IsHiddenInUI.'#Text' -eq "true"
                $attributes["LoginName"] = $entry.content.properties.LoginName
                $attributes["Title"] = $entry.content.properties.Title
                $attributes["PrincipalType"] = $entry.content.properties.PrincipalType.'#Text'
                $attributes["Email"] = $entry.content.properties.Email
                $attributes["IsEmailAuthenticationGuestUser"] = $entry.content.properties.IsEmailAuthenticationGuestUser.'#Text' -eq "true"
                $attributes["IsShareByEmailGuestUser"] = $entry.content.properties.IsShareByEmailGuestUser.'#Text' -eq "true"
                $attributes["IsSiteAdmin"] = $entry.content.properties.IsSiteAdmin.'#Text' -eq "true"
                $attributes["NameId"] = $entry.content.properties.UserId.NameId
                $attributes["NameIdIssuer"] = $entry.content.properties.UserId.NameIdIssuer
            

                if($entry.content.properties.UserPrincipalName.GetType().Name  -eq "String")
                {
                    $attributes["UserPrincipalName"] = $entry.content.properties.UserPrincipalName
                }
                else
                {
                    $attributes["UserPrincipalName"] = ""
                }
            
                $users+=New-Object PSObject -Property $attributes
            }

            # Return
            return $users
        }
    }
}

# Jul 18th 2019
function Get-SPOUserProperties
{
<#
    .SYNOPSIS
    Gets properties of SharePoint Online user
 
    .DESCRIPTION
    Gets properties of SharePoint Online user using PeopleManager API
 
    .Parameter Site
    Url of the SharePoint site
 
    .Parameter User
    SharePoint Online authentication header
 
    .Parameter AuthHeader
    LoginName of the user in format "i:0i.t|00000003-0000-0ff1-ce00-000000000000|app@sharepoint"
     
    .Example
    PS C:\>$auth=Get-AADIntSPOAuthenticationHeader -Site https://company.sharepoint.com
    PS C:\>Get-AADIntSPOUserProperties -Site https://company.sharepoint.com/sales -AuthHeader $auth -User "i:0i.t|00000003-0000-0ff1-ce00-000000000000|app@sharepoint"
#>

    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$True)]
        [String]$Site,
        [Parameter(Mandatory=$True)]
        [String]$UserName,
        [Parameter(Mandatory=$False)]
        [String]$AuthHeader,
        [Parameter(Mandatory=$False)]
        [String]$AccessToken
    )
    Process
    {
        # Check the site url
        $Site=$Site.Trim("/")

        $UserName=$UserName.Replace("#","%23")

        $siteDomain=$Site.Split("/")[2]

        $tenant = $siteDomain.Split(".")[0]

        # Check the username format
        if(!$UserName.StartsWith("i"))
        {
            $UserName="i:0%23.f|membership|$UserName"
        }

        if(![string]::IsNullOrEmpty($AuthHeader))
        {
            # Create a WebSession object
            $siteSession = Create-WebSession -SetCookieHeader $AuthHeader -Domain $siteDomain
        }
        else
        {
            # Get from cache if not provided
            $AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://$Tenant.sharepoint.com/" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"
            $headers=@{
                "Authorization" = "Bearer $AccessToken"
            }
        }

        # Invoke the request
        $response=Invoke-WebRequest2 -Uri "$Site/_api/sp.userprofiles.peoplemanager/getpropertiesfor(@v)?@v='$UserName'" -Method Get -WebSession $siteSession -Headers $headers -ErrorAction SilentlyContinue 

        if($response.StatusCode -eq 200)
        {
            [xml]$response=$response.Content
            $entry=$response.entry

            $attributes = [ordered]@{}

            $attributes["Updated"] = $response.entry.Updated
            $attributes["Author"] = $response.entry.Author.Name

            $properties = $response.entry.content.properties
            $attributes["AccountName"] = $properties.AccountName
            $attributes["DirectReports"] = Create-ListFromCollection $properties.DirectReports
            $attributes["DisplayName"] = $properties.DisplayName
            $attributes["Email"] = $properties.Email
            $attributes["ExtendedManagers"] = Create-ListFromCollection $properties.ExtendedManagers
            $attributes["ExtendedReports"] = Create-ListFromCollection $properties.ExtendedReports
            $attributes["IsFollowed"] = $properties.IsFollowed -eq "true"
            #$attributes["LatestPost"] = $properties.LatestPost
            $attributes["Peers"] = Create-ListFromCollection $properties.Peers
            #$attributes["PersonalSiteHostUrl"] = $properties.PersonalSiteHostUrl
            $attributes["PersonalUrl"] = [System.Net.WebUtility]::UrlDecode($properties.PersonalUrl)
            $attributes["PictureUrl"] = [System.Net.WebUtility]::UrlDecode($properties.PictureUrl)
            $attributes["UserUrl"] = [System.Net.WebUtility]::UrlDecode($properties.UserUrl)
            $attributes["Title"] = $properties.Title

            # Loop through the userprofile fields
            foreach($up in $properties.UserProfileProperties.Element)
            {
                $name = $up.Key
                $value = $up.Value
                $attributes[$name] = $value
            }

            # Return
            New-Object PSObject -Property $attributes
        }
    }
}

# Jun 10th 2020
function Get-SPOSiteUserProperties
{
<#
    .SYNOPSIS
    Gets the SPO user properties
 
    .DESCRIPTION
    Gets the SPO user properties
 
    .Parameter Site
    Url of the SharePoint site
 
    .Parameter AuthHeader
    SharePoint Online authentication header
 
    .Parameter AccessToken
    SharePoint Online Access Token
     
    .Example
    PS C:\>$auth=Get-AADIntSPOAuthenticationHeader -Site https://company.sharepoint.com
    PS C:\>Get-AADIntSPOSiteGroups -Site https://company.sharepoint.com/sales -AuthHeader $auth
 
    .Example
    PS C:\>$at=Get-AADIntAccessTokenForSPO
    PS C:\>Get-AADIntSPOSiteGroups -Site https://company.sharepoint.com/sales -AccessToken $at
#>

    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$True)]
        [String]$Site,
        [Parameter(Mandatory=$True)]
        [String]$UserName,
        [Parameter(Mandatory=$False)]
        [String]$AuthHeader,
        [Parameter(Mandatory=$False)]
        [String]$AccessToken
    )
    Process
    {
        # Check the site url
        if($Site.EndsWith("/"))
        {
            $Site=$Site.Substring(0,$Site.Length-1)
        }

        $siteDomain=$Site.Split("/")[2]

        # Check the username format
        if(!$UserName.StartsWith("i"))
        {
            $UserName="i:0%23.f|membership|$UserName"
        }

        if(![string]::IsNullOrEmpty($AuthHeader))
        {
            # Create a WebSession object
            $siteSession = Create-WebSession -SetCookieHeader $AuthHeader -Domain $siteDomain
        }
        else
        {
            # Get from cache if not provided
            $AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://$Tenant.sharepoint.com/" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"
            $headers=@{
                "Authorization" = "Bearer $AccessToken"
            }
        }

        # Invoke the request
        $response=Invoke-WebRequest -UseBasicParsing -Uri "$Site/_api/SP.UserProfiles.PeopleManager/GetPropertiesFor(accountName=@v)?@v='$UserName'" -Method Get -WebSession $siteSession -ErrorAction SilentlyContinue -Headers $headers

        if($response.StatusCode -eq 200)
        {
            # Get the response
            [xml]$response=$response.Content

            # Create the attributes varialbe
            $attributes=@{}

            # Loop through the elements
            foreach($element in $response.entry.content.properties.UserProfileProperties.element)
            {
                $key=$element.Key
                $value=$element.Value

                $attributes[$key] = $value
            }

            # Sort by the key
            $attributes_sorted=[ordered]@{}
            $entries = $attributes.GetEnumerator() | Sort-Object Key
            foreach($entry in $entries)
            {
                $attributes_sorted[$entry.Name]=$entry.Value
            }

            # Return
            return New-Object psobject -Property $attributes_sorted
        }
    }
}

# Jun 10th 2020
function Set-SPOSiteUserProperty
{
<#
    .SYNOPSIS
    Sets the SPO user property
 
    .DESCRIPTION
    Sets the SPO user property
 
    .Parameter Site
    Url of the SharePoint site
 
    .Parameter AuthHeader
    SharePoint Online authentication header
 
    .Parameter AccessToken
    SharePoint Online Access Token
 
    .Parameter Property
    Property name
     
    .Parameter Value
    Property value
 
    .Example
    PS C:\>$auth=Get-AADIntSPOAuthenticationHeader -Site https://company.sharepoint.com
    PS C:\>Set-AADIntSPOUserProperty -Site https://company.sharepoint.com/sales -AuthHeader $auth -UserName user@company.com -Property "AboutMe" -Value "I'm a happy SPO user!"
 
    .Example
    PS C:\>$at=Get-AADIntAccessTokenForSPO
    PS C:\>Set-AADIntSPOUserProperty -Site https://company.sharepoint.com/sales -AccessToken $at -UserName user@company.com -Property "AboutMe" -Value "I'm a happy SPO user!"
#>

    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$True)]
        [String]$Site,
        [Parameter(Mandatory=$True)]
        [String]$UserName,
        [Parameter(Mandatory=$False)]
        [String]$AuthHeader,
        [Parameter(Mandatory=$False)]
        [String]$AccessToken,
        [Parameter(Mandatory=$True)]
        [String]$Property,
        [Parameter(Mandatory=$False)]
        [String]$Value
    )
    Process
    {
        # Get the digest
        #$digest = Get-SPODigest -AccessToken $AccessToken -Cookie $Cookie -Site $Site
        # Set the headers
        $headers=@{
        # "X-RequestDigest" = $digest
        }

        # Check the site url
        if($Site.EndsWith("/"))
        {
            $Site=$Site.Substring(0,$Site.Length-1)
        }

        $siteDomain=$Site.Split("/")[2]

        # Check the username format
        if(!$UserName.StartsWith("i"))
        {
            $UserName="i:0#.f|membership|$UserName"
        }

        if(![string]::IsNullOrEmpty($AuthHeader))
        {
            # Create a WebSession object
            $siteSession = Create-WebSession -SetCookieHeader $AuthHeader -Domain $siteDomain
        }
        else
        {
            # Get from cache if not provided
            $AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://$Tenant.sharepoint.com/" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"
            $headers["Authorization"] = "Bearer $AccessToken"
        }

        # Create the body
        $body=@{
            "accountName" =   "$UserName"
            "propertyName" =  $Property
            "propertyValue" = $Value
        }

        # Invoke the request
        $response=Invoke-WebRequest2 -Uri "$Site/_api/SP.UserProfiles.PeopleManager/SetSingleValueProfileProperty" -Method Post -WebSession $siteSession -ErrorAction SilentlyContinue -Headers $headers -ContentType "application/json" -Body ($body | ConvertTo-Json)

        if($response.StatusCode -eq 200)
        {
            # All good, nothing to return :)
        }
    }
}

function Get-SPOSettings
{
<#
    .SYNOPSIS
    Gets SharePoint Online settings
 
    .DESCRIPTION
    Gets SharePoint Online settings
 
    .Parameter AccessToken
    SharePoint Online Access Token
 
    .Parameter Tenant
    The tenant name of the organization, ie. company.onmicrosoft.com -> "company"
 
    .Example
    PS C:\>Get-AADIntAccessTokenForSPO -Admin -SaveToCache -Tenant company
    PS C:\>Get-AADIntSPOSettings -Tenant Company
 
    _ObjectType_ : Microsoft.Online.SharePoint.TenantAdministration.Tenant
    _ObjectIdentity_ : 4b09819f-80c3-b000-9cfe-8c850fbea6d5|908bed80-a04a-4433-b4a0-883d9847d110:908c17b8-5ebe-450c-9073-15e52aa1739b
                                                            Tenant
    AIBuilderEnabled : False
    AIBuilderSiteInfoList : {}
    AIBuilderSiteList : {}
    AIBuilderSiteListFileName :
    AllowCommentsTextOnEmailEnabled : True
    AllowDownloadingNonWebViewableFiles : True
    AllowedDomainListForSyncClient : {}
    AllowEditing : True
    AllowGuestUserShareToUsersNotInSiteCollection : False
    AllowLimitedAccessOnUnmanagedDevices : False
    AllowSelectSGsInODBListInTenant :
    AnyoneLinkTrackUsers : False
    ApplyAppEnforcedRestrictionsToAdHocRecipients : True
    BccExternalSharingInvitations : False
    ...
 
#>

    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$False)]
        [String]$AccessToken,
        [Parameter(Mandatory=$True)]
        [String]$Tenant
    )
    Process
    {
        # Get from cache if not provided
        $AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://$Tenant-admin.sharepoint.com/" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"

        $body=@"
<Request xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009" SchemaVersion="15.0.0.0" LibraryVersion="16.0.0.0" ApplicationName="Javascript Library">
    <Actions>
        <ObjectPath Id="1" ObjectPathId="0" />
        <Query Id="2" ObjectPathId="0">
            <Query SelectAllProperties="true">
                <Properties />
            </Query>
        </Query>
    </Actions>
    <ObjectPaths>
        <Constructor Id="0" TypeId="{268004ae-ef6b-4e9b-8425-127220d84719}" />
    </ObjectPaths>
</Request>
"@

        $headers=@{
            "Authorization" = "Bearer $AccessToken"
        }

        
        # Invoke the request
        $response=Invoke-RestMethod -UseBasicParsing -Uri "https://$Tenant-admin.sharepoint.com/_vti_bin/client.svc/ProcessQuery" -Method Post -Body $body -Headers $headers

        if($response.count -gt 4)
        {
            $response[4]
        }

    }
}

# Oct 1st 2022 by Sapir Fed
function Set-SPOSiteMembers
{
    <#
        .SYNOPSIS
        Add a member into a site (also adding the member to the correlated Azure AD group)
 
        .DESCRIPTION
        Add a member into a site (also adding the member to the correlated AzureAD group)
     
        .Parameter Site
        Url of the SharePoint site
     
        .Parameter AuthHeader
        SharePoint Online authentication header
 
        .Parameter SiteName
        Name of the specific site on SharePoint
 
        .Parameter UserPrincipalName
        UserPrincipalName of the AzureAD user you wish to add to the site
         
        .Example
        PS C:\>$auth=Get-AADIntSPOAuthenticationHeader -Site https://company.sharepoint.com
        PS C:\>Set-AADIntSPOSiteMembers -Site https://company.sharepoint.com -AuthHeader $auth -SiteName CompanyWiki -UserPrincipalName user@company.com
 
        User user@company.com was added to group CompanyWiki!
    #>

        [cmdletbinding()]
        Param(
            [Parameter(Mandatory=$True)]
            [String]$Site,
            [Parameter(Mandatory=$True)]
            [String]$AuthHeader,
            [Parameter(Mandatory=$True)]
            [String]$SiteName,
            [Parameter(Mandatory=$True)]
            [String]$UserPrincipalName
        )
        Process
        {
            # Check the site url
            if($Site.EndsWith("/"))
            {
                $Site=$Site.Substring(0,$Site.Length-1)
            }            
            $siteDomain=$Site.Split("/")[2]

            # Create a WebSession object
            $siteSession = Create-WebSession -SetCookieHeader $AuthHeader -Domain $siteDomain
            
            # Invoke the request tp get groupId and digest
            $response=Invoke-WebRequest2 -Uri "$($Site)/sites/$($siteName)?sw=auth" -Method GET -WebSession $siteSession -ErrorAction SilentlyContinue -Headers $headers
            
            # Validate response
            $baseContent = $response.BaseResponse
            if($baseContent.StatusCode -eq "OK" -and $baseContent.ResponseUri -eq "$($Site)/sites/$($siteName)?sw=auth")
            {
                $requestContent = $response.Content
                
                # Parse digest
                $tempValue = $requestContent -match 'formDigestValue":"(.*?")'
                $digestTemp = $Matches[1]
                $digest = $digestTemp.Split('"')[0]
                $newheaders=@{
                        "X-RequestDigest" = $digest
                    }

                # Parse groupId
                $tempValue = $requestContent -match 'groupId":"(.*?")'
                $groupidTemp = $Matches[1]
                $groupid = $groupidTemp.Split('"')[0]

                # Invoke the request to add a member to the SharePoint site
                $newresponse=Invoke-WebRequest2 -Uri "$($Site)/sites/$($siteName)/_api/SP.Directory.DirectorySession/Group('$($groupid)')/Members/Add(objectId='00000000-0000-0000-0000-000000000000', principalName='$($UserPrincipalName)')" -Method POST -WebSession $siteSession -ErrorAction SilentlyContinue -Headers $newheaders -ContentType "application/json"
                
                # Validate response
                if($newresponse.StatusCode -eq 201 -and $newresponse.StatusDescription -eq "Created")
                {
                    Write-Host "User $($UserPrincipalName) was added to group $($siteName)!"
                }
                else
                {
                    Write-Error "Cannot Add user to the group."
                }
            }
            else
            {
                Write-Error "An error occurred while executing the request to the site."
            }
        }
    }


# Gets information of the given file from the given site
# Nov 28th 2022
function Get-SPOSiteFile
{
    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$True)]
        [String]$Site,
        [Parameter(Mandatory=$False)]
        [PSObject]$Id,
        [Parameter(Mandatory=$False)]
        [String]$RelativePath,
        [Parameter(Mandatory=$False)]
        [String]$AuthHeader,
        [Parameter(Mandatory=$False)]
        [String]$AccessToken,
        [Parameter(Mandatory=$False)]
        [Switch]$Download
    )
    Process
    {
        if($Id -eq $null -and [string]::IsNullOrEmpty($RelativePath))
        {
            Throw "Either file Id or RelativePath must be provided"
        }
        $Site=$Site.TrimEnd("/")
        
        $tenant=$Site.Split("/")[2]

        $webUrl = "https://$tenant"

        if(![string]::IsNullOrEmpty($AuthHeader))
        {
            # Create a WebSession object
            $siteSession = Create-WebSession -SetCookieHeader $AuthHeader -Domain $siteDomain
        }
        else
        {
            # Get from cache if not provided
            $AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://$Tenant" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"
            $headers=@{
                "Authorization" = "Bearer $AccessToken"
            }
        }

        # Invoke the initial requests
        try
        {
            if($Id)
            {
                $response = Invoke-WebRequest2 -Uri "$Site/_api/web/GetFileById('$Id')" -Method Get -WebSession $siteSession -Headers $headers -ErrorAction SilentlyContinue
            }
            else
            {
                $siteUrl = $Site.Substring($webUrl.Length)
                $response = Invoke-WebRequest2 -Uri "$Site/_api/web/GetFileByServerRelativePath(decodedurl='$([System.Net.WebUtility]::HtmlEncode("$siteUrl/$RelativePath"))')" -Method Get -WebSession $siteSession -Headers $headers -ErrorAction SilentlyContinue
            }
        }
        catch
        {
            Throw $_.Exception.Message
        }

        if($response.StatusCode -eq 200)
        {
            [xml]$response = $response.Content

            $fileInformation = [PSCustomObject]@{
                "IsPage"           = $response.entry.content.properties.CustomizedPageStatus.'#text' -ne "0"
                "Name"             = $response.entry.content.properties.Name
                "RelativeUrl"      = $response.entry.content.properties.ServerRelativeUrl
                "Id"               = [Guid]$response.entry.content.properties.UniqueId.'#text'
                "TimeCreated"      = [System.DateTime]$response.entry.content.properties.TimeCreated.'#text'
                "TimeLastModified" = [System.DateTime]$response.entry.content.properties.TimeLastModified.'#text'
            }
        }
        Remove-Variable -Name "response"

        # Download the file
        if($Download)
        {
            Invoke-WebRequest2 -Uri "$Site/_api/web/GetFileById('$($fileInformation.Id)')/OpenBinaryStream" -Method Get -WebSession $siteSession -Headers $headers -ErrorAction SilentlyContinue -OutFile $fileInformation.Name

            # Set the timestamps
            (Get-Item -Path $fileInformation.Name).LastWriteTime = $fileInformation.TimeLastModified
            (Get-Item -Path $fileInformation.Name).CreationTime  = $fileInformation.TimeCreated
            Write-Host "File saved to $($fileInformation.Name)"
        }
        else
        {
            # Get ParentId
        
            $response = Invoke-WebRequest2 -Uri "$Site/_api/web/GetFileById('$($fileInformation.Id)')/Properties" -Method Get -WebSession $siteSession -Headers $headers -ErrorAction SilentlyContinue

            if($response.StatusCode -eq 200)
            {
                [xml]$response = $response.Content

                $fileInformation | Add-Member -NotePropertyName "ParentId"   -NotePropertyValue ([Guid]$response.entry.content.properties.vti_x005f_parentid)
                $fileInformation | Add-Member -NotePropertyName "Author"     -NotePropertyValue $response.entry.content.properties.vti_x005f_author
                $fileInformation | Add-Member -NotePropertyName "ModifiedBy" -NotePropertyValue $response.entry.content.properties.vti_x005f_modifiedby
            }

            return $fileInformation
        }

    }
}

# Gets WebId of SPOSite
# Mar 9th 2023
function Get-SPOWebId
{
    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$True)]
        [String]$Site,
        [Parameter(Mandatory=$False)]
        [String]$AuthHeader,
        [Parameter(Mandatory=$False)]
        [String]$AccessToken
    )
    Process
    {
        $Site=$Site.TrimEnd("/")
        
        $tenant=$Site.Split("/")[2]

        if(![string]::IsNullOrEmpty($AuthHeader))
        {
            # Create a WebSession object
            $siteSession = Create-WebSession -SetCookieHeader $AuthHeader -Domain $siteDomain
        }
        else
        {
            # Get from cache if not provided
            $AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://$Tenant" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"
            $headers=@{
                "Authorization" = "Bearer $AccessToken"
            }
        }

        # Get WebId
        $response = Invoke-WebRequest2 -Uri "$Site/_api/web/id" -Method Get -WebSession $siteSession -Headers $headers -ErrorAction SilentlyContinue

        if($response.StatusCode -eq 200)
        {
            [xml]$response = $response.Content

            $retVal = [Guid]$response.id.'#text'
        }

        return $retVal
    }
}

# Gets information of the given folder from the given site
# Mar 9th 2023
function Get-SPOSiteFolder
{
    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$True)]
        [String]$Site,
        [Parameter(Mandatory=$False)]
        [PSObject]$Id,
        [Parameter(Mandatory=$False)]
        [String]$RelativePath,
        [Parameter(Mandatory=$False)]
        [String]$AuthHeader,
        [Parameter(Mandatory=$False)]
        [String]$AccessToken
    )
    Process
    {
        if($Id -eq $null -and [string]::IsNullOrEmpty($RelativePath))
        {
            Throw "Either file Id or RelativePath must be provided"
        }
        $Site=$Site.TrimEnd("/")
        
        $tenant=$Site.Split("/")[2]

        if(![string]::IsNullOrEmpty($AuthHeader))
        {
            # Create a WebSession object
            $siteSession = Create-WebSession -SetCookieHeader $AuthHeader -Domain $siteDomain
        }
        else
        {
            # Get from cache if not provided
            $AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://$Tenant" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"
            $headers=@{
                "Authorization" = "Bearer $AccessToken"
            }
        }

        # Invoke the initial requests
        try
        {
            if($Id)
            {
                $response = Invoke-WebRequest2 -Uri "$Site/_api/web/GetFolderById('$Id')" -Method Get -WebSession $siteSession -Headers $headers -ErrorAction SilentlyContinue
            }
            else
            {
                $response = Invoke-WebRequest2 -Uri "$Site/_api/web/GetFolderByServerRelativePath(decodedurl='$([System.Net.WebUtility]::HtmlEncode($RelativePath))')" -Method Get -WebSession $siteSession -Headers $headers -ErrorAction SilentlyContinue
            }
        }
        catch
        {
            throw $_.Exception.Message
        }

        if($response.StatusCode -eq 200)
        {
            [xml]$response = $response.Content

            $folderInformation = [PSCustomObject]@{
                "RelativeUrl"      = $response.entry.content.properties.ServerRelativeUrl
                "Id"               = [Guid]$response.entry.content.properties.UniqueId.'#text'
                "TimeCreated"      = [System.DateTime]$response.entry.content.properties.TimeCreated.'#text'
                "TimeLastModified" = [System.DateTime]$response.entry.content.properties.TimeLastModified.'#text'
            }

            # Parse the full folder name
            $webUrl = "https://$tenant"
            $siteUrl = $Site.Substring($webUrl.Length)
            $folderInformation | Add-Member -NotePropertyName "Name" -NotePropertyValue $folderInformation.RelativeUrl.Substring($siteUrl.Length+1)
        }

        # Get ParentId
        Remove-Variable -Name "response"
        $response = Invoke-WebRequest2 -Uri "$Site/_api/web/GetFolderById('$($folderInformation.Id)')/Properties" -Method Get -WebSession $siteSession -Headers $headers -ErrorAction SilentlyContinue

        if($response.StatusCode -eq 200)
        {
            [xml]$response = $response.Content
            if($response.entry.content.properties.vti_x005f_parentid)
            {
                $folderInformation | Add-Member -NotePropertyName "ParentId" -NotePropertyValue ([Guid]$response.entry.content.properties.vti_x005f_parentid)
            }
            # listid seems to be same as listname, but doesn't exist for all folders
            #if($response.entry.content.properties.vti_x005f_listid)
            #{
            # $folderInformation | Add-Member -NotePropertyName "ListId" -NotePropertyValue ([Guid]$response.entry.content.properties.vti_x005f_listid)
            #}
            if($response.entry.content.properties.vti_x005f_listname)
            {
                $folderInformation | Add-Member -NotePropertyName "ListId"   -NotePropertyValue ([Guid]$response.entry.content.properties.vti_x005f_listname)
            }
            if($response.entry.content.properties.vti_x005f_modifiedby)
            {
                $folderInformation | Add-Member -NotePropertyName "ModifiedBy" -NotePropertyValue $response.entry.content.properties.vti_x005f_modifiedby
            }
        }

        return $folderInformation
    }
}

# Downloads the given file from SPO
# Mar 10th 2023
<#
    .SYNOPSIS
    Downloads the given file from SPO
 
    .DESCRIPTION
    Downloads the given file from SPO
 
    .Parameter AccessToken
    SharePoint Online Access Token
 
    .Parameter Site
    The site name
 
    .Parameter RelativePath
    Path of the file to be exported
 
    .Example
    PS C:\>Get-AADIntAccessTokenForSPO -SaveToCache
    PS C:\>Export-AADIntSPOSiteFile -Site "https://company.sharepoint.com/sites/Sales" -RelativePath "Shared Documents/General/sales.xlsx"
 
    File saved to sales.xlsx
#>

function Export-SPOSiteFile
{
    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$True)]
        [String]$Site,
        [String]$RelativePath,
        [Parameter(Mandatory=$False)]
        [String]$AuthHeader,
        [Parameter(Mandatory=$False)]
        [String]$AccessToken
    )
    Process
    {
        # Download the file
        Get-SPOSiteFile -AccessToken $AccessToken -AuthHeader $AuthHeader -Site $site -RelativePath $RelativePath -Download
    }
}