Functions/Public/Users.ps1

# User, account, admin, and business unit management functions

Function Get-NectarUserAccountSettings {
    <#
        .SYNOPSIS
        Returns information about the current logged in user's UI settings.
 
        .DESCRIPTION
        Returns information about the current logged in user's UI settings.
         
        .EXAMPLE
        Get-NectarUserAccountSettings
         
        .NOTES
        Version 1.0
    #>

    
    [Alias("gnas")]
    Param ()
    
    Begin {
        Connect-NectarCloud
    }        
    Process {
        $URI = "https://$Global:NectarCloud/aapi/user"
        Write-Verbose $URI
        
        Try {
            $JSON = Invoke-RestMethod -Method GET -Uri $URI -Headers $Global:NectarAuthHeader
            Return $JSON
        }
        Catch {
            Write-Error "Unable to obtain user's UI settings. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }
        }
    }
}


Function Get-NectarUserAccountMapping {
    <#
        .SYNOPSIS
        Return user account mapping information
         
        .DESCRIPTION
        Return user account mapping information
 
        .PARAMETER MappingSource
        The source for the user mapping. Choose from 'Endpoint', 'WebRTC' or 'Jabra'
 
        .PARAMETER SearchQuery
        A string to search for. Will search for match against all fields
         
        .PARAMETER OrderByField
        Sort the output by the selected field
         
        .PARAMETER OrderDirection
        Sort ordered output in ascending or descending order
         
        .PARAMETER TenantName
        The name of the Nectar DXP tenant. Used in multi-tenant configurations.
     
        .PARAMETER PageSize
        The size of the page used to return data. Defaults to 10000
         
        .PARAMETER ResultSize
        The total number of results to return. Maximum result size is 9,999,999 results
 
        .EXAMPLE
        Get-NectarUserAccountMapping -MappingSource WebRTC
        Return all WebRTC account mappings
 
        .EXAMPLE
        Get-NectarUserAccountMapping -MappingSource Endpoint -SearchQuery TFerguson
        Find a endpoint account mapping for TFerguson
 
        .NOTES
        Version 1.0
    #>

    
    Param (
        [Parameter(Mandatory=$True)]
        [ValidateSet('Endpoint','WebRTC','Jabra', IgnoreCase=$True)]
        [string]$MappingSource = 'Endpoint',
        [Parameter(Mandatory=$False)]
        [string]$SearchQuery,        
        [Parameter(Mandatory=$False)]
        [string]$OrderByField,
        [Parameter(Mandatory=$False)]
        [ValidateSet('asc', 'desc', IgnoreCase=$True)]
        [string]$OrderDirection = 'asc',
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName,
        [Parameter(Mandatory=$False)]
        [ValidateRange(1,999999)]
        [int]$PageSize = 10000,
        [Parameter(Mandatory=$False)]
        [ValidateRange(1,999999)]
        [int]$ResultSize,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$Name
    )
    
    Begin {
        Connect-NectarCloud
    }
    Process {
        # 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 
        }

        # Take care of any cases that don't match the above rule
        Switch ($MappingSource) {
            'Endpoint' {
                $URLPath = 'testing/entities'
                $OrderByField = 'name'
                break
            }
            'WebRTC' {
                $URLPath = 'webrtc/user/entities'
                $OrderByField = 'loginId'
                break
            }
            'Jabra' {
                $URLPath = 'jabra/user/entities'
                $OrderByField = 'domainName'
                break
            }
        }

        # Set the page size to the result size if -ResultSize switch is used to limit the number of returned items
        # Otherwise, set page size (defaults to 1000)
        If ($ResultSize) { $PageSize = $ResultSize }

        $Params = @{
            'pageNumber'         = 1
            'pageSize'            = $PageSize
            'orderByField'        = $OrderByField
            'orderDirection'    = $OrderDirection
            'tenant'            = $TenantName            
        }

        If ($SearchQuery) { $Params.Add('q', $SearchQuery) }

        $URI = "https://$Global:NectarCloud/aapi/$URLPath"
        Write-Verbose $URI        
        
        $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Global:NectarAuthHeader -Body $Params
        
        $TotalPages = $JSON.totalPages

        If ($TenantName) { $JSON.elements | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty }

        $JSON.elements

        If ($TotalPages -gt 1 -and !($ResultSize)) {
            $PageNum = 2
            Write-Verbose "Page size: $PageSize"
            While ($PageNum -le $TotalPages) {
                Write-Verbose "Working on page $PageNum of $TotalPages"
                $Params.PageNumber = $PageNum
                $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Global:NectarAuthHeader -Body $Params
                If ($TenantName) { $JSON.elements | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty }
                $JSON.elements
                $PageNum++
            }
        }
    }
}


Function Set-NectarUserAccountMapping {
    <#
        .SYNOPSIS
        Return user account mapping information
         
        .DESCRIPTION
        Return user account mapping information
 
        .PARAMETER MappingSource
        The source for the user mapping. Choose from 'Endpoint', 'WebRTC' or 'Jabra'
 
        .PARAMETER SourceSearchQuery
        A string to search for. Will search for match against all fields
 
        .PARAMETER SourceID
        The ID of the source entity to map. Used to specify a unique source entity when multiple entities match the SourceSearchQuery
         
        .PARAMETER MapSearchQuery
        A string to search for to map the source account to. Will search for match against all fields
         
        .PARAMETER TenantName
        The name of the Nectar DXP tenant. Used in multi-tenant configurations.
     
        .EXAMPLE
        Get-NectarUserAccountMapping -MappingSource WebRTC
        Return all WebRTC account mappings
 
        .EXAMPLE
        Get-NectarUserAccountMapping -MappingSource Endpoint -SearchQuery TFerguson
        Find a endpoint account mapping for TFerguson
 
        .NOTES
        Version 1.0
    #>

    
    Param (
        [Parameter(Mandatory=$True)]
        [ValidateSet('Endpoint','WebRTC','Jabra', IgnoreCase=$True)]
        [string]$MappingSource,
        [Parameter(Mandatory=$False)]
        [string]$SourceSearchQuery,
        [Parameter(Mandatory=$False)]
        [int]$SourceID,
        [Parameter(Mandatory=$False)]
        [string]$MapSearchQuery,        
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$Name
    )
    
    Begin {
        Connect-NectarCloud
    }
    Process {
        # 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 
        }

        # Take care of any cases that don't match the above rule
        Switch ($MappingSource) {
            'Endpoint' {
                $URLPath = 'testing/entity'
                break
            }
            'WebRTC' {
                $URLPath = 'webrtc/user/entity'
                break
            }
            'Jabra' {
                $URLPath = 'jabra/user/entity'
                break
            }
        }

        # Get the source entity
        $SourceEntity = Get-NectarUserAccountMapping -MappingSource $MappingSource -SearchQuery $SourceSearchQuery -TenantName $TenantName

        If ($PSBoundParameters.ContainsKey('SourceID')) {
            $SourceEntity = @($SourceEntity | Where-Object { $_.id -eq $SourceID })
        }

        If ($SourceEntity.Count -gt 1) {
            Throw "Multiple source entities found matching '$SourceSearchQuery'. Please refine your search."
        } ElseIf ($SourceEntity.Count -eq 0) {
            Throw "No source entity found matching '$SourceSearchQuery'."
        }

        # Get the map entity
        If ($MapSearchQuery) { $MapEntity = Get-NectarUser -SearchQuery $MapSearchQuery -TenantName $TenantName }

        If ($MapEntity.Count -gt 1) {
            Throw "Multiple map entities found matching '$MapSearchQuery'. Please refine your search."
        } ElseIf ($MapSearchQuery -ne '' -and $MapEntity.Count -eq 0) {
            Throw "No map entity found matching '$MapSearchQuery'."
        }

        # Strip unnecessary properties
        $SourceEntity = $SourceEntity | Select-Object -Property $WantedProps

        $Params = [PSCustomObject]@{
            email            = $MapEntity.email
            id               = $SourceEntity.id
            ipAddress        = $SourceEntity.ipAddress
            isHub            = $SourceEntity.isHub
            location         = $SourceEntity.location
            mappedSource     = $MapEntity.platform
            name             = $SourceEntity.name
            phone            = $MapEntity.phone
            platformUserName = $MapEntity.platformUserName
            userDisplayName  = $MapEntity.displayName
            userName         = $MapEntity.userName
            userPbxNumber    = $MapEntity.userPbxNumber
            uuid             = $SourceEntity.uuid
        }

        $JSONBody = $Params | ConvertTo-Json
        Write-Verbose $JSONBody

        $URI = "https://$Global:NectarCloud/aapi/$($URLPath)?tenant=$TenantName"
        Write-Verbose $URI        
        
        $JSON = Invoke-RestMethod -Method PUT -URI $URI -Headers $Global:NectarAuthHeader -Body $JSONBody -ContentType 'application/json'
        Return $JSON
    }
}


Function Get-NectarWebRTCUser
{
    <#
        .SYNOPSIS
        Return information about WebRTC users in the environment
         
        .DESCRIPTION
        Return information about WebRTC users in the environment. Returns a list of all WebRTC users and their current status.
 
        .PARAMETER SearchQuery
        A string to search for. Will search for match against all fields
         
        .PARAMETER OrderByField
        Sort the output by the selected field
         
        .PARAMETER OrderDirection
        Sort ordered output in ascending or descending order
         
        .PARAMETER PageSize
        The size of the page used to return data. Defaults to 10000
         
        .PARAMETER ResultSize
        The total number of results to return. Maximum result size is 9,999,999 results
     
        .EXAMPLE
        Get-NectarWebRTCUser
        Returns information about all service providers in the environment
 
        .EXAMPLE
        Get-NectarWebRTCUser -SearchQuery TFerguson
        Returns information about users that match the query 'TFerguson'
 
        .NOTES
        Version 1.0
    #>

    
    Param (
        [Parameter(Mandatory=$False)]
        [string]$SearchQuery,        
        [Parameter(Mandatory=$False)]
        [ValidateSet('firstName', 'lastName', 'email', 'lastLoginDate', 'extensionVersion', IgnoreCase=$True)]
        [string]$OrderByField = 'email',
        [Parameter(Mandatory=$False)]
        [ValidateSet('asc', 'desc', IgnoreCase=$True)]
        [string]$OrderDirection = 'asc',
        [Parameter(Mandatory=$False)]
        [ValidateRange(1,999999)]
        [int]$PageSize = 10000,
        [Parameter(Mandatory=$False)]
        [ValidateRange(1,999999)]
        [int]$ResultSize,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName
    )
    
    Begin {
        Connect-NectarCloud
    }
    Process {
        # Set the page size to the result size if -ResultSize switch is used to limit the number of returned items
        # Otherwise, set page size (defaults to 1000)

        # 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 
        }

        If ($ResultSize) { $PageSize = $ResultSize }

        $Params = @{
            'pageNumber'         = 1
            'pageSize'            = $PageSize
            'orderByField'        = $OrderByField
            'orderDirection'    = $OrderDirection
            'tenant'            = $TenantName
        }

        If ($SearchQuery) { $Params.Add('searchQuery', $SearchQuery) }

        $URI = "https://$Global:NectarCloud/aapi/users/webrtc"
        Write-Verbose $URI
        
        $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Global:NectarAuthHeader -Body $Params
        
        $TotalPages = $JSON.totalPages

        $JSON.elements

        If ($TotalPages -gt 1 -and !($ResultSize)) {
            $PageNum = 2
            Write-Verbose "Page size: $PageSize"
            While ($PageNum -le $TotalPages) {
                Write-Verbose "Working on page $PageNum of $TotalPages"
                $Params.PageNumber = $PageNum
                $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Global:NectarAuthHeader -Body $Params
                $JSON.elements
                $PageNum++
            }
        }
    }
}


Function Get-NectarPinnedUsers {
    <#
        .SYNOPSIS
        Returns information about the signed in user's Pinned Users list
 
        .DESCRIPTION
        Users can be "pinned" to the Users screen. This command will return information about all pinned users
         
        .EXAMPLE
        Get-NectarPinnedUsers
         
        .NOTES
        Version 1.0
    #>

    
    [Alias("gnpu")]
    Param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName,
        [Parameter(Mandatory=$False)]
        [ValidateRange(1,100000)]
        [int]$PageSize = 1000,
        [Parameter(Mandatory=$False)]
        [ValidateRange(1,9999999)]
        [int]$ResultSize
    )
    
    Begin {
        Connect-NectarCloud
        
        $Params = @{}
        
        # Set the page size to the result size if -ResultSize switch is used to limit the number of returned items
        # Otherwise, set page size (defaults to 1000)
        If ($ResultSize) {
            $Params.Add('pageSize',$ResultSize)
        }
        Else {
            $Params.Add('pageSize',$PageSize)
        }        
    }        
    Process {
        # 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 ($TenantName) { $Params.Add('Tenant',$TenantName) }
        
        $URI = "https://$Global:NectarCloud/dapi/user/pinned"
        Write-Verbose $URI
        
        Try {
            $JSON = Invoke-RestMethod -Method GET -Uri $URI -Headers $Global:NectarAuthHeader -Body $Params
            
            $TotalPages = $JSON.totalPages
            
            # Add the tenant name to the output which helps pipelining
            If ($TenantName) {$JSON.elements | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty} 
            
            # Remove the Photo element, which takes a lot of room on the page and isn't necessary
            $JSON.elements | Select-Object -Property * -ExcludeProperty Photo
            
            If ($TotalPages -gt 1 -and !($ResultSize)) {
                $PageNum = 2
                Write-Verbose "Page size: $PageSize"
                While ($PageNum -le $TotalPages) {
                    Write-Verbose "Working on page $PageNum of $TotalPages"
                    $PagedURI = $URI + "?pageNumber=$PageNum"
                    $JSON = Invoke-RestMethod -Method GET -Uri $PagedURI -Headers $Global:NectarAuthHeader -Body $Params

                    # Remove the Photo element, which takes a lot of room on the page and isn't necessary
                    $JSON.elements | Select-Object -Property * -ExcludeProperty Photo
                    $PageNum++
                }
            }            
        }
        Catch {
            Write-Error "Error getting pinned user list. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }
        }
    }
}


Function Add-NectarPinnedUser {
    <#
        .SYNOPSIS
        Adds a user to the signed in user's Pinned Users list
 
        .DESCRIPTION
        Users can be "pinned" to the Users screen. This command will add a user to the signed in user's Pinned users list
         
        .EXAMPLE
        Add-NectarPinnedUser tferguson@contoso.com
        Adds the user TFerguson@contoso.com to the signed in Pinned Users list
         
        .EXAMPLE
        Import-Csv .\Users.csv | Add-NectarPinnedUser
        Adds users to pinned user list using a CSV file of email addresses as input. CSV file must have a header called EmailAddress
         
        .NOTES
        Version 1.0
    #>

    
    [Alias("anpu")]
    Param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)]
        [string]$EmailAddress,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName
    )
    
    Begin {
        Connect-NectarCloud
        
        # 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)"
                }
            }
        
        # Get the current preference settings and get the pinned user list
        $Preferences = (Get-NectarUserAccountSettings).Preferences
        [System.Collections.ArrayList]$PinnedUserList = $Preferences.pinnedUserIds
        
        $URI = "https://$Global:NectarCloud/aapi/user/preferences?tenant=$TenantName"
        Write-Verbose $URI
    }        
    Process {
        Try {
            # Get the ID of the desired user and add to the Pinned User List
            [string]$UserID = (Get-NectarUser $EmailAddress -ErrorAction Stop).Id

            If ($PinnedUserList.Contains($UserID)) {
                Write-Error "$EmailAddress already in Pinned User list"
            }
            Else {
                $Null = $PinnedUserList.Add($UserID)
            }
            
            # Re-insert pinned user list to user preferences
            $Preferences.pinnedUserIds = $PinnedUserList
            
            # Convert Preferences back to JSON
            $JSONPrefs = $Preferences | ConvertTo-JSON -Depth 10
            
            # Save the updated preferences
            $NULL = Invoke-RestMethod -Method POST -Uri $URI -Headers $Global:NectarAuthHeader -Body $JSONPrefs -ContentType "application/json"
                        
        }
        Catch {
            Write-Error "Error adding $EmailAddress to pinned user list. $($_.Exception.Message)"
        }
    }
    End {
        Try {
            # Re-insert pinned user list to user preferences
            $Preferences.pinnedUserIds = $PinnedUserList
            
            # Convert Preferences back to JSON
            $JSONPrefs = $Preferences | ConvertTo-JSON -Depth 10
            
            # Save the updated preferences
            $NULL = Invoke-RestMethod -Method POST -Uri $URI -Headers $Global:NectarAuthHeader -Body $JSONPrefs -ContentType "application/json"
        }
        Catch {
            Write-Error "Error adding $EmailAddress to pinned user list. $($_.Exception.Message)"
        }
    }
}


Function Remove-NectarPinnedUser {
    <#
        .SYNOPSIS
        Remove a user from the signed in user's Pinned Users list
 
        .DESCRIPTION
        Users can be "pinned" to the Users screen. This command will remove a user from the signed in user's Pinned users list
         
        .EXAMPLE
        Remove-NectarPinnedUsers tferguson@contoso.com
        Removes the user TFerguson@contoso.com from the signed in Pinned Users list
         
        .NOTES
        Version 1.0
    #>

    
    [Alias("rnpu")]
    Param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [Alias('Email')]
        [string]$EmailAddress,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$ID,        
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName
    )
    
    Begin {
        Connect-NectarCloud
        
        # 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)"
                }
            }
        
        # Get the current preference settings and get the pinned user list
        $Preferences = (Get-NectarUserAccountSettings).Preferences
        [System.Collections.ArrayList]$PinnedUserList = $Preferences.pinnedUserIds
        
        $URI = "https://$Global:NectarCloud/aapi/user/preferences?tenant=$TenantName"
        Write-Verbose $URI
    }        
    Process {
        Try {
            # Get the ID of the desired user and remove from the Pinned User List
            If (!$ID) {    [string]$ID = (Get-NectarUser $EmailAddress).Id }
        
            $PinnedUserList.Remove($ID)
        }
        Catch {
            Write-Error "Error removing $EmailAddress from pinned user list. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }
        }
    }
    End {
        Try {
            # Re-insert pinned user list to user preferences
            $Preferences.pinnedUserIds = $PinnedUserList
            
            # Convert Preferences back to JSON
            $JSONPrefs = $Preferences | ConvertTo-JSON -Depth 10
            
            # Save the updated preferences
            $NULL = Invoke-RestMethod -Method POST -Uri $URI -Headers $Global:NectarAuthHeader -Body $JSONPrefs -ContentType "application/json"
        }
        Catch {
            Write-Error "Error updating user preferences $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }
        }    
    }
}




#################################################################################################################################################
# #
# Tenant Email Domain Functions #
# #
#################################################################################################################################################

Function Get-NectarEmailDomain {
    <#
        .SYNOPSIS
        Returns a list of Nectar DXP allowed email domains that can be used for login IDs.
         
        .DESCRIPTION
        Returns a list of Nectar DXP allowed email domains that can be used for login IDs.
         
        .PARAMETER TenantName
        The name of the Nectar DXP tenant. Used in multi-tenant configurations.
 
        .PARAMETER ResultSize
        The number of results to return. Defaults to 1000.
         
        .EXAMPLE
        Get-NectarEmailDomain
        Returns all the allowed email domains for the logged in tenant.
 
        .NOTES
        Version 1.1
    #>

    
    [Alias("gne")]
    Param (
        [Parameter(Mandatory=$False)]
        [string]$SearchQuery,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName,
        [Parameter(Mandatory=$False)]
        [ValidateRange(1,100000)]
        [int]$ResultSize = 1000
    )
    
    Begin {
        Connect-NectarCloud
    }
    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)"
                }
            }

            $URI = "https://$Global:NectarCloud/aapi/client/domains?searchQuery=$SearchQuery&tenant=$TenantName&pageSize=$ResultSize"
            Write-Verbose $URI

            $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Global:NectarAuthHeader

            If ($JSON.domainNames) { Return $JSON.domainNames }
            If ($JSON.elements) { Return $JSON.elements }
        }
        Catch {
            Write-Error "No email domains found or insufficient permissions. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }
        }
    }
}


Function New-NectarEmailDomain {
    <#
        .SYNOPSIS
        Add a new allowed email domain that can be used for login IDs.
         
        .DESCRIPTION
        Add a new allowed email domain that can be used for login IDs.
         
        .PARAMETER EmailDomain
        The email domain to add to the tenant.
 
        .PARAMETER TenantName
        The name of the Nectar DXP tenant. Used in multi-tenant configurations.
         
        .EXAMPLE
        New-NectarEmailDomain -EmailDomain contoso.com
        Adds the contoso.com email domain to the logged in Nectar DXP tenant.
 
        .NOTES
        Version 1.1
    #>

    
    [Alias("nne")]
    Param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)]
        [string]$EmailDomain,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName
    )
    
    Begin {
        Connect-NectarCloud
    }
    Process {
        # 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)"
                }
            }
        
        $URI = "https://$Global:NectarCloud/aapi/client/domains?tenant=$TenantName"
        Write-Verbose $URI
        
        $JSONBody = $EmailDomain
        
        Try {
            $NULL = Invoke-RestMethod -Method POST -URI $URI -Headers $Global:NectarAuthHeader -Body $JSONBody -ContentType 'application/json; charset=utf-8'
            Write-Verbose "Successfully added $EmailDomain as an allowed email domain."
        }
        Catch {
            Write-Error "Unable to add $EmailDomain to list of allowed email domains. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }
        }
    }
}


Function Remove-NectarEmailDomain {
    <#
        .SYNOPSIS
        Remove an allowed email domain that can be used for login IDs.
         
        .DESCRIPTION
        Remove an allowed email domain that can be used for login IDs.
         
        .PARAMETER EmailDomain
        The email domain to remove from the tenant.
 
        .PARAMETER TenantName
        The name of the Nectar DXP tenant. Used in multi-tenant configurations.
         
        .EXAMPLE
        Remove-NectarEmailDomain -EmailDomain contoso.com
        Removes the contoso.com email domain from the list of allowed domains on the logged in Nectar DXP tenant.
 
        .NOTES
        Version 1.1
    #>

    [Alias("rne")]
    Param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [Alias("domainName")]
        [string]$EmailDomain,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName,    
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [Alias("id")]
        [int]$Identity
    )
    
    Begin {
        Connect-NectarCloud
    }        
    Process {
        # 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 ($EmailDomain -and !$Identity) {
            $Identity = (Get-NectarEmailDomain -TenantName $TenantName -SearchQuery $EmailDomain -ResultSize 1 -ErrorVariable GetDomainError).ID
        }

        If (!$GetDomainError) {
            $URI = "https://$Global:NectarCloud/aapi/client/domains/$Identity/?tenant=$TenantName"
            Write-Verbose $URI

            Try {
                $NULL = Invoke-RestMethod -Method DELETE -URI $URI -Headers $Global:NectarAuthHeader
                Write-Verbose "Successfully deleted $EmailDomain from list of allowed email account domains."
            }
            Catch {
                Write-Error "Unable to delete email domain $EmailDomain. Ensure you typed the name of the email domain correctly. $($_.Exception.Message)"
                If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }
            }
        }
    }
}




#################################################################################################################################################
# #
# Tenant Admin Functions #
# #
#################################################################################################################################################

Function Get-NectarUserAccount {
    <#
        .SYNOPSIS
        Get information about one or more Nectar DXP user accounts.
         
        .DESCRIPTION
        Get information about one or more Nectar DXP user accounts.
 
        .PARAMETER SearchQuery
        A full or partial match of the user's first or last name or email address
 
        .PARAMETER TenantName
        The name of the Nectar DXP tenant. Used in multi-tenant configurations.
         
        .PARAMETER ResultSize
        The number of results to return. Defaults to 10000.
         
        .EXAMPLE
        Get-NectarUserAccount -SearchQuery tferguson@contoso.com
        Returns information about the user tferguson@contoso.com
         
        .NOTES
        Version 1.1
    #>

    
    [Alias('gna', 'Get-NectarAdmin')]
    Param (
        [Parameter(Mandatory=$False)]
        [string]$SearchQuery,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName,
        [Parameter(Mandatory=$False)]
        [ValidateRange(1,100000)]
        [int]$ResultSize = 10000
    )    
    
    Begin {
        Connect-NectarCloud
    }        
    Process {
        # 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)"
            }
        }
        
        $URI = "https://$Global:NectarCloud/aapi/users?searchQuery=$SearchQuery&tenant=$TenantName&pageSize=$ResultSize"
        Write-Verbose $URI

        Try {
            $JSON = Invoke-RestMethod -Method POST -URI $URI -Headers $Global:NectarAuthHeader
            If ($TenantName) {$JSON.elements | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty} # Add the tenant name to the output which helps pipelining
            Return $JSON.elements
        }
        Catch {
            Write-Error "Unable to get user details. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }
        }
    }
}


Function Set-NectarUserAccount {
    <#
        .SYNOPSIS
        Update one or more Nectar DXP user accounts.
         
        .DESCRIPTION
        Update one or more Nectar DXP user accounts.
         
        .PARAMETER FirstName
        The first name of the user
         
        .PARAMETER LastName
        The last name of the user
 
        .PARAMETER EmailAddress
        The email address of the user
         
        .PARAMETER AdminStatus
        True if Admin, False if not. Used when importing many user accounts via CSV
         
        .PARAMETER IsAdmin
        Include if user is to be an admin. If not present, then user will be read-onl
 
        .PARAMETER TenantName
        The name of the Nectar DXP tenant. Used in multi-tenant configurations.
                 
        .PARAMETER Identity
        The numerical identity of the user
         
        .NOTES
        Version 1.1
    #>

    
    [Alias('sna', 'Set-NectarAdmin')]
    Param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [Alias("name")]
        [string]$FirstName,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$LastName,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [Alias("email")]
        [string]$EmailAddress,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [Alias("admin")]
        [string]$AdminStatus,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [switch]$IsAdmin,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$Role,        
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [Alias("id")]
        [String]$Identity
    )
    
    Begin {
        Connect-NectarCloud
    }        
    Process {
        # 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 ($IsAdmin -and !$AdminStatus) {
            # $AdminStatus = "true"
        # }
        # ElseIf (!$IsAdmin -and !$AdminStatus) {
            # $AdminStatus = "false"
        # }
        
        If ($EmailAddress -And !$Identity) {
            $UserInfo = Get-NectarUserAccount -SearchQuery $EmailAddress -Tenant $TenantName -ResultSize 1
            $Identity = $UserInfo.id
        }
        
        If (-not $FirstName) {$FirstName = $UserInfo.firstName}
        If (-not $LastName) {$LastName = $UserInfo.lastName}
        If (-not $EmailAddress) {$EmailAddress = $UserInfo.email}
        If (-not $IsAdmin -and !$AdminStatus) {$AdminStatus = $UserInfo.admin}
            
        $URI = "https://$Global:NectarCloud/aapi/user/$Identity/?tenant=$TenantName"
        Write-Verbose $URI

        $Body = @{
            id = $Identity
            email = $EmailAddress
            firstName = $FirstName
            lastName = $LastName
# isAdmin = $AdminStatus
            userRoleName = $Role
# userStatus = "ACTIVE"
        }
        
        $JSONBody = $Body | ConvertTo-Json

        Try {
            Write-Verbose $JSONBody
            $NULL = Invoke-RestMethod -Method PUT -URI $URI -Headers $Global:NectarAuthHeader -Body $JSONBody -ContentType 'application/json; charset=utf-8'
        }
        Catch {
            Write-Error "Unable to apply changes for user $EmailAddress. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }
        }
    }
}


Function New-NectarUserAccount {
    <#
        .SYNOPSIS
        Create a new Nectar DXP user account.
         
        .DESCRIPTION
        Create a new Nectar DXP user account.
 
        .PARAMETER FirstName
        The user's first name
 
        .PARAMETER LastName
        The user's last name
 
        .PARAMETER EmailAddress
        The user's email address
         
        .PARAMETER Password
        The password to assign to the new account
         
        .PARAMETER AdminStatus
        True if Admin, False if not. Used when importing many admin accounts via CSV
         
        .PARAMETER IsAdmin
        Include if user is to be an admin. If not present, then user will be read-only
         
        .PARAMETER TenantName
        The name of the Nectar DXP tenant. Used in multi-tenant configurations.
         
        .EXAMPLE
        New-NectarUser -FirstName Turd -LastName Ferguson -Email tferguson@contoso.com -Password VeryStrongPassword -IsAdmin
        Creates a new admin user called Turd Ferguson
         
        .EXAMPLE
        Import-Csv .\Users.csv | New-NectarUser
        Creates user accounts using a CSV file as input. CSV file must have the following headers: FirstName,LastName,Password,Email,AdminStatus (Use True/False for AdminStatus)
 
        .NOTES
        Version 1.2
    #>

    
    [Alias('nna', 'New-NectarAdmin')]
    Param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$FirstName,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$LastName,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)]
        [Alias("email")]
        [string]$EmailAddress,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [SecureString]$Password,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$AdminStatus,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [switch]$IsAdmin,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$Role,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName        
    )    

    Begin {
        Connect-NectarCloud
    }        
    Process {
        If ($IsAdmin -and !$AdminStatus) {
            $AdminStatus = "true"
        }
        ElseIf (!$IsAdmin -and !$AdminStatus) {
            $AdminStatus = "false"
        }

        # 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)"
                }
            }
                
        $URI = "https://$Global:NectarCloud/aapi/user?tenant=$TenantName"
        Write-Verbose $URI

        $Body = @{
            email = $EmailAddress
            firstName = $FirstName
            lastName = $LastName
            isAdmin = $AdminStatus
            userRoleName = $Role
            userStatus = "ACTIVE"
        }
        
        If ($Password) { $Body.Add('password',$Password) }
            
        $JSONBody = $Body | ConvertTo-Json

        Try {
            Write-Verbose $JSONBody
            $NULL = Invoke-RestMethod -Method POST -URI $URI -Headers $Global:NectarAuthHeader -Body $JSONBody -ContentType 'application/json; charset=utf-8'
        }
        Catch {
            Write-Error "Unable to create user account $EmailAddress. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }
        }
    }
}


Function Remove-NectarUserAccount {
    <#
        .SYNOPSIS
        Removes one or more Nectar DXP user account.
 
        .DESCRIPTION
        Removes one or more Nectar DXP user account.
         
        .PARAMETER EmailAddress
        The email address of the user account to remove.
 
        .PARAMETER TenantName
        The name of the Nectar DXP tenant. Used in multi-tenant configurations.
             
        .PARAMETER Identity
        The numerical ID of the user account to remove. Can be obtained via Get-NectarUserAccount and pipelined to Remove-NectarUser
         
        .EXAMPLE
        Remove-NectarUserAccount tferguson@nectarcorp.com
        Removes the user account tferguson@nectarcorp.com
         
        .NOTES
        Version 1.1
    #>

    
    [Alias('rna', 'Remove-NectarAdmin')]
    Param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [Alias("email")]
        [string]$EmailAddress,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [Alias("id")]
        [string]$Identity
    )
    
    Begin {
        Connect-NectarCloud
    }        
    Process {
        # 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 ($EmailAddress -and !$Identity) {
            $Identity = (Get-NectarUserAccount -SearchQuery $EmailAddress -Tenant $TenantName -ResultSize 1 -ErrorVariable GetUserError).ID
        }
            
        If (!$GetUserError) {
            $URI = "https://$Global:NectarCloud/aapi/user/$Identity/?tenant=$TenantName"
            Write-Verbose $URI
            
            Try {
                $NULL = Invoke-RestMethod -Method DELETE -URI $URI -Headers $Global:NectarAuthHeader
                Write-Verbose "Successfully deleted $EmailAddress from user account list."
            }
            Catch {
                Write-Error "Unable to delete user $EmailAddress. Ensure you typed the name of the user correctly. $($_.Exception.Message)"
                If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }
            }
        }
    }
}


Function Get-NectarAdminRole {
    <#
        .SYNOPSIS
        Returns a list of admin roles
 
        .DESCRIPTION
        Returns a list of admin roles
         
        .PARAMETER TenantName
        The name of the Nectar DXP tenant. Used in multi-tenant configurations.
             
        .EXAMPLE
        Get-NectarAdminRole
        Returns a list of admin roles associated with the current tenant
         
        .NOTES
        Version 1.0
    #>

    
    Param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName
    )
    
    Begin {
        Connect-NectarCloud
    }        
    Process {
        # 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)"
            }
        }
        
        $URI = "https://$Global:NectarCloud/aapi/user/roles?pageSize=9999&pageNumber=1&tenant=$TenantName"
        Write-Verbose $URI

        $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Global:NectarAuthHeader
        Return $JSON.elements
    }
}


Function New-NectarAdminRole {
    <#
        .SYNOPSIS
        Creates a new admin role
 
        .DESCRIPTION
        Creates a new admin role
 
        .PARAMETER Name
        The name to give to the role
 
        .PARAMETER Description
        The role description
         
        .PARAMETER PermissionList
        A list of permissions (authorities) to assign to the new role. This determines what the role can view or configure in the dashboard.
 
        .PARAMETER ServiceProviderName
        The name of the service provider associated with this role. Must be used with -TenantList. Required in the 'ServiceProvider' parameter set.
 
        .PARAMETER TenantList
        A list of tenant names associated with the service provider. Must be used with -ServiceProviderName. Required in the 'ServiceProvider' parameter set.
 
        .PARAMETER LockMinutes
        The number of minutes a user is locked out after exceeding the maximum login attempts. Default is 15.
 
        .PARAMETER ManualUnlock
        Indicates whether a manual unlock is required after a lockout. Default is $False.
 
        .PARAMETER MaxLoginAttempts
        The number of failed login attempts allowed before a lockout occurs. Default is 5.
 
        .PARAMETER TenantName
        The name of a single tenant. Used in the 'Tenant' parameter set. Optional.
         
        .NOTES
        Version 1.0
    #>

    [CmdletBinding(DefaultParameterSetName = 'Tenant')]
    Param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)]
        [string]$Name,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$Description,
        [Parameter(Mandatory=$True)]
        [ValidateSet(
            'DASHBOARD_ANALYTICS_SELF_SERVICE',
            'DASHBOARD_ANALYTICS_VIEWER',
            'DASHBOARD_EVENTS_VIEWER',
            'DASHBOARD_HISTORIC_CALL_DETAILS_QUALITY_VIEWER',
            'DASHBOARD_HISTORIC_CALL_DETAILS_VIEWER',
            'DASHBOARD_HISTORIC_CONFERENCE_PARTICIPANTS_VIEWER',
            'DASHBOARD_HISTORIC_CONFERENCE_SUMMARY_VIEWER',
            'DASHBOARD_HISTORIC_CONFERENCE_TIMELINE_VIEWER',
            'DASHBOARD_HISTORIC_SESSION_LIST_VIEWER',
            'DASHBOARD_HISTORIC_SESSION_SUMMARY_ADVANCED_VIEWER',
            'DASHBOARD_HISTORIC_SESSION_SUMMARY_DETAILS_VIEWER',
            'DASHBOARD_HISTORIC_SESSION_SUMMARY_VIEWER',
            'DASHBOARD_ITEMS_CONFIGURATOR',
            'DASHBOARD_ITEMS_VIEWER',
            'DASHBOARD_LIVE_CALL_DETAILS_VIEWER',
            'DASHBOARD_NUMBERS_CONFIGURATOR',
            'DASHBOARD_NUMBERS_VIEWER',
            'DASHBOARD_PLATFORM_AVAYA_VIEWER',
            'DASHBOARD_PLATFORM_CISCO_VIEWER',
            'DASHBOARD_PLATFORM_ENDPOINT_CLIENT_VIEWER',
            'DASHBOARD_PLATFORM_INVENTORY_VIEWER',
            'DASHBOARD_REPORTS_VIEWER',
            'DASHBOARD_ROOMS_DEVICES_VIEWER',
            'DASHBOARD_SERVICES_VIEWER',
            'DASHBOARD_SUMMARY_VIEWER',
            'DASHBOARD_USERS_ADVANCED_VIEWER',
            'DASHBOARD_USERS_VIEWER',
            'DASHBOARD_VIEWER'
        )]
        [string[]]$PermissionList,
        [Parameter(ParameterSetName = 'ServiceProvider', Mandatory=$True)]
        [string]$ServiceProviderName,
        [Parameter(ParameterSetName = 'ServiceProvider', Mandatory=$True)]
        [string[]]$TenantList,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [int]$LockMinutes = 15,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [bool]$ManualUnlock = $False,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [int]$MaxLoginAttempts = 5,
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Tenant', Mandatory=$False)]
        [string]$TenantName
    )

    Begin {
        Connect-NectarCloud
    }        
    Process {
        # 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)"
            }
        }

        # Convert TenantList to ClientIDs
        $ClientIDs = @()
        If ($TenantList) {
            $TenantName = $ServiceProviderName # Use ServiceProviderName as TenantName for Service Provider roles
            $AvailableTenants = Get-NectarServiceProviderTenants -ServiceProviderName $ServiceProviderName

            # Validate the tenants exist and add to ClientIDs
            ForEach ($Tenant in $TenantList) {
                $TenantInfo = $AvailableTenants | Where-Object { $_.Name -eq $Tenant } 
                If ($TenantInfo) {
                    $ClientIDs += $TenantInfo.Id
                } Else {
                    Throw "Tenant $Tenant not found in service provider $ServiceProviderName. Available tenants are: $($AvailableTenants.Name -join ', ')"
                }
            }
        }

        $URI = "https://$Global:NectarCloud/aapi/user/role?tenant=$TenantName"
        Write-Verbose $URI
            
        $Body = @{
            name                = $Name
            description            = $Description
            authorities            = $PermissionList
            reportTagIds        = @()
            reportCatalogIds    = @()
            clientIds            = $ClientIDs
            assignAllClients     = $false
            userRoleLockSettings = @{
                lockMinutes         = $LockMinutes
                manualUnlock         = $ManualUnlock
                maxLoginAttempts    = $MaxLoginAttempts
            }
        }

        $JSONBody = $Body | ConvertTo-Json -Depth 10
        Write-Verbose $JSONBody
        
        Try {
            $Response = Invoke-RestMethod -Uri $URI -Method POST -Body $JSONBody -Headers $Global:NectarAuthHeader -ContentType "application/json"
            Write-Host "Role created successfully:" -ForegroundColor Green
            $Response
        } Catch {
            Write-Error "Failed to create role: $_"
        }
    }
}







#################################################################################################################################################
# #
# Tenant Location Functions #
# #


Function Get-NectarUser {
    <#
        .SYNOPSIS
        Get information about 1 or more users via Nectar DXP.
         
        .DESCRIPTION
        Get information about 1 or more users via Nectar DXP.
 
        .PARAMETER SearchQuery
        A full or partial match of the user name. Will do an 'includes' type search by default. So searching for user@domain.com would return Auser@domain.com, Buser@domain.com etc.
        For a specific match, enclose the name in square brackets IE: [user@domain.com]
 
        .PARAMETER FilterSearch
        Uses an alternate API for returning users. Has better searching capabilities and should be used in conjunction with commands that allow user filtering
         
        .PARAMETER TenantName
        The name of the Nectar DXP tenant. Used in multi-tenant configurations.
         
        .PARAMETER ResultSize
        The number of results to return. Defaults to 1000.
         
        .EXAMPLE
        Get-NectarUser -SearchQuery tferguson
         
        .NOTES
        Version 1.2
    #>

    
    [Alias("gnu")]
    Param (
        [Parameter(Mandatory=$True)]
        [string]$SearchQuery,
        [Parameter(Mandatory=$False)]
        [switch]$FilterSearch,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName,
        [Parameter(Mandatory=$False)]
        [ValidateSet('DEFAULT','USERS','ENDPOINT_CLIENT','CALL_DETAILS_HISTORIC','QUALITY_DETAILS', IgnoreCase=$True)]
        [string]$Scope = 'DEFAULT',
        [Parameter(Mandatory=$False)]
        [ValidateRange(1,999999)]
        [int]$ResultSize = 10000
    )    
    
    Begin {
        Connect-NectarCloud
        
        # Get the encoding for resolving special characters
        $TextEncoding = [System.Text.Encoding]::GetEncoding('ISO-8859-1')
    }        
    Process {
        # 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)"
                }
            }
        
        $EncodedSearchQuery = [System.Web.HttpUtility]::UrlEncode($SearchQuery)
        
        # Filtersearch query should be used in support of a filter query for use in session searches. Has better searching ability than the other method.
        # For example, the FilterSearch API allows for searching with alternate characters like periods, while the other method seems to ignore it selectively
        # The other method presents more data, which may be useful for other purposes.
        If ($FilterSearch) { 
            $URI = "https://$Global:NectarCloud/dapi/info/session/users?q=$EncodedSearchQuery&usersForAlerts=false&pageSize=$ResultSize&tenant=$TenantName"
            $UserFilterSession = Set-NectarFilterParams -Scope $Scope
            $WebContent = Invoke-WebRequest -Method GET -URI $URI -UseBasicParsing -Headers $Global:NectarAuthHeader -WebSession $UserFilterSession 
        }
        Else {
            $URI = "https://$Global:NectarCloud/dapi/user/search?q=$EncodedSearchQuery&pageSize=$ResultSize&tenant=$TenantName"
            $WebContent = Invoke-WebRequest -Method GET -URI $URI -UseBasicParsing -Headers $Global:NectarAuthHeader
        }

        Write-Verbose $URI
        
        Try {
            $JSON = ([System.Text.Encoding]::UTF8).GetString($TextEncoding.GetBytes($WebContent.Content)) | ConvertFrom-Json

            If ($JSON.totalElements -eq 0) {
                Write-Error "Cannot find user with name $SearchQuery."
            }
            If ($TenantName) { # Add the tenant name to the output which helps pipelining
                $JSON.elements | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty
            } 
            Return $JSON.elements
        }
        Catch {
            Write-Error "Cannot find user with name $SearchQuery. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }
        }
    }
}


Function Get-NectarUserOverview {
    <#
        .SYNOPSIS
        Returns a usage overview about a given user
 
        .DESCRIPTION
        Returns a usage overview about a given user
         
        .PARAMETER EmailAddress
        The email address of the user.
     
        .PARAMETER Identity
        The numerical ID of the user. Can be obtained via Get-NectarUser and pipelined to Get-NectarUserOverview
 
        .PARAMETER ExcludeBaseInfo
        Exclude basic user information from the output. Saves an API call.
 
        .PARAMETER ExcludeCallCount
        Exclude call count information from the output. Saves an API call.
 
        .PARAMETER ExcludeHealth
        Exclude user health information from the output. Saves an API call.
         
        .EXAMPLE
        Get-NectarUserOverview tferguson@nectarcorp.com
         
        .NOTES
        Version 1.2
    #>

    
    [Alias("Get-NectarUserDetails")]
    Param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [Alias('name')]
        [string]$SearchQuery, 
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [Alias('userId')]
        [string]$Identity,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$Email,
        [Parameter(Mandatory=$False)]
        [switch]$ExcludeBaseInfo,
        [Parameter(Mandatory=$False)]
        [switch]$ExcludeCallCount,
        [Parameter(Mandatory=$False)]
        [switch]$ExcludeHealth,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName
    )
    
    Begin {
        Connect-NectarCloud
    }        
    Process {
        # 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)"
                }
            }
        
        Write-Verbose "Search Query: $SearchQuery"

        If ($SearchQuery -and !$Identity) {
            Write-Verbose "Searching..."
            $UserInfo = Get-NectarUser -SearchQuery $SearchQuery -Tenant $TenantName -ResultSize 1 -ErrorVariable GetUserError
            $Identity = $UserInfo.UserId
            If ($UserInfo.email) { $Email = $UserInfo.email }
            Write-Verbose "Identity: $Identity Email: $Email"
        }
            
        If (!$GetUserError) {
            $BaseURI = "https://$Global:NectarCloud/dapi/user/$($Identity)?tenant=$TenantName"
            $CallCountURI = "https://$Global:NectarCloud/dapi/user/$($Identity)/summary/calls?tenant=$TenantName"
            $HealthURI = "https://$Global:NectarCloud/dapi/user/$($Identity)/health/score?tenant=$TenantName"
            
            Try {
                If (!$ExcludeBaseInfo) {
                    $JSON = Invoke-RestMethod -Method GET -URI $BaseURI -Headers $Global:NectarAuthHeader
                }
                Else {
                    $JSON = [pscustomobject]@{}
                }

                If (!$ExcludeCallCount) { $CallCountJSON = Invoke-RestMethod -Method GET -URI $CallCountURI -Headers $Global:NectarAuthHeader }
                If (!$ExcludeHealth) { $HealthJSON = Invoke-RestMethod -Method GET -URI $HealthURI -Headers $Global:NectarAuthHeader }
                
                # Add the call count info to the output
                If ($CallCountJSON) {
                    If ($ExcludeBaseInfo) { 
                        $JSON | Add-Member -Name 'Id' -Value $Identity -MemberType NoteProperty
                        $JSON | Add-Member -Name 'Email' -Value $Email -MemberType NoteProperty
                    }
                    $CallCountJSON.psobject.Properties | ForEach-Object {
                        $JSON | Add-Member -MemberType $_.MemberType -Name $_.Name -Value $_.Value -Force
                    }
                }
                    
                # Add the health info to the output
                If ($HealthJSON) {
                    If ($ExcludeBaseInfo) { 
                        $JSON | Add-Member -Name 'Id' -Value $Identity -MemberType NoteProperty -ErrorAction SilentlyContinue
                        $JSON | Add-Member -Name 'Email' -Value $Email -MemberType NoteProperty -ErrorAction SilentlyContinue
                    }
                    $HealthJSON.psobject.Properties | ForEach-Object {
                        $JSON | Add-Member -MemberType $_.MemberType -Name $_.Name -Value $_.Value -Force
                    }
                }
                
                If ($TenantName) {$JSON | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty} # Add the tenant name to the output which helps pipelining
                $Identity = $NULL
                Return $JSON
            }
            Catch {
                Write-Error "Unable to find user $EmailAddress. Ensure you typed the name of the user correctly. $($_.Exception.Message)"
                If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }
            }
        }
    }
}


Function Get-NectarUserAdvancedInfo {
    <#
        .SYNOPSIS
        Returns a usage overview about a given user
 
        .DESCRIPTION
        Returns a usage overview about a given user
         
        .PARAMETER EmailAddress
        The email address of the user.
     
        .PARAMETER Identity
        The numerical ID of the user. Can be obtained via Get-NectarUser and pipelined to Get-NectarUserAdvanced
         
        .EXAMPLE
        Get-NectarUserAdvancedInfo tferguson@nectarcorp.com
 
        .NOTES
        Version 1.1
    #>

    
    Param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [Alias("email")]
        [string]$EmailAddress, 
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [Alias("userId")]
        [string]$Identity,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName
    )
    
    Begin {
        Connect-NectarCloud
    }        
    Process {
        # 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 ($EmailAddress -and !$Identity) {
            $Identity = (Get-NectarUser -SearchQuery $EmailAddress -Tenant $TenantName -ResultSize 1 -ErrorVariable GetUserError).UserId
            Write-Verbose "Identity: $Identity"
        }
            
        If (!$GetUserError) {
            $URI = "https://$Global:NectarCloud/dapi/user/$($Identity)/advanced?tenant=$TenantName"
            Write-Verbose $URI
            
            Try {
                $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Global:NectarAuthHeader
                
                If ($TenantName) {$JSON | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty} # Add the tenant name to the output which helps pipelining
                Return $JSON
            }
            Catch {
                Write-Error "Unable to find user $EmailAddress. Ensure you typed the name of the user correctly. $($_.Exception.Message)"
                If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }
            }
        }
    }
}
# SIG # Begin signature block
# MIIfjAYJKoZIhvcNAQcCoIIffTCCH3kCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCbww8WYSZFHE5H
# rQda6spFnyPY+HwiwUnVJGi/cagIPaCCDRIwggZyMIIEWqADAgECAghkM1HTxzif
# CDANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx
# EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8G
# A1UEAwwoU1NMLmNvbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTAe
# Fw0xNjA2MjQyMDQ0MzBaFw0zMTA2MjQyMDQ0MzBaMHgxCzAJBgNVBAYTAlVTMQ4w
# DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENv
# cnAxNDAyBgNVBAMMK1NTTC5jb20gQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBD
# QSBSU0EgUjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCfgxNzqrDG
# bSHL24t6h3TQcdyOl3Ka5LuINLTdgAPGL0WkdJq/Hg9Q6p5tePOf+lEmqT2d0bKU
# Vz77OYkbkStW72fL5gvjDjmMxjX0jD3dJekBrBdCfVgWQNz51ShEHZVkMGE6ZPKX
# 13NMfXsjAm3zdetVPW+qLcSvvnSsXf5qtvzqXHnpD0OctVIFD+8+sbGP0EmtpuNC
# GVQ/8y8Ooct8/hP5IznaJRy4PgBKOm8yMDdkHseudQfYVdIYyQ6KvKNc8HwKp4WB
# wg6vj5lc02AlvINaaRwlE81y9eucgJvcLGfE3ckJmNVz68Qho+Uyjj4vUpjGYDdk
# jLJvSlRyGMwnh/rNdaJjIUy1PWT9K6abVa8mTGC0uVz+q0O9rdATZlAfC9KJpv/X
# gAbxwxECMzNhF/dWH44vO2jnFfF3VkopngPawismYTJboFblSSmNNqf1x1KiVgMg
# Lzh4gL32Bq5BNMuURb2bx4kYHwu6/6muakCZE93vUN8BuvIE1tAx3zQ4XldbyDge
# VtSsSKbt//m4wTvtwiS+RGCnd83VPZhZtEPqqmB9zcLlL/Hr9dQg1Zc0bl0EawUR
# 0tOSjAknRO1PNTFGfnQZBWLsiePqI3CY5NEv1IoTGEaTZeVYc9NMPSd6Ij/D+KNV
# t/nmh4LsRR7Fbjp8sU65q2j3m2PVkUG8qQIDAQABo4H7MIH4MA8GA1UdEwEB/wQF
# MAMBAf8wHwYDVR0jBBgwFoAU3QQJB6L1en1SUxKSle44gCUNplkwMAYIKwYBBQUH
# AQEEJDAiMCAGCCsGAQUFBzABhhRodHRwOi8vb2NzcHMuc3NsLmNvbTARBgNVHSAE
# CjAIMAYGBFUdIAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwOwYDVR0fBDQwMjAwoC6g
# LIYqaHR0cDovL2NybHMuc3NsLmNvbS9zc2wuY29tLXJzYS1Sb290Q0EuY3JsMB0G
# A1UdDgQWBBRUwv4QlQCTzWr158DX2bJLuI8M4zAOBgNVHQ8BAf8EBAMCAYYwDQYJ
# KoZIhvcNAQELBQADggIBAPUPJodwr5miyvXWyfCNZj05gtOII9iCv49UhCe204MH
# 154niU2EjlTRIO5gQ9tXQjzHsJX2vszqoz2OTwbGK1mGf+tzG8rlQCbgPW/M9r1x
# xs19DiBAOdYF0q+UCL9/wlG3K7V7gyHwY9rlnOFpLnUdTsthHvWlM98CnRXZ7WmT
# V7pGRS6AvGW+5xI+3kf/kJwQrfZWsqTU+tb8LryXIbN2g9KR+gZQ0bGAKID+260P
# Z+34fdzZcFt6umi1s0pmF4/n8OdX3Wn+vF7h1YyfE7uVmhX7eSuF1W0+Z0duGwdc
# +1RFDxYRLhHDsLy1bhwzV5Qe/kI0Ro4xUE7bM1eV+jjk5hLbq1guRbfZIsr0WkdJ
# LCjoT4xCPGRo6eZDrBmRqccTgl/8cQo3t51Qezxd96JSgjXktefTCm9r/o35pNfV
# HUvnfWII+NnXrJlJ27WEQRQu9i5gl1NLmv7xiHp0up516eDap8nMLDt7TAp4z5T3
# NmC2gzyKVMtODWgqlBF1JhTqIDfM63kXdlV4cW3iSTgzN9vkbFnHI2LmvM4uVEv9
# XgMqyN0eS3FE0HU+MWJliymm7STheh2ENH+kF3y0rH0/NVjLw78a3Z9UVm1F5VPz
# iIorMaPKPlDRADTsJwjDZ8Zc6Gi/zy4WZbg8Zv87spWrmo2dzJTw7XhQf+xkR6Od
# MIIGmDCCBICgAwIBAgIQIc+kRkn+AQupTThE+j58IjANBgkqhkiG9w0BAQsFADB4
# MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24x
# ETAPBgNVBAoMCFNTTCBDb3JwMTQwMgYDVQQDDCtTU0wuY29tIENvZGUgU2lnbmlu
# ZyBJbnRlcm1lZGlhdGUgQ0EgUlNBIFIxMB4XDTI1MDgwODIwMTMzNFoXDTI2MDgw
# ODIwMTMzNFowfzELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMRAwDgYD
# VQQHDAdKZXJpY2hvMR4wHAYDVQQKDBVOZWN0YXIgU2VydmljZXMgQ29ycC4xCzAJ
# BgNVBAsMAklUMR4wHAYDVQQDDBVOZWN0YXIgU2VydmljZXMgQ29ycC4wggGiMA0G
# CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCJXN3SkHk8zvqqnHkfyImA6vDVtGrO
# zTVO6nlzNe85eCoGRBk5ToZ+/uUwcFyjcxSV+jbR29y4O6azNBmwvEw96ukUVKUh
# J+0NIQoH2DJqtkp4v3EppsevtxxwarqC1fvMXMhz2NVBZS6moStIiFyeGTBgyR7P
# gdU+JRqiG1muq5QiSZWjCyBLN6DTDimz5YdX1nhc/64V/oT0g79tYmZm7UEw1rN8
# HZgk46Gezt3IIQGX22ng1nh/3vy1Q46T/8mCT3UANrd3l/jS0XUmYgW8Z91nYlrl
# iNH3nHbONGNFFN8WLPAakt3ITeGmqhZkHyyXmlxKlkLHiR8XewRHn7PpD/DT03x7
# ngKS5Ie0fUM1ZAdXDoghvQ6uNuQ5Q3TAjL2ukJs9u5VmvWyFL1l9ujuKCiNGfy1D
# cS7u1WlcCIXdrX4Hpe2lt/M7fZFkSMeS1TD2gM2+a/7xK5MWwmbV6qK27nKlRpbG
# Q6Yj0VmqJmcgekSrCKPFudNAsDyD6rUYxlUCAwEAAaOCAZUwggGRMAwGA1UdEwEB
# /wQCMAAwHwYDVR0jBBgwFoAUVML+EJUAk81q9efA19myS7iPDOMwegYIKwYBBQUH
# AQEEbjBsMEgGCCsGAQUFBzAChjxodHRwOi8vY2VydC5zc2wuY29tL1NTTGNvbS1T
# dWJDQS1Db2RlU2lnbmluZy1SU0EtNDA5Ni1SMS5jZXIwIAYIKwYBBQUHMAGGFGh0
# dHA6Ly9vY3Nwcy5zc2wuY29tMFEGA1UdIARKMEgwCAYGZ4EMAQQBMDwGDCsGAQQB
# gqkwAQMDATAsMCoGCCsGAQUFBwIBFh5odHRwczovL3d3dy5zc2wuY29tL3JlcG9z
# aXRvcnkwEwYDVR0lBAwwCgYIKwYBBQUHAwMwTQYDVR0fBEYwRDBCoECgPoY8aHR0
# cDovL2NybHMuc3NsLmNvbS9TU0xjb20tU3ViQ0EtQ29kZVNpZ25pbmctUlNBLTQw
# OTYtUjEuY3JsMB0GA1UdDgQWBBQoc8kxtmxEx1bpEE+bhb8JNyGrHTAOBgNVHQ8B
# Af8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBAD1Wlyoz/oOVnzXFQKuv29SQ9J6G
# lqSNMvPzF6204eydcEDLwKh5IpZ7iGo6Km5AoVGNzYwJWyWaTwZpaxOoh+M+NK+E
# /QIOH+mItAaHhKogn96pHL7ZG9oD23s+/EP9jqNpenOZ0HSBWNHc4PO0e7Ys5zyQ
# 0a63PF83kU6hqkny0hca7Mr4gZoFyt6ynEJvv/TIkNCE5tqo/cBW94EpjQBfXTd4
# ycDoQ5Be1gbLboBoEjyrnPl0n+5dQgXSCg25hgf37iTfg+sCXzw3SKeiFauf4s8o
# h3wyXvNGzW4q0ZoE+ZNInk/puRVGnGmGZUr6iKuplqey1vneOOAv5we2hryR8PaA
# eqo6OqxyEw6a+nmRwf3lsp9k6Aml6ary1bdGjRY+ysScAxbeCvC9yS/EzwtaU3Z8
# R+2qLqh7cX8gztflHxrsV7Ql0nE3MrM0ry2bAWYzthbdwYtIuoeGHibng/qxuHOI
# uRLSTzddpXDdLSmw+G116kaxxHCBM+wf6K0nvehm08pXNyzAolNIJrVjsll9YHDT
# wws15kpojCglZsMIDYAlrFzF9MI0dRdiaj7/ttGSdblKZJoDOdfPrWMuBlSmjamk
# 5VxgKKHPQ9mHTP1Q5baEuotRYqJLgTWV1ZzCF+MYH2vmmuyjm4sSEjVfetLaYwIk
# RYAx7I+e9wcfhciSMYIR0DCCEcwCAQEwgYwweDELMAkGA1UEBhMCVVMxDjAMBgNV
# BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMREwDwYDVQQKDAhTU0wgQ29ycDE0
# MDIGA1UEAwwrU1NMLmNvbSBDb2RlIFNpZ25pbmcgSW50ZXJtZWRpYXRlIENBIFJT
# QSBSMQIQIc+kRkn+AQupTThE+j58IjANBglghkgBZQMEAgEFAKB8MBAGCisGAQQB
# gjcCAQwxAjAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcC
# AQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCBtAerrAGnFMIEKAUfk
# zTediDIFYQan0LXmY/0PKAxM3zANBgkqhkiG9w0BAQEFAASCAYCGCuApnGQ10aEY
# VFhAnHQwM6wBSJ4+Ykk0J9mnczyLpTvJkJzj0HGuHs2ZjmYU+bCPG08QZzE7IlxX
# WKGnB+CzU1sxq5A9FOh0iM1fQVZOvMGgOTHlJQ2v7vplBvOYXqfyMQmjaRKByFmX
# gqcntS6jOoztCWTySOM8OCRMVGEkPNnaRFjJJ9/938OiF42GtSZZn4s0OUY3rjAl
# NyabMy2vQBANCJOSYsCGgjkIkvogSxarbqiC67cKYSB6R0Eyce5c7qZwrsIwDQDH
# Gs0tSYGvTUVLNUskjotD8TIUHCxARghTlgPAif3h5utfln8F/TBkNBD30A96bZ7e
# /V1hNZiNAJJzl0yx9bZNk5QrOcYF+6/8LOMmBb+ryvrK3Q7vTrIPrluHTHupnYRD
# q8xZD1bA23G9cB74SsoIv29uIDlq5bgiA3BkFW3RkhC0UvBihFEJN/6/Q2XUMGFH
# t/LAytePOOa6CPdluuKYgci8wrEMgqY+8skGlY+FLGpybfwIljahgg8WMIIPEgYK
# KwYBBAGCNwMDATGCDwIwgg7+BgkqhkiG9w0BBwKggg7vMIIO6wIBAzENMAsGCWCG
# SAFlAwQCATB3BgsqhkiG9w0BCRABBKBoBGYwZAIBAQYMKwYBBAGCqTABAwYBMDEw
# DQYJYIZIAWUDBAIBBQAEIMNX2MxkPYmQI67tWwV4kyn9rYrNz06kG5Z/58y8uGQA
# AghR04K0O98sWRgPMjAyNjA2MDMxNDA5NTRaMAMCAQGgggwAMIIE/DCCAuSgAwIB
# AgIQH2sWYtIuG2xd8cDBoGAOODANBgkqhkiG9w0BAQsFADBzMQswCQYDVQQGEwJV
# UzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xETAPBgNVBAoMCFNT
# TCBDb3JwMS8wLQYDVQQDDCZTU0wuY29tIFRpbWVzdGFtcGluZyBJc3N1aW5nIFJT
# QSBDQSBSMTAeFw0yNTAyMTgxNjMyMDJaFw0zNDExMTIxODUwMDVaMG4xCzAJBgNV
# BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UE
# CgwIU1NMIENvcnAxKjAoBgNVBAMMIVNTTC5jb20gVGltZXN0YW1waW5nIFVuaXQg
# MjAyNSBFMTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBv7UVHHb+ZVluxPXlfE
# 3M6tg0Xnq8dic+O3vPOCRpalUM1vO9A+GRzSVVjyygHhYrBw62XLFh1kv7e+yRd/
# aTajggFaMIIBVjAfBgNVHSMEGDAWgBQMnRAljpqnG5mHQ88IfuG9gZD0zzBRBggr
# BgEFBQcBAQRFMEMwQQYIKwYBBQUHMAKGNWh0dHA6Ly9jZXJ0LnNzbC5jb20vU1NM
# LmNvbS10aW1lU3RhbXBpbmctSS1SU0EtUjEuY2VyMFEGA1UdIARKMEgwPAYMKwYB
# BAGCqTABAwYBMCwwKgYIKwYBBQUHAgEWHmh0dHBzOi8vd3d3LnNzbC5jb20vcmVw
# b3NpdG9yeTAIBgZngQwBBAIwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwRgYDVR0f
# BD8wPTA7oDmgN4Y1aHR0cDovL2NybHMuc3NsLmNvbS9TU0wuY29tLXRpbWVTdGFt
# cGluZy1JLVJTQS1SMS5jcmwwHQYDVR0OBBYEFM582cAEgMUkEGoJ6hyrJT0R/ajS
# MA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEAgHN1JIW9ZlNip07F
# fY9r1LJwLKTl1h/y9r1BjDgHMEz7U7J7mAy32JxA1KLyjCCJqNkuRfhDHGFEfZtj
# TOru7AQMDSDiUDvCCc3jaz5py9jGvwIe5npkLbXqXhA+SqCzG719USigwhHkYr7v
# 8NiXoaY0Xu03p0BiZZreZlpngmGB9+86N94FqIFx2I5Z5iuu7d4j7aAOZBNxfU9j
# FDIIGDtWfWeCpF0Q8rLE/3DHDlzJKs5Il3i/VoOl7rTU938oSrxMwww6GDBlKX8r
# GHxvkjgIb9MXkVglJSDHsNAZDeCS/inaxKHA+ChuB8K4OxIxa6k1e+eHBypakAY3
# wuN7w7PlPmhN6k5IdqZn3HZHs2VjoR841Z9wmVPFdGDdkBZ7XOmb5OZUaKufsvtE
# R0VbY2DkzaqAaItt1kc+I+FUz7PiVz2PUzpegkftRRxvrPyIL08blJTnbMQ0XrqD
# N0rEfuTLv4XhhnKyJaXhxCUdjO/cRumMzZ35QTUMItBWy1xGp0iyoFVnAya2pPsV
# MuAI+sy1zxlOS9l5iSxvKJ8gpnpOgjqYTa7u1eyZo+4JlcGoiiR17LthMTrF1q62
# tO5xOpI8txUZ6gtKGtkJV6wdj+vZLcwKZ2M93GqVpOo6UBlGjx1sKsVZ7AIUzJZl
# i4NJ0M2KYcrfNnlebkYKpDnSqWwwggb8MIIE5KADAgECAhBtUhhwh+gjTYVgANCA
# j5NWMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhh
# czEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTEw
# LwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNB
# MB4XDTE5MTExMzE4NTAwNVoXDTM0MTExMjE4NTAwNVowczELMAkGA1UEBhMCVVMx
# DjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMREwDwYDVQQKDAhTU0wg
# Q29ycDEvMC0GA1UEAwwmU1NMLmNvbSBUaW1lc3RhbXBpbmcgSXNzdWluZyBSU0Eg
# Q0EgUjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCuURAT0vk8IKAg
# hd7JUBxkyeH9xek0/wp/MUjoclrFXqhh/fGH91Fc+7fm0MHCE7A+wmOiqBj9ODrJ
# AYGq3rm33jCnHSsCBNWAQYyoauLq8IjqsS1JlXL29qDNMMdwZ8UNzQS7vWZMDJ40
# JSGNphMGTIA2qn2bohGtgRc4p1395ESypUOaGvJ3t0FNL3BuKmb6YctMcQUF2sqo
# oMzd89h0E6ujdvBDo6ZwNnWoxj7YmfWjSXg33A5GuY9ym4QZM5OEVgo8ebz/B+gy
# hyCLNNhh4Mb/4xvCTCMVmNYrBviGgdPZYrym8Zb84TQCmSuX0JlLLa6WK1aO6qlw
# ISbb9bVGh866ekKblC/XRP20gAu1CjvcYciUgNTrGFg8f8AJgQPOCc1/CCdaJSYw
# hJpSdheKOnQgESgNmYZPhFOC6IKaMAUXk5U1tjTcFCgFvvArXtK4azAWUOO1Y3fd
# ldIBL6LjkzLUCYJNkFXqhsBVcPMuB0nUDWvLJfPimstjJ8lF4S6ECxWnlWi7OElV
# wTnt1GtRqeY9ydvvGLntU+FecK7DbqHDUd366UreMkSBtzevAc9aqoZPnjVMjvFq
# V1pYOjzmTiVHZtAc80bAfFe5LLfJzPI6DntNyqobpwTevQpHqPDN9qqNO83r3kaw
# 8A9j+HZiSw2AX5cGdQP0kG0vhzfgBwIDAQABo4IBgTCCAX0wEgYDVR0TAQH/BAgw
# BgEB/wIBADAfBgNVHSMEGDAWgBTdBAkHovV6fVJTEpKV7jiAJQ2mWTCBgwYIKwYB
# BQUHAQEEdzB1MFEGCCsGAQUFBzAChkVodHRwOi8vd3d3LnNzbC5jb20vcmVwb3Np
# dG9yeS9TU0xjb21Sb290Q2VydGlmaWNhdGlvbkF1dGhvcml0eVJTQS5jcnQwIAYI
# KwYBBQUHMAGGFGh0dHA6Ly9vY3Nwcy5zc2wuY29tMD8GA1UdIAQ4MDYwNAYEVR0g
# ADAsMCoGCCsGAQUFBwIBFh5odHRwczovL3d3dy5zc2wuY29tL3JlcG9zaXRvcnkw
# EwYDVR0lBAwwCgYIKwYBBQUHAwgwOwYDVR0fBDQwMjAwoC6gLIYqaHR0cDovL2Ny
# bHMuc3NsLmNvbS9zc2wuY29tLXJzYS1Sb290Q0EuY3JsMB0GA1UdDgQWBBQMnRAl
# jpqnG5mHQ88IfuG9gZD0zzAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD
# ggIBAJIZdQ2mWkLPGQfZ8vyU+sCb8BXpRJZaL3Ez3VDlE3uZk3cPxPtybVfLuqac
# i0W6SB22JTMttCiQMnIVOsXWnIuAbD/aFTcUkTLBI3xys+wEajzXaXJYWACDS47B
# RjDtYlDW14gLJxf8W6DQoH3jHDGGy8kGJFOlDKG7/YrK7UGfHtBAEDVe6lyZ+FtC
# srk7dD/IiL/+Q3Q6SFASJLQ2XI89ihFugdYL77CiDNXrI2MFspQGswXEAGpHuaQD
# THUp/LdR3TyrIsLlnzoLskUGswF/KF8+kpWUiKJNC4rPWtNrxlbXYRGgdEdx8SMj
# UTDClldcrknlFxbqHsVmr9xkT2QtFmG+dEq1v5fsIK0vHaHrWjMMmaJ9i+4qGJSD
# 0stYfQ6v0PddT7EpGxGd867Ada6FZyHwbuQSadMb0K0P0OC2r7rwqBUe0BaMqTa6
# LWzWItgBjGcObXeMxmbQqlEz2YtAcErkZvh0WABDDE4U8GyV/32FdaAvJgTfe9Mi
# L2nSBioYe/g5mHUSWAay/Ip1RQmQCvmF9sNfqlhJwkjy/1U1ibUkTIUBX3HgymyQ
# vqQTZLLys6pL2tCdWcjI9YuLw30rgZm8+K387L7ycUvqrmQ3ZJlujHl3r1hgV76s
# 3WwMPgKk1bAEFMj+rRXimSC+Ev30hXZdqyMdl/il5Ksd0vhGMYICWDCCAlQCAQEw
# gYcwczELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3Vz
# dG9uMREwDwYDVQQKDAhTU0wgQ29ycDEvMC0GA1UEAwwmU1NMLmNvbSBUaW1lc3Rh
# bXBpbmcgSXNzdWluZyBSU0EgQ0EgUjECEB9rFmLSLhtsXfHAwaBgDjgwCwYJYIZI
# AWUDBAIBoIIBYTAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwHAYJKoZIhvcN
# AQkFMQ8XDTI2MDYwMzE0MDk1NFowKAYJKoZIhvcNAQk0MRswGTALBglghkgBZQME
# AgGhCgYIKoZIzj0EAwIwLwYJKoZIhvcNAQkEMSIEIHgVmO746c0Cd5nLGGhKLfVj
# CxeWfxAdtKfgnT4tLlhrMIHJBgsqhkiG9w0BCRACLzGBuTCBtjCBszCBsAQgVCr5
# oWqNci5mEUl4iumUwYqaruWmXLNEolSa+Wx5x4swgYswd6R1MHMxCzAJBgNVBAYT
# AlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwI
# U1NMIENvcnAxLzAtBgNVBAMMJlNTTC5jb20gVGltZXN0YW1waW5nIElzc3Vpbmcg
# UlNBIENBIFIxAhAfaxZi0i4bbF3xwMGgYA44MAoGCCqGSM49BAMCBEcwRQIhAJZm
# vWNXnUupX+3j16tpdY5p4QSrcZ3r6jiPfUICmvOZAiAJbAIPnn/iCt1NSKIf4zrI
# UTxv/FLkehuLMHvSH5MxFg==
# SIG # End signature block