Functions/Public/Zoom.ps1

# Zoom integration functions

Function Get-ZoomAccessToken {
    <#
        .SYNOPSIS
        Get a Zoom OAuth access token for a given Zoom tenant. Needed to run other Zoom API queries.
         
        .DESCRIPTION
        Get a Zoom OAuth access token for a given Zoom tenant. Needed to run other Zoom API queries.
 
        .PARAMETER ClientID
        The client ID for the Zoom app.
         
        .PARAMETER ClientSecret
        The MS client secret for the application granted access to Azure AD.
         
        .PARAMETER MSTenantID
        The MS tenant ID for the O365 customer granted access to Azure AD.
 
        .EXAMPLE
        $AuthToken = Get-MSGraphAccessToken -MSClientID 41a228ad-db6c-4e4e-4184-6d8a1175a35f -MSClientSecret 43Rk5Xl3K349w-pFf0i_Rt45Qd~ArqkE32. -MSTenantID 17e1e614-8119-48ab-8ba1-6ff1d94a6930
        Obtains an authtoken for the given tenant using secret-based auth and saves the results for use in other commands in a variable called $AuthToken
         
        .EXAMPLE
        $AuthToken = Get-MSGraphAccessToken -MSClientID 029834092-234234-234234-23442343 -MSTenantID 234234234-234234-234-23-42342342 -CertFriendlyName 'CertAuth' -CertStore LocalMachine
        Obtains an authtoken for the given tenant using certificate auth and saves the results for use in other commands in a variable called $AuthToken
 
        .NOTES
        Version 1.0
    #>

    
    Param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$ClientID,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$ClientSecret,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$RedirectURI
    )
    
    Process {
        # Get the details from the tenant's Zoom config
        $ZoomConfig = Get-NectarZoomConfig
        
        Try {
            # Get initial authorization code
            $Body = @{
                response_type = 'code'
                redirect_uri = $RedirectURI
                client_id = $ZoomConfig.apiClientID
            }    
            $AuthURI = "https://zoom.us/oauth/authorize?response_type=code&redirect_uri=$RedirectURI&client_id=$($ZoomConfig.apiClientID)"
            Write-Verbose $AuthURI
            $AuthCode = Invoke-RestMethod -Method GET -URI $AuthURI

            Return $AuthCode
            
            # Get Base64-encoded representation of Client_ID:Client_Secret
            $ClientDetailsText = "$($ZoomConfig.apiClientID):$($ZoomConfig.apiClientSecret)"
            Write-Verbose "ClientDetails: $ClientDetailsText"
            $Bytes = [System.Text.Encoding]::UTF8.GetBytes($ClientDetailsText)
            $ClientDetails64 =[Convert]::ToBase64String($Bytes)
        
            $Headers = @{
                Authorization = "Basic $ClientDetails64"
                'Content-Type' = 'application/x-www-form-urlencoded'
            }
            
            Write-Verbose "AuthHeader: Basic $CLientDetails64"
            
            $Body = @{
                code = $ZoomConfig.verificationToken
                grant_type = 'authorization_code'
                redirect_uri = $RedirectURI
            }
            
            $URI = "https://zoom.us/oauth/token"
            Write-Verbose $URI
            $JSONAuth = Invoke-RestMethod -Method POST -URI $URI -Headers $Headers -Body $Body
            $AuthToken = $JSONAuth.access_token
            
            Return $JSONAuth
        }
        Catch {
            Write-Error "Failed to get access token. Ensure the values for ClientID and ClientSecret are correct. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }
        }
    }
}


Function Get-NectarZoomConfig {
    <#
        .SYNOPSIS
        Returns information about the Nectar DXP Zoom configuration
         
        .DESCRIPTION
        Returns information about the Nectar DXP Zoom configuration.
        Requires a global admin account. Not available to tenant-level admins.
 
        .EXAMPLE
        Get-NectarZoomConfig
 
        .NOTES
        Version 1.0
    #>

    
    [Alias("gnzc")]
    [cmdletbinding()]
    Param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName
    )
    
    Begin {
        Connect-NectarCloud
    }
    Process {
        Try {
            If (!$TenantName) { $TenantName = Get-NectarDefaultTenantName }

            $URI = "https://$Global:NectarCloud/aapi/clouddatasources/configuration/zoom?tenant=$TenantName"
            Write-Verbose $URI
            
            $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Global:NectarAuthHeader
            Return $JSON.data
        }
        Catch {
            Write-Error 'No tenant Zoom data found or insufficient permissions. $($_.Exception.Message)'
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }
        }
    }
}


Function Get-NectarZoomOAuthApp {
    <#
        .SYNOPSIS
        Returns information about any configured Zoom OAuth applications on the server
         
        .DESCRIPTION
        Returns information about any configured Zoom OAuth applications on the server.
        Requires a global admin account. Not available to tenant-level admins.
         
        .PARAMETER ID
        The ID of a specific Zoom OAuth application.
 
        .EXAMPLE
        Get-NectarZoomOAuthApp
 
        .NOTES
        Version 1.0
    #>

    
    [Alias("gnzoa")]
    [cmdletbinding()]
    Param (
        [Parameter(Mandatory=$False)]
        [int]$ID
    )
    
    Begin {
        Connect-NectarCloud
    }
    Process {
        Try {
            $URI = "https://$Global:NectarCloud/aapi/client/oauth/zoom"
            Write-Verbose $URI
            
            If ($ID) { $URI += "/$ID" }
            
            $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Global:NectarAuthHeader
            Return $JSON.data
        }
        Catch {
            Write-Error 'No Zoom OAuth apps found or insufficient permissions. $($_.Exception.Message)'
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }
        }
    }
}


Function New-NectarZoomOAuthApp {
    <#
        .SYNOPSIS
        Creates a new Zoom OAuth application on the server
         
        .DESCRIPTION
        Creates a new Zoom OAuth application on the server.
        Requires a global admin account. Not available to tenant-level admins.
         
        .PARAMETER DisplayName
        The MS client ID for the application granted access to Azure AD.
         
        .PARAMETER ClientID
        The Zoom OAuth application Client ID.
         
        .PARAMETER ClientSecret
        The Zoom OAuth application Client secret.
         
        .PARAMETER VerificationToken
        The Zoom OAuth application verification token
         
        .EXAMPLE
        New-NectarZoomOAuthApp -DisplayName 'Contoso_Global' -ClientID 'abcdefWEwrelj32324' -ClientSecret 'asfd90832jn3mvknaswui3' -VerificationToken '09432jpg43in9024323rsdvf'
 
        .NOTES
        Version 1.0
    #>

    
    [Alias("nnzoa")]
    [cmdletbinding()]
    Param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)]
        [string]$DisplayName,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)]
        [string]$ClientID,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)]
        [string]$ClientSecret,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)]
        [string]$VerificationToken
    )
    
    Begin {
        Connect-NectarCloud
    }
    Process {
        Try {
            # Build the JSON body for creating the config
            $ZoomBody = @{
                displayName = $DisplayName
                clientId = $ClientID
                clientSecret = $ClientSecret
                verificationToken = $VerificationToken
            }
            
            $ZoomJSONBody = $ZoomBody | ConvertTo-Json
            
            $URI = "https://$Global:NectarCloud/aapi/client/oauth/zoom"
            Write-Verbose $URI
            
            $JSON = Invoke-RestMethod -Method POST -URI $URI -Headers $Global:NectarAuthHeader -Body $ZoomJSONBody -ContentType 'application/json; charset=utf-8'
            Return $JSON.data
        }
        Catch {
            Write-Error "Cound not create Zoom OAuth application. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }
        }
    }
}


Function Set-NectarZoomOAuthApp {
    <#
        .SYNOPSIS
        Updates an existing Zoom OAuth application on the server
         
        .DESCRIPTION
        Updates an existing Zoom OAuth application on the server.
        Requires a global admin account. Not available to tenant-level admins.
         
        .PARAMETER ID
        The ID of the OAuth application to modify. Obtain via Get-NectarZoomOAuthApp
 
        .PARAMETER DisplayName
        The MS client ID for the application granted access to Azure AD.
         
        .PARAMETER ClientID
        The Zoom OAuth application Client ID.
         
        .PARAMETER ClientSecret
        The Zoom OAuth application Client secret.
         
        .PARAMETER VerificationToken
        The Zoom OAuth application verification token
         
        .EXAMPLE
        Set-NectarZoomOAuthApp -ID 1 -DisplayName 'Contoso_Global'
 
        .NOTES
        Version 1.0
    #>

    
    [Alias("snzoa")]
    [cmdletbinding()]
    Param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)]
        [int]$ID,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$DisplayName,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$ClientID,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$ClientSecret,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$VerificationToken
    )
    
    Begin {
        Connect-NectarCloud
    }
    Process {
        Try {
            # Get the existing Zoom OAuth app configuration
            $ZoomBody = Get-NectarZoomOAuthApp -ID $ID
            $ZoomBody = $ZoomBody | Select-Object -Property * -ExcludeProperty ID
            
            # Remove ID and other common params (like Verbose) from PSBoundParameters
            $PSBoundParameters.Remove('ID') | Out-Null

            ForEach ($Param in $PSBoundParameters.GetEnumerator()) {
                # Skip any common parameters (Debug, Verbose, etc)
                If ([System.Management.Automation.PSCmdlet]::CommonParameters -contains $Param.key) {
                    Continue
                }
                $ZoomBody.$($Param.Key) = $Param.Value
            }
            
            $ZoomJSONBody = $ZoomBody | ConvertTo-Json
            Write-Verbose $ZoomJSONBody
            
            $URI = "https://$Global:NectarCloud/aapi/client/oauth/zoom/$ID"
            Write-Verbose $URI
            
            $JSON = Invoke-RestMethod -Method PUT -URI $URI -Headers $Global:NectarAuthHeader -Body $ZoomJSONBody -ContentType 'application/json; charset=utf-8'
            Return $JSON.data
        }
        Catch {
            Write-Error "Cound not update Zoom OAuth application. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }
        }
    }
}


Function Remove-NectarZoomOAuthApp {
    <#
        .SYNOPSIS
        Removes an existing Zoom OAuth application on the server
         
        .DESCRIPTION
        Removes an existing Zoom OAuth application on the server.
        Requires a global admin account. Not available to tenant-level admins.
         
        .PARAMETER ID
        The ID of the OAuth application to remove. Obtain via Get-NectarZoomOAuthApp
 
        .EXAMPLE
        Remove-NectarZoomOAuthApp -ID 1
 
        .NOTES
        Version 1.0
    #>

    
    [Alias("rnzoa")]
    [cmdletbinding()]
    Param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)]
        [int]$ID
    )
    
    Begin {
        Connect-NectarCloud
    }
    Process {
        Try {
            $URI = "https://$Global:NectarCloud/aapi/client/oauth/zoom/$ID"
            Write-Verbose $URI
            $NULL = Invoke-RestMethod -Method DELETE -URI $URI -Headers $Global:NectarAuthHeader
        }
        Catch {
            Write-Error "Cound not remove Zoom OAuth application. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }
        }
    }
}


Function Get-NectarZoomAuthURL {
    <#
        .SYNOPSIS
        Returns the Nectar DXP Zoom authorization URL needed for connecting Nectar DXP to Zoom
         
        .DESCRIPTION
        Returns the Nectar DXP Zoom authorization URL needed for connecting Nectar DXP to Zoom
 
        .EXAMPLE
        Get-NectarZoomAuthURL
 
        .NOTES
        Version 1.0
    #>

    
    [Alias("gnzau")]
    [cmdletbinding()]
    Param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName
    )
    
    Begin {
        Connect-NectarCloud
    }
    Process {
        Try {
            If (!$TenantName) { $TenantName = Get-NectarDefaultTenantName }

            $URI = "https://$Global:NectarCloud/aapi/zoom/oauth/url?tenant=$TenantName"
            Write-Verbose $URI
            $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Global:NectarAuthHeader
            Return $JSON.data
        }
        Catch {
            Write-Error "No tenant Zoom data found or insufficient permissions. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }
        }
    }
}


Function Get-ZoomAccessToken_LEGACY {
    <#
        .SYNOPSIS
        Get a Zoom JWT access token for a given Zoom tenant. Needed to run other Zoom API queries.
         
        .DESCRIPTION
        Get a Zoom access token for a given Zoom tenant. Needed to run other Zoom API queries. Generates a JSON Web Ticket (JWT)
 
        .EXAMPLE
        $AuthToken = Get-ZoomAccessToken -APIKey yourapikey -APISecret yourapisecret
 
        .NOTES
        Version 1.0
    #>

    
    Param(
        [Parameter(Mandatory = $False)]
        [ValidateSet('HS256', 'HS384', 'HS512')]
        $Algorithm = 'HS256',
        $type = $null,
        [Parameter(Mandatory = $True)]
        [string]$APIKey = $null,
        [int]$ValidforSeconds = 86400,
        [Parameter(Mandatory = $True)]
        $APISecret = $null
    )

    $exp = [int][double]::parse((Get-Date -Date $((Get-Date).addseconds($ValidforSeconds).ToUniversalTime()) -UFormat %s)) # Grab Unix Epoch Timestamp and add desired expiration.

    [hashtable]$header = @{alg = $Algorithm; typ = $type}
    [hashtable]$payload = @{iss = $APIKey; exp = $exp}

    $headerjson = $header | ConvertTo-Json -Compress
    $payloadjson = $payload | ConvertTo-Json -Compress
    
    $headerjsonbase64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($headerjson)).Split('=')[0].Replace('+', '-').Replace('/', '_')
    $payloadjsonbase64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($payloadjson)).Split('=')[0].Replace('+', '-').Replace('/', '_')

    $ToBeSigned = $headerjsonbase64 + "." + $payloadjsonbase64

    $SigningAlgorithm = switch ($Algorithm) {
        "HS256" {New-Object System.Security.Cryptography.HMACSHA256}
        "HS384" {New-Object System.Security.Cryptography.HMACSHA384}
        "HS512" {New-Object System.Security.Cryptography.HMACSHA512}
    }

    $SigningAlgorithm.Key = [System.Text.Encoding]::UTF8.GetBytes($APISecret)
    $Signature = [Convert]::ToBase64String($SigningAlgorithm.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($ToBeSigned))).Split('=')[0].Replace('+', '-').Replace('/', '_')
    
    $Token = "$headerjsonbase64.$payloadjsonbase64.$Signature"
    Return $Token
}


Function Get-ZoomMeeting {
    <#
        .SYNOPSIS
        Return a list of Zoom meetings.
         
        .DESCRIPTION
        Return a list of Zoom meetings.
         
        .PARAMETER MeetingID
        Return information about a specific meeting. Provide meeting ID or UUID. Don't use with From/To date range
         
        .PARAMETER Type
        Return live, past or past meetings with only one participant. Defaults to past
         
        .PARAMETER FromDateTime
        Start of date/time range to query. UTC, inclusive. Time range is based on the call start time. Defaults to yesterday.
 
        .PARAMETER ToDateTime
        End of date/time range to query. UTC, inclusive. Defaults to today
         
        .PARAMETER TenantName
        The name of the Nectar DXP tenant. Used in multi-tenant configurations.
 
        .PARAMETER APIKey
        The Zoom API key for the application granted access to Zoom.
         
        .PARAMETER APISecret
        The Zoom API secret for the application granted access to Zoom.
         
        .PARAMETER AuthToken
        The authorization token used for this request. Normally obtained via Get-ZoomAccessToken
         
        .PARAMETER PageSize
        The number of results to return per page. Defaults to 30.
         
        .EXAMPLE
        Get-ZoomMeetings -AuthToken $AuthToken
        Returns all past Zoom meetings from the previous 24 hours
         
        .NOTES
        Version 1.0
    #>


    Param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [Alias("uuid")]
        [string]$MeetingID,    
        [Parameter(Mandatory = $False)]
        [ValidateSet('past', 'pastone', 'live')]
        $Type = 'past',
        [Parameter(Mandatory=$False)]
        [string]$FromDateTime = ((Get-Date).AddDays(-1).ToString('yyyy-MM-dd')),
        [Parameter(Mandatory=$False)]
        [string]$ToDateTime = (Get-Date -Format 'yyyy-MM-dd'),        
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$APIKey,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$APISecret,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$AuthToken,
        [Parameter(Mandatory=$False)]
        [ValidateRange(1,300)]
        [int]$PageSize = 30
    )

    Process {
        Try {
            # Use globally set tenant name, if one was set and not explicitly included in the command
            If ($Global:NectarTenantName -And !$PSBoundParameters.ContainsKey('TenantName')) { 
                $TenantName = $Global:NectarTenantName 
            } ElseIf ($TenantName) {
                If ($TenantName -NotIn $Global:NectarTenantList) {
                    $TList = $Global:NectarTenantList -join ', '
                    Throw "Could not find a tenant with the name $TenantName on https://$Global:NectarCloud. Select one of $TList. $($_.Exception.Message)"
                }
            }    
            
            If ($APIKey) { 
                $AuthToken = Get-ZoomAccessToken -APIKey $APIKey -APISecret $APISecret
            }
            ElseIf (!$AuthToken) {
                $AuthToken = Get-NectarMSTeamsConfig -TenantName $TenantName | Get-MSGraphAccessToken
            }
        }
        Catch {
            Write-Error "Could not obtain authorization token. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }        
        }
        
        If ($AuthToken) {
            $Headers = @{
                Authorization = "Bearer $AuthToken"
            }
            
            If ($MeetingID) {
                $URI = "https://api.zoom.us/v2/metrics/meetings/$MeetingID"
                
                $Body = @{
                    type = $Type
                }
                $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Headers -Body $Body
                $JSON
            }
            Else {
                $URI = "https://api.zoom.us/v2/metrics/meetings"
                
                $Body = @{
                    from = $FromDateTime
                    to = $ToDateTime
                    type = $Type
                    page_size = $PageSize
                }
                
                $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Headers -Body $Body
                $JSON.meetings
                
                # If there is more than one page, use next_page_token to iterate through the pages
                While ($JSON.next_page_token) {
                    $Body.next_page_token = $JSON.next_page_token
                    $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Headers -Body $Body
                    $JSON.meetings
                }
            }
            Clear-Variable -Name AuthToken
        }
    } 
}


Function Get-ZoomMeetingParticipants {
    <#
        .SYNOPSIS
        Return a list of Zoom meeting participants for a given meeting and their connection details.
         
        .DESCRIPTION
        Return a list of Zoom meeting participants for a given meeting and their connection details.
         
        .PARAMETER MeetingID
        The ID of the meeting. Provide meeting ID or UUID.
         
        .PARAMETER Type
        Return live, past or past meetings with only one participant. Defaults to past
         
        .PARAMETER TenantName
        The name of the Nectar DXP tenant. Used in multi-tenant configurations.
 
        .PARAMETER APIKey
        The Zoom API key for the application granted access to Zoom.
         
        .PARAMETER APISecret
        The Zoom API secret for the application granted access to Zoom.
         
        .PARAMETER AuthToken
        The authorization token used for this request. Normally obtained via Get-ZoomAccessToken
         
        .PARAMETER PageSize
        The number of results to return per page. Defaults to 30.
         
        .EXAMPLE
        Get-ZoomMeetingParticipants 928340928 -AuthToken $AuthToken
        Returns participant information from a specific meeting
         
        .NOTES
        Version 1.0
    #>


    Param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)]
        [Alias("uuid")]
        [string]$MeetingID,    
        [Parameter(Mandatory = $False)]
        [ValidateSet('past', 'pastone', 'live')]
        [string]$Type = 'past',
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$APIKey,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$APISecret,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$AuthToken,
        [Parameter(Mandatory=$False)]
        [ValidateRange(1,300)]
        [int]$PageSize = 30
    )

    Process {
        Try {
            # Use globally set tenant name, if one was set and not explicitly included in the command
            If ($Global:NectarTenantName -And !$PSBoundParameters.ContainsKey('TenantName')) { 
                $TenantName = $Global:NectarTenantName 
            } ElseIf ($TenantName) {
                If ($TenantName -NotIn $Global:NectarTenantList) {
                    $TList = $Global:NectarTenantList -join ', '
                    Throw "Could not find a tenant with the name $TenantName on https://$Global:NectarCloud. Select one of $TList. $($_.Exception.Message)"
                }
            }    
            
            If ($APIKey) { 
                $AuthToken = Get-ZoomAccessToken -APIKey $APIKey -APISecret $APISecret
            }
            ElseIf (!$AuthToken) {
                $AuthToken = Get-NectarMSTeamsConfig -TenantName $TenantName | Get-MSGraphAccessToken
            }
        }
        Catch {
            Write-Error "Could not obtain authorization token. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }        
        }
        
        If ($AuthToken) {
            $Headers = @{
                Authorization = "Bearer $AuthToken"
            }

            $URI = "https://api.zoom.us/v2/metrics/meetings/$MeetingID/participants"
            
            $Body = @{
                type = $Type
                page_size = $PageSize
            }
            
            $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Headers -Body $Body
            $JSON.participants
            
            # If there is more than one page, use next_page_token to iterate through the pages
            While ($JSON.next_page_token) {
                $Body.next_page_token = $JSON.next_page_token
                $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Headers -Body $Body
                $JSON.participants
            }
            Clear-Variable -Name AuthToken
        }
    } 
}


Function Get-ZoomMeetingParticipantQoS {
    <#
        .SYNOPSIS
        Return the participant QoS from a given meeting.
         
        .DESCRIPTION
        Return the participant QoS from a given meeting.
         
        .PARAMETER MeetingID
        Return information about a specific meeting.
         
        .PARAMETER ParticipantID
        Return information about a specific participant in a given meeting. Optional
         
        .PARAMETER Type
        Return live, past or past meetings with only one participant. Defaults to past
         
        .PARAMETER TenantName
        The name of the Nectar DXP tenant. Used in multi-tenant configurations.
 
        .PARAMETER APIKey
        The Zoom API key for the application granted access to Zoom.
         
        .PARAMETER APISecret
        The Zoom API secret for the application granted access to Zoom.
         
        .PARAMETER AuthToken
        The authorization token used for this request. Normally obtained via Get-ZoomAccessToken
         
        .PARAMETER PageSize
        The number of results to return per page. Defaults to 30.
         
        .EXAMPLE
        Get-ZoomMeetingParticipantQoS 928340928 -AuthToken $AuthToken
        Returns all past Zoom meetings from the previous 24 hours
         
        .NOTES
        Version 1.0
    #>


    Param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)]
        [Alias("uuid")]
        [string]$MeetingID,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$ParticipantID,
        [Parameter(Mandatory = $False)]
        [ValidateSet('past', 'pastone', 'live')]
        [string]$Type = 'past',    
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$APIKey,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$APISecret,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$AuthToken,
        [Parameter(Mandatory=$False)]
        [ValidateRange(1,300)]
        [int]$PageSize = 30
    )

    Process {
        Try {
            # Use globally set tenant name, if one was set and not explicitly included in the command
            If ($Global:NectarTenantName -And !$PSBoundParameters.ContainsKey('TenantName')) { 
                $TenantName = $Global:NectarTenantName 
            } ElseIf ($TenantName) {
                If ($TenantName -NotIn $Global:NectarTenantList) {
                    $TList = $Global:NectarTenantList -join ', '
                    Throw "Could not find a tenant with the name $TenantName on https://$Global:NectarCloud. Select one of $TList. $($_.Exception.Message)"
                }
            }    
            
            If ($APIKey) { 
                $AuthToken = Get-ZoomAccessToken -APIKey $APIKey -APISecret $APISecret
            }
            ElseIf (!$AuthToken) {
                $AuthToken = Get-NectarMSTeamsConfig -TenantName $TenantName | Get-MSGraphAccessToken
            }
        }
        Catch {
            Write-Error "Could not obtain authorization token. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }        
        }
        
        If ($AuthToken) {
            $Headers = @{
                Authorization = "Bearer $AuthToken"
            }
            
            If ($ParticipantID) {
                $URI = "https://api.zoom.us/v2/metrics/meetings/$MeetingID/participants/$ParticipantID/qos"
                
                $Body = @{
                    type = $Type
                }
                $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Headers -Body $Body
                $JSON
            }
            Else {
                $URI = "https://api.zoom.us/v2/metrics/meetings/$MeetingID/participants/qos"
                
                $Body = @{
                    type = $Type
                    page_size = $PageSize
                }
                
                $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Headers -Body $Body
                $JSON.participants
                
                # If there is more than one page, use next_page_token to iterate through the pages
                While ($JSON.next_page_token) {
                    $Body.next_page_token = $JSON.next_page_token
                    $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Headers -Body $Body
                    $JSON.participants
                }
            }
            Clear-Variable -Name AuthToken
        }
    } 
}



#################################################################################################################################################
# #
# Informational Functions #
# #