Optimized.Mga.psm1

#region main
function Connect-Mga {
    <#
    .LINK
    https://github.com/baswijdenes/Optimized.Mga/tree/main
 
    .SYNOPSIS
    Connect-Mga will retreive a RefreshToken from Microsoft Graph.
     
    .DESCRIPTION
    By selecting one of these parameters you log on with the following:
 
    ClientSecret: Will log you on with a ClientSecret.
    Certificate: Will log you on with a Certificate.
    Thumbprint: Will search for a Certificate under thumbprint on local device and log you on with a Certificate.
    UserCredentials: Will log you on with basic authentication.
    RedirectUri: Will log you on with MFA Authentication.
    The OauthToken is automatically renewed when you use cmdlets.
 
    .PARAMETER Thumbprint
    Use a certificate thumbprint to log on with. Connec-Mga will search for the certificate in the cert store.
     
    .PARAMETER Certificate
    Use a Cert to log on. you can use where X's is the certificate thumbprint:
    $Cert = get-ChildItem 'Cert:\LocalMachine\My\XXXXXXXXXXXXXXXXXXX'
    Connect-Mga -Certificate $Cert -ApplicationID 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX' -Tenant 'XXXXXXXX.onmicrosoft.com'
     
    .PARAMETER ClientSecret
    Parameter description
     
    .PARAMETER RedirectUri
    Use the RedirectUri in your AzureAD app to connect with MFA.
    RedirectUri should look something like this:
    'msalXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX://auth'
 
    If you want to know more about how to log in via MFA with a RedirectUri, go to my blog:
    https://bwit.blog/how-to-start-with-microsoft-graph-in-powershell/#I_will_use_credentials
     
     
    .PARAMETER UserCredentials
    Use Get-Credential to log on with Basic Authentication.
     
    .PARAMETER ApplicationID
    ApplicationID is the ID for the AzureAD application. It should look like this:
    'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX'
 
    .PARAMETER Tenant
    Tenant is the TenantID or onmicrosoft.com address. Don't confuse this with ApplicationID.
 
    I should look like this:
    'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX'
    Or
    XXXXXXX.onmicrosoft.com
     
    .PARAMETER LoginScope
    You can only use LoginScope with RedirectUri, but unfortunately the token will always include all permissions the app has.
     
    .PARAMETER Force
    Use -Force when you want to overwrite another connection (or Accept the confirmation).
     
    .EXAMPLE
    Connect-Mga -ClientSecret '1yD3h~.KgROPO.K1sbRF~XXXXXXXXXXXXX' -ApplicationID 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX' -Tenant 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX'
 
    .EXAMPLE
    $Cert = get-ChildItem 'Cert:\LocalMachine\My\XXXXXXXXXXXXXXXXXXX'
    Connect-Mga -Certificate $Cert -ApplicationID 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX' -Tenant 'XXXXXXXX.onmicrosoft.com'
 
    .EXAMPLE
    Connect-Mga -Thumbprint '3A7328F1059E9802FAXXXXXXXXXXXXXX' -ApplicationID 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX' -Tenant 'XXXXXXXX.onmicrosoft.com'
 
    .EXAMPLE
    Connect-Mga -UserCredentials $Cred -Tenant 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX' -ApplicationID 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX'
 
    .EXAMPLE
    Connect-Mga -redirectUri 'msalXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX://auth' -Tenant 'XXXXXXXX.onmicrosoft.com' -ApplicationID 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX'
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ParameterSetName = 'Thumbprint')]
        [ValidateScript( { $_.length -eq 40 })]
        [string]
        $Thumbprint, 
        [Parameter(Mandatory = $true, ParameterSetName = 'Certificate')]
        $Certificate,
        [Parameter(Mandatory = $true, ParameterSetName = 'ClientSecret')]
        [string]
        $ClientSecret, 
        [Parameter(Mandatory = $true, ParameterSetName = 'RedirectUri')]
        [String]
        $RedirectUri,
        [Parameter(Mandatory = $true, ParameterSetName = 'Credentials')]
        [System.Net.ICredentials]
        $UserCredentials,
        [Parameter(Mandatory = $true)]
        [String]
        $ApplicationID,
        [Parameter(Mandatory = $true)]
        [String]
        $Tenant,
        [Parameter(Mandatory = $false, ParameterSetName = 'RedirectUri')]
        [AllowEmptyString()]  
        [Object]
        $LoginScope,
        [Parameter(Mandatory = $false)]
        [Switch]
        $Force
    )
    begin {
        if ($Force) {
            Write-Verbose 'Connect-Mga: -Force parameter found. Running Disconnect-Mga to force a log on.'
            $null = Disconnect-Mga
        }
        else {
            Initialize-MgaConnect
        }
    }
    process {
        if ($Thumbprint) {
            Write-Verbose "Connect-Mga: Thumbprint: Logging in with Thumbprint."
            Receive-MgaOauthToken `
                -ApplicationID $ApplicationID `
                -Tenant $Tenant `
                -Thumbprint $Thumbprint 
        }
        elseif ($Certificate) {
            Write-Verbose "Connect-Mga: Certificate: Logging in with certificate."
            Receive-MgaOauthToken `
                -ApplicationID $ApplicationID `
                -Tenant $Tenant `
                -Certificate $Certificate 
        }
        elseif ($ClientSecret) {
            Write-Verbose "Connect-Mga: RedirectUri: Logging in with RedirectUri."
            Receive-MgaOauthToken `
                -ApplicationID $ApplicationID `
                -Tenant $Tenant `
                -ClientSecret $ClientSecret
        }
        elseif ($RedirectUri) {
            Write-Verbose "Connect-Mga: MFA UserCredentials: Logging in with MFA UserCredentials."
            Receive-MgaOauthToken `
                -ApplicationID $ApplicationID `
                -Tenant $Tenant `
                -RedirectUri $RedirectUri `
                -LoginScope $LoginScope
        }
        elseif ($UserCredentials) {
            Write-Verbose "Connect-Mga: Basic UserCredentials: Logging in with Basic UserCredentials."
            Receive-MgaOauthToken `
                -ApplicationID $ApplicationID `
                -Tenant $Tenant `
                -UserCredentials $UserCredentials 
        }
    }
    end {
        return "You've successfully logged in to Microsoft.Graph.API."
    }
}

function Disconnect-Mga {
    <#
    .LINK
    https://github.com/baswijdenes/Optimized.Mga/tree/main
 
    .SYNOPSIS
    Use this to log off Microsoft Graph.
     
    .DESCRIPTION
    To update the OauthToken I fill the global scope with a number of properties.
    The properties are emptied by Disconnect-Mga.
     
    .EXAMPLE
    Disconnect-Mga
    #>

    [CmdletBinding()]
    param (
    )
    begin {
        if ($global:MgaLoginType.length -ge 1) {
            Write-Verbose "Disconnect-Mga: Disconnecting from Microsoft.Graph.API."
        }
    }
    process {
        try {
            $Null = Get-Variable -Name "Mga*" -Scope Global | Remove-Variable -Force -Scope Global
        }
        catch {
            throw $_.Exception.Message
        }
    }
    end {
        return "You've successfully logged out."
    }
}

function Get-Mga {
    <#
    .LINK
    https://github.com/baswijdenes/Optimized.Mga/tree/main
 
    .SYNOPSIS
    Get-Mga speaks for itself. All you have to provide is the URL.
     
    .DESCRIPTION
    You can grab the URL via the browser developer tools, Fiddler, or from the Microsoft Graph docs. You can use all query parameters in the URL like some in the examples.
    It will automatically use the Next Link when there is one in the returned request.
     
    .PARAMETER URL
    The URL to get data from Microsoft Graph.
     
    .PARAMETER Once
    If you only want to retrieve data once, you can use the -Once parameter.
    For example, I used this in the beta version to get the latest login. Nowadays this property is a property under the user: signInActivity.
     
    .EXAMPLE
    Get-Mga -URL 'https://graph.microsoft.com/v1.0/users' -Once
 
    .EXAMPLE
    Get-Mga -URL 'https://graph.microsoft.com/v1.0/users?$top=999'
 
    .EXAMPLE
    $URL = 'https://graph.microsoft.com/v1.0/users?$select={0}' -f 'id,userPrincipalName,lastPasswordChangeDateTime,createdDateTime,PasswordPolicies'
    Get-Mga -URL $URL
 
    .EXAMPLE
    $URL = 'https://graph.microsoft.com/beta/users?$filter=({0})&$select=displayName,userPrincipalName,createdDateTime,signInActivity' -f "UserType eq 'Guest'"
    Get-Mga URL $URL
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0)]
        [string]
        $URL,
        [Parameter(Mandatory = $false)]      
        [switch]
        $Once
    )
    begin {
        Update-MgaOauthToken
    }
    process {
        try {
            Write-Verbose "Get-Mga: Getting results from $URL."
            $Result = Invoke-WebRequest -UseBasicParsing -Headers $global:MgaHeaderParameters -Uri $URL -Method get
            if ($result.Headers.'Content-Type' -like "application/octet-stream*") {
                Write-Verbose "Get-Mga: Result is in Csv format. Converting to Csv and returning end result."
                $EndResult = ConvertFrom-Csv -InputObject $Result
            }
            if ($result.Headers.'Content-Type' -like "application/json*") {   
                Write-Verbose "Get-Mga: Result is in JSON format. Converting to JSON."
                $Result = ConvertFrom-Json -InputObject $Result
                if ($Result.'@odata.nextLink') {
                    if (!($Once)) {
                        Write-Verbose "Get-Mga: There is an @odata.nextLink for more output. We will run Get-Mga again with the next data link."
                        $EndResult = @()
                        foreach ($Line in ($Result).value) {
                            $EndResult += $Line
                        }
                        While ($Result.'@odata.nextLink') {
                            Write-Verbose "Get-Mga: There is another @odata.nextLink for more output. We will run Get-Mga again with the next data link."
                            Update-MgaOauthToken
                            $Result = (Invoke-WebRequest -UseBasicParsing -Headers $global:MgaHeaderParameters -Uri $Result.'@odata.nextLink' -Method Get).Content | ConvertFrom-Json
                            foreach ($Line in ($Result).value) {
                                $EndResult += $Line
                            }
                            Write-Verbose "Get-Mga: Count is: $($EndResult.count)."
                        }
                    }
                    else {
                        $EndResult = @()
                        foreach ($Line in ($Result).value) {
                            $EndResult += $Line
                        }
                        Write-Verbose 'Get-Mga: Parameter -Once found. Even if there is an @odata.nextLink for more output, we will not extract more data.'
                    }
                }
                elseif ($Result.value) {
                    Write-Verbose "Get-Mga: There is no @odata.nextLink. We will add the data to end result."
                    $EndResult = $Result.value
                }
                else {
                    Write-Verbose "Get-Mga: There is no @odata.nextLink. We will add the data to end result."
                    $EndResult = $Result
                }
            }
        }
        catch [System.Net.WebException] {
            Write-Warning "WebException Error message! This could be due to throttling limit."
            $WebResponse = $_.Exception.Response
            if ($WebResponse.StatusCode -eq 429) {
                [int]$RetryValue = $WebResponse.Headers['Retry-After']
                Write-Warning "WebException Error message! Throttling error. Retry-After header value: $($RetryValue) seconds. Sleeping for $($RetryValue + 1)s"
                Start-Sleep -Seconds $($RetryValue + 1) 
                if ($Result.'@odata.nextLink') {
                    Get-Mga -URL $Result.'@odata.nextLink'
                }
                else {
                    Get-Mga -URL $URL
                }
            }
            else {
                throw $_.Exception.Message
            }
        }
        catch {
            throw $_.Exception.Message
        }
    }
    end {
        return $EndResult
    }
}

function Post-Mga {
    <#
    .LINK
    https://github.com/baswijdenes/Optimized.Mga/tree/main
 
    .SYNOPSIS
    Post-Mga can be seen as the 'new' Verb. With this cmdlet you create objects in AzureAD.
 
    .PARAMETER URL
    URL to 'POST' to.
     
    .PARAMETER InputObject
    -InputObject will accept a PSObject or JSON.
     
    .EXAMPLE
    $InputObject = @{
        accountEnabled = 'true'
        displayName = "Test User Post MSGraph"
        mailNickname = "TestUserPostMSGraph"
        userPrincipalName = "TestUserPostMSGraph@XXXXXXXXX.onmicrosoft.com"
        passwordProfile = @{
            forceChangePasswordNextSignIn = 'true'
            password = 'XXXXXXXXXX'
        }
    }
    Post-Mga -URL 'https://graph.microsoft.com/v1.0/users' -InputObject $InputObject
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0)]
        [string]
        $URL,
        [Parameter(Mandatory = $false)]
        [object]
        $InputObject
    )
    begin {
        Update-MgaOauthToken
        $InputObject = ConvertTo-MgaJson -InputObject $InputObject
    }
    process {
        try {
            if ($InputObject) {
                Write-Verbose "Post-Mga: Posting InputObject to Microsoft.Graph.API."
                $Result = Invoke-RestMethod -Uri $URL -Headers $global:MgaheaderParameters -Method post -Body $InputObject -ContentType application/json
            }
            else {
                $Result = Invoke-RestMethod -Uri $URL -Headers $global:MgaheaderParameters -Method post -ContentType application/json    
            }
        }
        catch [System.Net.WebException] {
            Write-Warning "WebException Error message! This could be due to throttling limit."
            $WebResponse = $_.Exception.Response
            if ($WebResponse.StatusCode -eq 429) {
                [int]$RetryValue = $WebResponse.Headers['Retry-After']
                Write-Warning "WebException Error message! Throttling error. Retry-After header value: $($RetryValue) seconds. Sleeping for $($RetryValue + 1)s"
                Start-Sleep -Seconds $($RetryValue + 1) 
                $Result = Post-Mga -URL $URL -InputObject $InputObject
            }
            else {
                throw $_.Exception.Message
            }
        }
        catch {
            throw $_.Exception.Message
        }
    }
    end {
        Write-Verbose "Post-Mga: We've successfully Posted the data to Microsoft.Graph.API."
        return $Result
    }
}

function Patch-Mga {
    <#
    .LINK
    https://github.com/baswijdenes/Optimized.Mga/tree/main
 
    .SYNOPSIS
    Patch-Mga can be seen as the 'Update' Verb.
    In the below example I add users to a Group.
 
    .PARAMETER URL
    URL to 'PATCH' to.
 
    .PARAMETER InputObject
    -InputObject will accept a PSObject or JSON.
    InputObject with members@odata.bind property over 20+ users will automatically be handled for you.
 
    .PARAMETER Batch
    -Batch is a switch to use Batch in the backend. -Batch only works with 'members@odata.bind' property.
 
    .EXAMPLE
    $users = Get-Mga 'https://graph.microsoft.com/v1.0/users'
    $UserPostList = [System.Collections.Generic.List[Object]]::new()
    foreach ($User in $users)
    {
        $DirectoryObject = 'https://graph.microsoft.com/v1.0/directoryObjects/{0}' -f $User.id
        $UserPostList.Add($DirectoryObject)
    }
    $PostBody = [PSCustomObject] @{
        "members@odata.bind" = $UserPostList
    }
    Patch-Mga -URL 'https://graph.microsoft.com/v1.0/groups/4c9d31a2-c662-4f76-b3f8-52290d2aa788' -InputObject $PostBody
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0)]
        [string]
        $URL,
        [Parameter(Mandatory = $true)]
        [object]
        $InputObject,
        [Parameter(Mandatory = $false)]
        [switch]
        $Batch
    )
    begin {
        Update-MgaOauthToken
        $ValidateJson = ConvertTo-MgaJson -InputObject $InputObject -Validate
        if ($Batch -eq $true) {
            Write-Warning 'Patch-Mga: begin: Parameter Batch will only work when the InputObject contains property: members@odata.bind. If this is not the case -Batch will be ignored.'
        }
    }
    process {
        try {
            if (($ValidateJson -eq $false) -and (($InputObject."members@odata.bind").count -gt 20)) {
                if ($Batch -eq $true) {
                    Optimize-Mga -InputObject $InputObject -URL $URL -Request 'Patch-Mga' -Batch
                }
                else {
                    Optimize-Mga -InputObject $InputObject -URL $URL -Request 'Patch-Mga'     
                }
            }
            else {
                $InputObject = ConvertTo-MgaJson -InputObject $InputObject
                Write-Verbose "Patch-Mga: Patching InputObject to Microsoft.Graph.API."
                $Result = Invoke-RestMethod -Uri $URL -Headers $global:MgaheaderParameters -Method Patch -Body $InputObject -ContentType application/json
            }
        }
        catch [System.Net.WebException] {
            Write-Warning "WebException Error message! This could be due to throttling limit."
            $WebResponse = $_.Exception.Response
            if ($WebResponse.StatusCode -eq 429) {
                [int]$RetryValue = $WebResponse.Headers['Retry-After']
                Write-Warning "WebException Error message! Throttling error. Retry-After header value: $($RetryValue) seconds. Sleeping for $($RetryValue + 1)s"
                Start-Sleep -Seconds $($RetryValue + 1) 
                $Result = Patch-Mga -URL $URL -InputObject $InputObject
            }
            else {
                throw $_.Exception.Message
            }
        }
        catch {
            throw $_.Exception.Message
        }
    }
    end {
        Write-Verbose "Patch-Mga: We've successfully Patched the data to Microsoft.Graph.API."
        return $Result
    }
}

function Delete-Mga {
    <#
    .LINK
    https://github.com/baswijdenes/Optimized.Mga/tree/main
 
    .SYNOPSIS
    Delete speaks for itself. With this cmdlet you can remove objects from AzureAD.
 
    .PARAMETER URL
    -URL accepts an array of URLS, it will use Batch-Mga in the backend.
     
    .PARAMETER InputObject
    -InputObject will accept a PSObject or JSON.
     
    .EXAMPLE
    $GroupUsers = Get-Mga -URL 'https://graph.microsoft.com/v1.0/groups/ac252320-4194-402f-8182-2d14e4a2db5c/members'
    $UserList = @()
    foreach ($User in $GroupUsers) {
        $URL = 'https://graph.microsoft.com/v1.0/groups/ac252320-4194-402f-8182-2d14e4a2db5c/members/{0}/$ref' -f $User.Id
        $UserList += $URL
    }
    Delete-Mga -URL $UserList
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0)]
        $URL,
        [Parameter(Mandatory = $false)]
        [string]
        $InputObject
    )
    begin {
        Update-MgaOauthToken
        if ($InputObject) {
            $ValidateJson = ConvertTo-MgaJson -InputObject $InputObject -Validate
        }
    }
    process {
        try {
            if (($ValidateJson -eq $false) -and (($InputObject."members@odata.bind").count -gt 20)) {
                Optimize-Mga -InputObject $InputObject -URL $URL -Request 'Delete-Mga'
            }
            elseif ($URL.count -gt 1) {
                Optimize-Mga -URL $URL -Request 'Delete-Mga'
            }
            elseif ($InputObject) {
                Write-Verbose "Delete-Mga: Deleting InputObject on $URL to Microsoft.Graph.API."
                $InputObject = ConvertTo-MgaJson -InputObject $InputObject
                $Result = Invoke-RestMethod -Uri $URL -body $InputObject -Headers $global:MgaheaderParameters -Method Delete -ContentType application/json
            }
            else {
                Write-Verbose "Delete-Mga: Deleting conent on $URL to Microsoft.Graph.API."
                $Result = Invoke-RestMethod -Uri $URL -Headers $global:MgaheaderParameters -Method Delete -ContentType application/json
            }

        }
        catch [System.Net.WebException] {
            Write-Warning "WebException Error message! This could be due to throttling limit."
            $WebResponse = $_.Exception.Response
            if ($WebResponse.StatusCode -eq 429) {
                [int]$RetryValue = $WebResponse.Headers['Retry-After']
                Write-Warning "WebException Error message! Throttling error. Retry-After header value: $($RetryValue) seconds. Sleeping for $($RetryValue + 1)s"
                Start-Sleep -Seconds $($RetryValue + 1) 
                if ($InputObject) {
                    $Result = Delete-Mga -URL $URL -InputObject $InputObject
                }
                else {
                    $Result = Delete-Mga -URL $URL
                }
            }
            else {
                throw $_.Exception.Message
            }
        }
        catch {
            throw $_.Exception.Message
        }
    }
    end {
        Write-Verbose "Delete-Mga: We've successfully deleted the data on Microsoft.Graph.API."
        return $Result
    }
}

function Batch-Mga {
    <#
    .LINK
    https://github.com/baswijdenes/Optimized.Mga/tree/main
 
    .SYNOPSIS
    Batch-Mga is for speed and bulk.
    See the related link for more.
     
    .DESCRIPTION
    Batch-Mga will take care of the limitations(20 requests per batch) and will sleep for the amount of time a throttle limit is returned and then continue.
 
    .PARAMETER InputObject
    -InputObject will accept an ArrayList.
    See the examples for more information.
     
    .PARAMETER Headers
    You can manually change the header for the Batch, but this will change all headers.
     
    .PARAMETER Beta
    Switch to batch to beta instead.
    Default is v1.0.
     
    .EXAMPLE
    $DeletedObjects = Get-Mga -URL 'https://graph.microsoft.com/v1.0/directory/deletedItems/microsoft.graph.user?$top=999'
    $Batch = [System.Collections.Generic.List[Object]]::new()
    foreach ($User in $DeletedObjects) {
        $Object = [PSCustomObject]@{
            url = "/directory/deletedItems/$($User.id)"
            method = 'delete'
        }
        $Batch.Add($object)
    }
    Batch-Mga -InputObject $batch
 
    .EXAMPLE
    $BatchDependsOn = [System.Collections.Generic.List[Object]]::new()
    foreach ($User in $Response) {
        $Object = [PSCustomObject]@{
            Url = "/users/$($User.UserPrincipalName)"
            method = 'patch'
            body = [PSCustomObject] @{
                officeLocation = "18/2111"
            }
            dependsOn = 2
        }
        $test.Add($object)
    }
    Batch-Mga -InputObject $BatchDependsOn
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [object]
        $InputObject,
        [Parameter(Mandatory = $false)]
        [string]
        $Headers,
        [Parameter(Mandatory = $false)]
        [switch]
        $Beta
    )
    begin {
        $i = 1
        $Batch = [System.Collections.Generic.List[System.Object]]::new()
        $ValidateJson = ConvertTo-MgaJson -Validate
        if ($Beta -eq $true) {
            $URI = 'https://graph.microsoft.com/beta/$batch'
        }
        else {
            $URI = 'https://graph.microsoft.com/v1.0/$batch'
        }
    }
    process {
        if (($ValidateJson -eq $false) -and ($InputObject.count -gt 20)) {
            Optimize-Mga -InputObject $InputObject -Request 'Batch-Mga'
        }
        else {
            Write-Verbose "Batch-Mga: Creating Batch request."
            foreach ($Line in $InputObject) {
                try {
                    if ($Line.Url -like "https://graph.microsoft.com/v1.0*") {
                        $URL = ($Line.Url).Replace('https://graph.microsoft.com/v1.0', '')
                    }
                    elseif ($Line.Url -like "https://graph.microsoft.com/beta*") {
                        $URL = ($Line.Url).Replace('https://graph.microsoft.com/beta', '')
                    }
                    else {
                        $URL = $Line.Url
                    }
                    if ($null -eq $Line.Header) {
                        $Header = [PSCustomObject]@{
                            'Content-Type' = 'application/json'
                        }
                    }
                    else {
                        $Header = $Line.Header
                    }
                    $Hashtable = @{
                        id      = $i
                        Method  = ($Line.Method).ToUpper()
                        url     = $URL
                        Headers = $Header
                    }
                    if ($Line.Body.length -ne 0) {
                        $Hashtable.Add('body', $($Line.Body))
                    }
                    if ($Line.DependsOn.length -ne 0) {
                        if ($Line.DependsOn.count -gt 1) {
                            $Hashtable.Add('dependsOn', $Line.DependsOn )
                        }
                        else {
                            $Hashtable.Add('dependsOn', ($Line.DependsOn).ToString().ToCharArray() )
                        }
                    }
                    $Object = [PSCustomObject]$Hashtable
                    $Batch.Add($Object)
                    if ($i -eq $($InputObject.Count)) {
                        $EndBatch = [PSCustomObject]@{
                            Requests = $Batch
                        }
                    }
                    else {
                        $i++
                    }
                }
                catch {
                    throw $_.Exception.Message
                }
            }
            Write-Verbose "Batch-Mga: Patching Batch request."
            $Results = Post-Mga -URL $URI -InputObject $EndBatch
            $EndResult = [System.Collections.Generic.List[System.Object]]::new()
            :EndResults foreach ($result in $results.Responses) {
                try {
                    $Object = [PSCustomObject]@{
                        id     = $Result.id
                        status = $Result.status
                        code   = $Result.body.error.code
                        body   = $Result.body.error.message
                    }
                    $EndResult.Add($Object)
                    if ($Object.body -like "*Your request is throttled temporarily.*") {
                        $ThrottleHit = $true
                        break :EndResults
                    }
                }
                catch {
                    throw $_.Exception.Message
                }
            }
            if ($ThrottleHit -eq $true) {
                $ThrottleHit = $null
                $ThrottleTime = $object.body -replace "[^0-9]" , ''
                Write-Warning "WebException Error message! This could be due to throttling limit."
                Write-Warning "WebException Error message! Throttling error. Retry-After value: $($ThrottleTime) seconds. Sleeping for $($ThrottleTime)s."
                Start-Sleep -seconds ([int]$ThrottleTime + 1)
                $Results = Post-Mga -URL $URI -InputObject $EndBatch
                $EndResult = [System.Collections.Generic.List[System.Object]]::new()
                foreach ($result in $results.Responses) {
                    try {
                        $Object = [PSCustomObject]@{
                            id     = $Result.id
                            status = $Result.status
                            code   = $Result.body.error.code
                            body   = $Result.body.error.message
                        }
                        $EndResult.Add($Object)
                    }
                    catch {
                        continue
                    }
                    
                }
            }
        }
    }
    end {
        Write-Verbose "Delete-Mga: We've successfully batched the data to Microsoft.Graph.API."
        return $EndResult | Sort-Object id
    }
}

function Send-MgaMail {
    <#
    .LINK
    https://github.com/baswijdenes/Optimized.Mga/tree/main
 
    .SYNOPSIS
    Sends emails with Microsoft Graph.
     
    .DESCRIPTION
    Send-MgaMail uses the Microsoft Graph v1.0 REST API.
    You need Mail.Send permissions.
 
    .PARAMETER To
    To accepts an array of addresses.
     
    .PARAMETER Subject
    Is the email subject.
     
    .PARAMETER Body
    Is the email body.
     
    .PARAMETER From
    Add the From address when logged in with Application permissions.
    When logged in with user credentials the From address will automatically be the userLogon. This is also displayed in a warning message.
     
    .PARAMETER Attachments
    Attachments accepts an Array. Make sure to use the FullName (Including Path).
    Example:
    'C:\Temp\Attachment.txt','C:\Temp\Attachment2.txt'
 
    .EXAMPLE
    Send-MgaMail -From 'John.Doe@XXXXXXXXXXX.onmicrosoft.com' -To 'Jack.Doe@contoso.com' -Subject 'Test message' -Body 'This is a test message'
 
    .EXAMPLE
    Send-MgaMail -To 'Jack.Doe@contoso.com' -Subject 'Test message' -Body 'This is a test message'
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateScript({$_ -like "*@*"})]
        [string[]]
        $To,
        [Parameter(Mandatory = $true)]
        [string]
        $Subject,
        [Parameter(Mandatory = $true)]
        [string]
        $Body,
        [Parameter(Mandatory = $false)]
        [ValidateScript({$_ -like "*@*"})]
        [string]
        $From,
        [Parameter(Mandatory = $false)]
        [object]
        $Attachments
    )
    begin {
        try {
            $URL = 'https://graph.microsoft.com/v1.0/me/sendMail'
            $ToList = [System.Collections.Generic.List[System.Object]]::new()
            foreach ($Address in $To) {
                $Object = [PSCustomObject]@{
                    emailAddress = [PSCustomObject] @{
                        'address' = $Address
                    }
                }
                $ToList.Add($Object)
            }
            $Message = [PSCustomObject] @{
                message = [PSCustomObject] @{
                    subject      = $subject
                    body         = [PSCustomObject] @{
                        contentType = 'HTML'
                        content     = $body
                    }
                    toRecipients = @($ToList)
                }
            }
            if ($Attachments.Length -gt 0) {
                $AttachmentsList = [System.Collections.Generic.List[System.Object]]::new()
                foreach ($attachment in $Attachments) {
                    $FileBytes = Get-Content -Path $Attachment -Encoding Byte
                    $AttachmentName = $Attachment.split('\') | Select-Object -Last 1
                    $Base64String = ([System.Convert]::ToBase64String($FileBytes))
                    $AttachmentsNode = [PSCustomObject]@{
                        "@odata.type"  = "#microsoft.graph.fileAttachment"
                        "name"         = $AttachmentName
                        "contentBytes" = $Base64String
                    }
                    $AttachmentsList.Add($AttachmentsNode)
                }
                $Message = [PSCustomObject] @{
                    message = [PSCustomObject] @{
                        subject      = $subject
                        body         = [PSCustomObject] @{
                            contentType = 'HTML'
                            content     = $body
                        }
                        toRecipients = @($ToList)
                        Attachments  = @($AttachmentsList)
                    }
                }
            }
            if ($From.length -gt 0) {
                if (($global:MgaRU.result.length -ge 1) -and ($global:MgaRU.result.account.Username -ne $From)) {
                    Write-Warning "You have logged in with Credentials. We cannot use a different Email Address than $From. We will use $From to send email from." 
                    $From = $global:MgaRU.result.account.Username
                }
                if (($global:MgaBasic.access_token.length -ge 1) -and ($global:MgaUserCredentials.UserName -ne $From)) {
                    Write-Warning "You have logged in with Credentials. We cannot use a different Email Address than $From. We will use $From to send email from." 
                    $From = $global:MgaUserCredentials.UserName 
                }
                Write-Verbose "Send-MgaMail: From address is $From."
                $FromNode = [PSCustomObject] @{
                    emailAddress = [PSCustomObject] @{
                        'address' = $From
                    }
                }
                $Message | Add-Member -MemberType NoteProperty -Name 'From' -Value $FromNode
                $URL = "https://graph.microsoft.com/v1.0/users/$($From)/sendMail"
            }
        }
        catch {
            throw $_.Exception.Message
        }
    }
    process {
        try {
            Write-Verbose 'Send-MgaMail: Sending email...'
            Post-Mga -URL $URL -InputObject $Message
        }
        catch {
            throw $_.Exception.Message
        }
    }
    end {
        return "Email to $To with subject $Subject has been sent succesfully."
    }
}
#endregion main

#region internal
function Initialize-MgaConnect {
    [CmdletBinding()]
    param (
    )
    if ($global:MgaLoginType.length -ge 1) {
        Write-Verbose "Initialize-MgaConnect: You're already logged on."
        $Confirmation = Read-Host 'You already logged on. Are you sure you want to proceed? Type (Y)es to continue.'
        if (($Confirmation -eq 'y') -or ($Confirmation -eq 'yes') -or ($Confirmation -eq 'true') -or ($Confirmation -eq '(Y)es')) {
            Write-Verbose "Initialize-MgaConnect: We will continue logging in."
            $null = Disconnect-Mga
        }
        else {
            Write-Verbose "Initialize-MgaConnect: Aborting log in."
            throw 'Login aborted.'
        }
    }
}

function Update-MgaOauthToken {  
    [CmdletBinding()]
    param (
    )
    if ($null -ne $global:MgaAppPass) {
        Receive-MgaOauthToken `
            -ApplicationID $global:MgaApplicationID `
            -Tenant $global:MgaTenant `
            -ClientSecret $global:MgaSecret
    }
    elseif ($null -ne $global:MgaCert) {
        Receive-MgaOauthToken `
            -ApplicationID $global:MgaApplicationID `
            -Tenant $global:MgaTenant `
            -Certificate $global:MgaCertificate
    }
    elseif ($null -ne $global:MgaTPrint) {
        Receive-MgaOauthToken `
            -ApplicationID $global:MgaApplicationID `
            -Tenant $global:MgaTenant `
            -Thumbprint $global:MgaThumbprint 
    }
    elseif ($null -ne $global:MgaRU) {
        Receive-MgaOauthToken `
            -ApplicationID $global:MgaApplicationID `
            -Tenant $global:MgaTenant `
            -RedirectUri $global:MgaRedirectUri `
            -LoginScope $global:MgaLoginScope
    }
    elseif ($null -ne $global:MgaBasic) {
        Receive-MgaOauthToken `
            -ApplicationID $global:MgaApplicationID `
            -Tenant $global:MgaTenant `
            -UserCredentials $global:MgaUserCredentials 
    }
    else {
        Throw "You need to run Connect-Mga before you can continue. Exiting script..."
    }
}

function Receive-MgaOauthToken {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $ApplicationID,
        [Parameter(Mandatory = $true)]
        [string]
        $Tenant,
        [Parameter(Mandatory = $true, ParameterSetName = 'Thumbprint')]
        [string]
        $Thumbprint, 
        [Parameter(Mandatory = $true, ParameterSetName = 'Certificate')]
        $Certificate, 
        [Parameter(Mandatory = $true, ParameterSetName = 'ClientSecret')]
        $ClientSecret, 
        [Parameter(Mandatory = $true, ParameterSetName = 'Redirecturi')]
        [string]
        $RedirectUri,
        [Parameter(Mandatory = $false, ParameterSetName = 'Redirecturi')]
        [AllowEmptyString()]  
        [Object]
        $LoginScope,
        [Parameter(Mandatory = $true, ParameterSetName = 'UserCredentials')]
        [System.Net.ICredentials]
        $UserCredentials
    )
    begin {
        try { 
            $global:MgaTenant = $Tenant
            $global:MgaApplicationID = $ApplicationID
            if ($null -eq $LoginScope) {
                [System.Collections.Generic.List[String]]$LoginScope = @('https://graph.microsoft.com/.default')
            }
            else {
                $Data = @('https://graph.microsoft.com/')
                foreach ($Scp in $LoginScope) {
                    $Data += $Scp
                }
                [System.Collections.Generic.List[String]]$LoginScope = ([string]$Data).replace('/ ', '/')
            }
            [datetime]$UnixDateTime = '1970-01-01 00:00:00'
            $Date = Get-Date
            $UTCDate = [System.TimeZoneInfo]::ConvertTimeToUtc($Date)
            if ($thumbprint.length -gt 5) { 
                Write-Verbose "Receive-MgaOauthToken: Certificate: We will continue logging in with Certificate."
                if (($null -eq $global:MgaTPCertificate) -or ($Thumbprint -ne ($global:MgaTPCertificate).Thumbprint)) {
                    Write-Verbose "Receive-MgaOauthToken: Certificate: Starting search in CurrentUser\my."
                    $TPCertificate = Get-Item Cert:\CurrentUser\My\$Thumbprint -ErrorAction SilentlyContinue
                    if ($null -eq $TPCertificate) {
                        Write-Verbose "Receive-MgaOauthToken: Certificate not found in CurrentUser. Continuing in LocalMachine\my."
                        $TPCertificate = Get-Item Cert:\localMachine\My\$Thumbprint -ErrorAction SilentlyContinue
                    }
                    if ($null -eq $TPCertificate) {
                        throw "We did not find a certificate under: $Thumbprint. Exiting script..."
                    }
                }
                else {
                    $TPCertificate = $global:MgaTPCertificate
                    Write-Verbose "Receive-MgaOauthToken: Certificate: We already obtained a certificate from a previous login. We will continue logging in."
                }
            }
        }
        catch {
            throw $_.Exception.Message          
        }
    }
    process {
        try {
            if ($ClientSecret) {
                if ($clientsecret.gettype().name -ne 'securestring') {
                    $Secret = $ClientSecret | ConvertTo-SecureString -AsPlainText -Force
                }
                else {
                    $Secret = $ClientSecret
                }
                $TempPass = [PSCredential]::new(".", $Secret).GetNetworkCredential().Password
                if (!($global:MgaAppPass)) {
                    Write-Verbose "Receive-MgaOauthToken: ApplicationSecret: This is the first time logging in with a ClientSecret."
                    $Builder = [Microsoft.Identity.Client.ConfidentialClientApplicationBuilder]::Create($ApplicationID).WithTenantId($Tenant).WithClientSecret($TempPass).Build()
                    $global:MgaAppPass = $Builder.AcquireTokenForClient($LoginScope).ExecuteAsync()
                    if ($null -eq $global:MgaAppPass.result.AccessToken) {
                        throw 'We did not retrieve an Oauth access token to continue script. Exiting script...'
                    }
                    else {
                        $global:MgaheaderParameters = @{
                            Authorization = $global:MgaAppPass.result.CreateAuthorizationHeader()
                        }
                        $global:MgaLoginType = 'ClientSecret'
                        $global:MgaSecret = $Secret
                    }
                }
                else {
                    Write-Verbose "Receive-MgaOauthToken: ApplicationSecret: Oauth token already exists from previously running cmdlets."
                    Write-Verbose "Receive-MgaOauthToken: ApplicationSecret: Running test to see if Oauth token expired."
                    $OauthExpiryTime = $global:MgaAppPass.Result.ExpiresOn.UtcDateTime
                    if ($OauthExpiryTime -le $UTCDate) {
                        Write-Verbose "Receive-MgaOauthToken: ApplicationSecret: Oauth token expired. Emptying Oauth variable and re-running function."
                        $global:MgaAppPass = $null
                        Receive-MgaOauthToken `
                            -ApplicationID $ApplicationID `
                            -Tenant $Tenant `
                            -ClientSecret $ClientSecret           
                    }
                    else {
                        Write-Verbose "Receive-MgaOauthToken: ApplicationSecret: Oauth token from last run is still active."
                    }
                }
            }
            elseif ($Certificate) {
                if (!($global:MgaCert)) {
                    Write-Verbose "Receive-MgaOauthToken: Certificate: This is the first time logging in with a Certificate."
                    $Builder = [Microsoft.Identity.Client.ConfidentialClientApplicationBuilder]::Create($ApplicationID).WithTenantId($tenant).WithCertificate($Certificate).Build()  
                    $global:MgaCert = $Builder.AcquireTokenForClient($LoginScope).ExecuteAsync()
                    if ($null -eq $global:MgaCert.result.AccessToken) {
                        throw 'We did not retrieve an Oauth access token to continue script. Exiting script...'
                    }
                    else {
                        $global:MgaheaderParameters = @{
                            Authorization = $global:MgaCert.result.CreateAuthorizationHeader()
                        }
                        $global:MgaLoginType = 'Certificate'
                        $global:MgaCertificate = $Certificate
                    }
                }
                else {
                    Write-Verbose "Receive-MgaOauthToken: Certificate: Oauth token already exists from previously running cmdlets."
                    Write-Verbose "Receive-MgaOauthToken: Certificate: Running test to see if Oauth token expired."
                    $OauthExpiryTime = $global:MgaCert.Result.ExpiresOn.UtcDateTime
                    if ($OauthExpiryTime -le $UTCDate) {
                        Write-Verbose "Receive-MgaOauthToken: Certificate: Oauth token expired. Emptying Oauth variable and re-running function."
                        $global:MgaCert = $null
                        Receive-MgaOauthToken `
                            -ApplicationID $ApplicationID `
                            -Certificate $Certificate `
                            -Tenant $Tenant
                    }
                    else {
                        Write-Verbose "Receive-MgaOauthToken: Certificate: Oauth token from last run is still active."
                    }
                }
            }
            elseif ($Thumbprint) {
                if (!($global:MgaTPrint)) {
                    Write-Verbose "Receive-MgaOauthToken: Certificate: This is the first time logging in with a Certificate."
                    $Builder = [Microsoft.Identity.Client.ConfidentialClientApplicationBuilder]::Create($ApplicationID).WithTenantId($tenant).WithCertificate($TPCertificate).Build()  
                    $global:MgaTPrint = $Builder.AcquireTokenForClient($LoginScope).ExecuteAsync()
                    if ($null -eq $global:MgaTPrint.result.AccessToken) {
                        throw 'We did not retrieve an Oauth access token to continue script. Exiting script...'
                    }
                    else {
                        $global:MgaheaderParameters = @{
                            Authorization = $global:MgaTPrint.result.CreateAuthorizationHeader()
                        }
                        $global:MgaLoginType = 'Thumbprint'
                        $global:MgaThumbprint = $Thumbprint
                        $global:MgaTPCertificate = $TPCertificate
                    }
                }
                else {
                    Write-Verbose "Receive-MgaOauthToken: Certificate: Oauth token already exists from previously running cmdlets."
                    Write-Verbose "Receive-MgaOauthToken: Certificate: Running test to see if Oauth token expired."
                    $OauthExpiryTime = $global:MgaTPrint.Result.ExpiresOn.UtcDateTime
                    if ($OauthExpiryTime -le $UTCDate) {
                        Write-Verbose "Receive-MgaOauthToken: Certificate: Oauth token expired. Emptying Oauth variable and re-running function."
                        $global:MgaTPrint = $null
                        Receive-MgaOauthToken `
                            -ApplicationID $ApplicationID `
                            -Thumbprint $Thumbprint `
                            -Tenant $Tenant
                    }
                    else {
                        Write-Verbose "Receive-MgaOauthToken: Certificate: Oauth token from last run is still active."
                    }
                }
            }
            elseif ($RedirectUri) { 
                if (!($global:MgaRU)) {
                    $Builder = [Microsoft.Identity.Client.PublicClientApplicationBuilder]::Create($ApplicationID).WithTenantId($Tenant).WithRedirectUri($RedirectUri).Build()
                    $global:MgaRU = $Builder.AcquireTokenInteractive($LoginScope).ExecuteAsync()
                    if ($null -eq $global:MgaRU.result.AccessToken) {
                        throw 'We did not retrieve an Oauth access token to continue script. Exiting script...'
                    }
                    else {
                        $global:MgaheaderParameters = @{
                            Authorization = $global:MgaRU.Result.CreateAuthorizationHeader()
                        }
                        $global:MgaLoginType = 'RedirectUri'
                        $global:MgaRedirectUri = $RedirectUri
                        $global:MgaLoginScope = $LoginScope
                    }
                }
                else {
                    Write-Verbose "Receive-MgaOauthToken: MFA UserCredentials: Oauth token already exists from previously running cmdlets."
                    Write-Verbose "Receive-MgaOauthToken: MFA UserCredentials: Running test to see if Oauth token expired."
                    $OauthExpiryTime = $global:MgaRU.Result.ExpiresOn.UtcDateTime
                    if ($OauthExpiryTime -le $UTCDate) {
                        Write-Verbose "Receive-MgaOauthToken: MFA UserCredentials: Oauth token expired. Emptying Oauth variable and re-running function."
                        $global:MgaRU = $null
                        Receive-MgaOauthToken `
                            -ApplicationID $ApplicationID `
                            -Tenant $Tenant `
                            -RedirectUri $RedirectUri `
                            -LoginScope $LoginScope
                    }
                    else {
                        Write-Verbose "Receive-MgaOauthToken: MFA UserCredentials: Oauth token from last run is still active."
                    }
                }
            }
            elseif ($userCredentials) {
                $loginURI = "https://login.microsoft.com"
                $Resource = "https://graph.microsoft.com"
                $Body = @{
                    grant_type = 'password';
                    resource   = $Resource;
                    username   = $($userCredentials.UserName)
                    password   = $($UserCredentials.Password)
                    client_id  = $ApplicationID;
                    scope      = 'openid'
                }
                if (!($global:MgaBasic)) {
                    $global:MgaBasic = Invoke-RestMethod -Method Post -Uri $loginURI/$Tenant/oauth2/token?api-version=1.0 -Body $Body -UseBasicParsing
                    if ($null -eq $global:MgaBasic.access_token) {
                        throw 'We did not retrieve an Oauth access token to continue script. Exiting script...'
                    }
                    else {
                        $global:MgaheaderParameters = @{
                            Authorization = "$($global:MgaBasic.token_type) $($global:MgaBasic.access_token)"
                        }
                        $global:MgaLoginType = 'UserCredentials'
                        $global:MgaUserCredentials = $UserCredentials
                    }
                }
                else {
                    Write-Verbose "Receive-MgaOauthToken: Basic UserCredentials: Oauth token already exists from previously running cmdlets."
                    Write-Verbose "Receive-MgaOauthToken: Basic UserCredentials: Running test to see if Oauth token expired."
                    $OauthExpiryTime = $UnixDateTime.AddSeconds($global:MgaBasic.expires_on)
                    if ($OauthExpiryTime -le $UTCDate) {
                        $global:MgaBasic = $null
                        Receive-MgaOauthToken `
                            -UserCredentials $UserCredentials `
                            -Tenant $Tenant `
                            -ApplicationID $ApplicationID
                    }
                    else {
                        Write-Verbose "Receive-MgaOauthToken: Basic UserCredentials: Oauth token from last run is still active."
                    }
                }
            }
        }
        catch {
            throw $_.Exception.Message
        }
    }
    end {
    }
}

function ConvertTo-MgaJson {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        $InputObject,
        [Parameter(Mandatory = $false)]
        [switch]
        $Validate
    )
    begin {
        
    }  
    process {
        try {
            $null = ConvertFrom-Json -InputObject $InputObject -ErrorAction Stop
            $ValidateJson = $true
        }
        catch {
            if ($Validate -ne $true) {
                $InputObject = ConvertTo-Json -InputObject $InputObject -Depth 100
            }
            else {
                $ValidateJson = $false
            }
        }    
    }
    end {
        if ($Validate -ne $true) {
            return $InputObject
        }
        else {
            return $ValidateJson
        }
    }
}

function Optimize-Mga {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        $InputObject,
        [Parameter(Mandatory = $true)]
        [ValidateSet('Batch-Mga', 'Patch-Mga', 'Delete-Mga')]
        $Request,
        [Parameter(Mandatory = $false)]
        $URL,
        [Parameter(Mandatory = $false)]
        [switch]
        $Batch
    )
    begin {
        Write-Verbose 'Optimize-Mga: InputObject is multiple requests. Splitting up request.'
    } 
    process {  
        $GroupedInputObject = [system.Collections.Generic.List[system.Object]]::new()
        if ($Request -eq 'Batch-Mga') {
            foreach ($Line in $InputObject) {
                $GroupedInputObject.Add($Line)
                if ($($GroupedInputObject).count -eq 20) {
                    Write-Verbose "Optimize-Mga: Batching $($GroupedInputObject.count) requests."
                    Batch-Mga -InputObject $GroupedInputObject
                    $GroupedInputObject = [system.Collections.Generic.List[system.Object]]::new()
                }
            }
        }
        if ($Request -eq 'Patch-Mga') {
            foreach ($Line in $InputObject."members@odata.bind") {
                $GroupedInputObject.Add($Line)
                if ($($GroupedInputObject).count -eq 20) {
                    $GroupedInputObject = [PSCustomObject] @{
                        "members@odata.bind" = $GroupedInputObject
                    }
                    if ($Batch -eq $true) {
                        if ($null -eq $PatchToBatch) {
                            
                            $PatchToBatch = [system.Collections.Generic.List[system.Object]]::new()
                        }
                        $ToBatch = [PSCustomObject]@{
                            Method = 'PATCH'
                            Url    = $URL
                            Body   = $GroupedInputObject
                        }
                        $PatchToBatch.Add($ToBatch)
                    }
                    else {
                        Write-Verbose 'Optimize-Mga: patching request.'
                        Patch-Mga -InputObject $GroupedInputObject -URL $URL
                    }
                    $GroupedInputObject = [system.Collections.Generic.List[system.Object]]::new()
                }
            }
        }
        if (($Batch -eq $true) -and ($Request -eq 'Patch-Mga')) {
            Write-Verbose 'Optimize-Mga: Batching Patch to Batch-Mga.'
            Batch-Mga -InputObject $PatchToBatch
        } 
        if ($Request -eq 'Delete-Mga') {
            if ($InputObject."members@odata.bind") {
                foreach ($Line in $InputObject."members@odata.bind") {
                    $GroupedInputObject.Add($Line)
                    if ($($GroupedInputObject).count -eq 20) {
                        $OdataBind = [PSCustomObject] @{
                            "members@odata.bind" = $GroupedInputObject
                        }
                        Write-Verbose 'Optimize-Mga: Delete request.'
                        Delete-Mga -InputObject $OdataBind -URL $URL 
                        $GroupedInputObject = [system.Collections.Generic.List[system.Object]]::new()
                    }
                }
            }
            else {
                foreach ($Line in $URL) {
                    $Object = [PSCustomObject]@{
                        url    = $Line
                        method = 'Delete'
                    }
                    $GroupedInputObject.Add($Object)
                    if ($($GroupedInputObject).count -eq 20) {
                        Write-Verbose "Optimize-Mga: Batching $($GroupedInputObject.count) delete requests."
                        Batch-Mga -InputObject $GroupedInputObject
                        $GroupedInputObject = [system.Collections.Generic.List[system.Object]]::new()
                    }
                }
            }
        }
        if ($($GroupedInputObject.count) -ge 1) {
            if ($Request -eq 'Batch-Mga') {
                Write-Verbose 'Optimize-Mga: Batching last Batch-Mga.'
                Batch-Mga -InputObject $GroupedInputObject
            }
            if ($Request -eq 'Patch-Mga') {
                Write-Verbose 'Optimize-Mga: Batching last Patch-Mga.'
                if ($GroupedInputObject.count -gt 1) {
                    $GroupedInputObject = [PSCustomObject] @{
                        "members@odata.bind" = $GroupedInputObject
                    }
                }
                else {
                    $GroupedInputObject = [PSCustomObject] @{
                        "@odata.id" = $GroupedInputObject
                    }
                }
                Patch-Mga -InputObject $GroupedInputObject -URL $URL

            }
            if ($Request -eq 'Delete-Mga') {
                Write-Verbose 'Optimize-Mga: Batching last Delete-Mga.'
                if ($InputObject."members@odata.bind") {   
                    Delete-Mga -InputObject $OdataBind -URL $URL 
                }
                else {
                    Batch-Mga -InputObject $GroupedInputObject
                }
            }
        }
    }
    end {
        return $Results
    }
}
#endregion internal