Nectar10.psm1
<#
These series of PowerShell functions allow administrators to automate many functions in Nectar DXP and the Endpoint Client Controller that are otherwise difficult or time-consuming to perform in the UI. To install, follow the instructions at https://support.nectarcorp.com/docs/powershell If you want help with a particular command, type Get-Help <commandname> -Full (ie. Get-Help Connect-NectarCloud -Full). Full documentation is available at https://support.nectarcorp.com/docs/powershell #> ################################################################################################################################################# ################################################################################################################################################# ## ## ## Nectar DXP Functions ## ## ## ################################################################################################################################################# ################################################################################################################################################# ################################################################################################################################################# # # # Tenant Connection Functions # # # ################################################################################################################################################# Function Connect-NectarCloud { <# .SYNOPSIS Connects to Nectar DXP cloud and store the credentials for later use. .DESCRIPTION Connects to Nectar DXP cloud and store the credentials for later use. .PARAMETER CloudFQDN The FQDN of the Nectar DXP cloud. .PARAMETER TenantName The name of a Nectar DXP cloud tenant to connect to and use for subsequent commands. Only useful for multi-tenant deployments .PARAMETER Credential The credentials used to access the Nectar DXP UI. Normally in username@domain.com format .PARAMETER CredSecret Use stored credentials saved via Set-Secret. Requires prior installation of Microsoft.PowerShell.SecretManagement PS module and an appropriate secret vault, such as Microsoft.PowerShell.SecretStore. Locally, the Microsoft.PowerShell.SecretStore can be used to store secrets securely on the local machine. This is the minimum requirement for using this feature. Install the modules by running: Install-Module Microsoft.PowerShell.SecretManagement Install-Module Microsoft.PowerShell.SecretStore Register a credential secret by doing something like: Set-Secret -Name NectarCreds -Vault SecretStore -Secret (Get-Credential) .PARAMETER EnvFromFile Use a CSV file called N10EnvList.csv located in the user's default Documents folder to show a list of environments to select from. Run [Environment]::GetFolderPath("MyDocuments") to find your default document folder. This parameter is only available if N10EnvList.csv is found in the user's default Documents folder (ie: C:\Users\username\Documents) Also sets the default credentials to use for the selected environment. This feature uses the Microsoft.PowerShell.SecretManagement PS module, which must be installed and configured with a secret store prior to using this option. N10EnvList.csv must have a header with three columns defined as "Environment, DefaultTenant, Secret". Each environment and Secret (if used) should be on their own separate lines .PARAMETER UseToken Use a JWT (JSON web token) to connect to Nectar DXP instead of using credentials. This feature uses the Microsoft.PowerShell.SecretManagement PS module, which must be installed and configured with a secret store prior to using this option. The PS SecretManagement module can use any number of 3rd party secret stores that provide access to centralized secret management tools, such as Keeper and AWS Secrets. Locally, the Microsoft.PowerShell.SecretStore can be used to store secrets securely on the local machine. This is the minimum requirement for using this feature. Install the modules by running: Install-Module Microsoft.PowerShell.SecretManagement Install-Module Microsoft.PowerShell.SecretStore When -UseToken is selected, the function will check for a secret called <envname>-accesstoken (ie contoso.nectar.services-accesstoken). The secret must contain two fields called AccessToken and RefreshToken and must be writable. The token itself can be generated in the Nectar DXP UI or via New-NectarToken (when logged in with a local account). The New-NectarTokenRegistration can be used to generate a token using the default secret store (if supported by the secret store). If using the default Microsoft SecretStore, you can generate a token and save it as a secret on the local machine by running: New-NectarToken -TokenName <tokenname> | New-NectarTokenRegistration -CloudFQDN <NectarDXPFQDN> ie. New-NectarToken -TokenName laptop | New-NectarTokenRegistration -CloudFQDN contoso.nectar.services .PARAMETER TokenIdentifier An optional unique identifier (such as script name or username) to use when retreiving secrets from a secret store shared by multiple parties/scripts .EXAMPLE $Cred = Get-Credential Connect-NectarCloud -Credential $cred -CloudFQDN contoso.nectar.services Connects to the contoso.nectar.services Nectar DXP cloud using the credentials supplied to the Get-Credential command .EXAMPLE Connect-NectarCloud -CloudFQDN contoso.nectar.services -CredSecret MyNectarCreds Connects to contoso.nectar.services Nectar DXP cloud using previously stored secret called MyNectarCreds .EXAMPLE Connect-NectarCloud -CloudFQDN contoso.nectar.services -UseToken Connects to contoso.nectar.services Nectar DXP cloud using previously stored token stored in a Microsoft Secret Vault called contoso.nectar.services-accesstoken .NOTES Version 2.0 #> [Alias("cnc")] Param ( [Parameter(ValueFromPipeline, Mandatory=$False)] [ValidateScript ({ If ($_ -Match "^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$") { $True } Else { Throw "ERROR: Nectar DXP cloud name must be in FQDN format." } })] [string]$CloudFQDN, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.Credential()] [PSCredential]$Credential, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$CredSecret, [Parameter(Mandatory=$False)] [switch]$UseToken ) DynamicParam { $DefaultDocPath = [Environment]::GetFolderPath("MyDocuments") $EnvPath = "$DefaultDocPath\N10EnvList.csv" If (Test-Path $EnvPath -PathType Leaf) { # Set the dynamic parameters' name $ParameterName = 'EnvFromFile' # Create the dictionary $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary # Create the collection of attributes $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] # Create and set the parameters' attributes $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute $ParameterAttribute.Mandatory = $False $ParameterAttribute.Position = 1 # Add the attributes to the attributes collection $AttributeCollection.Add($ParameterAttribute) # Generate and set the ValidateSet $EnvSet = Import-Csv -Path $EnvPath $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($EnvSet.Environment) # Add the ValidateSet to the attributes collection $AttributeCollection.Add($ValidateSetAttribute) # Create and return the dynamic parameter $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection) $RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter) Return $RuntimeParameterDictionary } } Begin { # Bind the dynamic parameter to a friendly variable If (Test-Path $EnvPath -PathType Leaf) { If ($PsBoundParameters[$ParameterName]) { $CloudFQDN = $PsBoundParameters[$ParameterName] Write-Verbose "CloudFQDN: $CloudFQDN" # Get the array position of the selected environment $EnvPos = $EnvSet.Environment.IndexOf($CloudFQDN) # Check for default tenant in N10EnvList.csv and use if available, but don't override if user explicitly set the TenantName If (!$PsBoundParameters['TenantName']) { $TenantName = $EnvSet[$EnvPos].DefaultTenant Write-Verbose "DefaultTenant: $TenantName" } # Check for stored credential target in N10EnvList.csv and use if available $CredSecret = $EnvSet[$EnvPos].CredSecret Write-Verbose "Secret: $CredSecret" } } } Process { # Need to force TLS 1.2, if not already set If ([Net.ServicePointManager]::SecurityProtocol -ne 'Tls12') { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } # Ask for the tenant name if global Nectar tenant variable not available and not entered on command line If ((-not $Global:NectarCloud) -And (-not $CloudFQDN)) { $CloudFQDN = Read-Host "Enter the Nectar DXP cloud FQDN" $RegEx = "^(?:http(s)?:\/\/)?([\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:?#[\]@!\$&'\(\)\*\+,;=.]+)" $FQDNMatch = Select-String -Pattern $Regex -InputObject $CloudFQDN $CloudFQDN = $FQDNMatch.Matches.Groups[2].Value } ElseIf (($Global:NectarCloud) -And (-not $CloudFQDN)) { $CloudFQDN = $Global:NectarCloud } # Ask for credentials if global Nectar creds aren't available If (((-not $Global:NectarCred) -And (-not $Credential)) -Or (($Global:NectarCloud -ne $CloudFQDN) -And (-Not $Credential)) -And (-Not $CredSecret) -And (-Not $UseToken) -And (-Not $Global:NectarSecretName)) { $Credential = Get-Credential } ElseIf ($Global:NectarCred -And (-not $Credential)) { $Credential = $Global:NectarCred } # Set the token name based on the inputs If ($TokenIdentifier) { $NectarSecretName = "$($CloudFQDN)-$($TokenIdentifier)-accesstoken" } ElseIf ($UseToken) { $NectarSecretName = "$($CloudFQDN)-accesstoken" } # Check secret store for stored token If ($Global:NectarSecretName -ne $NectarSecretName -And $UseToken) { Try { $Global:NectarDefaultVault = (Get-SecretVault | Where-Object {$_.IsDefault -eq $True}).Name $Global:NectarToken = Get-Secret -Name $NectarSecretName -AsPlainText -ErrorAction SilentlyContinue #Stop # Temporary fix for Keeper module not working with secret name properly (see: https://github.com/Keeper-Security/secrets-manager/issues/627) If (-Not $Global:NectarToken) { $KeeperSecret = Get-SecretInfo | Where-Object {$_.Name -match " $NectarSecretName"} If (-Not $KeeperSecret) { Throw "Could not find access token for $CloudFQDN."; Return } $Match = Select-String -Pattern '^\S+' -InputObject $KeeperSecret.Name $NectarSecretName = $Match.Matches.Groups[0].Value $Global:NectarToken = Get-Secret -Name $NectarSecretName -AsPlainText -ErrorAction Stop } $Global:NectarSecretName = $NectarSecretName } Catch { Throw "Could not find access token for $CloudFQDN. Run New-NectarTokenRegistration to create one." Return } } # Pull stored credentials if specified If ($CredSecret) { Try { $Credential = Get-Secret $CredSecret } Catch { Throw "Cannot find secret: $CredSecret" } } # Only run on first execution of Connect-NectarCloud or if the CloudFQDN has changed If ((-Not $Global:NectarCred -And -Not $Global:NectarSecretName) -Or (-Not $Global:NectarCloud) -Or ($Global:NectarCloud -ne $CloudFQDN)) { # Check and notify if updated Nectar PS module available [string]$InstalledNectarPSVer = (Get-InstalledModule -Name Nectar10 -ErrorAction SilentlyContinue).Version If ($InstalledNectarPSVer -gt 0) { [string]$LatestNectarPSVer = (Find-Module Nectar10).Version If ($LatestNectarPSVer -gt $InstalledNectarPSVer) { Write-Host "=============== Nectar PowerShell module version $LatestNectarPSVer available ===============" -ForegroundColor Yellow Write-Host "You are running version $InstalledNectarPSVer. Type " -ForegroundColor Yellow -NoNewLine Write-Host 'Update-Module Nectar10' -ForegroundColor Green -NoNewLine Write-Host ' to update. NOTE: Close and reopen the PowerShell window for any module update to take effect.' -ForegroundColor Yellow } } # Create authorization header If ($UseToken) { # -Or $Global:NectarSecretName) { # Refresh token and create auth header Try { $RefreshHeaders = @{ 'x-refresh-token' = $Global:NectarToken.RefreshToken 'authorization' = "Bearer $($Global:NectarToken.AccessToken)" } Write-Verbose 'Refreshing token' $WebRequest = Invoke-WebRequest -Uri "https://$CloudFQDN/aapi/jwt/token/renew" -Method POST -Headers $RefreshHeaders -UseBasicParsing $Global:NectarToken.AccessToken = ($WebRequest.Content | ConvertFrom-JSON).AccessToken $Global:NectarTokenRefreshTime = Get-Date If ($Global:NectarDefaultVault -eq 'Keeper') { Set-Secret -Name "$($Global:NectarSecretName).AccessToken" -Secret $Global:NectarToken.AccessToken } Else { Set-Secret -Name $Global:NectarSecretName -Secret $Global:NectarToken } $Headers = @{ 'Authorization' = "Bearer $($Global:NectarToken.AccessToken)" } $ConnectionString = $NectarSecretName # Remove credential global variables Remove-Variable NectarCred -Scope Global -ErrorAction:SilentlyContinue } Catch { Throw "Could not refresh token for $($Global:NectarSecretName). Check that the token exists." } } Else { # Create basic auth header $UserName = $Credential.UserName $Password = $Credential.GetNetworkCredential().Password $Base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($UserName):$($Password)")) $Headers = @{Authorization = "Basic $Base64AuthInfo"} $ConnectionString = $Credential.UserName } # Attempt connection to tenant $URI = "https://$CloudFQDN/dapi/info/network/types" Write-Verbose $URI $WebRequest = Invoke-WebRequest -Uri $URI -Method GET -Headers $Headers -UseBasicParsing -SessionVariable NectarSession If ($WebRequest.StatusCode -ne 200) { Throw "Could not connect to $CloudFQDN using $ConnectionString" } Else { Write-Host -ForegroundColor Green "Successful connection to " -NoNewLine Write-Host -ForegroundColor Yellow "https://$CloudFQDN" -NoNewLine Write-Host -ForegroundColor Green " using " -NoNewLine Write-Host -ForegroundColor Yellow $ConnectionString $Global:NectarCloud = $CloudFQDN $Global:NectarCred = $Credential $Global:NectarAuthHeader = $Headers $UserInfo = Invoke-RestMethod -Uri "https://$Global:NectarCloud/adminapi/user" -Method GET -Headers $Global:NectarAuthHeader -WebSession $NectarSession # If there is only one available tenant, assign that to the NectarTenantName global variable $Global:NectarTenantList = $UserInfo.tenants.tenant If ($Global:NectarTenantList.Count -eq 1) { $Global:NectarTenantName = $Global:NectarTenantList } } } # If token was used, check the last refresh time and update it if its more than 90 minutes old If ($Global:NectarToken -And (New-TimeSpan -Start $Global:NectarTokenRefreshTime -End (Get-Date)).TotalMinutes -gt 90) { $RefreshHeaders = @{ 'x-refresh-token' = $Global:NectarToken.RefreshToken 'authorization' = "Bearer $($Global:NectarToken.AccessToken)" } Write-Verbose 'Refreshing token' $WebRequest = Invoke-WebRequest -Uri "https://$($Global:NectarCloud)/aapi/jwt/token/renew" -Method POST -Headers $RefreshHeaders -UseBasicParsing -SessionVariable NectarSession $Global:NectarToken.AccessToken = ($WebRequest.Content | ConvertFrom-JSON).AccessToken $Global:NectarTokenRefreshTime = Get-Date If ($Global:NectarDefaultVault -eq 'Keeper') { Set-Secret -Name "$($Global:NectarSecretName).AccessToken" -Secret $Global:NectarToken.AccessToken } Else { Set-Secret -Name $Global:NectarSecretName -Secret $Global:NectarToken } $Headers = @{ 'authorization' = "Bearer $($Global:NectarToken.AccessToken)" } $Global:NectarAuthHeader = $Headers } # Check to see if tenant name was entered and set global variable, if valid. If ($TenantName) { Try { If ($Global:NectarTenantList -Contains $TenantName) { $Global:NectarTenantName = $TenantName.ToLower() Write-Host -ForegroundColor Green "Successsfully set the tenant name to " -NoNewLine Write-Host -ForegroundColor Yellow "$TenantName" -NoNewLine Write-Host -ForegroundColor Green ". This tenantname will be used in all subsequent commands." } Else { $Global:NectarTenantList | ForEach-Object{ $TList += ($(if($TList){", "}) + $_) } Write-Error "Could not find a tenant with the name $TenantName on https://$Global:NectarCloud. Select one of $TList" } } Catch { # Just set the tenant name if we are unable to validate the tenant name. $Global:NectarTenantName = $TenantName Write-Host -ForegroundColor Green "Set the tenant name to " -NoNewLine Write-Host -ForegroundColor Yellow "$TenantName" -NoNewLine Write-Host -ForegroundColor Green " but unable to verify if the tenant exists. This tenantname will be used in all subsequent commands." } } ElseIf ($PSBoundParameters.ContainsKey('TenantName')) { # Remove the NectarTenantName global variable only if TenantName is explicitly set to NULL Remove-Variable NectarTenantName -Scope Global -ErrorAction:SilentlyContinue } } } Function Disconnect-NectarCloud { <# .SYNOPSIS Disconnects from any active Nectar DXP connection .DESCRIPTION Essentially deletes any stored credentials and FQDN from global variables .EXAMPLE Disconnect-NectarCloud Disconnects from all active connections to Nectar DXP tenants .NOTES Version 1.1 #> [Alias("dnc")] [cmdletbinding()] param () Remove-Variable NectarCred -Scope Global -ErrorAction:SilentlyContinue Remove-Variable NectarCloud -Scope Global -ErrorAction:SilentlyContinue Remove-Variable NectarTenantName -Scope Global -ErrorAction:SilentlyContinue Remove-Variable NectarTenantList -Scope Global -ErrorAction:SilentlyContinue Remove-Variable NectarAuthHeader -Scope Global -ErrorAction:SilentlyContinue Remove-Variable NectarSecretName -Scope Global -ErrorAction:SilentlyContinue Remove-Variable NectarToken -Scope Global -ErrorAction:SilentlyContinue Remove-Variable NectarTokenRefreshTime -Scope Global -ErrorAction:SilentlyContinue Remove-Variable NectarDefaultVault -Scope Global -ErrorAction:SilentlyContinue } Function Get-NectarToken { <# .SYNOPSIS Returns a list of all JWT tokens assigned to the logged in user .DESCRIPTION Returns a list of all JWT tokens assigned to the logged in user .EXAMPLE Get-NectarJWTToken .NOTES Version 1.0 #> [cmdletbinding()] param () Begin { Connect-NectarCloud } Process { Try { $URI = "https://$Global:NectarCloud/aapi/jwt/token" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader Return $JSON } Catch { } } } Function New-NectarToken { <# .SYNOPSIS Creates a new JSON web token (JWT) to be used in scripts .DESCRIPTION Creates a new JSON web token (JWT) to be used in scripts .PARAMETER TokenName A descriptive name for the token .EXAMPLE New-NectarToken -TokenName 'ScriptToken' .NOTES Version 1.0 #> [cmdletbinding()] param ( [Parameter(Mandatory=$True)] [string]$TokenName ) Begin { Connect-NectarCloud } Process { Try { $URI = "https://$Global:NectarCloud/aapi/jwt/token?tokenName=$TokenName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method POST -uri $URI -Headers $Global:NectarAuthHeader Return $JSON } Catch { Write-Error "Could not create access token." } } } Function Update-NectarToken { <# .SYNOPSIS Refreshes an expired Nectar token .DESCRIPTION Refreshes the currently used Nectar token .EXAMPLE Update-NectarToken .NOTES Version 1.0 #> $RefreshHeaders = @{ 'x-refresh-token' = $Global:NectarToken.RefreshToken 'authorization' = "Bearer $($Global:NectarToken.AccessToken)" } Try { $Global:NectarToken.AccessToken = Invoke-RestMethod -Uri "https://$Global:NectarCloud/aapi/jwt/token/renew" -Method POST -Headers $RefreshHeaders $Headers = @{ 'authorization' = "Bearer $($Global:NectarToken.AccessToken)" } $Global:NectarAuthHeader = $Headers Set-Secret -Name "$($Global:NectarSecretName).AccessToken" -Secret $Global:NectarToken.AccessToken Write-Host -ForegroundColor Green "Successfully updated " -NoNewLine Write-Host -ForegroundColor Yellow $Global:NectarSecretName } Catch { Throw "Error refreshing $($Global:NectarSecretName)" } } Function Remove-NectarToken { <# .SYNOPSIS Remove a Nectar token .DESCRIPTION Remove a Nectar token .PARAMETER RefreshToken The GUID of a refresh token to remove. .EXAMPLE Remove-NectarToken -AccessToken fd173c75-891c-4357-b5a3-0855c2a56299 .EXAMPLE Get-NectarToken | Where-Object {$_.Name -eq 'Testing'} | Remove-NectarToken .NOTES Version 1.0 #> [cmdletbinding()] param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$RefreshToken ) Begin { Connect-NectarCloud } Process { $RefreshHeaders = $Global:NectarAuthHeader $RefreshHeaders['x-refresh-token'] = $RefreshToken Try { $URI = "https://$Global:NectarCloud/aapi/jwt/token/revoke" Write-Verbose $URI $JSON = Invoke-RestMethod -Method DELETE -uri $URI -Headers $RefreshHeaders Return "Total number of tokens removed: $JSON" } Catch { Write-Error "Could not delete access token with refresh token $($RefreshToken)." } } } Function New-NectarTokenRegistration { <# .SYNOPSIS Registers a Nectar DXP token for connecting to Nectar DXP using JWT .DESCRIPTION Registers a Nectar DXP token for connecting to Nectar DXP using JWT. One-time task required before attempting to access Nectar DXP APIs. Stored using Microsoft SecretManagement PS module. If SecretManagement PS module is is not installed, install via: Install-Module Microsoft.PowerShell.SecretManagement There are several PS modules that connect to different secret providers. Install the one appropriate for your situation prior to running this command. For example, the PS SecretStore stores secrets on the local machine and can be installed via: Install-Module Microsoft.PowerShell.SecretStore .PARAMETER CloudFQDN The FQDN of the Nectar DXP cloud. .PARAMETER Identifier An optional unique identifier (such as script name or username) to use when saving secrets to a secret store shared by multiple parties/scripts .PARAMETER AccessToken The access token to use for connecting to Nectar DXP .PARAMETER RefreshToken The refresh token used to refresh the Nectar DXP access token every 2 hours .PARAMETER SecretVault The name of the secret vault to install the secret. Use if installing to non-default secret vault .EXAMPLE Connect-NectarCloud -Credential $cred -CloudFQDN contoso.nectar.services Connects to the contoso.nectar.services Nectar DXP cloud using the credentials supplied to the Get-Credential command .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipeline, Mandatory=$True)] [ValidateScript ({ If ($_ -Match "^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$") { $True } Else { Throw "ERROR: Nectar DXP cloud name must be in FQDN format." } })] [string]$CloudFQDN, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$Identifier, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$AccessToken, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$RefreshToken, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$SecretVault ) Begin { # Verify the SecretManagement module is installed If (!(Get-InstalledModule -Name 'Microsoft.PowerShell.SecretManagement' -ErrorAction SilentlyContinue)) { Throw "SecretManagement module not installed. Please install using 'Install-Module Microsoft.PowerShell.SecretManagement'" } } Process { # Need to force TLS 1.2, if not already set If ([Net.ServicePointManager]::SecurityProtocol -ne 'Tls12') { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } # Make sure CloudFQDN does not have extraneous characters and just includes the FQDN $RegEx = "^(?:http(s)?:\/\/)?([\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:?#[\]@!\$&'\(\)\*\+,;=.]+)" $FQDNMatch = Select-String -Pattern $Regex -InputObject $CloudFQDN $CloudFQDN = $FQDNMatch.Matches.Groups[2].Value # Create hash table with token information $TokenData = @{ 'AccessToken' = $AccessToken 'RefreshToken' = $RefreshToken } If ($Identifier) { $SecretName = "$($CloudFQDN)-$($Identifier)-accesstoken" } Else { $SecretName = "$($CloudFQDN)-accesstoken" } $Params = @{ 'Name' = $SecretName 'Secret' = $TokenData } If ($SecretVault) {$Params.Add('Vault',$SecretVault)} Set-Secret @Params Write-Host "Successfully created $SecretName" } } Function Get-NectarCloudInfo { <# .SYNOPSIS Shows information about the active Nectar DXP connection .DESCRIPTION Shows information about the active Nectar DXP connection .EXAMPLE Get-NectarCloud .NOTES Version 1.1 #> [Alias("gnci")] [cmdletbinding()] param () $CloudInfo = "" | Select-Object -Property CloudFQDN, Credential $CloudInfo.CloudFQDN = $Global:NectarCloud $CloudInfo.Credential = ($Global:NectarCred).UserName $CloudInfo | Add-Member -TypeName 'Nectar.CloudInfo' Try { $TenantCount = Get-NectarTenantNames If ($TenantCount.Count -gt 1) { If ($Global:NectarTenantName) { $CloudInfo | Add-Member -NotePropertyName 'TenantName' -NotePropertyValue $Global:NectarTenantName } Else { $CloudInfo | Add-Member -NotePropertyName 'TenantName' -NotePropertyValue '<Not Set>' } } } Catch { } Return $CloudInfo } Function Get-NectarTenantNames { <# .SYNOPSIS Shows all the available Nectar tenants on the cloud host. .DESCRIPTION Shows all the available Nectar tenants on the cloud host. Only available for multi-tenant deployments and not visible to read-only admins. .EXAMPLE Get-NectarTenantNames .NOTES Version 1.0 #> [Alias("gntn")] [cmdletbinding()] param () Begin { Connect-NectarCloud } Process { Try { $URI = "https://$Global:NectarCloud/aapi/tenant" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader $TenantList = Foreach ($Item in $JSON) { $PSObject = New-Object PSObject -Property @{ TenantName = $Item } $PSObject } Return $TenantList } Catch { Write-Error 'No tenants found or insufficient permissions.' Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarTenantDetails { <# .SYNOPSIS Shows detailed information about a specific tenant .DESCRIPTION Shows detailed information about a specific tenant. Not visible to read-only admins .PARAMETER TenantName Return information for the specified tenant .EXAMPLE Get-NectarTenantDetails .NOTES Version 1.0 #> [Alias("gntn")] [cmdletbinding()] param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) Begin { Connect-NectarCloud } Process { Try { If (!$TenantName) { $TenantName = Get-NectarDefaultTenantName } $URI = "https://$Global:NectarCloud/aapi/clients/$($TenantName)?tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader Return $JSON } Catch { Write-Error 'Invalid tenantname or insufficient permissions.' Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarRIGAgent { <# .SYNOPSIS Return information about RIG agents connected to the environment .DESCRIPTION Return information about RIG agents connected to the environment .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-NectarRIGAgent Returns information about all RIG agents in the environment .EXAMPLE Get-NectarRIGAgent -SearchQuery contoso -OrderByField Returns information about gateways from all Cisco clusters .NOTES Version 1.0 #> Param ( [Parameter(Mandatory=$False)] [string]$SearchQuery, [Parameter(Mandatory=$False)] [ValidateSet('name', 'clientName', 'lastRequestDate', 'cpu', 'memory', 'disk', IgnoreCase=$True)] [string]$OrderByField = 'name', [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]$Name ) 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) If ($ResultSize) { $PageSize = $ResultSize } $Params = @{ 'pageNumber' = 1 'pageSize' = $PageSize 'orderByField' = $OrderByField 'orderDirection' = $OrderDirection } If ($SearchQuery) { $Params.Add('searchQuery', $SearchQuery) } $URI = "https://$Global:NectarCloud/aapi/tenant/agents" 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-NectarTenantMetrics { <# .SYNOPSIS Shows all the tenant sizing metrics on the platform .DESCRIPTION Shows all the tenant sizing metrics on the platform. Only available for global admins. .PARAMETER TenantName Return metric values for a single tenant .EXAMPLE Get-NectarTenantMetrics Returns all metrics for all tenants .EXAMPLE Get-NectarTenantMetrics -TenantName Contosu Returns all metrics for the Contoso tenant .EXAMPLE Get-NectarTenantMetrics | Where {$_.name -like '*USERS' -And $_.value -gt 0} | FT Returns all user counts for all tenants where the value is greater than 0 .NOTES Version 1.0 #> [Alias("gntm")] [cmdletbinding()] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) Begin { Connect-NectarCloud } Process { Try { # The JSON format in this API doesn't lend itself to manipulation in PS, so we have to fix it up $URI = "https://$Global:NectarCloud/aapi/tenant/datasource/metrics?lastOnly=true" Write-Verbose $URI $MetricListRaw = (Invoke-WebRequest -Method GET -uri $URI -Headers $Global:NectarAuthHeader).content $MetricListRawUpdated = $MetricListRaw -Replace '(\"[a-z_0-9]+\")\ \:\ \[\ \{', '{"tenantName" : $1, "data" : [ {' $MetricListRawUpdated = $MetricListRawUpdated -Replace '\{\s+\{', '[{' $MetricListRawUpdated = $MetricListRawUpdated -Replace '\]\,', ']},' $MetricListRawUpdated = $MetricListRawUpdated -Replace '\}\s+\]\s+\}', '}]}]' $MetricList = ConvertFrom-JSON $MetricListRawUpdated If ($TenantName) { Return ($MetricList | Where-Object {$_.TenantName -eq $TenantName}).data } Else { Return $MetricList } } Catch { Write-Error 'No tenants found or insufficient permissions.' Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarTenantMetricTimeline { <# .SYNOPSIS Shows a monthly timeline of specific metric values for a given tenant .DESCRIPTION Shows a monthly timeline of specific metric values for a given tenant. Only available for global admins. .PARAMETER TenantName The tenant to return metrics on .PARAMETER Metric The specified metric to return data on .EXAMPLE Get-NectarTenantMetricTimeline -Metric CISCO_USERS -TenantName Contoso Returns the daily CISCO_USERS count for the past month for the Contoso tenant .NOTES Version 1.0 #> [Alias("gntmt")] [cmdletbinding()] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$TenantName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias("name")] [string]$Metric ) Begin { Connect-NectarCloud } Process { Try { $URI = "https://$Global:NectarCloud/aapi/tenant/datasource/metrics" $Params = @{ lastOnly = 'false' name = $Metric periodCount = 1 tenant = $TenantName } $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader -Body $Params Return $JSON.$TenantName } Catch { Write-Error "Could not find tenant with name $TenantName or a matching metric called $Metric" Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarTenantLicenseCount { <# .SYNOPSIS Shows the current and historical license status for a given tenant .DESCRIPTION Shows the current and historical license status for a given tenant .EXAMPLE Get-NectarTenantLicenseCount .NOTES Version 1.0 #> [cmdletbinding()] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) Begin { Connect-NectarCloud } Process { Try { If (!$TenantName) { $TenantName = Get-NectarDefaultTenantName } $URI = "https://$Global:NectarCloud/aapi/client/license/user-counts?tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader Return $JSON } Catch { Write-Error 'No tenant license information found or insufficient permissions.' Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarTenantDatasources { <# .SYNOPSIS Shows all the datasources available on a given tenant. .DESCRIPTION Shows all the datasources available on a given tenant. .EXAMPLE Get-NectarTenantDatasources .NOTES Version 1.0 #> [Alias("gntdc")] [cmdletbinding()] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) Begin { Connect-NectarCloud } Process { Try { If (!$TenantName) { $TenantName = Get-NectarDefaultTenantName } $URI = "https://$Global:NectarCloud/aapi/tenant/datasources?tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader Return $JSON.data } Catch { Write-Error 'No tenant datasources found or insufficient permissions.' Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarTenantPlatforms { <# .SYNOPSIS Shows all the platforms available on a given tenant. .DESCRIPTION Shows all the platforms available on a given tenant. .EXAMPLE Get-NectarTenantPlatforms .NOTES Version 1.0 #> [Alias("gntp")] [cmdletbinding()] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) Begin { Connect-NectarCloud } Process { Try { If (!$TenantName) { $TenantName = Get-NectarDefaultTenantName } $URI = "https://$Global:NectarCloud/aapi/tenant/platforms?tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader ForEach ($Item in $JSON) { $PlatformList = [pscustomobject][ordered]@{ TenantName = $TenantName Platform = $Item.platform Supported = $Item.supported } $PlatformList } } Catch { Write-Error 'No tenant platforms found or insufficient permissions.' Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarTenantSSOConfig { <# .SYNOPSIS Shows the SSO config for a given tenant. .DESCRIPTION Shows the SSO config for a given tenant. .EXAMPLE Get-NectarTenantSSOConfig .NOTES Version 1.0 #> [Alias("gntsc")] [cmdletbinding()] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) Begin { Connect-NectarCloud } Process { Try { If (!$TenantName) { $TenantName = Get-NectarDefaultTenantName } $URI = "https://$Global:NectarCloud/aapi/client/sso-config?tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader Return $JSON ForEach ($Item in $JSON) { $PlatformList = [pscustomobject][ordered]@{ TenantName = $TenantName Platform = $Item.platform Supported = $Item.supported } $PlatformList } } Catch { Write-Error 'No tenant platforms found or insufficient permissions.' Get-JSONErrorStream -JSONResponse $_ } } } 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" Try { $JSON = Invoke-RestMethod -Method GET -Uri $URI -Headers $Global:NectarAuthHeader Return $JSON } Catch { Write-Error "Unable to obtain user's UI settings." 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 !$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 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 !$TenantName) { $TenantName = $Global:NectarTenantName } If ($TenantName) { $Params.Add('Tenant',$TenantName) } $URI = "https://$Global:NectarCloud/dapi/user/pinned" 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" 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 !$TenantName) { $TenantName = $Global:NectarTenantName } # 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" } 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" } } 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" } } } 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 !$TenantName) { $TenantName = $Global:NectarTenantName } # 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" } 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" 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' 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $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.' 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/aapi/client/domains?tenant=$TenantName" $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." 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 !$TenantName) { $TenantName = $Global:NectarTenantName } 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" 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." 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/aapi/users?searchQuery=$SearchQuery&tenant=$TenantName&pageSize=$ResultSize" 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." 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 !$TenantName) { $TenantName = $Global:NectarTenantName } # 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" $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." 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/aapi/user?tenant=$TenantName" $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." 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 !$TenantName) { $TenantName = $Global:NectarTenantName } 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" 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." Get-JSONErrorStream -JSONResponse $_ } } } } ################################################################################################################################################# # # # Tenant Location Functions # # # ################################################################################################################################################# Function Get-NectarLocation { <# .SYNOPSIS Returns a list of Nectar DXP locations .DESCRIPTION Returns a list of Nectar DXP locations .PARAMETER SearchQuery The name of the location to get information on based on either network, networkName, City, StreetAddress, State, SiteName or SiteCode. Can be a partial match, and may return more than one entry. .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-NectarLocation Returns the first 10 locations .EXAMPLE Get-NectarLocation -ResultSize 100 Returns the first 100 locations .EXAMPLE Get-NectarLocation -LocationName Location2 Returns up to 10 locations that contains "location2" anywhere in the name. The search is not case-sensitive. This example would return Location2, Location20, Location214, MyLocation299 etc .EXAMPLE Get-NectarLocation -LocationName ^Location2 Returns up to 10 locations that starts with "location2" in the name. The search is not case-sensitive. This example would return Location2, Location20, Location214 etc, but NOT MyLocation299 .EXAMPLE Get-NectarLocation -LocationName ^Location2$ Returns a location explicitly named "Location2". The search is not case-sensitive. .NOTES Version 1.1 #> [Alias("gnl")] Param ( [Parameter(Mandatory=$False)] [string]$SearchQuery, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [ValidateRange(1,100000)] [int]$ResultSize = 5000 ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/aapi/config/locations?pageNumber=1&tenant=$TenantName&pageSize=$ResultSize&searchQuery=$SearchQuery" $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader If (!$JSON.elements) { Write-Error "Location $SearchQuery not found." } Else { If ($TenantName) { $JSON.elements | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty } # Add the tenant name to the output which helps pipelining $JSON.elements | Add-Member -TypeName 'Nectar.LocationList' Return $JSON.elements } } Catch { Write-Error "Unable to get location details." Get-JSONErrorStream -JSONResponse $_ } } } Function Set-NectarLocation { <# .SYNOPSIS Update a Nectar DXP location in the location database .DESCRIPTION Update a Nectar DXP location in the location database. This command can use the Google Geocode API to automatically populate the latitude/longitude for each location. You can register for an API key and save it as persistent environment variable called GoogleGeocode_API_Key on this machine. This command will prompt for the GeoCode API key and will save it in the appropriate location. Follow this link to get an API Key - https://developers.google.com/maps/documentation/geocoding/get-api-key. If this is not an option, then use the -SkipGeoLocate switch .PARAMETER SearchQuery A string to search for. Will search in Network, NetworkName, City, Street Address, Region etc. .PARAMETER Network The IP subnet of the network .PARAMETER NetworkRange The subnet mask of the network .PARAMETER ExtNetwork The IP subnet of the external/public network. Optional. Used to help differentiate calls from corporate locations that use common home subnets (192.168.x.x) .PARAMETER ExtNetworkRange The subnet mask of the external/public network. Optional. Used to help differentiate calls from corporate locations that use common home subnets (192.168.x.x) .PARAMETER NetworkName The name to give to the network .PARAMETER SiteName The name to give to the siteCode .PARAMETER SiteCode A site code to assign to the site .PARAMETER Region The name of the region. Typically is set to country name or whatever is appropriate for the company .PARAMETER StreetAddress The street address of the location .PARAMETER City The city of the location .PARAMETER State The state/province of the location .PARAMETER PostCode The postal/zip code of the location .PARAMETER Country The 2-letter ISO country code of the location .PARAMETER Description A description to apply to the location .PARAMETER IsWireless True or false if the network is strictly wireless .PARAMETER IsExternal True or false if the network is outside the corporate network .PARAMETER IsVPN True or false if the network is a VPN .PARAMETER NetworkRangeType The type of network range. Choose from Sequential or Subnet .PARAMETER Latitude The geographical latitude of the location. If not specified, will attempt automatic geolocation. .PARAMETER Longitude The geographical longitude of the location. If not specified, will attempt automatic geolocation. .PARAMETER SkipGeoLocate Don't attempt geolocation. Do this if you don't have a valid Google Maps API key. .PARAMETER Identity The numerical ID of the location to update. Can be obtained via Get-NectarLocation and pipelined to Set-NectarLocation .EXAMPLE Set-NectarLocation HeadOffice -Region WestUS Changes the region for HeadOffice to WestUS .EXAMPLE Get-NectarLocation | Set-NectarLocation Will go through each location and update the latitude/longitude. Useful if a Google Geocode API key was obtained after initial location loading .NOTES Version 1.11 #> [Alias("snl")] Param ( [Parameter(Mandatory=$False)] [string]$SearchQuery, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias("subnet")] [string]$Network, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateRange(0,32)] [string]$NetworkRange, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$ExtNetwork, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateRange(0,32)] [string]$ExtNetworkRange, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateLength(1,99)] [Alias("Network Name")] [string]$NetworkName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$SiteName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias("Site Code")] [string]$SiteCode, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$Region, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias("address")] [string]$StreetAddress, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$City, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias("province")] [string]$State, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias("zipcode")] [string]$PostCode, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateLength(0,2)] [string]$Country, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$Description, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet("True","False","Yes","No",0,1, IgnoreCase=$True)] [Alias("isWirelessNetwork","Wireless(Yes/No)","Wireless","Wifi")] [string]$IsWireless, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet("True","False","Yes","No",0,1, IgnoreCase=$True)] [Alias("External(Yes/No)","External")] [string]$IsExternal, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet("True","False","Yes","No",0,1, IgnoreCase=$True)] [Alias("VPN","VPN(Yes/No)")] [string]$IsVPN, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('Sequential','Subnet', IgnoreCase=$True)] [string]$NetworkRangeType, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateRange(-90,90)] [Alias("CoordinatesLatitude")] [double]$Latitude, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateRange(-180,180)] [Alias("CoordinatesLongitude")] [double]$Longitude, [Parameter(Mandatory=$False)] [switch]$ForceGeoLocate, [Parameter(Mandatory=$False)] [switch]$SkipGeoLocate, [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 !$TenantName) { $TenantName = $Global:NectarTenantName } Try { If ($SearchQuery) { $LocationInfo = Get-NectarLocation -SearchQuery $SearchQuery -Tenant $TenantName -ResultSize 1 $Identity = $LocationInfo.id #.ToString() } If (-not $Network) {$Network = $LocationInfo.network} If (-not $NetworkRange) {$NetworkRange = $LocationInfo.networkRange} If (-not $ExtNetwork) {$ExtNetwork = $LocationInfo.externalNetwork} If (-not $ExtNetworkRange) {$ExtNetworkRange = $LocationInfo.externalNetworkRange} If (-not $NetworkRangeType) {$NetworkRangeType = $LocationInfo.networkRangeType} If (-not $NetworkName) {$NetworkName = $LocationInfo.networkName} If (-not $SiteName) {$SiteName = $LocationInfo.siteName} If (-not $SiteCode) {$SiteCode = $LocationInfo.siteCode} If (-not $Region) {$Region = $LocationInfo.region} If (-not $StreetAddress) {$StreetAddress = $LocationInfo.streetAddress} If (-not $City) {$City = $LocationInfo.city} If (-not $State) {$State = $LocationInfo.state} If (-not $PostCode) {$PostCode = $LocationInfo.zipCode} If (-not $Country) {$Country = $LocationInfo.country} If (-not $Description) {$Description = $LocationInfo.description} If (-not $IsWireless) {$IsWireless = $LocationInfo.isWirelessNetwork} If (-not $IsExternal) {$IsExternal = $LocationInfo.isExternal} If (-not $IsVPN) {$IsVPN = $LocationInfo.vpn} If ($NULL -eq $Latitude -or $Latitude -eq 0) {$Latitude = $LocationInfo.latitude} If ($NULL -eq $Longitude -or $Longitude -eq 0) {$Longitude = $LocationInfo.longitude} If (-not $IsVPN) {$IsVPN = $LocationInfo.vpn} If ((($NULL -eq $Latitude -Or $NULL -eq $Longitude) -Or ($Latitude -eq 0 -And $Longitude -eq 0)) -Or $ForceGeoLocate -And !$SkipGeoLocate) { Write-Verbose "Lat/Long missing. Getting Lat/Long." $LatLong = Get-LatLong "$StreetAddress, $City, $State, $PostCode, $Region" $Latitude = $LatLong.Latitude $Longitude = $LatLong.Longitude } $URI = "https://$Global:NectarCloud/aapi/config/location/$Identity/?tenant=$TenantName" $Body = @{ city = $City description = $Description id = $Identity isExternal = ParseBool $IsExternal isWirelessNetwork = ParseBool $IsWireless latitude = $Latitude longitude = $Longitude network = $Network networkRange = $NetworkRange externalNetwork = $ExtNetwork externalNetworkRange = $ExtNetworkRange networkName = $NetworkName networkRangeIpEnd = $NULL networkRangeIpStart = $NULL networkRangeType = $NetworkRangeType region = $Region siteCode = $SiteCode siteName = $SiteName state = $State streetAddress = $StreetAddress country = $Country vpn = ParseBool $IsVPN zipCode = $PostCode } Write-Verbose $Body $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 { If ($NetworkName) { $IDText = $NetworkName } Else { $IDText = "with ID $Identity" } Write-Error "Unable to apply changes for location $IDText." Get-JSONErrorStream -JSONResponse $_ } } Catch { Write-Error $_ } } } Function New-NectarLocation { <# .SYNOPSIS Creates a Nectar DXP location in the location database .DESCRIPTION Creates a Nectar DXP location in the location database. This command can use the Google Geocode API to automatically populate the latitude/longitude for each location. You can register for an API key and save it as persistent environment variable called GoogleGeocode_API_Key on this machine. This command will prompt for the GeoCode API key and will save it in the appropriate location. Follow this link to get an API Key - https://developers.google.com/maps/documentation/geocoding/get-api-key. If this is not an option, then use the -SkipGeoLocate switch .PARAMETER Network The IP subnet of the network .PARAMETER NetworkRange The subnet mask of the network .PARAMETER ExtNetwork The IP subnet of the external/public network. Optional. Used to help differentiate calls from corporate locations that use common home subnets (192.168.x.x) .PARAMETER ExtNetworkRange The subnet mask of the external/public network. Optional. Used to help differentiate calls from corporate locations that use common home subnets (192.168.x.x) .PARAMETER NetworkName The name to give to the network .PARAMETER SiteName The name to give to the site .PARAMETER SiteCode A site code to assign to the site .PARAMETER Region The name of the region. Typically is set to country name or whatever is appropriate for the company .PARAMETER StreetAddress The street address of the location .PARAMETER City The city of the location .PARAMETER State The state/province of the location .PARAMETER PostCode The postal/zip code of the location .PARAMETER Country The 2-letter ISO country code of the location .PARAMETER Description A description to apply to the location .PARAMETER IsWireless True or false if the network is strictly wireless .PARAMETER IsExternal True or false if the network is outside the corporate network .PARAMETER IsVPN True or false if the network is a VPN .PARAMETER NetworkRangeType The type of network range. Choose from Sequential, Subnet or Virtual .PARAMETER SkipGeoLocate Don't attempt geolocation. Do this if you don't have a valid Google Maps API key. .PARAMETER Latitude The geographical latitude of the location. If not specified, will attempt automatic geolocation. .PARAMETER Longitude The geographical longitude of the location. If not specified, will attempt automatic geolocation. .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE New-NectarLocation -Network 10.14.3.0 -NetworkRange 24 -NetworkName Corp5thFloor -SiteName 'Head Office' Creates a new location using the minimum required information .EXAMPLE New-NectarLocation -Network 10.15.1.0 -NetworkRange 24 -ExtNetwork 79.23.155.71 -ExtNetworkRange 28 -NetworkName Corp3rdFloor -SiteName 'Head Office' -SiteCode HO3 -IsWireless True -IsVPN False -Region EastUS -StreetAddress '366 North Broadway' -City Jericho -State 'New York' -Country US -PostCode 11753 -Description 'Head office 3rd floor' -Latitude 40.7818283 -Longitude -73.5351438 Creates a new location using all available fields .EXAMPLE Import-Csv LocationData.csv | New-NectarLocation Imports a CSV file called LocationData.csv and creates new locations .EXAMPLE Import-Csv LocationData.csv | New-NectarLocation -SkipGeolocate Imports a CSV file called LocationData.csv and creates new locations but will not attempt geolocation .NOTES Version 1.1 #> [Alias("nnl")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias('subnet','searchquery')] [string]$Network, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateRange(0,32)] [string]$NetworkRange, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias('ExternalNetwork')] [string]$ExtNetwork, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias('ExternalNetworkRange')] [ValidateRange(0,32)] [string]$ExtNetworkRange, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [ValidateLength(1,99)] [Alias('Network Name')] [string]$NetworkName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias('Site Name')] [string]$SiteName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias('Site Code')] [string]$SiteCode, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$Region, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias('Street Address', 'address')] [string]$StreetAddress, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$City, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias('province')] [string]$State, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias('Zip Code', 'zipcode')] [string]$PostCode, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateLength(0,2)] [string]$Country, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$Description, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('True','False','Yes','No',0,1, IgnoreCase=$True)] [Alias('isWirelessNetwork','Wireless(Yes/No)','Wireless(True/False)','Wireless','Wifi')] [string]$IsWireless, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('True','False','Yes','No',0,1, IgnoreCase=$True)] [Alias('External(Yes/No)','External(True/False)','External','Internal/External')] [string]$IsExternal, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('True','False','Yes','No',0,1, IgnoreCase=$True)] [Alias('VPN','VPN(Yes/No)','VPN(True/False)')] [string]$IsVPN, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('Sequential','Subnet','Virtual', IgnoreCase=$True)] [string]$NetworkRangeType = 'Subnet', [Parameter(Mandatory=$False)] [switch]$SkipGeoLocate, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateRange(-90,90)] [Alias('Coordinates Latitude','CoordinatesLatitude')] [double]$Latitude, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateRange(-180,180)] [Alias('Coordinates Longitude','CoordinatesLongitude')] [double]$Longitude, [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 !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/aapi/config/location?tenant=$TenantName" If (-not $Latitude -Or -not $Longitude -And !$SkipGeoLocate) { $LatLong = Get-LatLong "$StreetAddress, $City, $State, $PostCode, $Country" [double]$Latitude = $LatLong.Latitude [double]$Longitude = $LatLong.Longitude } $Body = @{ city = $City description = $Description isExternal = ParseBool $IsExternal isWirelessNetwork = ParseBool $IsWireless latitude = $Latitude longitude = $Longitude network = $Network networkName = $NetworkName networkRange = $NetworkRange externalNetwork = $ExtNetworkName externalNetworkRange = $ExtNetworkRange region = $Region siteCode = $SiteCode siteName = $SiteName state = $State streetAddress = $StreetAddress country = $Country vpn = ParseBool $IsVPN zipCode = $PostCode networkRangeType = $NetworkRangeType } $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 location $NetworkName with network $Network/$NetworkRange" Get-JSONErrorStream -JSONResponse $_ } } } Function Remove-NectarLocation { <# .SYNOPSIS Removes a Nectar DXP location from the location database .DESCRIPTION Removes a Nectar DXP location from the location database .PARAMETER SearchQuery The name of the location to remove. Can be a partial match. To return an exact match and to avoid ambiguity, enclose location name with ^ at the beginning and $ at the end. .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER Identity The numerical ID of the location to remove. Can be obtained via Get-NectarLocation and pipelined to Remove-NectarLocation .NOTES Version 1.1 #> [Alias("rnl")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias("networkName")] [string]$SearchQuery, [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 !$TenantName) { $TenantName = $Global:NectarTenantName } If ($SearchQuery -And !$Identity) { $LocationInfo = Get-NectarLocation -SearchQuery $SearchQuery -Tenant $TenantName -ResultSize 1 -ErrorVariable GetLocationError $Identity = $LocationInfo.id $NetworkName = $LocationInfo.networkName } If (!$GetLocationError) { $URI = "https://$Global:NectarCloud/aapi/config/location/$Identity/?tenant=$TenantName" Try { $NULL = Invoke-RestMethod -Method DELETE -uri $URI -Headers $Global:NectarAuthHeader Write-Verbose "Successfully deleted $LocationName." } Catch { Write-Error "Unable to delete location $NetworkName. Ensure you typed the name of the location correctly." Get-JSONErrorStream -JSONResponse $_ } } } } Function Import-NectarLocations { <# .SYNOPSIS Imports a CSV list of locations into Nectar DXP .DESCRIPTION Import a CSV list of locations into Nectar DXP. This will overwrite any existing locations with the same network ID. Useful for making wholesale changes without wiping and replacing everything. Assumes you are working from an export from the existing Nectar DXP location list. .PARAMETER Path The path to the CSV file to import into Nectar DXP. The CSV file must use the standard column heading template used by Nectar DXP exports. .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER SkipGeoLocate Don't attempt geolocation. Do this if you don't have a valid Google Maps API key or the lat/long is already included in the CSV. .NOTES Version 1.1 #> Param ( [Parameter(Mandatory=$True)] [string]$Path, [Parameter(Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [switch]$SkipGeoLocate ) $LocTable = ((Get-Content -Path $Path -Raw) -replace '\(Yes/No\)','') $LocTable = $LocTable -replace '\"?Network\"?\,',"""SearchQuery""," $LocationList = ConvertFrom-Csv $LocTable ForEach ($Location in $LocationList) { $LocationHashTable = @{} $Location.psobject.properties | ForEach-Object { $LocationHashTable[$_.Name] = $_.Value } If ($TenantName) { $LocationHashTable += @{TenantName = $TenantName } } # Add the tenant name to the hashtable If ($SkipGeoLocate) { $LocationHashTable += @{SkipGeoLocate = $TRUE} } Try { Write-Host "Updating location with subnet $($Location.SearchQuery)" Write-Verbose $LocationHashTable Set-NectarLocation @LocationHashTable -ErrorAction:Stop } Catch { Write-Host "Location does not exist. Creating location $($Location.SearchQuery)" New-NectarLocation @LocationHashTable } } } Function Import-MSTeamsLocations { <# .SYNOPSIS Imports a CSV list of locations downloaded from Microsoft CQD into Nectar DXP .DESCRIPTION Import a CSV list of locations downloaded from Microsoft CQD into Nectar DXP. This will overwrite any existing locations with the same network ID. Useful for making wholesale changes without wiping and replacing everything. .PARAMETER Path The path to the CSV file to import into Nectar DXP. The CSV file must be in the same format as downloaded from Microsoft CQD as per https://docs.microsoft.com/en-us/microsoftteams/cqd-upload-tenant-building-data .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER SkipGeoLocate Don't attempt geolocation. Do this if you don't have a valid Google Maps API key. .NOTES Version 1.0 #> Param ( [Parameter(Mandatory=$True)] [string]$Path, [Parameter(Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [switch]$SkipGeoLocate ) $Header = 'SearchQuery', 'NetworkName', 'NetworkRange', 'SiteName', 'OwnershipType', 'BuildingType', 'BuildingOfficeType', 'City', 'PostCode', 'Country', 'State', 'Region', 'IsExternal', 'ExpressRoute', 'IsVPN' $LocationList = Import-Csv $Path -Header $Header | Select-Object 'SearchQuery','NetworkRange','NetworkName','SiteName','City','PostCode','Country','State','Region','IsExternal','IsVPN' ForEach ($Location in $LocationList) { If ($Location.IsExternal -eq 0) { $Location.IsExternal = 1 } Else { $Location.IsExternal = 0 } If ($Location.IsVPN -eq 1) { $Location.IsVPN = 1 } Else { $Location.IsVPN = 0 } $LocationHashTable = @{} $Location.psobject.properties | ForEach-Object { $LocationHashTable[$_.Name] = $_.Value } If ($TenantName) { $LocationHashTable += @{TenantName = $TenantName } }# Add the tenant name to the hashtable If ($SkipGeoLocate) { $LocationHashTable += @{SkipGeoLocate = $TRUE} } Try { Write-Host "Updating location with subnet $($Location.SearchQuery)" Write-Verbose $LocationHashTable Set-NectarLocation @LocationHashTable -ErrorAction:Stop } Catch { Write-Host "Location does not exist. Creating location $($Location.SearchQuery)" New-NectarLocation @LocationHashTable } } } ################################################################################################################################################# # # # Tenant Alert Functions # # # ################################################################################################################################################# Function Get-NectarAlerts { <# .SYNOPSIS Returns a list of Nectar DXP alert configurations .DESCRIPTION Returns a list of Nectar DXP alert configurations .PARAMETER AlertType The type of alert to return data on. Choose from 'NectarScore-Users', 'NectarScore-Locations', or 'PoorCalls' .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-NectarScoreAlerts Returns the first 10000 alert configurations .EXAMPLE Get-NectarScoreAlerts -ResultSize 100 Returns the first 100 alert configurations .NOTES Version 1.0 #> [Alias("gna")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias("ClientNotificationType")] [ValidateSet('NECTAR_SCORE_ALERT','NECTAR_SCORE_ALERT_BY_LOCATION','POOR_CALLS_BY_USER', IgnoreCase=$True)] [string]$AlertType, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [ValidateRange(1,100000)] [int]$ResultSize=10000 ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $Params = @{ 'tenant' = $TenantName 'orderByField' = 'name' 'orderDirection' = 'asc' 'pageSize' = $ResultSize } Switch ($AlertType) { 'NECTAR_SCORE_ALERT' { $URI = "https://$Global:NectarCloud/aapi/client/notification/uhs/configurations" $Params.Add('type','NECTAR_SCORE_ALERT') } 'NECTAR_SCORE_ALERT_BY_LOCATION' { $URI = "https://$Global:NectarCloud/aapi/client/notification/poor-calls/configurations" $Params.Add('clientNotificationType','NECTAR_SCORE_ALERT_BY_LOCATION') } 'POOR_CALLS_BY_USER' { $URI = "https://$Global:NectarCloud/aapi/client/notification/poor-calls/configurations" } } Write-Verbose $URI If (!$NectarError) { $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader -Body $Params If ($TenantName) { $JSON.elements | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty } # Add the tenant name to the output which helps pipelining $JSON.elements } } Catch { Write-Error "Unable to retrieve alert information" Get-JSONErrorStream -JSONResponse $_ } } } Function Set-NectarAlert { <# .SYNOPSIS Modify a Nectar DXP alert configuration .DESCRIPTION Modify a Nectar DXP alert configuration .PARAMETER ID The numerical ID of the alert to configure .PARAMETER Enabled Enable or disable the specified alert .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-NectarScoreAlerts Returns the first 10000 alert configurations .EXAMPLE Get-NectarScoreAlerts -ResultSize 100 Returns the first 100 alert configurations .NOTES Version 1.0 #> [Alias("sna")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [int]$ID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias("ClientNotificationType")] [ValidateSet('NECTAR_SCORE_ALERT','NECTAR_SCORE_ALERT_BY_LOCATION','POOR_CALLS_BY_USER', IgnoreCase=$True)] [string]$AlertType, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [bool]$Enabled, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [ValidateRange(1,100000)] [int]$ResultSize=10000 ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } If ($Null -ne $Enabled) { Switch ($AlertType) { 'NECTAR_SCORE_ALERT' { $URI = "https://$Global:NectarCloud/aapi/client/notification/uhs/configuration/$ID/enabled?enabled=$Enabled&tenant=$TenantName" } 'NECTAR_SCORE_ALERT_BY_LOCATION' { $URI = "https://$Global:NectarCloud/aapi/client/notification/poor-calls/configuration/$ID/enabled?enabled=$Enabled&tenant=$TenantName" } 'POOR_CALLS_BY_USER' { $URI = "https://$Global:NectarCloud/aapi/client/notification/poor-calls/configuration/$ID/enabled?enabled=$Enabled&tenant=$TenantName" } } } Write-Verbose $URI If (!$NectarError) { $JSON = Invoke-RestMethod -Method PUT -uri $URI -Headers $Global:NectarAuthHeader } } Catch { Write-Error "Unable to set alert configuration" Get-JSONErrorStream -JSONResponse $_ } } } ################################################################################################################################################# # # # Tenant Data Diagnostic Functions # # # ################################################################################################################################################# Function Get-NectarDataDiagnosticSummary { <# .SYNOPSIS Returns the current summary of a tenant's data diagnostics .DESCRIPTION Returns the current summary of a tenant's data diagnostics .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarDataDiagnosticSummary Returns the current summary of a tenant's data diagnostics .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/aapi/diagnostics/latest-pk-sources?tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader Return $JSON } Catch { Write-Error "Could not retrieve data diagnostic summary" Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarDataDiagnosticDetail { <# .SYNOPSIS Returns the detail of a specific source for a tenant's data diagnostics .DESCRIPTION Returns the detail of a specific source for a tenant's data diagnostics .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarDataDiagnosticDetail -Source 'RIGDB Derby' Returns the current detail of a tenant's RIG Derby data diagnostics .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$Source, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/aapi/diagnostics/latest-pk-source?source=$Source&tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader Return $JSON } Catch { Write-Error "Could not retrieve data diagnostic detail for $Source" Get-JSONErrorStream -JSONResponse $_ } } } ################################################################################################################################################# # # # Tenant Datamart Functions # # # ################################################################################################################################################# Function Get-NectarDatamartStatus { <# .SYNOPSIS Returns the current status of a tenant's datamart process .DESCRIPTION Returns the current status of a tenant's datamart process .PARAMETER TimePeriod The time period to show session data from. Select from 'LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM'. CUSTOM requires using TimePeriodFrom and TimePeriodTo parameters. .PARAMETER TimePeriodFrom The earliest date/time to show session data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodTo parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. .PARAMETER TimePeriodTo The latest date/time to show session data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodFrom parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarDatamartStatus Returns the last 30 days daily status for the datamart .NOTES Version 1.0 #> [Alias("gnds")] Param ( [Parameter(Mandatory=$False)] [ValidateSet('LAST_DAY','LAST_WEEK','LAST_MONTH', IgnoreCase=$True)] [string]$TimePeriod = 'LAST_WEEK', [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/aapi/datamart?timePeriod=$TimePeriod&tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader Return $JSON } Catch { Write-Error "Could not retrieve datamart status" Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarDatamartLag { <# .SYNOPSIS Returns the current lag of a tenant's datamart process .DESCRIPTION Returns the current lag of a tenant's datamart process .PARAMETER Platform The platform to show datamart lag .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarDatamartLag -Platform 'MS Teams' Returns the current lag for all MS Teams datamart processes .NOTES Version 1.0 #> [Alias("gnds")] Param ( [Parameter(Mandatory=$True)] [ValidateSet('MS Teams','Skype for Business SQL Server - CDR','Skype for Business SQL Server - QoE','RIGDB Derby', IgnoreCase=$False)] [string]$Platform, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $Params = @{ 'source' = $PLatform 'tenant' = $TenantName.ToLower() } $URI = "https://$Global:NectarCloud/aapi/diagnostics/latest-pk-source" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Global:NectarAuthHeader -Body $Params $CurrentTime = (Get-Date).ToUniversalTime() Return $JSON | Select-Object table, @{n='lastActivity';e={$_.pk}}, @{n='lag';e={(New-TimeSpan -Start $_.pk -End $CurrentTime).ToString("dd\.hh\:mm\:ss")}} } Catch { Write-Error "Could not retrieve datamart lag" Get-JSONErrorStream -JSONResponse $_ } } } Function Invoke-NectarDatamartUpdate { <# .SYNOPSIS Trigger a reload of the datamart to process new call records .DESCRIPTION Trigger a reload of the datamart to process new call records .PARAMETER TimePeriodFrom The earliest date/time to invoke a datamart reload for. Must be used in conjunction with TimePeriodTo parameters. Use format 'YYYY-MM-DD'. All time/dates in UTC. .PARAMETER TimePeriodTo The latest date/time to invoke a datamart reload for. Must be used in conjunction with TimePeriodFrom parameters. Use format 'YYYY-MM-DD'. All time/dates in UTC. .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Invoke-NectarDatamartStatus -Platform TEAMS -TimePeriodFrom '2022-03-01' -TimePeriodTo '2022-03-05' Reloads the Teams datamart tables for the period between March 1 to 5 .NOTES Version 1.0 #> [Alias("inds")] Param ( [Parameter(Mandatory=$True)] [Alias("StartDateFrom")] [DateTime]$TimePeriodFrom, [Parameter(Mandatory=$True)] [Alias("StartDateTo")] [DateTime]$TimePeriodTo, [Parameter(Mandatory=$True)] [ValidateSet('SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','RIG','LYNC_VKM','MISCELLANEOUS','CORE','ALL', IgnoreCase=$False)] [string]$Platform, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $Params = @{ 'startDate' = $TimePeriodFrom.ToString('yyyy-MM-ddT00:00:00.000') 'endDate' = $TimePeriodTo.ToString('yyyy-MM-ddT00:00:00.000') 'platform' = $Platform 'tenant' = $TenantName 'module' = 'PLATFORM' 'clearOld' = 'false' } $URI = "https://$Global:NectarCloud/aapi/datamart" $NULL = Invoke-RestMethod -Method POST -uri $URI -Headers $Global:NectarAuthHeader -Body $Params Return # } # Catch { # Write-Error "Could not invoke datamart update" # Get-JSONErrorStream -JSONResponse $_ # } } } ################################################################################################################################################# # # # DID Number Location Management Functions # # # ################################################################################################################################################# Function Get-NectarNumberLocation { <# .SYNOPSIS Returns a list of Nectar DXP service locations used in the DID Management tool. .DESCRIPTION Returns a list of Nectar DXP service locations used in the DID Management tool. .PARAMETER LocationName The name of the service location to get information on. Can be a partial match. To return an exact match and to avoid ambiguity, enclose service location name with ^ at the beginning and $ at the end. .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-NectarNumberLocation Returns the first 10 service locations .EXAMPLE Get-NectarNumberLocation -ResultSize 100 Returns the first 100 service locations .EXAMPLE Get-NectarNumberLocation -LocationName Location2 Returns up to 10 service locations that contains "location2" anywhere in the name. The search is not case-sensitive. This example would return Location2, Location20, Location214, MyLocation299 etc .EXAMPLE Get-NectarNumberLocation -LocationName ^Location2 Returns up to 10 service locations that starts with "location2" in the name. The search is not case-sensitive. This example would return Location2, Location20, Location214 etc, but NOT MyLocation299 .EXAMPLE Get-NectarNumberLocation -LocationName ^Location2$ Returns a service location explicitly named "Location2". The search is not case-sensitive. .NOTES Version 1.1 #> [Alias("gnnl")] Param ( [Parameter(Mandatory=$False)] [string]$LocationName, [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 !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/dapi/numbers/locations?pageNumber=1&tenant=$TenantName&pageSize=$ResultSize&q=$LocationName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -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 $JSON.elements | Add-Member -TypeName 'Nectar.Number.LocationList' Return $JSON.elements } Catch { Write-Error "Service location not found. Ensure you typed the name of the service location correctly." Get-JSONErrorStream -JSONResponse $_ } } } Function Set-NectarNumberLocation { <# .SYNOPSIS Update a Nectar DXP service location used in the DID Management tool. .DESCRIPTION Update a Nectar DXP service location used in the DID Management tool. .PARAMETER LocationName The name of the service location to get information on. Can be a partial match. To return an exact match and to avoid ambiguity, enclose location name with ^ at the beginning and $ at the end. .PARAMETER NewLocationName Replace the existing service location name with this one. .PARAMETER ServiceID The service ID associated with the telephony provider for this service location .PARAMETER ServiceProvider The name of the service provider that provides telephony service to this service location .PARAMETER NetworkLocation The phyiscal location for this service location .PARAMETER Notes Can be used for any additional information .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Set-NectarNumberLocation -LocationName Dallas -ServiceID 44FE98 -ServiceProvider Verizon -Notes "Head office" Returns the first 10 locations .NOTES Version 1.1 #> [Alias("snnl")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias("name")] [string]$LocationName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$NewLocationName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] $ServiceID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias("provider")] $ServiceProvider, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias("location")] $NetworkLocation, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] $Notes, [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 !$TenantName) { $TenantName = $Global:NectarTenantName } If ($LocationName -And !$Identity) { $LocationInfo = Get-NectarNumberLocation -LocationName $LocationName -Tenant $TenantName -ResultSize 1 $Identity = $LocationInfo.id } If ($LocationInfo.Count -gt 1) { Write-Error "Multiple number locations found that match $LocationName. Please refine your location name search query" Break } If ($NewLocationName) {$LocationName = $NewLocationName} If (-not $ServiceID) {$ServiceID = $LocationInfo.ServiceId} If (-not $ServiceProvider) {$ServiceProvider = $LocationInfo.provider} If (-not $NetworkLocation) {$NetworkLocation = $LocationInfo.location} If (-not $Notes) {$Notes = $LocationInfo.notes} $URI = "https://$Global:NectarCloud/dapi/numbers/location/$Identity/?tenant=$TenantName" $Body = @{ name = $LocationName serviceId = $ServiceID provider = $ServiceProvider location = $NetworkLocation notes = $Notes } $JSONBody = $Body | ConvertTo-Json Try { $NULL = Invoke-RestMethod -Method PUT -uri $URI -Headers $Global:NectarAuthHeader -Body $JSONBody -ContentType 'application/json; charset=utf-8' Write-Verbose $JSONBody } Catch { Write-Error "Unable to apply changes for location $LocationName." Get-JSONErrorStream -JSONResponse $_ } } } Function New-NectarNumberLocation { <# .SYNOPSIS Create a new Nectar Service Location for DID Management used in the DID Management tool. .DESCRIPTION Create a new Nectar Service Location for DID Management used in the DID Management tool. .PARAMETER LocationName The name of the new service location. Must be unique. .PARAMETER ServiceID The service ID for telephony services at the newservice location. Can be used as desired. Not required. .PARAMETER ServiceProvider The service provider for telephony services at the newservice location. Can be used as desired. Not required. .PARAMETER ServiceProvider The network location to associate with the newservice location. Can be used as desired. Not required. .PARAMETER Notes Any relevent notes about the service location. Can be used as desired. Not required. .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE New-NectarNumberLocation -LocationName Dallas -ServiceID 348FE22 -ServiceProvider Verizon -NetworkLocation Dallas -Notes "This is headquarters" .NOTES Version 1.1 #> [Alias("nnnl")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias("name")] [string]$LocationName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$ServiceID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias("provider")] [string]$ServiceProvider, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias("location")] [string]$NetworkLocation, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$Notes, [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 !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/dapi/numbers/location/?tenant=$TenantName" $Body = @{ name = $LocationName serviceId = $ServiceID provider = $ServiceProvider location = $NetworkLocation notes = $Notes } $JSONBody = $Body | ConvertTo-Json Try { Invoke-RestMethod -Method POST -uri $URI -Headers $Global:NectarAuthHeader -Body $JSONBody -ContentType 'application/json; charset=utf-8' Write-Verbose $JSONBody } Catch { Write-Error "Unable to create service location $LocationName. The service location may already exist." Get-JSONErrorStream -JSONResponse $_ } } } Function Remove-NectarNumberLocation { <# .SYNOPSIS Removes one or more service locations in the DID Management tool. .DESCRIPTION Removes one or more service locations in the DID Management tool. .PARAMETER LocationName The name of the number service location to remove. .PARAMETER Identity The numerical ID of the number service location. Can be obtained via Get-NectarNumberLocation and pipelined to Remove-NectarNumberLocation .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Remove-NectarNumberLocation Tokyo Removes the Toyota location. The command will fail if the location has number ranges assigned. .NOTES Version 1.1 #> [Alias("rnnl")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$LocationName, [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 !$TenantName) { $TenantName = $Global:NectarTenantName } If ($LocationName -And !$Identity) { $Identity = (Get-NectarNumberLocation -LocationName $LocationName -Tenant $TenantName -ResultSize 1 -ErrorVariable GetLocationError).ID } If ($Identity.Count -gt 1) { Write-Error "Multiple number locations found that match $LocationName. Please refine your location name search query" Break } If (!$GetLocationError) { $URI = "https://$Global:NectarCloud/dapi/numbers/location/$Identity/?tenant=$TenantName" Try { $NULL = Invoke-RestMethod -Method DELETE -uri $URI -Headers $Global:NectarAuthHeader Write-Verbose "Successfully deleted $LocationName." } Catch { Write-Error "Unable to delete service location $LocationName. Ensure you typed the name of the service location correctly and that the service location has no assigned ranges." Get-JSONErrorStream -JSONResponse $_ } } } } ################################################################################################################################################# # # # DID Number Range Management Functions # # # ################################################################################################################################################# Function Get-NectarNumberRange { <# .SYNOPSIS Returns a list of Nectar DXP number ranges in the DID Management tool .DESCRIPTION Returns a list of Nectar DXP ranges in the DID Management tool .PARAMETER RangeName The name of the number range to get information on. Can be a partial match. To return an exact match and to avoid ambiguity, enclose range name with ^ at the beginning and $ at the end. .PARAMETER LocationName The name of the location to get information on. Will be an exact match. .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-NectarNumberRange Returns the first 10 number ranges .EXAMPLE Get-NectarNumberRange -ResultSize 100 Returns the first 100 number ranges .EXAMPLE Get-NectarNumberRange -LocationName Tokyo Returns the first 10 number ranges at the Tokyo location .EXAMPLE Get-NectarNumberRange -RangeName Range2 Returns up to 10 ranges that contains "range2" anywhere in the name. The search is not case-sensitive. This example would return Range2, Range20, Range214, MyRange299 etc .EXAMPLE Get-NectarNumberRange -RangeName ^Range2 Returns up to 10 ranges that starts with "range2" in the name. The search is not case-sensitive. This example would return Range2, Range20, Range214 etc, but NOT MyRange299. .EXAMPLE Get-NectarNumberRange -RangeName ^Range2$ Returns any range explicitly named "Range2". The search is not case-sensitive. This example would return Range2 only. If there are multiple ranges with the name Range2, all will be returned. .EXAMPLE Get-NectarNumberRange -RangeName ^Range2$ -LocationName Tokyo Returns a range explicitly named "Range2" in the Tokyo location. The search is not case-sensitive. .NOTES Version 1.1 #> [Alias("gnnr")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$RangeName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$LocationName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [ValidateRange(1,100000)] [int]$ResultSize = 1000 ) Begin { Connect-NectarCloud } Process { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If ($LocationName) { $LocationID = (Get-NectarNumberLocation -LocationName "$LocationName" -Tenant $TenantName -ErrorVariable LocError).ID } If ($LocError) { Break} $URI = "https://$Global:NectarCloud/dapi/numbers/ranges?pageNumber=1&tenant=$TenantName&pageSize=$ResultSize&serviceLocationId=$LocationID&q=$RangeName" Write-Verbose $URI Try { $JSON = Invoke-RestMethod -Method GET -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 $JSON.elements | Add-Member -TypeName 'Nectar.Number.RangeList' Return $JSON.elements } Catch { Write-Error "An error occurred." Get-JSONErrorStream -JSONResponse $_ } } } Function Set-NectarNumberRange { <# .SYNOPSIS Make changes to a Nectar range for DID Management .DESCRIPTION Make changes to a Nectar range for DID Management .PARAMETER RangeName The name of the range. Must be unique. .PARAMETER RangeType The type of range. Can be either STANDARD (for DID ranges) or EXTENSION (for extension-based ranges). .PARAMETER FirstNumber The first number in a STANDARD range. Must be numeric, but can start with +. .PARAMETER LastNumber The last number in a STANDARD range. Must be numeric, but can start with +. Must be larger than FirstNumber, and must have the same number of digits. .PARAMETER BaseNumber The base DID for an EXTENSION range. Must be numeric, but can start with +. .PARAMETER ExtStart The first extension number in an EXTENSION range. Must be numeric. .PARAMETER ExtEnd The last extension number in an EXTENSION range. Must be numeric. Must be larger than ExtStart, and must have the same number of digits. .PARAMETER RangeSize The number of phone numbers/extensions in a range. Can be used instead of LastNumber/ExtEnd. .PARAMETER HoldDays The number of days to hold a newly-freed number before returning it to the pool of available numbers. .PARAMETER LocationName The service location to assign the range to. .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Set-NectarNumberRange -RangeName DIDRange1 -RangeType STANDARD -FirstNumber +15552223333 -LastNumber +15552224444 -LocationName Dallas Edits a DID range for numbers that fall in the range of +15552223333 to +15552224444 .NOTES Version 1.1 #> [Alias("snnr")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias("name")] [string]$RangeName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet("STANDARD","EXTENSION", IgnoreCase=$True)] [Alias("type")] [string]$RangeType, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] # [ValidatePattern("^(\+|%2B)?\d+$")] [string]$FirstNumber, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] # [ValidatePattern("^(\+|%2B)?\d+$")] [string]$LastNumber, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] # [ValidatePattern("^(\+|%2B)?\d+$")] [string]$BaseNumber, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] # [ValidatePattern("^\d+$")] [string]$ExtStart, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] # [ValidatePattern("^\d+$")] [string]$ExtEnd, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [int]$RangeSize, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [int]$HoldDays = 0, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias("serviceLocationId")] [string]$LocationName, [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 !$TenantName) { $TenantName = $Global:NectarTenantName } If ($RangeName -And !$Identity) { $RangeInfo = Get-NectarNumberRange -RangeName $RangeName -Tenant $TenantName -ResultSize 1 $Identity = $RangeInfo.id If ($NewRangeName) {$RangeName = $NewRangeName} If (-not $FirstNumber) {$FirstNumber = $RangeInfo.firstNumber} If (-not $LastNumber) {$LastNumber = $RangeInfo.lastNumber} If (-not $RangeType) {$RangeType = $RangeInfo.type} If (-not $HoldDays) {$HoldDays = $RangeInfo.holdDays} If (-not $BaseNumber) {$BaseNumber = $RangeInfo.baseNumber} If (-not $ExtStart) {$ExtStart = $RangeInfo.extStart} If (-not $ExtEnd) {$ExtEnd = $RangeInfo.extEnd} # If (-not $LocationName) {$LocationID = $RangeInfo.serviceLocationId} } $LocationID = (Get-NectarNumberLocation -LocationName "$LocationName" -Tenant $TenantName -ErrorVariable LocError).ID $URI = "https://$Global:NectarCloud/dapi/numbers/range/$Identity/?tenant=$TenantName" $Body = @{ name = $RangeName type = $RangeType holdDays = $HoldDays serviceLocationId = $LocationID } If ($FirstNumber) { $Body.Add('firstNumber', $FirstNumber) } If ($LastNumber) { $Body.Add('lastNumber', $LastNumber) } If ($BaseNumber) { $Body.Add('baseNumber', $BaseNumber) } If ($ExtStart) { $Body.Add('extStart', $ExtStart) } If ($ExtEnd) { $Body.Add('extEnd', $ExtEnd) } If ($RangeSize) { $Body.Add('rangeSize', $RangeSize) } $JSONBody = $Body | ConvertTo-Json Try { $NULL = Invoke-RestMethod -Method PUT -uri $URI -Headers $Global:NectarAuthHeader -Body $JSONBody -ContentType 'application/json; charset=utf-8' Write-Verbose $JSONBody } Catch { Write-Error "Unable to apply changes for range $RangeName." Get-JSONErrorStream -JSONResponse $_ } } } Function New-NectarNumberRange { <# .SYNOPSIS Create a new Nectar range for DID Management .DESCRIPTION Create a new Nectar range for DID Management .PARAMETER RangeName The name of the new range. Must be unique. .PARAMETER RangeType The type of range. Can be either STANDARD (for DID ranges) or EXTENSION (for extension-based ranges). .PARAMETER FirstNumber The first number in a STANDARD range. Must be numeric, but can start with +. .PARAMETER LastNumber The last number in a STANDARD range. Must be numeric, but can start with +. Must be larger than FirstNumber, and must have the same number of digits. .PARAMETER BaseNumber The base DID for an EXTENSION range. Must be numeric, but can start with +. .PARAMETER ExtStart The first extension number in an EXTENSION range. Must be numeric. .PARAMETER ExtEnd The last extension number in an EXTENSION range. Must be numeric. Must be larger than ExtStart, and must have the same number of digits. .PARAMETER RangeSize The number of phone numbers/extensions in a range. Can be used instead of LastNumber/ExtEnd. .PARAMETER HoldDays The number of days to hold a newly-freed number before returning it to the pool of available numbers. .PARAMETER LocationName The location to assign the range to. .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE New-NectarNumberRange -RangeName DIDRange1 -RangeType STANDARD -FirstNumber +15552223333 -LastNumber +15552224444 -LocationName Dallas Creates a DID range for numbers that fall in the range of +15552223333 to +15552224444 .EXAMPLE New-NectarNumberRange -RangeName DIDRange1 -RangeType STANDARD -FirstNumber +15552223000 -RangeSize 1000 -LocationName Dallas Creates a DID range for numbers that fall in the range of +15552223000 to +15552223999 .EXAMPLE New-NectarNumberRange -RangeName ExtRange1 -RangeType EXTENSION -BaseNumber +15552223000 -ExtStart 2000 -ExtEnd 2999 -LocationName Dallas Creates an extension range for numbers that fall in the range of +15552223000 x2000 to x2999 .NOTES Version 1.2 #> [Alias("nnnr")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias("name")] [string]$RangeName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [ValidateSet("STANDARD","EXTENSION", IgnoreCase=$True)] [string]$RangeType, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidatePattern("^(\+|%2B)?\d+$")] [string]$FirstNumber, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidatePattern("^(\+|%2B)?\d+$")] [string]$LastNumber, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidatePattern("^(\+|%2B)?\d+$")] [string]$BaseNumber, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidatePattern("^\d+$")] [string]$ExtStart, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidatePattern("^\d+$")] [string]$ExtEnd, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [int]$RangeSize, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [int]$HoldDays, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$LocationName, [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 !$TenantName) { $TenantName = $Global:NectarTenantName } $LocationID = (Get-NectarNumberLocation -LocationName $LocationName -Tenant $TenantName -ErrorVariable NumLocError).ID If ($LocationID.Count -gt 1) { Write-Error "Multiple locations found that match $LocationName. Please refine your location name search query" Break } $URI = "https://$Global:NectarCloud/dapi/numbers/range?tenant=$TenantName" $Body = @{ name = $RangeName type = $RangeType holdDays = $HoldDays serviceLocationId = $LocationID } If ($FirstNumber) { $Body.Add('firstNumber', $FirstNumber) } If ($LastNumber) { $Body.Add('lastNumber', $LastNumber) } If ($BaseNumber) { $Body.Add('baseNumber', $BaseNumber) } If ($ExtStart) { $Body.Add('extStart', $ExtStart) } If ($ExtEnd) { $Body.Add('extEnd', $ExtEnd) } If ($RangeSize) { $Body.Add('rangeSize', $RangeSize) } $JSONBody = $Body | ConvertTo-Json Try { Invoke-RestMethod -Method POST -uri $URI -Headers $Global:NectarAuthHeader -Body $JSONBody -ContentType 'application/json; charset=utf-8' Write-Verbose $JSONBody } Catch { Write-Error "Unable to create range $RangeName." Get-JSONErrorStream -JSONResponse $_ } } } Function Remove-NectarNumberRange { <# .SYNOPSIS Removes one or more ranges from a service location in the DID Management tool. .DESCRIPTION Removes one or more ranges from a service location in the DID Management tool. .PARAMETER RangeName The name of the number range to remove. .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER Identity The numerical ID of the number range. Can be obtained via Get-NectarNumberRange and pipelined to Remove-NectarNumberRange .EXAMPLE Remove-NectarNumberRange Range1 Removes the range Range1 .NOTES Version 1.1 #> [Alias("rnnr")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$RangeName, [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 !$TenantName) { $TenantName = $Global:NectarTenantName } If ($RangeName -And !$Identity) { $Identity = (Get-NectarNumberRange -RangeName $RangeName -Tenant $TenantName -ErrorVariable GetRangeError).ID } If ($Identity.Count -gt 1) { Write-Error "Multiple ranges found that match $RangeName. Please refine your range name search query" Break } If (!$GetRangeError) { $URI = "https://$Global:NectarCloud/dapi/numbers/range/$Identity/?tenant=$TenantName" Try { $NULL = Invoke-RestMethod -Method DELETE -uri $URI -Headers $Global:NectarAuthHeader Write-Verbose "Successfully deleted $RangeName number range." } Catch { Write-Error "Unable to delete $RangeName number range. Ensure you typed the name of the range correctly." Get-JSONErrorStream -JSONResponse $_ } } } } ################################################################################################################################################# # # # DID Number Management Functions # # # ################################################################################################################################################# Function Get-NectarNumber { <# .SYNOPSIS Returns a list of Nectar DXP numbers from the DID Management tool .DESCRIPTION Returns a list of Nectar DXP numbers from the DID Management tool .PARAMETER PhoneNumber The phone number to return information about. Can be a partial match. To return an exact match and to avoid ambiguity, enclose number with ^ at the beginning and $ at the end. .PARAMETER LocationName The name of the location to get number information about. Will be an exact match. .PARAMETER RangeName The name of the range to get number information about. Will be an exact match. .PARAMETER NumberState Returns information about numbers that are either USED, UNUSED or RESERVED .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-NectarNumber Returns the first 10 numbers .EXAMPLE Get-NectarNumber -ResultSize 100 Returns the first 100 numbers .EXAMPLE Get-NectarNumber -LocationName Tokyo Returns the first 10 numbers at the Tokyo location .EXAMPLE Get-NectarNumber -RangeName Range2 Returns up to 10 numbers from a number range called Range2. .EXAMPLE Get-NectarNumber -RangeName Range2 -NumberState UNUSED -ResultSize 100 Returns up to 100 unused numbers in the Range2 range. .NOTES Version 1.1 #> [Alias("gnn")] Param ( [Parameter(Mandatory=$False)] [ValidatePattern("^(\+|%2B)?\d+$")] [string]$PhoneNumber, [Parameter(Mandatory=$False)] [string]$LocationName, [Parameter(Mandatory=$False)] [string]$RangeName, [Parameter(Mandatory=$False)] [ValidateSet("USED","UNUSED","RESERVED", IgnoreCase=$True)] [string]$NumberState, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [ValidateRange(1,100000)] [int]$PageSize = 10000, [Parameter(Mandatory=$False)] [ValidateRange(1,100000)] [int]$ResultSize ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } If ($LocationName) { $LocationID = (Get-NectarNumberLocation -LocationName ^$LocationName$ -Tenant $TenantName -ResultSize 1 -ErrorVariable NectarError).ID } If ($RangeName) { $RangeID = (Get-NectarNumberRange -RangeName $RangeName -LocationName $LocationName -Tenant $TenantName -ResultSize 1 -ErrorVariable +NectarError).ID } If ($PhoneNumber) { # Replace + with %2B if present $PhoneNumber = $PhoneNumber.Replace("+", "%2B") } $URI = "https://$Global:NectarCloud/dapi/numbers/" $Params = @{ 'orderByField' = 'number' 'orderDirection' = 'asc' } If ($ResultSize) { $Params.Add('pageSize', $ResultSize) } Else { $Params.Add('pageSize', $PageSize) } If ($LocationID) { $Params.Add('serviceLocationId', $LocationID) } If ($RangeID) { $Params.Add('numbersRangeId', $RangeID) } If ($NumberState) { $Params.Add('states', $NumberState) } If ($PhoneNumber) { $Params.Add('q', $PhoneNumber) } If ($TenantName) { $Params.Add('Tenant', $TenantName) } If (!$NectarError) { $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader -Body $Params If ($TenantName) { $JSON.elements | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty } # Add the tenant name to the output which helps pipelining $JSON.elements | Add-Member -TypeName 'Nectar.Number.List' $JSON.elements $TotalPages = $JSON.totalPages 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 If ($TenantName) { $JSON.elements | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty } $JSON.elements | Add-Member -TypeName 'Nectar.Number.List' $JSON.elements $PageNum++ } } } } Catch { Write-Error "Unable to retrieve number information" Get-JSONErrorStream -JSONResponse $_ } } } Function Set-NectarNumber { <# .SYNOPSIS Makes changes to one or more phone numbers. .DESCRIPTION Makes changes to one or more phone numbers. .PARAMETER PhoneNumber A phone number to make changes to. Must be an exact match. .PARAMETER NumberState Change the state of a phone number to either UNUSED or RESERVED. A number marked USED cannot be modified. .PARAMETER Comment A comment to add to a reserved phone number. .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Set-NectarNumber +12223334444 -NumberState RESERVED Reserves the number +12223334444 .NOTES Version 1.1 #> [Alias("snn")] Param ( [Parameter(ValueFromPipelineByPropertyName,Mandatory=$False)] [ValidatePattern("^(\+|%2B)?\d+$")] [string]$PhoneNumber, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet("UNUSED","RESERVED", IgnoreCase=$True)] [string]$NumberState, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateLength(0,254)] [string]$Comment, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(ValueFromPipelineByPropertyName)] [Alias("id")] [String]$Identity ) Process { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If ($PhoneNumber -And !$Identity) { $Identity = (Get-NectarNumber -PhoneNumber $PhoneNumber -Tenant $TenantName -ResultSize 1 -ErrorVariable PhoneNumError).ID } If (!$PhoneNumError) { $URI = "https://$Global:NectarCloud/dapi/numbers/$Identity/state?state=$NumberState&tenant=$TenantName" If (($Comment) -And ($NumberState -eq "RESERVED")) { # Convert special characters to URI-compatible versions #$Comment = [uri]::EscapeDataString($Comment) $URI += "&comment=$Comment" } Try { $NULL = Invoke-RestMethod -Method PUT -uri $URI -Headers $Global:NectarAuthHeader Write-Verbose "Successfully applied changes to $PhoneNumber." } Catch { Write-Error "Unable to apply changes for phone number $PhoneNumber. The number may already be in the desired state." Get-JSONErrorStream -JSONResponse $_ } } } } Function Get-NectarUnallocatedNumber { <# .SYNOPSIS Returns the next available number in a given location/range. .DESCRIPTION Returns the next available number in a given location/range. .PARAMETER LocationName The service location to return a number for .PARAMETER RangeName The range to return a number for .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarUnallocatedNumber -RangeName Jericho Returns the next available number in the Jericho range. .NOTES Version 1.1 #> [Alias("gnun")] Param ( [Parameter(Mandatory=$False)] [string]$LocationName, [Parameter(Mandatory=$False)] [string]$RangeName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } $NextFreeNum = Get-NectarNumber -LocationName $LocationName -RangeName $RangeName -NumberState UNUSED -Tenant $TenantName -ResultSize 1 If ($NextFreeNum) { Return $NextFreeNum } Else { Write-Error "No available phone number found." } } ################################################################################################################################################# # # # Supported Device Functions # # # ################################################################################################################################################# Function Get-NectarSupportedDevice { <# .SYNOPSIS Get information about 1 or more Nectar DXP supported devices. .DESCRIPTION Get information about 1 or more Nectar DXP supported devices. .PARAMETER SearchQuery A full or partial match of the device's name .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-NectarSupportedDevice -SearchQuery Realtek .NOTES Version 1.1 #> [Alias("gnd")] 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/aapi/supported/devices?q=$SearchQuery&tenant=$TenantName&pageSize=$ResultSize" Try { $JSON = Invoke-RestMethod -Method GET -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 $JSON.elements | Add-Member -TypeName 'Nectar.DeviceList' Return $JSON.elements } Catch { Write-Error "Unable to get device details." Get-JSONErrorStream -JSONResponse $_ } } } Function Set-NectarSupportedDevice { <# .SYNOPSIS Update 1 or more Nectar DXP supported device. .DESCRIPTION Update 1 or more Nectar DXP supported device. .PARAMETER DeviceName The name of the supported device .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER Identity The numerical identity of the supported device .EXAMPLE Set-NectarSupportedDevice Identity 233 -Supported $FALSE .EXAMPLE Get-NectarSupportedDevice -SearchQuery realtek | Set-NectarSupportedDevice -Supported $FALSE Sets all devices with 'Realtek' in the name to Unsupported .NOTES Version 1.1 #> [Alias("snsd")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$DeviceName, [Alias("deviceSupported")] [bool]$Supported, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias("deviceKey")] [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 !$TenantName) { $TenantName = $Global:NectarTenantName } If ($DeviceName -and !$Identity) { $DeviceInfo = Get-NectarSupportedDevice -SearchQuery $DeviceName -Tenant $TenantName -ResultSize 1 $DeviceName = $DeviceInfo.DeviceName If ($NULL -eq $Supported) {$Supported = $DeviceInfo.deviceSupported} $Identity = $DeviceInfo.deviceKey } $URI = "https://$Global:NectarCloud/aapi/supported/device/$Identity/?tenant=$TenantName" $Body = @{ deviceKey = $Identity deviceSupported = $Supported } $JSONBody = $Body | ConvertTo-Json Try { $NULL = Invoke-RestMethod -Method PUT -uri $URI -Headers $Global:NectarAuthHeader -Body $JSONBody -ContentType 'application/json; charset=utf-8' Write-Verbose $JSONBody } Catch { If ($DeviceName) { $IDText = $DeviceName } Else { $IDText = "with ID $Identity" } Write-Error "Unable to apply changes for device $IDText." Get-JSONErrorStream -JSONResponse $_ } } } ################################################################################################################################################# # # # Supported Client Functions # # # ################################################################################################################################################# Function Get-NectarSupportedClient { <# .SYNOPSIS Get information about 1 or more Nectar DXP supported client versions. .DESCRIPTION Get information about 1 or more Nectar DXP supported client versions. .PARAMETER SearchQuery A full or partial match of the client versions's name .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-NectarSupportedClient -SearchQuery Skype .NOTES Version 1.1 #> [Alias("gnsc")] 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/aapi/supported/client/versions?q=$SearchQuery&tenant=$TenantName&pageSize=$ResultSize" Try { $JSON = Invoke-RestMethod -Method GET -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 $JSON.elements | Add-Member -TypeName 'Nectar.ClientList' Return $JSON.elements } Catch { Write-Error "Unable to get client version details." Get-JSONErrorStream -JSONResponse $_ } } } Function Set-NectarSupportedClient { <# .SYNOPSIS Update 1 or more Nectar DXP supported client versions. .DESCRIPTION Update 1 or more Nectar DXP supported client versions. .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER Identity The numerical identity of the supported client version. .EXAMPLE Set-NectarSupportedClient Identity 233 -Supported $FALSE Sets the device with identity 233 to unsupported .NOTES Version 1.1 #> [Alias("snsc")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias("version")] [string]$ClientVersion, [Alias("clientVersionSupported")] [bool]$Supported, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias("versionId")] [int]$Identity, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$Platform ) Begin { Connect-NectarCloud } Process { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If ($ClientVersion -and !$Identity) { $ClientInfo = Get-NectarSupportedClient -SearchQuery $ClientVersion -Tenant $TenantName -ResultSize 1 $ClientVersion = $ClientInfo.version If ($NULL -eq $Supported) {$Supported = $ClientInfo.clientVersionSupported} $Identity = $ClientInfo.versionId $Platform = $ClientInfo.platform } $URI = "https://$Global:NectarCloud/aapi/supported/client/version?versionName=$ClientVersion&tenant=$TenantName" $Body = @{ clientVersionSupported = $Supported platform = $Platform versionId = $Identity } $JSONBody = $Body | ConvertTo-Json Try { $NULL = Invoke-RestMethod -Method PUT -uri $URI -Headers $Global:NectarAuthHeader -Body $JSONBody -ContentType 'application/json; charset=utf-8' Write-Verbose $JSONBody } Catch { If ($ClientVersion) { $IDText = $ClientVersion } Else { $IDText = "with ID $Identity" } Write-Error "Unable to apply changes for client version $IDText." Get-JSONErrorStream -JSONResponse $_ } } } ################################################################################################################################################# # # # User 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $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 -WebSession $UserFilterSession } Else { $URI = "https://$Global:NectarCloud/dapi/user/search?q=$EncodedSearchQuery&pageSize=$ResultSize&tenant=$TenantName" $WebContent = Invoke-WebRequest -Method GET -uri $URI } 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." 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 !$TenantName) { $TenantName = $Global:NectarTenantName } 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." 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 oioioioioioioioioioioioioioio .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 !$TenantName) { $TenantName = $Global:NectarTenantName } 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" 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." Get-JSONErrorStream -JSONResponse $_ } } } } ################################################################################################################################################# # # # Session Detail Functions # # # ################################################################################################################################################# Function Set-NectarFilterParams { <# .SYNOPSIS Sets the filter parameters used for querying session data .DESCRIPTION Sets the filter parameters used for querying session data. This is called before any API used for filtering session data .OUTPUTS WebSession cookie to use in other JSON requests .EXAMPLE Set-NectarFilterParams .NOTES Version 1.1 #> [Alias("snfp")] Param ( [Parameter(Mandatory=$False)] [ValidateSet('LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM', IgnoreCase=$True)] [string]$TimePeriod = 'LAST_HOUR', [Parameter(Mandatory=$False)] [Alias("StartDateFrom")] [DateTime]$TimePeriodFrom, [Parameter(Mandatory=$False)] [Alias("StartDateTo")] [DateTime]$TimePeriodTo, [Parameter(Mandatory=$False)] [ValidateSet('GOOD','POOR_0_25','PARTIALLY_GOOD_25_50','PARTIALLY_GOOD_50_75','PARTIALLY_GOOD_75_100','UNAVAILABLE','UNKNOWN', IgnoreCase=$True)] [string[]]$SessionQualities, [Parameter(Mandatory=$False)] [string]$NectarScore, [Parameter(Mandatory=$False)] [ValidateRange(0,99999999)] [int]$DurationFrom, [Parameter(Mandatory=$False)] [ValidateRange(0,99999999)] [int]$DurationTo, [Parameter(Mandatory=$False)] [ValidateSet('AUDIO','VIDEO','APP_SHARING','IM','UNKNOWN','TOTAL','VBSS','REMOTE_ASSISTANCE','APP_INVITE','FOCUS','UNKNOWN', IgnoreCase=$True)] [string[]]$Modalities, [Parameter(Mandatory=$False)] [ValidateSet('TCP','UDP','Unknown', IgnoreCase=$False)] [string[]]$Protocols, [Parameter(Mandatory=$False)] [ValidateRange(0,608)] [string[]]$ResponseCodes, [Parameter(Mandatory=$False)] [ValidateSet('INTERNAL','EXTERNAL','FEDERATED','INTERNAL_EXTERNAL','EXTERNAL_INTERNAL','FEDERATED_INTERNAL','INTERNAL_FEDERATED','FEDERATED_EXTERNAL','EXTERNAL_FEDERATED','UNKNOWN', IgnoreCase=$True)] [string[]]$SessionScenarios, [Parameter(Mandatory=$False)] [ValidateSet('CONFERENCE','CONFERENCE_SESSION','PEER2PEER','PEER2PEER_MULTIMEDIA','PSTN','WEBINAR', IgnoreCase=$False)] [string[]]$SessionTypes, [Parameter(Mandatory=$False)] [string[]]$Codecs, [Parameter(Mandatory=$False)] [string[]]$CallerCodecs, [Parameter(Mandatory=$False)] [string[]]$CalleeCodecs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$Devices, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerDevices, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeDevices, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$DeviceVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerDeviceVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeDeviceVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$AgentVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerAgentVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeAgentVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ipaddress[]]$IPAddresses, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ipaddress[]]$CallerIPAddresses, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ipaddress[]]$CalleeIPAddresses, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$Locations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerLocations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeLocations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtISPs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtISPs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtISPs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('Cable','Dialup','DSL','FTTx','ISDN','Unknown','Wireless', IgnoreCase=$False)] [string[]]$ExtConnectionTypes, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('Cable','Dialup','DSL','FTTx','ISDN','Unknown','Wireless', IgnoreCase=$False)] [string[]]$CallerExtConnectionTypes, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('Cable','Dialup','DSL','FTTx','ISDN','Unknown','Wireless', IgnoreCase=$False)] [string[]]$CalleeExtConnectionTypes, [Parameter(Mandatory=$False)] [ValidateSet('CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED', IgnoreCase=$False)] [string[]]$NetworkTypes, [Parameter(Mandatory=$False)] [ValidateSet('CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED', IgnoreCase=$False)] [string[]]$CallerNetworkTypes, [Parameter(Mandatory=$False)] [ValidateSet('CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED', IgnoreCase=$False)] [string[]]$CalleeNetworkTypes, [Parameter(Mandatory=$False)] [ValidateSet('SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS', IgnoreCase=$True)] [string]$Platform, [Parameter(Mandatory=$False)] [ValidateSet('SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS', IgnoreCase=$True)] [string[]]$Platforms, [Parameter(Mandatory=$False)] [string[]]$Servers, [Parameter(Mandatory=$False)] [ValidateSet('AAEP','AAMS','medsvr','mgdsp','node','phone','SBC','sip','unknown', IgnoreCase=$True)] [string[]]$EndpointTypes, [Parameter(Mandatory=$False)] [ValidateSet('AAEP','AAMS','medsvr','mgdsp','node','phone','SBC','sip','unknown', IgnoreCase=$True)] [string[]]$CallerEndpointTypes, [Parameter(Mandatory=$False)] [ValidateSet('AAEP','AAMS','medsvr','mgdsp','node','phone','SBC','sip','unknown', IgnoreCase=$True)] [string[]]$CalleeEndpointTypes, [Parameter(Mandatory=$False)] [ValidateSet('EXTERNAL','EXTERNAL_FEDERATED','EXTERNAL_INTERNAL','FEDERATED','FEDERATED_EXTERNAL','FEDERATED_INTERNAL','INTERNAL','INTERNAL_EXTERNAL','INTERNAL_FEDERATED','UNKNOWN', IgnoreCase=$True)] [string[]]$Scenarios, [Parameter(Mandatory=$False)] [ValidateSet('External','Internal','Federated','Unknown', IgnoreCase=$True)] [string[]]$CallerScenarios, [Parameter(Mandatory=$False)] [ValidateSet('External','Internal','Federated','Unknown', IgnoreCase=$True)] [string[]]$CalleeScenarios, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$Users, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$FromUsers, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ToUsers, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$UserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$FromUserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ToUserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ConfOrganizers, [Parameter(Mandatory=$False)] [ValidateSet('TRUE','FALSE','UNKNOWN', IgnoreCase=$True)] [string[]]$VPN, [Parameter(Mandatory=$False)] [ValidateSet('TRUE','FALSE','UNKNOWN', IgnoreCase=$True)] [string[]]$CallerVPN, [Parameter(Mandatory=$False)] [ValidateSet('TRUE','FALSE','UNKNOWN', IgnoreCase=$True)] [string[]]$CalleeVPN, [Parameter(Mandatory=$False)] [ValidateRange(0,99999)] [int]$ParticipantsMinCount, [Parameter(Mandatory=$False)] [ValidateRange(0,99999)] [int]$ParticipantsMaxCount, [Parameter(Mandatory=$False)] [ValidateSet('BAD','POOR','FAIR','GOOD','EXCELLENT', IgnoreCase=$False)] [string[]]$FeedbackRating, [Parameter(Mandatory=$False)] [ValidateSet('NORMAL_SESSION','VOICE_MAIL','HIGH_JITTER','HIGH_PACKET_LOSS','HIGH_ROUNDTRIP_DELAY', IgnoreCase=$False)] [string[]]$Insights, [Parameter(Mandatory=$False)] [ValidateSet('P2P','PING','AUDIO','VIDEO', IgnoreCase=$False)] [string[]]$TestTypes, [Parameter(Mandatory=$False)] [ValidateSet('PASSED','FAILED','UNKNOWN','INCOMPLETE','INCOMPLETE', IgnoreCase=$False)] [string[]]$TestResults, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [ValidateSet('DEFAULT','USERS','ENDPOINT_CLIENT','CALL_DETAILS_HISTORIC','QUALITY_DETAILS', IgnoreCase=$True)] [string]$Scope = 'DEFAULT', [switch]$ShowQualityDetails ) Begin { Connect-NectarCloud } Process { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } # Convert to all-caps If ($Scope) { $Scope = $Scope.ToUpper() } If ($TimePeriod) { $TimePeriod = $TimePeriod.ToUpper() } $FilterParams = @{ 'TimePeriod' = $TimePeriod } # Convert any PowerShell array objects to comma-separated strings to add to the GET querystring # Convert multi-string variables into a comma-delimited list and add to the FilterParams array. For variables that need to be in upper-case $MultiVarListUpper = 'SessionQualities','SessionScenarios','SessionTypes','Modalities','Platforms','Scenarios', 'VPN','CallerVPN','CalleeVPN', 'FeedbackRating','Insights','TestTypes','TestResults' ForEach ($MultiVar in $MultiVarListUpper) { If ((Get-Variable $MultiVar).Value) { (Get-Variable $MultiVar).Value.ToUpper() | ForEach-Object { $MultiVarString += ($(if($MultiVarString){","}) + $_) } $FilterParams.Add($MultiVar,$MultiVarString) Write-Verbose "$MultiVar`: $MultiVarString" Remove-Variable MultiVarString } } # Convert multi-string variables into a comma-delimited list and add to the FilterParams array. For variables that do not need to be in upper-case $MultiVarList = 'Protocols','ResponseCodes','Servers', 'Codecs','CallerCodecs','CalleeCodecs', 'Devices','CallerDevices','CalleeDevices', 'DeviceVersions','CallerDeviceVersions','CalleeDeviceVersions', 'Locations','CallerLocations','CalleeLocations', 'ExtCities','CallerExtCities','CalleeExtCities', 'ExtCountries','CallerExtCountries','CalleeExtCountries', 'ExtISPs','CallerExtISPs','CalleeExtISPs', 'extConnectionTypes','callerExtConnectionTypes','calleeExtConnectionTypes', 'NetworkTypes','CallerNetworkTypes','CalleeNetworkTypes', 'ipAddresses','callerIpAddresses','calleeIpAddresses', 'endpointTypes','callerEndpointTypes','calleeEndpointTypes' ForEach ($MultiVar in $MultiVarList) { If ((Get-Variable $MultiVar).Value) { (Get-Variable $MultiVar).Value | ForEach-Object { [string]$MultiVarString += ($(if([string]$MultiVarString){","}) + $_) } # Update name of parameters if necessary Switch ($MultiVar) { 'Servers' { $MultiVar = 'platformServersOrDataCenters' } } $FilterParams.Add($MultiVar,$MultiVarString) Write-Verbose "$MultiVar`: $MultiVarString" Remove-Variable MultiVarString } } # Get user IDs and convert into a comma-delimited list and add to the FilterParams array. $UserVarList = 'Users','FromUsers','ToUsers' ForEach ($UserVar in $UserVarList) { If ((Get-Variable $UserVar).Value) { $UserList = (Get-Variable $UserVar).Value $UserIDList = @() $PlatformUserNames = @() ForEach ($User in $UserList) { Write-Verbose "UserSearch: $User" $UserInfo = Get-NectarUser $User -Scope $Scope -TenantName $TenantName -FilterSearch -ResultSize 10000 If ($UserInfo.id) { $UserIDList += $UserInfo.id } ElseIf ($UserInfo.platformUserName) { $PlatformUserNames += $UserInfo.platformUserName } } If ($UserIDList) { $UserIDList | ForEach-Object { $UserIDString += ($(if($UserIDString){","}) + $_) } $FilterParams.Add($UserVar,$UserIDString) Write-Verbose "UserID $UserVar`: $UserIDString" Remove-Variable UserIDString } If ($PlatformUserNames) { $PlatformUserNames | ForEach-Object { $PlatformUserNameString += ($(if($PlatformUserNameString){","}) + $_) } $FilterParams.Add($UserVar.Replace('Users','PlatformUserNames'),$PlatformUserNameString) Write-Verbose "Platform $UserVar`: $PlatformUserNameString" Remove-Variable PlatformUserNameString } If (!$UserIDList -And !$PlatformUserNames) { Write-Error 'No matching users found'; Return } } } # Do the same for UserIDs $UserIDVarList = 'UserIDs','FromUserIDs','ToUserIDs' ForEach ($UserIDVar in $UserIDVarList) { If ((Get-Variable $UserIDVar).Value) { $UserIDList = (Get-Variable $UserIDVar).Value If ($UserIDList) { $UserIDList | ForEach-Object { $UserIDString += ($(if($UserIDString){","}) + $_) } $FilterParams.Add($UserIDVar.Replace('ID',''),$UserIDString) Write-Verbose "UserID $($UserIDVar.Replace('ID',''))`: $UserIDString" Remove-Variable UserIDString } } } # Get agent versions and convert into a comma-delimited list and add to the FilterParams array. $AgentVarList = 'AgentVersions','CallerAgentVersions','CalleeAgentVersions' ForEach ($AgentVar in $AgentVarList) { If ((Get-Variable $AgentVar).Value) { $AgentList = (Get-Variable $AgentVar).Value # Parse through each entry and search for results $FinalAgentList = @() ForEach ($Agent in $AgentList) { Write-Verbose "AgentSearch: $Agent" $AgentInfo = Get-NectarAgentVersion -SearchQuery $Agent -TenantName $TenantName If ($AgentInfo) { $FinalAgentList += $AgentInfo } } # Convert to comma-delimited list and add to FilterParams array If ($FinalAgentList) { $FinalAgentList | ForEach-Object { $AgentString += ($(if($AgentString){","}) + $_) } # Make var match with the API (ie replace AgentVersions with AgentVersion) $FilterParams.Add($AgentVar.Substring(0,$AgentVar.Length-1),$AgentString) Write-Verbose "Agent $AgentVar.Substring(0,$AgentVar.Length-1)`: $AgentString" Remove-Variable AgentString } If (!$FinalAgentList) { Write-Error 'No matching agent versions found'; Return } } } # Add single parameter variables to the FilterParams array. $VarList = 'NectarScore','DurationFrom','DurationTo','ParticipantsMinCount','ParticipantsMaxCount' ForEach ($Var in $VarList) { If ((Get-Variable $Var).Value) { $FilterParams.Add($Var,(Get-Variable $Var).Value) Write-Verbose "$Var`: (Get-Variable $Var).Value" } } # Add time-based parameter variables to the FilterParams array. This converts to UNIX timestamp $TimeVarList = 'TimePeriodFrom','TimePeriodTo' # Set TimePeriodTo to NOW if not explicitly set If ($TimePeriodFrom -And !$TimePeriodTo) { [String]$TimePeriodTo = Get-Date } ForEach ($TimeVar in $TimeVarList) { If ((Get-Variable $TimeVar).Value) { [decimal]$TimePeriodUnix = Get-Date -Date (Get-Variable $TimeVar).Value -UFormat %s [long]$TimePeriodUnix = $TimePeriodUnix * 1000 $FilterParams.Add($TimeVar.Replace('TimePeriod','StartDate'),$TimePeriodUnix) Write-Verbose "$TimeVar`: $TimePeriodUnix" } } If ($ConfOrganizers) { $ConfOrganizerIDs = ForEach($Organizer in $ConfOrganizers) { (Get-NectarUser $Organizer -Scope $Scope -TenantName $TenantName -ErrorAction:Stop).Userid } $ConfOrganizerIDs | ForEach-Object { $ConfOrganizerIDsStr += ($(if($ConfOrganizerIDsStr){","}) + $_) } $FilterParams.Add('organizersOrSpaces',$ConfOrganizerIDsStr) } If ($ShowQualityDetails) { $FilterParams.Add('sessionQualitySources','CDS,CDR_CDS') $FilterParams.Add('excludeIncompleteRecords','true') } Try { # Run the filter POST and obtain the session cookie for the GET $URI = "https://$Global:NectarCloud/dapi/filter/apply?scope=$Scope&tenant=$TenantName" $NULL = Invoke-WebRequest -Method POST -Headers $Global:NectarAuthHeader -uri $URI -Body $FilterParams -UseBasicParsing -SessionVariable Session $Cookie = $Session.Cookies.GetCookies($URI) | Where-Object {$_.Name -eq 'SESSION'} $FilterSession = New-Object Microsoft.PowerShell.Commands.WebRequestSession $FilterSession.Cookies.Add($Cookie) Write-Verbose "Successfully set filter parameters." Write-Verbose $Uri ForEach($k in $FilterParams.Keys){ Write-Verbose "$k`: $($FilterParams[$k])" } Return $FilterSession } Catch { Write-Error "Unable to set filter parameters." (Get-JSONErrorStream -JSONResponse $_).Replace("startDate","TimePeriod") } } } Function Get-NectarSession { <# .SYNOPSIS Returns all session information. .DESCRIPTION Returns all session information as presented on the SESSION LIST section of the CALL DETAILS page UI_ELEMENT .PARAMETER TimePeriod The time period to show session data from. Select from 'LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM'. CUSTOM requires using TimePeriodFrom and TimePeriodTo parameters. .PARAMETER TimePeriodFrom The earliest date/time to show session data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodTo parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. .PARAMETER TimePeriodTo The latest date/time to show session data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodFrom parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. .PARAMETER SessionQualities Show sessions that match a given quality rating. Case sensitive. Choose one or more from: 'GOOD','POOR_0_25','PARTIALLY_GOOD_25_50','PARTIALLY_GOOD_50_75','PARTIALLY_GOOD_75_100','UNAVAILABLE','UNKNOWN' .PARAMETER NectarScore Show sessions that match the given range of NectarScores. Use format aa-xx or aa.bb-xx.yy, where aa is less than xx, and xx is less than 100. Eg. 70-90 or 95.00-95.59 .PARAMETER DurationFrom The shortest call length (in seconds) to show session data. .PARAMETER DurationTo The longest call length (in seconds) to show session data. .PARAMETER Modalities Show sessions that match one or more modality types. Not case sensitive. Choose one or more from: 'AUDIO','VIDEO','APP_SHARING','IM','UNKNOWN','TOTAL','VBSS','REMOTE_ASSISTANCE','APP_INVITE','FOCUS','UNKNOWN' .PARAMETER Protocols Show sessions that match one or more network protocol types. Case sensitive. Choose one or more from: 'TCP','UDP','Unknown' .PARAMETER ResponseCodes Show sessions that match one or more SIP response codes. Accepts numbers from 200 to 699 .PARAMETER SessionScenarios Show sessions that match one or more session scenarios. Not case sensitive. Choose one or more from: 'External','Internal','Internal-External','External-Internal','Federated','Internal-Federated','External-Federated','Unknown' .PARAMETER SessionTypes Show sessions that match one or more session scenarios. Case sensitive. Choose one or more from: 'Conference','Peer To Peer','Peer To Peer (Multimedia)','PSTN/External' .PARAMETER Codecs Show sessions where the selected codec was used by either caller or callee. Can query for multiple codecs. Case sensitive. Use Get-NectarCodecs for a list of valid codecs. .PARAMETER CallerCodecs Show sessions where the selected codec was used by the caller. Can query for multiple codecs. Case sensitive. Use Get-NectarCodecs for a list of valid codecs. .PARAMETER CalleeCodecs Show sessions where the selected codec was used by the callee. Can query for multiple codecs. Case sensitive. Use Get-NectarCodecs for a list of valid codecs. .PARAMETER Devices Show sessions where the selected device was used by either caller or callee. Can query for multiple devices. Case sensitive. Use Get-NectarSupportedDevice for a list of valid devices. .PARAMETER CallerDevices Show sessions where the selected device was used by the caller. Can query for multiple devices. Case sensitive. Use Get-NectarSupportedDevice for a list of valid devices. .PARAMETER CalleeDevices Show sessions where the selected device was used by the callee. Can query for multiple devices. Case sensitive. Use Get-NectarSupportedDevice for a list of valid devices. .PARAMETER DeviceVersions Show sessions where the selected device version was used by either caller or callee. Can query for multiple devices. Case sensitive. Use Get-NectarClientVersion for a list of valid client versions. .PARAMETER CallerDeviceVersions Show sessions where the selected device version was used by the caller. Can query for multiple devices. Case sensitive. Use Get-NectarClientVersion for a list of valid client versions. .PARAMETER CalleeDeviceVersions Show sessions where the selected device version was used by the callee. Can query for multiple devices. Case sensitive. Use Get-NectarClientVersion for a list of valid client versions. .PARAMETER AgentVersions Show sessions where the selected agent version was used by either caller or callee. Can query for multiple agent. Case sensitive. Use Get-NectarAgentVersion for a list of valid agent versions. .PARAMETER CallerAgentVersions Show sessions where the selected agent version was used by either caller or callee. Can query for multiple agent. Case sensitive. Use Get-NectarAgentVersion for a list of valid agent versions. .PARAMETER CalleeAgentVersions Show sessions where the selected agent version was used by either caller or callee. Can query for multiple agent. Case sensitive. Use Get-NectarAgentVersion for a list of valid agent versions. .PARAMETER IPAddresses Show sessions where the selected IP address was used by either caller or callee. Can query for multiple IPs. .PARAMETER CallerIPAddresses Show sessions where the selected IP address was used by the caller. Can query for multiple IPs. .PARAMETER CalleeIPAddresses Show sessions where the selected IP address was used by the callee. Can query for multiple IPs. .PARAMETER Locations Show sessions where the selected location was used by either caller or callee. Can query for multiple locations. .PARAMETER CallerLocations Show sessions where the selected location was used by the caller. Can query for multiple locations. .PARAMETER CalleeLocations Show sessions where the selected location was used by the callee. Can query for multiple locations. .PARAMETER ExtCities Show sessions where the caller or callee was located in the selected city (as detected via geolocating the user's external IP address). Can query for multiple cities. .PARAMETER CallerExtCities Show sessions where the caller was located in the selected city (as detected via geolocating the user's external IP address). Can query for multiple cities. .PARAMETER CalleeExtCities Show sessions where the callee was located in the selected city (as detected via geolocating the user's external IP address). Can query for multiple cities. .PARAMETER ExtCountries Show sessions where the caller or callee was located in the selected country (as detected via geolocating the user's external IP address). Can query for multiple countries. .PARAMETER CallerExtCountries Show sessions where the caller was located in the selected country (as detected via geolocating the user's external IP address). Can query for multiple countries. .PARAMETER CalleeExtCountries Show sessions where the callee was located in the selected country (as detected via geolocating the user's external IP address). Can query for multiple countries. .PARAMETER ExtISPs Show sessions where the caller or callee was located in the selected ISP (as detected via geolocating the user's external IP address). Can query for multiple ISPs. .PARAMETER CallerExtISPs Show sessions where the caller was located in the selected ISP (as detected via geolocating the user's external IP address). Can query for multiple ISPs. .PARAMETER CalleeExtISPs Show sessions where the callee was located in the selected ISP (as detected via geolocating the user's external IP address). Can query for multiple ISPs. .PARAMETER ExtConnectionTypes Show sessions that match the caller or callee external connection type (as detected via geolocating the user's external IP address). Can query for multiple types. .PARAMETER CallerExtConnectionTypes Show sessions that match the caller's external connection type (as detected via geolocating the user's external IP address). Can query for multiple types. .PARAMETER CalleeExtConnectionTypes Show sessions that match the callee's external connection type (as detected via geolocating the user's external IP address). Can query for multiple types. .PARAMETER NetworkTypes Show sessions where the selected network type was used by either caller or callee. Can query for multiple network types. Case sensitive. Choose one or more from: 'CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED' .PARAMETER CallerNetworkTypes Show sessions where the selected network type was used by the caller. Can query for multiple network types. Case sensitive. Choose one or more from: 'CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED' .PARAMETER CalleeNetworkTypes Show sessions where the selected network type was used by the callee. Can query for multiple network types. Case sensitive. Choose one or more from: 'CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED' .PARAMETER Platforms Show sessions where the selected platform was used by either caller or callee. Can query for multiple platforms. Case sensitive. Choose one or more from: 'SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS' .PARAMETER EndpointTypes Show sessions where one or more selected Avaya endpoint types have been used. Choose one or more from: 'medsvr','mgdsp','node','phone','unknown' .PARAMETER CallerEndpointTypes Show sessions where one or more selected Avaya endpoint types have been used by the caller. Choose one or more from: 'medsvr','mgdsp','node','phone','unknown' .PARAMETER CalleeEndpointTypes Show sessions where one or more selected Avaya endpoint types have been used by the callee. Choose one or more from: 'medsvr','mgdsp','node','phone','unknown' .PARAMETER Users Show sessions where the selected user was either caller or callee. Can query for multiple users. .PARAMETER FromUsers Show sessions where the selected user was the caller. Can query for multiple users. .PARAMETER ToUsers Show sessions where the selected user was the callee. Can query for multiple users. .PARAMETER UserIDs Show sessions where the selected user ID was either caller or callee. Can query for multiple user IDs. .PARAMETER FromUserIDs Show sessions where the selected user ID was the caller. Can query for multiple user IDs. .PARAMETER ToUserIDs Show sessions where the selected user ID was the callee. Can query for multiple user IDs. .PARAMETER ConfOrganizers Show sessions hosted by a specified conference organizer. Can query for multiple organizers. .PARAMETER VPN Show sessions where the selected VPN was used by either caller or callee. .PARAMETER CallerVPN Show sessions where the selected VPN was used by the caller. .PARAMETER CalleeVPN Show sessions where the selected VPN was used by the callee. .PARAMETER ParticipantsMinCount Show sessions where the number of participants is greater than or equal to the entered value .PARAMETER ParticipantsMaxCount Show sessions where the number of participants is less than or equal to the entered value .PARAMETER FeedbackRating Show sessions where users provided specific feedback ratings from BAD to EXCELLENT. Allowed values are BAD, POOR, FAIR, GOOD, EXCELLENT. Corresponds to ratings from 1 to 5 stars. .PARAMETER Insights Show sessions that match one or more given insights. Choose from NORMAL_SESSION, VOICE_MAIL, HIGH_JITTER, HIGH_PACKET_LOSS, HIGH_ROUNDTRIP_DELAY .PARAMETER OrderByField Sort the output by the selected field .PARAMETER OrderDirection Sort direction. Use with OrderByField. Not case sensitive. Choose from: ASC, DESC .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 1000 .PARAMETER ResultSize The total number of results to return. Defaults to 1000. Maximum result size is 9,999,999 results .EXAMPLE Get-NectarSession -TimePeriod LAST_HOUR -Platforms TEAMS -Modalities AUDIO -NectarScore 50-70 Returns a list of all Teams audio sessions for the past hour where the NectarScore is between 50% and 70% .EXAMPLE (Get-NectarSession -SessionTypes CONFERENCE -TimePeriod CUSTOM -TimePeriodFrom '2021-05-06' -TimePeriodTo '2021-05-07').Count Returns a count of all conferences between May 6 and 7 (all times/dates UTC) .EXAMPLE Get-NectarSession -SessionTypes PEER2PEER,PEER2PEER_MULTIMEDIA -TimePeriod CUSTOM -TimePeriodFrom '2021-05-06 14:00' -TimePeriodTo '2021-05-06 15:00' Returns a list of all P2P calls between 14:00 and 15:00 UTC on May 6 .EXAMPLE Get-NectarSession -TimePeriod LAST_WEEK -SessionTypes CONFERENCE | Select-Object confOrganizerOrSpace | Group-Object confOrganizerOrSpace | Select-Object Name, Count | Sort-Object Count -Descending Returns a list of conference organizers and a count of the total conferences organized by each, sorted by count. .NOTES Version 1.2 #> [Alias('gns', 'Get-NectarSessions')] [CmdletBinding(PositionalBinding=$False)] Param ( [Parameter(Mandatory=$False)] [ValidateSet('LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM', IgnoreCase=$True)] [string]$TimePeriod = 'LAST_HOUR', [Parameter(Mandatory=$False)] [Alias("StartDateFrom")] [DateTime]$TimePeriodFrom, [Parameter(Mandatory=$False)] [Alias("StartDateTo")] [DateTime]$TimePeriodTo, [Parameter(Mandatory=$False)] [ValidateSet('GOOD','POOR_0_25','PARTIALLY_GOOD_25_50','PARTIALLY_GOOD_50_75','PARTIALLY_GOOD_75_100','UNAVAILABLE','UNKNOWN', IgnoreCase=$True)] [string[]]$SessionQualities, [Parameter(Mandatory=$False)] [ValidateScript ({ If ($_ -Match '^\d?\d(.\d+)?\-(\d?\d|100)(.\d+)?$') { $True } Else { Throw 'NectarScore must be in the format aa-xx (ie. 80-90) or aa.bb-xx.yy (ie 77.32-80.99), where aa is less than xx and xx cannot be greater than 100.' } })] [string]$NectarScore, [Parameter(Mandatory=$False)] [ValidateRange(0,99999999)] [int]$DurationFrom = 0, [Parameter(Mandatory=$False)] [ValidateRange(0,99999999)] [int]$DurationTo = 99999999, [Parameter(Mandatory=$False)] [ValidateSet('AUDIO','VIDEO','APP_SHARING','IM','UNKNOWN','TOTAL','VBSS','REMOTE_ASSISTANCE','APP_INVITE','FOCUS','UNKNOWN', IgnoreCase=$True)] [string[]]$Modalities, [Parameter(Mandatory=$False)] [ValidateSet('TCP','UDP','Unknown', IgnoreCase=$False)] [string[]]$Protocols, [Parameter(Mandatory=$False)] [ValidateRange(200,699)] [string[]]$ResponseCodes, [Parameter(Mandatory=$False)] [ValidateSet('INTERNAL','EXTERNAL','FEDERATED','INTERNAL_EXTERNAL','EXTERNAL_INTERNAL','FEDERATED_INTERNAL','INTERNAL_FEDERATED','FEDERATED_EXTERNAL','EXTERNAL_FEDERATED','UNKNOWN', IgnoreCase=$True)] [string[]]$SessionScenarios, [Parameter(Mandatory=$False)] [ValidateSet('CONFERENCE','CONFERENCE_SESSION','PEER2PEER','PEER2PEER_MULTIMEDIA','PSTN','WEBINAR', IgnoreCase=$False)] [string[]]$SessionTypes, [Parameter(Mandatory=$False)] [string[]]$Codecs, [Parameter(Mandatory=$False)] [string[]]$CallerCodecs, [Parameter(Mandatory=$False)] [string[]]$CalleeCodecs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$Devices, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerDevices, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeDevices, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$DeviceVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerDeviceVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeDeviceVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$AgentVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerAgentVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeAgentVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ipaddress[]]$IPAddresses, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ipaddress[]]$CallerIPAddresses, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ipaddress[]]$CalleeIPAddresses, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$Locations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerLocations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeLocations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtISPs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtISPs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtISPs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('Cable','Dialup','DSL','FTTx','ISDN','Unknown','Wireless', IgnoreCase=$False)] [string[]]$ExtConnectionTypes, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('Cable','Dialup','DSL','FTTx','ISDN','Unknown','Wireless', IgnoreCase=$False)] [string[]]$CallerExtConnectionTypes, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('Cable','Dialup','DSL','FTTx','ISDN','Unknown','Wireless', IgnoreCase=$False)] [string[]]$CalleeExtConnectionTypes, [Parameter(Mandatory=$False)] [ValidateSet('CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED', IgnoreCase=$False)] [string[]]$NetworkTypes, [Parameter(Mandatory=$False)] [ValidateSet('CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED', IgnoreCase=$False)] [string[]]$CallerNetworkTypes, [Parameter(Mandatory=$False)] [ValidateSet('CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED', IgnoreCase=$False)] [string[]]$CalleeNetworkTypes, [Parameter(Mandatory=$False)] [ValidateSet('SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS', IgnoreCase=$True)] [string[]]$Platforms, [Parameter(Mandatory=$False)] [string[]]$Servers, [Parameter(Mandatory=$False)] [ValidateSet('AAEP','AAMS','medsvr','mgdsp','node','phone','SBC','sip','unknown', IgnoreCase=$True)] [string[]]$EndpointTypes, [Parameter(Mandatory=$False)] [ValidateSet('AAEP','AAMS','medsvr','mgdsp','node','phone','SBC','sip','unknown', IgnoreCase=$True)] [string[]]$CallerEndpointTypes, [Parameter(Mandatory=$False)] [ValidateSet('AAEP','AAMS','medsvr','mgdsp','node','phone','SBC','sip','unknown', IgnoreCase=$True)] [string[]]$CalleeEndpointTypes, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$Users, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$FromUsers, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ToUsers, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$UserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$FromUserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ToUserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ConfOrganizers, [Parameter(Mandatory=$False)] [ValidateSet('TRUE','FALSE','UNKNOWN', IgnoreCase=$True)] [string[]]$VPN, [Parameter(Mandatory=$False)] [ValidateSet('TRUE','FALSE','UNKNOWN', IgnoreCase=$True)] [string[]]$CallerVPN, [Parameter(Mandatory=$False)] [ValidateSet('TRUE','FALSE','UNKNOWN', IgnoreCase=$True)] [string[]]$CalleeVPN, [Parameter(Mandatory=$False)] [ValidateRange(0,99999)] [int]$ParticipantsMinCount, [Parameter(Mandatory=$False)] [ValidateRange(0,99999)] [int]$ParticipantsMaxCount, [Parameter(Mandatory=$False)] [ValidateSet('BAD','POOR','FAIR','GOOD','EXCELLENT', IgnoreCase=$False)] [string[]]$FeedbackRating, [Parameter(Mandatory=$False)] [ValidateSet('NORMAL_SESSION','VOICE_MAIL','HIGH_JITTER','HIGH_PACKET_LOSS','HIGH_ROUNDTRIP_DELAY', IgnoreCase=$False)] [string[]]$Insights, [switch]$ShowQualityDetails, [Parameter(Mandatory=$False)] [string]$OrderByField, [Parameter(Mandatory=$False)] [ValidateSet('ASC','DESC', IgnoreCase=$True)] [string]$OrderDirection, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [ValidateSet('DEFAULT','USERS','ENDPOINT_CLIENT','CALL_DETAILS_HISTORIC','QUALITY_DETAILS', IgnoreCase=$True)] [string]$Scope = 'CALL_DETAILS_HISTORIC', [Parameter(Mandatory=$False)] [ValidateRange(1,10000)] [int]$PageSize = 1000, [Parameter(Mandatory=$False)] [ValidateRange(1,50000)] [int]$ResultSize ) Begin { # 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 !$TenantName) { $TenantName = $Global:NectarTenantName } If ($PageSize) { $PSBoundParameters.Remove('PageSize') | Out-Null } If ($ResultSize) { $PSBoundParameters.Remove('ResultSize') | Out-Null } If ($OrderByField) { $PSBoundParameters.Remove('OrderByField') | Out-Null } If ($OrderDirection) { $PSBoundParameters.Remove('OrderDirection') | Out-Null } $FilterSession = Set-NectarFilterParams @PsBoundParameters $URI = "https://$Global:NectarCloud/dapi/session" Write-Verbose $URI # 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 = @{ 'pageSize' = $ResultSize } } Else { $Params = @{ 'pageSize' = $PageSize } } If ($OrderByField) { $Params.Add('OrderByField',$OrderByField) } If ($OrderDirection) { $Params.Add('OrderDirection',$OrderDirection) } If ($TenantName) { $Params.Add('Tenant',$TenantName) } # Return results in pages # Try { $WebContent = Invoke-WebRequest -Method GET -Uri $URI -Headers $Global:NectarAuthHeader -Body $Params -WebSession $FilterSession $JSON = ([System.Text.Encoding]::UTF8).GetString($TextEncoding.GetBytes($WebContent.Content)) | ConvertFrom-Json #$JSON = Invoke-RestMethod -Method GET -Uri $URI -Headers $Global:NectarAuthHeader -Body $Params -WebSession $FilterSession $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" $PagedURI = $URI + "?pageNumber=$PageNum" $WebContent = Invoke-WebRequest -Method GET -uri $PagedURI -Headers $Global:NectarAuthHeader -Body $Params -WebSession $FilterSession $JSON = ([System.Text.Encoding]::UTF8).GetString($TextEncoding.GetBytes($WebContent.Content)) | ConvertFrom-Json If ($TenantName) { $JSON.elements | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty } $JSON.elements $PageNum++ } } # } # Catch { # Write-Error "No results. Try specifying a less-restrictive filter" # Get-JSONErrorStream -JSONResponse $_ # } } } Function Get-NectarSessionCount { <# .SYNOPSIS Returns session counts broken down by hour/day (depending on selected time filter) for a given timeframe. Includes breakdown by quality and NectarScore by hour/day. .DESCRIPTION Returns session counts broken down by hour/day (depending on selected time filter) for a given timeframe. This returns the numbers used to build the chart in the SESSIONS section of the CALL DETAILS screen UI_ELEMENT .PARAMETER TimePeriod The time period to show session data from. Select from 'LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM'. CUSTOM requires using TimePeriodFrom and TimePeriodTo parameters. .PARAMETER TimePeriodFrom The earliest date/time to show session data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodTo parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. .PARAMETER TimePeriodTo The latest date/time to show session data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodFrom parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. .PARAMETER SessionQualities Show sessions that match a given quality rating. Case sensitive. Choose one or more from: 'GOOD','POOR_0_25','PARTIALLY_GOOD_25_50','PARTIALLY_GOOD_50_75','PARTIALLY_GOOD_75_100','UNAVAILABLE','UNKNOWN' .PARAMETER NectarScore Show sessions that match the given range of NectarScores. Use format aa-xx or aa.bb-xx.yy, where aa is less than xx, and xx is less than 100. Eg. 70-90 or 95.00-95.59 .PARAMETER DurationFrom The shortest call length (in seconds) to show session data. .PARAMETER DurationTo The longest call length (in seconds) to show session data. .PARAMETER Modalities Show sessions that match one or more modality types. Not case sensitive. Choose one or more from: 'AUDIO','VIDEO','APP_SHARING','IM','UNKNOWN','TOTAL','VBSS','REMOTE_ASSISTANCE','APP_INVITE','FOCUS','UNKNOWN' .PARAMETER Protocols Show sessions that match one or more network protocol types. Case sensitive. Choose one or more from: 'TCP','UDP','Unknown' .PARAMETER ResponseCodes Show sessions that match one or more SIP response codes. Accepts numbers from 200 to 699 .PARAMETER SessionScenarios Show sessions that match one or more session scenarios. Not case sensitive. Choose one or more from: 'External','Internal','Internal-External','External-Internal','Federated','Internal-Federated','External-Federated','Unknown' .PARAMETER SessionTypes Show sessions that match one or more session scenarios. Case sensitive. Choose one or more from: 'Conference','Peer To Peer','Peer To Peer (Multimedia)','PSTN/External' .PARAMETER Codecs Show sessions where the selected codec was used by either caller or callee. Can query for multiple codecs. Case sensitive. Use Get-NectarCodecs for a list of valid codecs. .PARAMETER CallerCodecs Show sessions where the selected codec was used by the caller. Can query for multiple codecs. Case sensitive. Use Get-NectarCodecs for a list of valid codecs. .PARAMETER CalleeCodecs Show sessions where the selected codec was used by the callee. Can query for multiple codecs. Case sensitive. Use Get-NectarCodecs for a list of valid codecs. .PARAMETER Devices Show sessions where the selected device was used by either caller or callee. Can query for multiple devices. Case sensitive. Use Get-NectarSupportedDevice for a list of valid devices. .PARAMETER CallerDevices Show sessions where the selected device was used by the caller. Can query for multiple devices. Case sensitive. Use Get-NectarSupportedDevice for a list of valid devices. .PARAMETER CalleeDevices Show sessions where the selected device was used by the callee. Can query for multiple devices. Case sensitive. Use Get-NectarSupportedDevice for a list of valid devices. .PARAMETER DeviceVersions Show sessions where the selected device version was used by either caller or callee. Can query for multiple devices. Case sensitive. Use Get-NectarClientVersion for a list of valid client versions. .PARAMETER CallerDeviceVersions Show sessions where the selected device version was used by the caller. Can query for multiple devices. Case sensitive. Use Get-NectarClientVersion for a list of valid client versions. .PARAMETER CalleeDeviceVersions Show sessions where the selected device version was used by the callee. Can query for multiple devices. Case sensitive. Use Get-NectarClientVersion for a list of valid client versions. .PARAMETER AgentVersions Show sessions where the selected agent version was used by either caller or callee. Can query for multiple agent. Case sensitive. Use Get-NectarAgentVersion for a list of valid agent versions. .PARAMETER CallerAgentVersions Show sessions where the selected agent version was used by either caller or callee. Can query for multiple agent. Case sensitive. Use Get-NectarAgentVersion for a list of valid agent versions. .PARAMETER CalleeAgentVersions Show sessions where the selected agent version was used by either caller or callee. Can query for multiple agent. Case sensitive. Use Get-NectarAgentVersion for a list of valid agent versions. .PARAMETER IPAddresses Show sessions where the selected IP address was used by either caller or callee. Can query for multiple IPs. .PARAMETER CallerIPAddresses Show sessions where the selected IP address was used by the caller. Can query for multiple IPs. .PARAMETER CalleeIPAddresses Show sessions where the selected IP address was used by the callee. Can query for multiple IPs. .PARAMETER Locations Show sessions where the selected location was used by either caller or callee. Can query for multiple locations. .PARAMETER CallerLocations Show sessions where the selected location was used by the caller. Can query for multiple locations. .PARAMETER CalleeLocations Show sessions where the selected location was used by the callee. Can query for multiple locations. .PARAMETER ExtCities Show sessions where the caller or callee was located in the selected city (as detected via geolocating the user's external IP address). Can query for multiple cities. .PARAMETER CallerExtCities Show sessions where the caller was located in the selected city (as detected via geolocating the user's external IP address). Can query for multiple cities. .PARAMETER CalleeExtCities Show sessions where the callee was located in the selected city (as detected via geolocating the user's external IP address). Can query for multiple cities. .PARAMETER ExtCountries Show sessions where the caller or callee was located in the selected country (as detected via geolocating the user's external IP address). Can query for multiple countries. .PARAMETER CallerExtCountries Show sessions where the caller was located in the selected country (as detected via geolocating the user's external IP address). Can query for multiple countries. .PARAMETER CalleeExtCountries Show sessions where the callee was located in the selected country (as detected via geolocating the user's external IP address). Can query for multiple countries. .PARAMETER ExtISPs Show sessions where the caller or callee was located in the selected ISP (as detected via geolocating the user's external IP address). Can query for multiple ISPs. .PARAMETER CallerExtISPs Show sessions where the caller was located in the selected ISP (as detected via geolocating the user's external IP address). Can query for multiple ISPs. .PARAMETER CalleeExtISPs Show sessions where the callee was located in the selected ISP (as detected via geolocating the user's external IP address). Can query for multiple ISPs. .PARAMETER ExtConnectionTypes Show sessions that match the caller or callee external connection type (as detected via geolocating the user's external IP address). Can query for multiple types. .PARAMETER CallerExtConnectionTypes Show sessions that match the caller's external connection type (as detected via geolocating the user's external IP address). Can query for multiple types. .PARAMETER CalleeExtConnectionTypes Show sessions that match the callee's external connection type (as detected via geolocating the user's external IP address). Can query for multiple types. .PARAMETER NetworkTypes Show sessions where the selected network type was used by either caller or callee. Can query for multiple network types. Case sensitive. Choose one or more from: 'CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED' .PARAMETER CallerNetworkTypes Show sessions where the selected network type was used by the caller. Can query for multiple network types. Case sensitive. Choose one or more from: 'CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED' .PARAMETER CalleeNetworkTypes Show sessions where the selected network type was used by the callee. Can query for multiple network types. Case sensitive. Choose one or more from: 'CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED' .PARAMETER Platforms Show sessions where the selected platform was used by either caller or callee. Can query for multiple platforms. Case sensitive. Choose one or more from: 'SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS' .PARAMETER EndpointTypes Show sessions where one or more selected Avaya endpoint types have been used. Choose one or more from: 'medsvr','mgdsp','node','phone','unknown' .PARAMETER CallerEndpointTypes Show sessions where one or more selected Avaya endpoint types have been used by the caller. Choose one or more from: 'medsvr','mgdsp','node','phone','unknown' .PARAMETER CalleeEndpointTypes Show sessions where one or more selected Avaya endpoint types have been used by the callee. Choose one or more from: 'medsvr','mgdsp','node','phone','unknown' .PARAMETER Users Show sessions where the selected user was either caller or callee. Can query for multiple users. .PARAMETER FromUsers Show sessions where the selected user was the caller. Can query for multiple users. .PARAMETER ToUsers Show sessions where the selected user was the callee. Can query for multiple users. .PARAMETER UserIDs Show sessions where the selected user ID was either caller or callee. Can query for multiple user IDs. .PARAMETER FromUserIDs Show sessions where the selected user ID was the caller. Can query for multiple user IDs. .PARAMETER ToUserIDs Show sessions where the selected user ID was the callee. Can query for multiple user IDs. .PARAMETER ConfOrganizers Show sessions hosted by a specified conference organizer. Can query for multiple organizers. .PARAMETER VPN Show sessions where the selected VPN was used by either caller or callee. .PARAMETER CallerVPN Show sessions where the selected VPN was used by the caller. .PARAMETER CalleeVPN Show sessions where the selected VPN was used by the callee. .PARAMETER ParticipantsMinCount Show sessions where the number of participants is greater than or equal to the entered value .PARAMETER ParticipantsMaxCount Show sessions where the number of participants is less than or equal to the entered value .PARAMETER FeedbackRating Show sessions where users provided specific feedback ratings from BAD to EXCELLENT. Allowed values are BAD, POOR, FAIR, GOOD, EXCELLENT. Corresponds to ratings from 1 to 5 stars. .PARAMETER Insights Show sessions that match one or more given insights. Choose from NORMAL_SESSION, VOICE_MAIL, HIGH_JITTER, HIGH_PACKET_LOSS, HIGH_ROUNDTRIP_DELAY .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarSessionCount -TimePeriod LAST_DAY Returns an hour-by-hour count of the number of sessions occuring over the past day along with quality breakdown .EXAMPLE Get-NectarSessionCount -TimePeriod LAST_WEEK -Platform TEAMS Returns an day-by-day count of the number of sessions occuring over the past week along with quality breakdown for MS Teams .NOTES Version 1.0 #> [Alias("Get-NectarSessionHistogram")] Param ( [Parameter(Mandatory=$False)] [ValidateSet('LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM', IgnoreCase=$True)] [string]$TimePeriod = 'LAST_DAY', [Parameter(Mandatory=$False)] [Alias("StartDateFrom")] [DateTime]$TimePeriodFrom, [Parameter(Mandatory=$False)] [Alias("StartDateTo")] [DateTime]$TimePeriodTo, [Parameter(Mandatory=$False)] [ValidateSet('GOOD','POOR_0_25','PARTIALLY_GOOD_25_50','PARTIALLY_GOOD_50_75','PARTIALLY_GOOD_75_100','UNAVAILABLE','UNKNOWN', IgnoreCase=$True)] [string[]]$SessionQualities, [Parameter(Mandatory=$False)] [ValidateScript ({ If ($_ -Match '^\d\d(.\d+)?\-(\d\d|100)(.\d+)?$') { $True } Else { Throw 'NectarScore must be in the format aa-xx (ie. 80-90) or aa.bb-xx.yy (ie 77.32-80.99), where aa is less than xx and xx cannot be greater than 100.' } })] [string]$NectarScore, [Parameter(Mandatory=$False)] [ValidateRange(0,99999999)] [int]$DurationFrom = 0, [Parameter(Mandatory=$False)] [ValidateRange(0,99999999)] [int]$DurationTo = 99999999, [Parameter(Mandatory=$False)] [ValidateSet('AUDIO','VIDEO','APP_SHARING','IM','UNKNOWN','TOTAL','VBSS','REMOTE_ASSISTANCE','APP_INVITE','FOCUS','UNKNOWN', IgnoreCase=$True)] [string[]]$Modalities = 'TOTAL', [Parameter(Mandatory=$False)] [ValidateSet('TCP','UDP','Unknown', IgnoreCase=$False)] [string[]]$Protocols, [Parameter(Mandatory=$False)] [ValidateRange(200,699)] [string[]]$ResponseCodes, [Parameter(Mandatory=$False)] [ValidateSet('INTERNAL','EXTERNAL','FEDERATED','INTERNAL_EXTERNAL','EXTERNAL_INTERNAL','FEDERATED_INTERNAL','INTERNAL_FEDERATED','FEDERATED_EXTERNAL','EXTERNAL_FEDERATED','UNKNOWN', IgnoreCase=$True)] [string[]]$SessionScenarios, [Parameter(Mandatory=$False)] [ValidateSet('CONFERENCE','CONFERENCE_SESSION','PEER2PEER','PEER2PEER_MULTIMEDIA','PSTN','WEBINAR', IgnoreCase=$False)] [string[]]$SessionTypes, [Parameter(Mandatory=$False)] [string[]]$Codecs, [Parameter(Mandatory=$False)] [string[]]$CallerCodecs, [Parameter(Mandatory=$False)] [string[]]$CalleeCodecs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$Devices, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerDevices, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeDevices, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$DeviceVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerDeviceVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeDeviceVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$AgentVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerAgentVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeAgentVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ipaddress[]]$IPAddresses, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ipaddress[]]$CallerIPAddresses, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ipaddress[]]$CalleeIPAddresses, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$Locations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerLocations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeLocations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtISPs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtISPs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtISPs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('Cable','Dialup','DSL','FTTx','ISDN','Unknown','Wireless', IgnoreCase=$False)] [string[]]$ExtConnectionTypes, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('Cable','Dialup','DSL','FTTx','ISDN','Unknown','Wireless', IgnoreCase=$False)] [string[]]$CallerExtConnectionTypes, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('Cable','Dialup','DSL','FTTx','ISDN','Unknown','Wireless', IgnoreCase=$False)] [string[]]$CalleeExtConnectionTypes, [Parameter(Mandatory=$False)] [ValidateSet('CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED', IgnoreCase=$False)] [string[]]$NetworkTypes, [Parameter(Mandatory=$False)] [ValidateSet('CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED', IgnoreCase=$False)] [string[]]$CallerNetworkTypes, [Parameter(Mandatory=$False)] [ValidateSet('CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED', IgnoreCase=$False)] [string[]]$CalleeNetworkTypes, [Parameter(Mandatory=$False)] [ValidateSet('SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS', IgnoreCase=$True)] [string[]]$Platforms, [Parameter(Mandatory=$False)] [string[]]$Servers, [Parameter(Mandatory=$False)] [ValidateSet('AAEP','AAMS','medsvr','mgdsp','node','phone','SBC','sip','unknown', IgnoreCase=$True)] [string[]]$EndpointTypes, [Parameter(Mandatory=$False)] [ValidateSet('AAEP','AAMS','medsvr','mgdsp','node','phone','SBC','sip','unknown', IgnoreCase=$True)] [string[]]$CallerEndpointTypes, [Parameter(Mandatory=$False)] [ValidateSet('AAEP','AAMS','medsvr','mgdsp','node','phone','SBC','sip','unknown', IgnoreCase=$True)] [string[]]$CalleeEndpointTypes, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$Users, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$FromUsers, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ToUsers, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$UserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$FromUserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ToUserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ConfOrganizers, [Parameter(Mandatory=$False)] [ValidateSet('TRUE','FALSE','UNKNOWN', IgnoreCase=$True)] [string[]]$VPN, [Parameter(Mandatory=$False)] [ValidateSet('TRUE','FALSE','UNKNOWN', IgnoreCase=$True)] [string[]]$CallerVPN, [Parameter(Mandatory=$False)] [ValidateSet('TRUE','FALSE','UNKNOWN', IgnoreCase=$True)] [string[]]$CalleeVPN, [Parameter(Mandatory=$False)] [ValidateRange(0,99999)] [int]$ParticipantsMinCount, [Parameter(Mandatory=$False)] [ValidateRange(0,99999)] [int]$ParticipantsMaxCount, [Parameter(Mandatory=$False)] [ValidateSet('BAD','POOR','FAIR','GOOD','EXCELLENT', IgnoreCase=$False)] [string[]]$FeedbackRating, [Parameter(Mandatory=$False)] [ValidateSet('NORMAL_SESSION','VOICE_MAIL','HIGH_JITTER','HIGH_PACKET_LOSS','HIGH_ROUNDTRIP_DELAY', IgnoreCase=$False)] [string[]]$Insights, [switch]$ShowQualityDetails, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [ValidateSet('DEFAULT','USERS','ENDPOINT_CLIENT','CALL_DETAILS_HISTORIC','QUALITY_DETAILS', IgnoreCase=$True)] [string]$Scope = 'CALL_DETAILS_HISTORIC' ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $Params = @{} If ($Modalities) { $Modalities | ForEach-Object { $ModalitiesStr += ($(if($ModalitiesStr){","}) + $_) }; $Params.Add('Modalities',$ModalitiesStr) } If ($TenantName) { $Params.Add('Tenant',$TenantName) } #Remove Modalities from FilterSession POST. For some reason, TOTAL results come back as empty if this is set $PSBoundParameters.Remove('Modalities') | Out-Null $FilterSession = Set-NectarFilterParams @PsBoundParameters $URI = "https://$Global:NectarCloud/dapi/quality/nectarscore/bar/graph?tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader -Body $Params -WebSession $FilterSession # Flatten the output so that session summary details are at the top level instead of nested under "sessions" $FinalList = @() $CultureTextInfo = (Get-Culture).TextInfo ForEach ($Row in $JSON) { $NewArray = New-Object PSObject $NewArray | Add-Member -NotePropertyName 'StartDate' -NotePropertyValue $Row.StartDate $NewArray | Add-Member -NotePropertyName 'EndDate' -NotePropertyValue $Row.EndDate ForEach ($Item in $Row.sessions) { $ObjectProperties = $Item.PSObject.Properties ForEach ($Property in $ObjectProperties) { If ($Property.Name -ne 'avgnectarscore') { $NewArray | Add-Member -NotePropertyName $CultureTextInfo.ToTitleCase($Property.Name) -NotePropertyValue $Property.Value } Else { $NewArray | Add-Member -NotePropertyName 'AvgNectarScore' -NotePropertyValue ([math]::Round($Property.Value, 3)) } } If ($TenantName) {$NewArray | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty} } $FinalList += $NewArray } Return $FinalList } Catch { Write-Error 'Session count not found.' Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarSessionSummary { <# .SYNOPSIS Returns call summary information for a given session .DESCRIPTION Returns call summary information for a given session. This is used to populate the top few sections of an individual session on the session OVERVIEW screen. UI_ELEMENT .PARAMETER SessionID The session ID of the selected session .PARAMETER Platform The platform where the session took place .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarSessionSummary 2021-04-30T16:04:28.572701_1_1_*_*_*_6_*_29fe15a4-99e5-4a2c-92a6-fbf3024944fc_29abe23a4-33e5-4a2c-92a6-faf30445e5bc_* -Platform TEAMS Returns summary information for a specific Teams session .EXAMPLE Get-NectarSession -Platform TEAMS -Users tferguson@contoso.com -SessionTypes PEER2PEER -TimePeriod LAST_DAY | Get-NectarSessionSummary -Platform TEAMS Returns summary information for all Teams peer-to-peer calls for TFerguson for the last day. .NOTES Version 1.1 #> [Alias("gnss")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$SessionID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias('callerPlatform', 'calleePlatform', 'Platforms')] [ValidateSet('SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS', IgnoreCase=$True)] [string[]]$Platform, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $Platform = $Platform.ToUpper() $URI = "https://$Global:NectarCloud/dapi/session/$SessionID/summary?platform=$Platform&tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader $SessionSummary = [pscustomobject][ordered]@{ SessionID = $SessionID StartTime = $JSON.startTime EndTime = $JSON.endTime Duration = $JSON.duration Quality = $JSON.quality CallerRenderDevice = $JSON.caller.renderDevice.value CallerCaptureDevice = $JSON.caller.captureDevice.value CallerClientVersion = $JSON.caller.clientVersion.value CallerNetworkType = $JSON.caller.networkType.value CallerNetworkWarning = $JSON.caller.networkType.warning CallerServer = $JSON.caller.server.value CallerServerAlertLevel = $JSON.caller.server.alertLevel CalleeRenderDevice = $JSON.callee.renderDevice.value CalleeCaptureDevice = $JSON.callee.captureDevice.value CalleeClientVersion = $JSON.callee.clientVersion.value CalleeNetworkType = $JSON.callee.networkType.value CalleeNetworkWarning = $JSON.callee.networkType.warning CalleeServer = $JSON.callee.server.value CalleeServerAlertLevel = $JSON.callee.server.alertLevel } Return $SessionSummary } Catch { Write-Error 'Session diagnostics not found.' Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarSessionParticipant { <# .SYNOPSIS Returns session participant information for a given session .DESCRIPTION Returns session participant information for a given session. This is used to populate the participants view in an individual session on the session OVERVIEW screen. UI_ELEMENT .PARAMETER SessionID The session ID of the selected session .PARAMETER CallerOrCallee Which participant to return participant information from. Select either Caller or Callee .PARAMETER Platform The platform where the session took place .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarSessionParticipant 2021-04-30T16:04:28.572701_1_1_*_*_*_6_*_29fe15a4-99e5-4a2c-92a6-fbf3024944fc_29abe23a4-33e5-4a2c-92a6-faf30445e5bc_* -Platform TEAMS -CallerOrCallee Caller Returns caller information for a specific Teams session .NOTES Version 1.0 #> [Alias("gnsp")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$SessionID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [ValidateSet('Caller','Callee', IgnoreCase=$True)] [string]$CallerOrCallee, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias('callerPlatform', 'calleePlatform', 'Platforms')] [ValidateSet('SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS', IgnoreCase=$True)] [string[]]$Platform, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $Platform = $Platform.ToUpper() $URI = "https://$Global:NectarCloud/dapi/session/$SessionID/$($CallerOrCallee.ToLower())?platform=$Platform&tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader Return $JSON } Catch { Write-Error 'Session participant info not found.' Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarSessionDetails { <# .SYNOPSIS Returns details for a given session .DESCRIPTION Returns details for a given session. This is used to populate the session ADVANCED screen for a given session. UI_ELEMENT .PARAMETER SessionID The session ID of the selected session .PARAMETER Platform The platform where the session took place .PARAMETER IncludeMetrics The name of one or more metrics separated by commas to include in the output. All other metrics will be excluded. Useful in situations where only a few metrics are required in large pipelining scripts, where the time to execute can be reduced by up to 33% .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarSessionDetails 2021-04-30T16:04:28.572701_1_1_*_*_*_6_*_29fe15a4-99e5-4a2c-92a6-fbf3024944fc_29abe23a4-33e5-4a2c-92a6-faf30445e5bc_* -Platform TEAMS Returns detailed information for a specific Teams session .EXAMPLE Get-NectarSession -Platform TEAMS -Users tferguson@contoso.com -SessionTypes PEER2PEER,PEER2PEER_MULTIMEDIA -TimePeriod LAST_DAY | Get-NectarSessionDetails Returns detailed information for all Teams peer-to-peer and multimedia P2P calls for TFerguson for the last day. .NOTES Version 1.2 #> [Alias("gnsd")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias("ID")] [string]$SessionID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias('callerPlatform', 'calleePlatform', 'Platforms')] [ValidateSet('SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS', IgnoreCase=$True)] [string[]]$Platform, [Parameter( Mandatory=$False)] [string[]]$IncludeMetrics, [Parameter(ValueFromPipelineByPropertyName=$True, Mandatory=$False)] [string]$TenantName ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $Platform = $Platform.ToUpper() $URI = "https://$Global:NectarCloud/dapi/session/$SessionID/advanced?platform=$Platform&tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader $SessionDetails = [pscustomobject][ordered]@{ SessionID = $SessionID SessionType = $JSON.type Caller = $JSON.caller Callee = $JSON.callee } If ($IncludeMetrics) { # Only pull the described metrics ForEach ($DataGroup in $JSON.groups) { ForEach ($DataElement in $DataGroup.data | Where-Object {$_.AlertName -In $IncludeMetrics}) { $SessionDetails | Add-Member -NotePropertyName $DataElement.AlertName -NotePropertyValue $DataElement.Value } } } Else { # Otherwise, pull all metrics ForEach ($DataGroup in $JSON.groups) { ForEach ($DataElement in $DataGroup.data | Where-Object {$_.AlertName -ne 'SessionType'}) { $SessionDetails | Add-Member -NotePropertyName $DataElement.AlertName -NotePropertyValue $DataElement.Value } } } Return $SessionDetails } Catch { Write-Error 'Session details not found.' Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarSessionAlerts { <# .SYNOPSIS Returns alerts for a given session .DESCRIPTION Returns alerts for a given session. This is used to populate the SESSION ALERTS portion of the session OVERVIEW screen. UI_ELEMENT .PARAMETER SessionID The session ID of the selected session .PARAMETER Platform The platform where the session took place .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarSessionAlerts 2021-04-30T16:04:28.572701_1_1_*_*_*_6_*_29fe15a4-99e5-4a2c-92a6-fbf3024944fc_29abe23a4-33e5-4a2c-92a6-faf30445e5bc_* -Platform TEAMS Returns alert information for a specific Teams session .EXAMPLE Get-NectarSession -Platform TEAMS -Users tferguson@contoso.com -SessionTypes PEER2PEER -TimePeriod LAST_DAY | Get-NectarSessionAlerts -Platform TEAMS Returns session alerts for all Teams peer-to-peer calls for TFerguson for the last day. .NOTES Version 1.2 #> [Alias("gnsa")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$SessionID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias('callerPlatform', 'calleePlatform', 'Platforms')] [ValidateSet('SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS', IgnoreCase=$True)] [string[]]$Platform, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $Platform = $Platform.ToUpper() $URI = "https://$Global:NectarCloud/dapi/session/$SessionID/alerts?platform=$Platform&tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader $UserList = 'Caller','Callee' ForEach ($User in $UserList) { ForEach ($Alert in $JSON.$User.alerts.PsObject.Properties) { $AlertDetails = [pscustomobject][ordered]@{ SessionID = $SessionID User = $User Parameter = $Alert.Name Value = $Alert.Value.Value AlertLevel = $Alert.Value.AlertLevel } $AlertDetails } } } Catch { Write-Error 'Session alerts not found.' Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarSessionDiagnostics { <# .SYNOPSIS Returns call diagnostics for a given session .DESCRIPTION Returns call diagnostics for a given session. This is used to populate the session DIAGNOSTICS screen for a given session. UI_ELEMENT .PARAMETER SessionID The session ID of the selected session .PARAMETER Platform The platform where the session took place .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarSessionDetails 2021-04-30T16:04:28.572701_1_1_*_*_*_6_*_29fe15a4-99e5-4a2c-92a6-fbf3024944fc_29abe23a4-33e5-4a2c-92a6-faf30445e5bc_* -Platform SKYPE Returns diagnostic information for a specific Skype for Business session .EXAMPLE Get-NectarSession -Platform SKYPE -Users tferguson@contoso.com -SessionTypes PEER2PEER -TimePeriod LAST_DAY | Get-NectarSessionDetails -Platform SKYPE Returns detailed information for all Skype for Business peer-to-peer calls for TFerguson for the last day. .NOTES Version 1.2 #> [Alias("gnsd")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias("id")] [string]$SessionID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias('callerPlatform', 'calleePlatform', 'Platforms')] [ValidateSet('SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS', IgnoreCase=$True)] [string[]]$Platform, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $Platform = $Platform.ToUpper() $URI = "https://$Global:NectarCloud/dapi/session/$SessionID/diagnostic?platform=$Platform&tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader $JSON.elements | Add-Member -NotePropertyName 'sessionId' -NotePropertyValue $SessionID Return $JSON.elements } Catch { Write-Error 'Session diagnostics not found.' Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarConferenceSummary { <# .SYNOPSIS Returns session summaries for a given conference .DESCRIPTION Returns session summaries for a given conference. This is used to populate the CONFERENCE section of the details of a specific conference. UI_ELEMENT .PARAMETER ConferenceID The conference ID of the selected conference .PARAMETER Platform The platform where the conference took place .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarConferenceSummary 2021-05-06T13:30:34.795296_*_*_*_*_*_*_173374c1-a15a-47dd-b11c-d32ab5442774_*_* Returns conference summary information for a specific conference .EXAMPLE Get-NectarSession -TimePeriod LAST_DAY -Users tferguson@contoso.com-SessionTypes CONFERENCE -Platforms TEAMS | Get-NectarConferenceSummary -Platform TEAMS Returns the conference summary information for all Teams conferences participated by tferguson@contoso.com for the past day .NOTES Version 1.2 #> [Alias("gncs")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias("id")] [string]$ConferenceID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias('callerPlatform', 'calleePlatform', 'Platforms')] [ValidateSet('SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS', IgnoreCase=$True)] [string[]]$Platform, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $Platform = $Platform.ToUpper() $URI = "https://$Global:NectarCloud/dapi/conference/$ConferenceID/?platform=$Platform&tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader $ConferenceSummary = [pscustomobject][ordered]@{ ConferenceID = $ConferenceID StartTime = $JSON.conference.startTime EndTime = $JSON.conference.endTime Duration = $JSON.duration AverageMOS = $JSON.avgMos Participants = $JSON.participants TotalSessions = $JSON.sessions.total GoodSessions = $JSON.sessions.good PoorSessions = $JSON.sessions.poor UnknownSessions = $JSON.sessions.unknown } Return $ConferenceSummary } Catch { Write-Error 'Conference not found.' Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarConferenceTimeline { <# .SYNOPSIS Returns session timeline details for a given conference .DESCRIPTION Returns session timeline details for a given conference. This is used to build the Gantt chart view of a specific conference. UI_ELEMENT .PARAMETER ConferenceID The conference ID of the selected conference .PARAMETER Platform The platform where the conference took place .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarConferenceTimeline 2021-05-06T13:30:34.795296_*_*_*_*_*_*_173374c1-a15a-47dd-b11c-d32ab5442774_*_* -Platform TEAMS Returns conference summary information for a specific conference .EXAMPLE Get-NectarSession -TimePeriod LAST_DAY -Users tferguson@contoso.com-SessionTypes CONFERENCE -Platforms TEAMS | Get-NectarConferenceTimeline -Platform TEAMS Returns the conference timeline information for all Teams conferences participated by tferguson@contoso.com for the past day .NOTES Version 1.2 #> [Alias("gnct")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias("id")] [string]$ConferenceID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias('callerPlatform', 'calleePlatform', 'Platforms')] [ValidateSet('SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS', IgnoreCase=$True)] [string[]]$Platform, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $Platform = $Platform.ToUpper() $URI = "https://$Global:NectarCloud/dapi/conference/$ConferenceID/timeline?platform=$Platform&tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader $JSON | Add-Member -NotePropertyName 'conferenceId' -NotePropertyValue $ConferenceID $JSON | Add-Member -TypeName 'Nectar.Conference.Timeline' Return $JSON } Catch { Write-Error 'Conference not found.' Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarConferenceParticipants { <# .SYNOPSIS Returns session participant details for a given conference .DESCRIPTION Returns session participant details for a given conference. This is used to build the PARTICIPANTS section of a specific conference. UI_ELEMENT .PARAMETER ConferenceID The conference ID of the selected conference .PARAMETER Platform The platform where the conference took place .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarConferenceParticipants 2021-05-06T13:30:34.795296_*_*_*_*_*_*_173374c1-a15a-47dd-b11c-d32ab5442774_*_* -Platform TEAMS Returns conference participant information for a specific conference .EXAMPLE Get-NectarSession -TimePeriod LAST_DAY -Users tferguson@contoso.com-SessionTypes CONFERENCE -Platforms TEAMS | Get-NectarConferenceParticipants -Platform TEAMS Returns the conference participant information for all Teams conferences participated by tferguson@contoso.com for the past day .NOTES Version 1.1 #> [Alias("gncp")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias("id")] [string]$ConferenceID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias('callerPlatform', 'calleePlatform', 'Platforms')] [ValidateSet('SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS', IgnoreCase=$True)] [string[]]$Platform, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $Platform = $Platform.ToUpper() $URI = "https://$Global:NectarCloud/dapi/conference/$ConferenceID/participants?platform=$Platform&tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader $JSON | Add-Member -NotePropertyName 'conferenceId' -NotePropertyValue $ConferenceID $JSON | Add-Member -TypeName 'Nectar.Conference.Participants' Return $JSON } Catch { Write-Error 'Conference not found.' Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarSessionMultiTimeline { <# .SYNOPSIS Returns multimedia session timeline details for a given P2P multimedia session .DESCRIPTION Returns multimedia session (an audio/video P2P session or an audio/appsharing P2P session) timeline details for a given session Used to build the Gantt chart view for a given P2P multimedia session on the Session Details screen. UI_ELEMENT .PARAMETER SessionID The sessionID of a multimedia P2P session .PARAMETER Platform The platform where the P2P session took place .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarSessionMultiTimeline 2021-05-05T19:17:14.324027_1_1_*_*_*_6_*_6efc12345-4229-4c11-9001-9a00667a761e9_6efc8348-4809-4caf-9141-9afa87a761e9_* -Platform TEAMS Returns multimedia session timeline information for a specific multimedia P2P session .EXAMPLE Get-NectarSession -TimePeriod LAST_DAY -Users tferguson@contoso.com-SessionTypes PEER2PEER_MULTIMEDIA -Platforms TEAMS | Get-NectarSessionMultiTimeline -Platform TEAMS Returns the multimedia session timeline information for all Teams P2P multimedia sessions participated by tferguson@contoso.com for the past day .NOTES Version 1.3 #> [Alias("gnsmt")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$SessionID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias('callerPlatform', 'calleePlatform', 'Platforms')] [ValidateSet('SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS', IgnoreCase=$True)] [string[]]$Platform, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/dapi/session/$SessionID/multi/timeline?platform=$Platform&tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader #$JSON | Add-Member -NotePropertyName 'sessionId' -NotePropertyValue $SessionID Return $JSON } Catch { Write-Error 'Multimedia session not found.' Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarQualitySummary { <# .SYNOPSIS Returns summary quality information for Avaya/Cisco quality records .DESCRIPTION Returns summary quality information for Avaya/Cisco quality records. Used to build the QUALITY section of the CALL DETAILS Quality Details screen. UI_ELEMENT .PARAMETER TimePeriod The time period to show session data from. Select from 'LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM'. CUSTOM requires using TimePeriodFrom and TimePeriodTo parameters. .PARAMETER TimePeriodFrom The earliest date/time to show session data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodTo parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. .PARAMETER TimePeriodTo The latest date/time to show session data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodFrom parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. .PARAMETER SessionQualities Show sessions that match a given quality rating. Case sensitive. Choose one or more from: 'GOOD','POOR_0_25','PARTIALLY_GOOD_25_50','PARTIALLY_GOOD_50_75','PARTIALLY_GOOD_75_100','UNAVAILABLE','UNKNOWN' .PARAMETER Codecs Show sessions where the selected codec was used by either caller or callee. Can query for multiple codecs. Case sensitive. Use Get-NectarCodecs for a list of valid codecs. .PARAMETER CallerCodecs Show sessions where the selected codec was used by the caller. Can query for multiple codecs. Case sensitive. Use Get-NectarCodecs for a list of valid codecs. .PARAMETER CalleeCodecs Show sessions where the selected codec was used by the callee. Can query for multiple codecs. Case sensitive. Use Get-NectarCodecs for a list of valid codecs. .PARAMETER IPAddresses Show sessions where the selected IP address was used by either caller or callee. Can query for multiple IPs. .PARAMETER CallerIPAddresses Show sessions where the selected IP address was used by the caller. Can query for multiple IPs. .PARAMETER CalleeIPAddresses Show sessions where the selected IP address was used by the callee. Can query for multiple IPs. .PARAMETER Locations Show sessions where the selected location was used by either caller or callee. Can query for multiple locations. .PARAMETER CallerLocations Show sessions where the selected location was used by the caller. Can query for multiple locations. .PARAMETER CalleeLocations Show sessions where the selected location was used by the callee. Can query for multiple locations. .PARAMETER Platforms Show sessions where the selected platform was used by either caller or callee. Can query for multiple platforms. Case sensitive. Choose one or more from: 'SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS' .PARAMETER CallerPlatforms Show sessions where the selected platform was used by the caller. Can query for multiple platforms. Case sensitive. Choose one or more from: 'SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS' .PARAMETER CalleePlatforms Show sessions where the selected platform was used by the callee. Can query for multiple platforms. Case sensitive. Choose one or more from: 'SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS' .PARAMETER EndpointTypes Show sessions where one or more selected Avaya endpoint types have been used. Choose one or more from: 'medsvr','mgdsp','node','phone','unknown' .PARAMETER CallerEndpointTypes Show sessions where one or more selected Avaya endpoint types have been used by the caller. Choose one or more from: 'medsvr','mgdsp','node','phone','unknown' .PARAMETER CalleeEndpointTypes Show sessions where one or more selected Avaya endpoint types have been used by the callee. Choose one or more from: 'medsvr','mgdsp','node','phone','unknown' .PARAMETER Users Show sessions where the selected user was either caller or callee. Can query for multiple users. .PARAMETER FromUsers Show sessions where the selected user was the caller. Can query for multiple users. .PARAMETER ToUsers Show sessions where the selected user was the callee. Can query for multiple users. .PARAMETER UserIDs Show sessions where the selected user ID was either caller or callee. Can query for multiple user IDs. .PARAMETER FromUserIDs Show sessions where the selected user ID was the caller. Can query for multiple user IDs. .PARAMETER ToUserIDs Show sessions where the selected user ID was the callee. Can query for multiple user IDs. .PARAMETER AgentVersions Show sessions where the selected agent version was used by either caller or callee. Can query for multiple agent. Case sensitive. Use Get-NectarAgentVersion for a list of valid agent versions. .PARAMETER CallerAgentVersions Show sessions where the selected agent version was used by either caller or callee. Can query for multiple agent. Case sensitive. Use Get-NectarAgentVersion for a list of valid agent versions. .PARAMETER CalleeAgentVersions Show sessions where the selected agent version was used by either caller or callee. Can query for multiple agent. Case sensitive. Use Get-NectarAgentVersion for a list of valid agent versions. .PARAMETER OrderByField Sort the output by the selected field .PARAMETER OrderDirection Sort direction. Use with OrderByField. Not case sensitive. Choose from: ASC, DESC .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 1000 .PARAMETER ResultSize The total number of results to return. Defaults to 1000. Maximum result size is 9,999,999 results .EXAMPLE Get-NectarQualitySummary -TimePeriod LAST_DAY Returns the quality summary for the past day .NOTES Version 1.1 #> [Alias("gnmqs")] Param ( [Parameter(Mandatory=$False)] [ValidateSet('LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM', IgnoreCase=$True)] [string]$TimePeriod = 'LAST_HOUR', [Parameter(Mandatory=$False)] [Alias("StartDateFrom")] [DateTime]$TimePeriodFrom, [Parameter(Mandatory=$False)] [Alias("StartDateTo")] [DateTime]$TimePeriodTo, [Parameter(Mandatory=$False)] [ValidateSet('GOOD','POOR_0_25','PARTIALLY_GOOD_25_50','PARTIALLY_GOOD_50_75','PARTIALLY_GOOD_75_100','UNAVAILABLE','UNKNOWN', IgnoreCase=$True)] [string[]]$SessionQualities, [Parameter(Mandatory=$False)] [ValidateScript ({ If ($_ -Match '^\d\d(.\d+)?\-(\d\d|100)(.\d+)?$') { $True } Else { Throw 'NectarScore must be in the format aa-xx (ie. 80-90) or aa.bb-xx.yy (ie 77.32-80.99), where aa is less than xx and xx cannot be greater than 100.' } })] [string]$NectarScore, [Parameter(Mandatory=$False)] [string[]]$Codecs, [Parameter(Mandatory=$False)] [string[]]$CallerCodecs, [Parameter(Mandatory=$False)] [string[]]$CalleeCodecs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ipaddress[]]$IPAddresses, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ipaddress[]]$CallerIPAddresses, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ipaddress[]]$CalleeIPAddresses, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias("SiteName")] [string[]]$Locations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerLocations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeLocations, [Parameter(Mandatory=$False)] [ValidateSet('CISCO','CISCO_CMS','CISCO_VKM','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','MISCELLANEOUS', IgnoreCase=$True)] [string[]]$Platforms, [Parameter(Mandatory=$False)] [string[]]$Servers, [Parameter(Mandatory=$False)] [ValidateSet('AAEP','AAMS','medsvr','mgdsp','node','phone','SBC','sip','unknown', IgnoreCase=$True)] [string[]]$EndpointTypes, [Parameter(Mandatory=$False)] [ValidateSet('AAEP','AAMS','medsvr','mgdsp','node','phone','SBC','sip','unknown', IgnoreCase=$True)] [string[]]$CallerEndpointTypes, [Parameter(Mandatory=$False)] [ValidateSet('AAEP','AAMS','medsvr','mgdsp','node','phone','SBC','sip','unknown', IgnoreCase=$True)] [string[]]$CalleeEndpointTypes, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$Users, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$FromUsers, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ToUsers, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$UserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$FromUserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ToUserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$AgentVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerAgentVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeAgentVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [ValidateSet('DEFAULT','USERS','QUALITY_DETAILS', IgnoreCase=$True)] [string]$Scope = 'QUALITY_DETAILS', [Parameter(Mandatory=$False)] [ValidateRange(1,100000)] [int]$PageSize = 1000, [Parameter(Mandatory=$False)] [ValidateRange(1,9999999)] [int]$ResultSize ) Process { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } $PSBoundParameters.Add('ShowQualityDetails',$TRUE) Write-Verbose $PsBoundParameters $FilterSession = Set-NectarFilterParams @PsBoundParameters $QualitySummaryURI = "https://$Global:NectarCloud/dapi/quality/summary" $QualitySummaryDetailsURI = "https://$Global:NectarCloud/dapi/quality/summary/data" Write-Verbose $QualitySummaryDetailsURI If ($TenantName) { $Params = @{'tenant' = $TenantName} } Try { $QS_JSON = Invoke-RestMethod -Method GET -uri $QualitySummaryURI -Headers $Global:NectarAuthHeader -Body $Params -WebSession $FilterSession $QSD_JSON = Invoke-RestMethod -Method GET -uri $QualitySummaryDetailsURI -Headers $Global:NectarAuthHeader -Body $Params -WebSession $FilterSession $QualitySummary = [pscustomobject][ordered]@{} $QualitySummary | Add-Member -NotePropertyName 'TOTAL_GOOD' -NotePropertyValue $QS_JSON.Good $QualitySummary | Add-Member -NotePropertyName 'TOTAL_POOR' -NotePropertyValue $QS_JSON.Poor $QualitySummary | Add-Member -NotePropertyName 'TOTAL_UNKNOWN' -NotePropertyValue $QS_JSON.Unknown ForEach ($QualityMetric in $QSD_JSON.QualityMetrics) { $QualitySummary | Add-Member -NotePropertyName $QualityMetric.name -NotePropertyValue $QualityMetric.value $TrendMetricName = $QualityMetric.name + '_TREND' $QualitySummary | Add-Member -NotePropertyName $TrendMetricName -NotePropertyValue $QualityMetric.trends } If ($TenantName) { $QualitySummary | Add-Member -NotePropertyName 'TenantName' -NotePropertyValue $TenantName } $QualitySummary | Add-Member -TypeName 'Nectar.ModalityQuality' Return $QualitySummary } Catch { Write-Error "No results. Try specifying a less-restrictive filter" Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarModalityQualitySummary { <# .SYNOPSIS Returns summary quality information for different modalities over a given timeperiod. .DESCRIPTION Returns summary quality information for audio, video and appsharing modalities. Used to build the QUALITY section of the CALL DETAILS screen. UI_ELEMENT .PARAMETER Modality Show sessions that match one or more modality types. Not case sensitive. Choose one or more from: 'AUDIO','VIDEO','APPSHARING' .PARAMETER TimePeriod The time period to show session data from. Select from 'LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM'. CUSTOM requires using TimePeriodFrom and TimePeriodTo parameters. .PARAMETER TimePeriodFrom The earliest date/time to show session data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodTo parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. .PARAMETER TimePeriodTo The latest date/time to show session data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodFrom parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. .PARAMETER SessionQualities Show sessions that match a given quality rating. Case sensitive. Choose one or more from: 'GOOD','POOR_0_25','PARTIALLY_GOOD_25_50','PARTIALLY_GOOD_50_75','PARTIALLY_GOOD_75_100','UNAVAILABLE','UNKNOWN' .PARAMETER NectarScore Show sessions that match the given range of NectarScores. Use format aa-xx or aa.bb-xx.yy, where aa is less than xx, and xx is less than 100. Eg. 70-90 or 95.00-95.59 .PARAMETER DurationFrom The shortest call length (in seconds) to show session data. .PARAMETER DurationTo The longest call length (in seconds) to show session data. .PARAMETER Protocols Show sessions that match one or more network protocol types. Case sensitive. Choose one or more from: 'TCP','UDP','Unknown' .PARAMETER ResponseCodes Show sessions that match one or more SIP response codes. Accepts numbers from 200 to 699 .PARAMETER SessionScenarios Show sessions that match one or more session scenarios. Not case sensitive. Choose one or more from: 'External','Internal','Internal-External','External-Internal','Federated','Internal-Federated','External-Federated','Unknown' .PARAMETER SessionTypes Show sessions that match one or more session scenarios. Case sensitive. Choose one or more from: 'Conference','Peer To Peer','Peer To Peer (Multimedia)','PSTN/External' .PARAMETER Codecs Show sessions where the selected codec was used by either caller or callee. Can query for multiple codecs. Case sensitive. Use Get-NectarCodecs for a list of valid codecs. .PARAMETER CallerCodecs Show sessions where the selected codec was used by the caller. Can query for multiple codecs. Case sensitive. Use Get-NectarCodecs for a list of valid codecs. .PARAMETER CalleeCodecs Show sessions where the selected codec was used by the callee. Can query for multiple codecs. Case sensitive. Use Get-NectarCodecs for a list of valid codecs. .PARAMETER Devices Show sessions where the selected device was used by either caller or callee. Can query for multiple devices. Case sensitive. Use Get-NectarSupportedDevice for a list of valid devices. .PARAMETER CallerDevices Show sessions where the selected device was used by the caller. Can query for multiple devices. Case sensitive. Use Get-NectarSupportedDevice for a list of valid devices. .PARAMETER CalleeDevices Show sessions where the selected device was used by the callee. Can query for multiple devices. Case sensitive. Use Get-NectarSupportedDevice for a list of valid devices. .PARAMETER DeviceVersions Show sessions where the selected device version was used by either caller or callee. Can query for multiple devices. Case sensitive. Use Get-NectarClientVersion for a list of valid client versions. .PARAMETER CallerDeviceVersions Show sessions where the selected device version was used by the caller. Can query for multiple devices. Case sensitive. Use Get-NectarClientVersion for a list of valid client versions. .PARAMETER CalleeDeviceVersions Show sessions where the selected device version was used by the callee. Can query for multiple devices. Case sensitive. Use Get-NectarClientVersion for a list of valid client versions. .PARAMETER AgentVersions Show sessions where the selected agent version was used by either caller or callee. Can query for multiple agent. Case sensitive. Use Get-NectarAgentVersion for a list of valid agent versions. .PARAMETER CallerAgentVersions Show sessions where the selected agent version was used by either caller or callee. Can query for multiple agent. Case sensitive. Use Get-NectarAgentVersion for a list of valid agent versions. .PARAMETER CalleeAgentVersions Show sessions where the selected agent version was used by either caller or callee. Can query for multiple agent. Case sensitive. Use Get-NectarAgentVersion for a list of valid agent versions. .PARAMETER IPAddresses Show sessions where the selected IP address was used by either caller or callee. Can query for multiple IPs. .PARAMETER CallerIPAddresses Show sessions where the selected IP address was used by the caller. Can query for multiple IPs. .PARAMETER CalleeIPAddresses Show sessions where the selected IP address was used by the callee. Can query for multiple IPs. .PARAMETER Locations Show sessions where the selected location was used by either caller or callee. Can query for multiple locations. .PARAMETER CallerLocations Show sessions where the selected location was used by the caller. Can query for multiple locations. .PARAMETER CalleeLocations Show sessions where the selected location was used by the callee. Can query for multiple locations. .PARAMETER ExtCities Show sessions where the caller or callee was located in the selected city (as detected via geolocating the user's external IP address). Can query for multiple cities. .PARAMETER CallerExtCities Show sessions where the caller was located in the selected city (as detected via geolocating the user's external IP address). Can query for multiple cities. .PARAMETER CalleeExtCities Show sessions where the callee was located in the selected city (as detected via geolocating the user's external IP address). Can query for multiple cities. .PARAMETER ExtCountries Show sessions where the caller or callee was located in the selected country (as detected via geolocating the user's external IP address). Can query for multiple countries. .PARAMETER CallerExtCountries Show sessions where the caller was located in the selected country (as detected via geolocating the user's external IP address). Can query for multiple countries. .PARAMETER CalleeExtCountries Show sessions where the callee was located in the selected country (as detected via geolocating the user's external IP address). Can query for multiple countries. .PARAMETER ExtISPs Show sessions where the caller or callee was located in the selected ISP (as detected via geolocating the user's external IP address). Can query for multiple ISPs. .PARAMETER CallerExtISPs Show sessions where the caller was located in the selected ISP (as detected via geolocating the user's external IP address). Can query for multiple ISPs. .PARAMETER CalleeExtISPs Show sessions where the callee was located in the selected ISP (as detected via geolocating the user's external IP address). Can query for multiple ISPs. .PARAMETER ExtConnectionTypes Show sessions that match the caller or callee external connection type (as detected via geolocating the user's external IP address). Can query for multiple types. .PARAMETER CallerExtConnectionTypes Show sessions that match the caller's external connection type (as detected via geolocating the user's external IP address). Can query for multiple types. .PARAMETER CalleeExtConnectionTypes Show sessions that match the callee's external connection type (as detected via geolocating the user's external IP address). Can query for multiple types. .PARAMETER NetworkTypes Show sessions where the selected network type was used by either caller or callee. Can query for multiple network types. Case sensitive. Choose one or more from: 'CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED' .PARAMETER CallerNetworkTypes Show sessions where the selected network type was used by the caller. Can query for multiple network types. Case sensitive. Choose one or more from: 'CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED' .PARAMETER CalleeNetworkTypes Show sessions where the selected network type was used by the callee. Can query for multiple network types. Case sensitive. Choose one or more from: 'CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED' .PARAMETER Platforms Show sessions where the selected platform was used by either caller or callee. Can query for multiple platforms. Case sensitive. Choose one or more from: 'SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS' .PARAMETER EndpointTypes Show sessions where one or more selected Avaya endpoint types have been used. Choose one or more from: 'medsvr','mgdsp','node','phone','unknown' .PARAMETER CallerEndpointTypes Show sessions where one or more selected Avaya endpoint types have been used by the caller. Choose one or more from: 'medsvr','mgdsp','node','phone','unknown' .PARAMETER CalleeEndpointTypes Show sessions where one or more selected Avaya endpoint types have been used by the callee. Choose one or more from: 'medsvr','mgdsp','node','phone','unknown' .PARAMETER Users Show sessions where the selected user was either caller or callee. Can query for multiple users. .PARAMETER FromUsers Show sessions where the selected user was the caller. Can query for multiple users. .PARAMETER ToUsers Show sessions where the selected user was the callee. Can query for multiple users. .PARAMETER UserIDs Show sessions where the selected user ID was either caller or callee. Can query for multiple user IDs. .PARAMETER FromUserIDs Show sessions where the selected user ID was the caller. Can query for multiple user IDs. .PARAMETER ToUserIDs Show sessions where the selected user ID was the callee. Can query for multiple user IDs. .PARAMETER ConfOrganizers Show sessions hosted by a specified conference organizer. Can query for multiple organizers. .PARAMETER VPN Show sessions where the selected VPN was used by either caller or callee. .PARAMETER CallerVPN Show sessions where the selected VPN was used by the caller. .PARAMETER CalleeVPN Show sessions where the selected VPN was used by the callee. .PARAMETER ParticipantsMinCount Show sessions where the number of participants is greater than or equal to the entered value .PARAMETER ParticipantsMaxCount Show sessions where the number of participants is less than or equal to the entered value .PARAMETER FeedbackRating Show sessions where users provided specific feedback ratings from BAD to EXCELLENT. Allowed values are BAD, POOR, FAIR, GOOD, EXCELLENT. Corresponds to ratings from 1 to 5 stars. .PARAMETER Insights Show sessions that match one or more given insights. Choose from NORMAL_SESSION, VOICE_MAIL, HIGH_JITTER, HIGH_PACKET_LOSS, HIGH_ROUNDTRIP_DELAY .PARAMETER OrderByField Sort the output by the selected field .PARAMETER OrderDirection Sort direction. Use with OrderByField. Not case sensitive. Choose from: ASC, DESC .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 1000 .PARAMETER ResultSize The total number of results to return. Defaults to 1000. Maximum result size is 9,999,999 results .EXAMPLE Get-NectarModalityQualitySummary -Modality AUDIO -TimePeriod LAST_DAY Returns the modality quality summary for the past day .NOTES Version 1.1 #> [Alias("gnmqs")] Param ( [Parameter(Mandatory=$True)] [ValidateSet('AUDIO','VIDEO','APPSHARE', IgnoreCase=$True)] [string]$Modality, [Parameter(Mandatory=$False)] [ValidateSet('LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM', IgnoreCase=$True)] [string]$TimePeriod = 'LAST_HOUR', [Parameter(Mandatory=$False)] [Alias("StartDateFrom")] [DateTime]$TimePeriodFrom, [Parameter(Mandatory=$False)] [Alias("StartDateTo")] [DateTime]$TimePeriodTo, [Parameter(Mandatory=$False)] [ValidateSet('GOOD','POOR_0_25','PARTIALLY_GOOD_25_50','PARTIALLY_GOOD_50_75','PARTIALLY_GOOD_75_100','UNAVAILABLE','UNKNOWN', IgnoreCase=$True)] [string[]]$SessionQualities, [Parameter(Mandatory=$False)] [ValidateScript ({ If ($_ -Match '^\d\d(.\d+)?\-(\d\d|100)(.\d+)?$') { $True } Else { Throw 'NectarScore must be in the format aa-xx (ie. 80-90) or aa.bb-xx.yy (ie 77.32-80.99), where aa is less than xx and xx cannot be greater than 100.' } })] [string]$NectarScore, [Parameter(Mandatory=$False)] [ValidateRange(0,99999999)] [int]$DurationFrom = 0, [Parameter(Mandatory=$False)] [ValidateRange(0,99999999)] [int]$DurationTo = 99999999, [Parameter(Mandatory=$False)] [ValidateSet('TCP','UDP','Unknown', IgnoreCase=$False)] [string[]]$Protocols, [Parameter(Mandatory=$False)] [ValidateRange(200,699)] [string[]]$ResponseCodes, [Parameter(Mandatory=$False)] [ValidateSet('INTERNAL','EXTERNAL','FEDERATED','INTERNAL_EXTERNAL','EXTERNAL_INTERNAL','FEDERATED_INTERNAL','INTERNAL_FEDERATED','FEDERATED_EXTERNAL','EXTERNAL_FEDERATED','UNKNOWN', IgnoreCase=$True)] [string[]]$SessionScenarios, [Parameter(Mandatory=$False)] [ValidateSet('CONFERENCE','CONFERENCE_SESSION','PEER2PEER','PEER2PEER_MULTIMEDIA','PSTN','WEBINAR', IgnoreCase=$False)] [string[]]$SessionTypes, [Parameter(Mandatory=$False)] [string[]]$Codecs, [Parameter(Mandatory=$False)] [string[]]$CallerCodecs, [Parameter(Mandatory=$False)] [string[]]$CalleeCodecs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$Devices, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerDevices, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeDevices, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$DeviceVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerDeviceVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeDeviceVersions, [string[]]$AgentVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerAgentVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeAgentVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ipaddress[]]$IPAddresses, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ipaddress[]]$CallerIPAddresses, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ipaddress[]]$CalleeIPAddresses, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias("SiteName")] [string[]]$Locations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerLocations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeLocations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtISPs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtISPs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtISPs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('Cable','Dialup','DSL','FTTx','ISDN','Unknown','Wireless', IgnoreCase=$False)] [string[]]$ExtConnectionTypes, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('Cable','Dialup','DSL','FTTx','ISDN','Unknown','Wireless', IgnoreCase=$False)] [string[]]$CallerExtConnectionTypes, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('Cable','Dialup','DSL','FTTx','ISDN','Unknown','Wireless', IgnoreCase=$False)] [string[]]$CalleeExtConnectionTypes, [Parameter(Mandatory=$False)] [ValidateSet('CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED', IgnoreCase=$False)] [string[]]$NetworkTypes, [Parameter(Mandatory=$False)] [ValidateSet('CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED', IgnoreCase=$False)] [string[]]$CallerNetworkTypes, [Parameter(Mandatory=$False)] [ValidateSet('CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED', IgnoreCase=$False)] [string[]]$CalleeNetworkTypes, [Parameter(Mandatory=$False)] [ValidateSet('SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS', IgnoreCase=$True)] [string[]]$Platforms, [Parameter(Mandatory=$False)] [string[]]$Servers, [Parameter(Mandatory=$False)] [ValidateSet('AAEP','AAMS','medsvr','mgdsp','node','phone','SBC','sip','unknown', IgnoreCase=$True)] [string[]]$EndpointTypes, [Parameter(Mandatory=$False)] [ValidateSet('AAEP','AAMS','medsvr','mgdsp','node','phone','SBC','sip','unknown', IgnoreCase=$True)] [string[]]$CallerEndpointTypes, [Parameter(Mandatory=$False)] [ValidateSet('AAEP','AAMS','medsvr','mgdsp','node','phone','SBC','sip','unknown', IgnoreCase=$True)] [string[]]$CalleeEndpointTypes, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$Users, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$FromUsers, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ToUsers, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$UserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$FromUserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ToUserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ConfOrganizers, [Parameter(Mandatory=$False)] [ValidateSet('TRUE','FALSE','UNKNOWN', IgnoreCase=$True)] [string[]]$VPN, [Parameter(Mandatory=$False)] [ValidateSet('TRUE','FALSE','UNKNOWN', IgnoreCase=$True)] [string[]]$CallerVPN, [Parameter(Mandatory=$False)] [ValidateSet('TRUE','FALSE','UNKNOWN', IgnoreCase=$True)] [string[]]$CalleeVPN, [Parameter(Mandatory=$False)] [ValidateRange(0,99999)] [int]$ParticipantsMinCount, [Parameter(Mandatory=$False)] [ValidateRange(0,99999)] [int]$ParticipantsMaxCount, [Parameter(Mandatory=$False)] [ValidateSet('BAD','POOR','FAIR','GOOD','EXCELLENT', IgnoreCase=$False)] [string[]]$FeedbackRating, [Parameter(Mandatory=$False)] [ValidateSet('NORMAL_SESSION','VOICE_MAIL','HIGH_JITTER','HIGH_PACKET_LOSS','HIGH_ROUNDTRIP_DELAY', IgnoreCase=$False)] [string[]]$Insights, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [ValidateSet('DEFAULT','USERS','ENDPOINT_CLIENT','CALL_DETAILS_HISTORIC','QUALITY_DETAILS', IgnoreCase=$True)] [string]$Scope = 'CALL_DETAILS_HISTORIC', [Parameter(Mandatory=$False)] [ValidateRange(1,100000)] [int]$PageSize = 1000, [Parameter(Mandatory=$False)] [ValidateRange(1,9999999)] [int]$ResultSize ) Process { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } $PSBoundParameters.Remove('Modality') | Out-Null $FilterSession = Set-NectarFilterParams @PsBoundParameters $Modality = $Modality.ToLower() $URI = "https://$Global:NectarCloud/dapi/quality/session/$Modality" If ($TenantName) { $Params = @{'Tenant' = $TenantName} } Try { Write-Verbose $PsBoundParameters $QualitySummaryList = @() $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader -Body $Params -WebSession $FilterSession ForEach ($SessionType in $JSON) { If ($SessionType.QualityType -in $SessionTypes -or !$SessionTypes) { $QualitySummary = [pscustomobject][ordered]@{} If ($Modality -ne 'appshare') { $QualitySummary | Add-Member -NotePropertyName 'SessionType' -NotePropertyValue $SessionType.QualityType } ForEach ($QualityMetric in $SessionType.QualityMetrics) { $QualitySummary | Add-Member -NotePropertyName $QualityMetric.name -NotePropertyValue $QualityMetric.value $TrendMetricName = $QualityMetric.name + '_TREND' $QualitySummary | Add-Member -NotePropertyName $TrendMetricName -NotePropertyValue $QualityMetric.trends } If ($TenantName) { $QualitySummary | Add-Member -NotePropertyName 'TenantName' -NotePropertyValue $TenantName } If ($PSItem.SiteName) { $QualitySummary | Add-Member -NotePropertyName 'SiteName' -NotePropertyValue $PSItem.SiteName } $QualitySummary | Add-Member -TypeName 'Nectar.ModalityQuality' $QualitySummaryList += $QualitySummary } } Return $QualitySummaryList } Catch { Write-Error "No results. Try specifying a less-restrictive filter" Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarLocationQualitySummary { <# .SYNOPSIS Returns summary information for different locations over a given timeperiod. .DESCRIPTION Returns summary information for different locations over a given timeperiod. This is used to build the SUMMARY page in Nectar DXP. UI_ELEMENT .PARAMETER TimePeriod The time period to show session data from. Select from 'LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM'. CUSTOM requires using TimePeriodFrom and TimePeriodTo parameters. .PARAMETER TimePeriodFrom The earliest date/time to show session data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodTo parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. .PARAMETER TimePeriodTo The latest date/time to show session data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodFrom parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. .PARAMETER SessionQualities Show sessions that match a given quality rating. Case sensitive. Choose one or more from: 'GOOD','POOR_0_25','PARTIALLY_GOOD_25_50','PARTIALLY_GOOD_50_75','PARTIALLY_GOOD_75_100','UNAVAILABLE','UNKNOWN' .PARAMETER NectarScore Show sessions that match the given range of NectarScores. Use format aa-xx or aa.bb-xx.yy, where aa is less than xx, and xx is less than 100. Eg. 70-90 or 95.00-95.59 .PARAMETER DurationFrom The shortest call length (in seconds) to show session data. .PARAMETER DurationTo The longest call length (in seconds) to show session data. .PARAMETER Modalities Show sessions that match one or more modality types. Not case sensitive. Choose one or more from: 'AUDIO','VIDEO','APP_SHARING','IM','UNKNOWN','TOTAL','VBSS','REMOTE_ASSISTANCE','APP_INVITE','FOCUS','UNKNOWN' .PARAMETER Protocols Show sessions that match one or more network protocol types. Case sensitive. Choose one or more from: 'TCP','UDP','Unknown' .PARAMETER ResponseCodes Show sessions that match one or more SIP response codes. Accepts numbers from 200 to 699 .PARAMETER SessionScenarios Show sessions that match one or more session scenarios. Not case sensitive. Choose one or more from: 'External','Internal','Internal-External','External-Internal','Federated','Internal-Federated','External-Federated','Unknown' .PARAMETER SessionTypes Show sessions that match one or more session scenarios. Case sensitive. Choose one or more from: 'Conference','Peer To Peer','Peer To Peer (Multimedia)','PSTN/External' .PARAMETER Codecs Show sessions where the selected codec was used by either caller or callee. Can query for multiple codecs. Case sensitive. Use Get-NectarCodecs for a list of valid codecs. .PARAMETER CallerCodecs Show sessions where the selected codec was used by the caller. Can query for multiple codecs. Case sensitive. Use Get-NectarCodecs for a list of valid codecs. .PARAMETER CalleeCodecs Show sessions where the selected codec was used by the callee. Can query for multiple codecs. Case sensitive. Use Get-NectarCodecs for a list of valid codecs. .PARAMETER Devices Show sessions where the selected device was used by either caller or callee. Can query for multiple devices. Case sensitive. Use Get-NectarSupportedDevice for a list of valid devices. .PARAMETER CallerDevices Show sessions where the selected device was used by the caller. Can query for multiple devices. Case sensitive. Use Get-NectarSupportedDevice for a list of valid devices. .PARAMETER CalleeDevices Show sessions where the selected device was used by the callee. Can query for multiple devices. Case sensitive. Use Get-NectarSupportedDevice for a list of valid devices. .PARAMETER DeviceVersions Show sessions where the selected device version was used by either caller or callee. Can query for multiple devices. Case sensitive. Use Get-NectarClientVersion for a list of valid client versions. .PARAMETER CallerDeviceVersions Show sessions where the selected device version was used by the caller. Can query for multiple devices. Case sensitive. Use Get-NectarClientVersion for a list of valid client versions. .PARAMETER CalleeDeviceVersions Show sessions where the selected device version was used by the callee. Can query for multiple devices. Case sensitive. Use Get-NectarClientVersion for a list of valid client versions. .PARAMETER AgentVersions Show sessions where the selected agent version was used by either caller or callee. Can query for multiple agent. Case sensitive. Use Get-NectarAgentVersion for a list of valid agent versions. .PARAMETER CallerAgentVersions Show sessions where the selected agent version was used by either caller or callee. Can query for multiple agent. Case sensitive. Use Get-NectarAgentVersion for a list of valid agent versions. .PARAMETER CalleeAgentVersions Show sessions where the selected agent version was used by either caller or callee. Can query for multiple agent. Case sensitive. Use Get-NectarAgentVersion for a list of valid agent versions. .PARAMETER IPAddresses Show sessions where the selected IP address was used by either caller or callee. Can query for multiple IPs. .PARAMETER CallerIPAddresses Show sessions where the selected IP address was used by the caller. Can query for multiple IPs. .PARAMETER CalleeIPAddresses Show sessions where the selected IP address was used by the callee. Can query for multiple IPs. .PARAMETER Locations Show sessions where the selected location was used by either caller or callee. Can query for multiple locations. .PARAMETER CallerLocations Show sessions where the selected location was used by the caller. Can query for multiple locations. .PARAMETER CalleeLocations Show sessions where the selected location was used by the callee. Can query for multiple locations. .PARAMETER ExtCities Show sessions where the caller or callee was located in the selected city (as detected via geolocating the user's external IP address). Can query for multiple cities. .PARAMETER CallerExtCities Show sessions where the caller was located in the selected city (as detected via geolocating the user's external IP address). Can query for multiple cities. .PARAMETER CalleeExtCities Show sessions where the callee was located in the selected city (as detected via geolocating the user's external IP address). Can query for multiple cities. .PARAMETER ExtCountries Show sessions where the caller or callee was located in the selected country (as detected via geolocating the user's external IP address). Can query for multiple countries. .PARAMETER CallerExtCountries Show sessions where the caller was located in the selected country (as detected via geolocating the user's external IP address). Can query for multiple countries. .PARAMETER CalleeExtCountries Show sessions where the callee was located in the selected country (as detected via geolocating the user's external IP address). Can query for multiple countries. .PARAMETER ExtISPs Show sessions where the caller or callee was located in the selected ISP (as detected via geolocating the user's external IP address). Can query for multiple ISPs. .PARAMETER CallerExtISPs Show sessions where the caller was located in the selected ISP (as detected via geolocating the user's external IP address). Can query for multiple ISPs. .PARAMETER CalleeExtISPs Show sessions where the callee was located in the selected ISP (as detected via geolocating the user's external IP address). Can query for multiple ISPs. .PARAMETER ExtConnectionTypes Show sessions that match the caller or callee external connection type (as detected via geolocating the user's external IP address). Can query for multiple types. .PARAMETER CallerExtConnectionTypes Show sessions that match the caller's external connection type (as detected via geolocating the user's external IP address). Can query for multiple types. .PARAMETER CalleeExtConnectionTypes Show sessions that match the callee's external connection type (as detected via geolocating the user's external IP address). Can query for multiple types. .PARAMETER NetworkTypes Show sessions where the selected network type was used by either caller or callee. Can query for multiple network types. Case sensitive. Choose one or more from: 'CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED' .PARAMETER CallerNetworkTypes Show sessions where the selected network type was used by the caller. Can query for multiple network types. Case sensitive. Choose one or more from: 'CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED' .PARAMETER CalleeNetworkTypes Show sessions where the selected network type was used by the callee. Can query for multiple network types. Case sensitive. Choose one or more from: 'CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED' .PARAMETER Platforms Show sessions where the selected platform was used by either caller or callee. Can query for multiple platforms. Case sensitive. Choose one or more from: 'SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS' .PARAMETER EndpointTypes Show sessions where one or more selected Avaya endpoint types have been used. Choose one or more from: 'medsvr','mgdsp','node','phone','unknown' .PARAMETER CallerEndpointTypes Show sessions where one or more selected Avaya endpoint types have been used by the caller. Choose one or more from: 'medsvr','mgdsp','node','phone','unknown' .PARAMETER CalleeEndpointTypes Show sessions where one or more selected Avaya endpoint types have been used by the callee. Choose one or more from: 'medsvr','mgdsp','node','phone','unknown' .PARAMETER Users Show sessions where the selected user was either caller or callee. Can query for multiple users. .PARAMETER FromUsers Show sessions where the selected user was the caller. Can query for multiple users. .PARAMETER ToUsers Show sessions where the selected user was the callee. Can query for multiple users. .PARAMETER UserIDs Show sessions where the selected user ID was either caller or callee. Can query for multiple user IDs. .PARAMETER FromUserIDs Show sessions where the selected user ID was the caller. Can query for multiple user IDs. .PARAMETER ToUserIDs Show sessions where the selected user ID was the callee. Can query for multiple user IDs. .PARAMETER ConfOrganizers Show sessions hosted by a specified conference organizer. Can query for multiple organizers. .PARAMETER VPN Show sessions where the selected VPN was used by either caller or callee. .PARAMETER CallerVPN Show sessions where the selected VPN was used by the caller. .PARAMETER CalleeVPN Show sessions where the selected VPN was used by the callee. .PARAMETER ParticipantsMinCount Show sessions where the number of participants is greater than or equal to the entered value .PARAMETER ParticipantsMaxCount Show sessions where the number of participants is less than or equal to the entered value .PARAMETER FeedbackRating Show sessions where users provided specific feedback ratings from BAD to EXCELLENT. Allowed values are BAD, POOR, FAIR, GOOD, EXCELLENT. Corresponds to ratings from 1 to 5 stars. .PARAMETER Insights Show sessions that match one or more given insights. Choose from NORMAL_SESSION, VOICE_MAIL, HIGH_JITTER, HIGH_PACKET_LOSS, HIGH_ROUNDTRIP_DELAY .PARAMETER OrderByField Sort the output by the selected field .PARAMETER OrderDirection Sort direction. Use with OrderByField. Not case sensitive. Choose from: ASC, DESC .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 1000 .PARAMETER ResultSize The total number of results to return. Defaults to 1000. Maximum result size is 9,999,999 results .EXAMPLE Get-NectarLocationQualitySummary -TimePeriod LAST_DAY Returns the quality summary for each location for the last day .NOTES Version 1.1 #> [Alias("gnlqs")] Param ( [Parameter(Mandatory=$False)] [ValidateSet('LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM', IgnoreCase=$True)] [string]$TimePeriod = 'LAST_HOUR', [Parameter(Mandatory=$False)] [Alias("StartDateFrom")] [DateTime]$TimePeriodFrom, [Parameter(Mandatory=$False)] [Alias("StartDateTo")] [DateTime]$TimePeriodTo, [Parameter(Mandatory=$False)] [ValidateSet('GOOD','POOR_0_25','PARTIALLY_GOOD_25_50','PARTIALLY_GOOD_50_75','PARTIALLY_GOOD_75_100','UNAVAILABLE','UNKNOWN', IgnoreCase=$True)] [string[]]$SessionQualities, [Parameter(Mandatory=$False)] [ValidateScript ({ If ($_ -Match '^\d\d(.\d+)?\-(\d\d|100)(.\d+)?$') { $True } Else { Throw 'NectarScore must be in the format aa-xx (ie. 80-90) or aa.bb-xx.yy (ie 77.32-80.99), where aa is less than xx and xx cannot be greater than 100.' } })] [string]$NectarScore, [Parameter(Mandatory=$False)] [ValidateRange(0,99999999)] [int]$DurationFrom = 0, [Parameter(Mandatory=$False)] [ValidateRange(0,99999999)] [int]$DurationTo = 99999999, [Parameter(Mandatory=$False)] [ValidateSet('AUDIO','VIDEO','APP_SHARING','IM','UNKNOWN','TOTAL','VBSS','REMOTE_ASSISTANCE','APP_INVITE','FOCUS','UNKNOWN', IgnoreCase=$True)] [string[]]$Modalities, [Parameter(Mandatory=$False)] [ValidateSet('TCP','UDP','Unknown', IgnoreCase=$False)] [string[]]$Protocols, [Parameter(Mandatory=$False)] [ValidateRange(200,699)] [string[]]$ResponseCodes, [Parameter(Mandatory=$False)] [ValidateSet('INTERNAL','EXTERNAL','FEDERATED','INTERNAL_EXTERNAL','EXTERNAL_INTERNAL','FEDERATED_INTERNAL','INTERNAL_FEDERATED','FEDERATED_EXTERNAL','EXTERNAL_FEDERATED','UNKNOWN', IgnoreCase=$True)] [string[]]$SessionScenarios, [Parameter(Mandatory=$False)] [ValidateSet('CONFERENCE','CONFERENCE_SESSION','PEER2PEER','PEER2PEER_MULTIMEDIA','PSTN','WEBINAR', IgnoreCase=$False)] [string[]]$SessionTypes, [Parameter(Mandatory=$False)] [string[]]$Codecs, [Parameter(Mandatory=$False)] [string[]]$CallerCodecs, [Parameter(Mandatory=$False)] [string[]]$CalleeCodecs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$Devices, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerDevices, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeDevices, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$DeviceVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerDeviceVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeDeviceVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$AgentVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerAgentVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeAgentVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ipaddress[]]$IPAddresses, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ipaddress[]]$CallerIPAddresses, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ipaddress[]]$CalleeIPAddresses, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$Locations, [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerLocations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeLocations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtISPs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtISPs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtISPs, [Parameter(Mandatory=$False)] [ValidateSet('CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED', IgnoreCase=$False)] [string[]]$NetworkTypes, [Parameter(Mandatory=$False)] [ValidateSet('CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED', IgnoreCase=$False)] [string[]]$CallerNetworkTypes, [Parameter(Mandatory=$False)] [ValidateSet('CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED', IgnoreCase=$False)] [string[]]$CalleeNetworkTypes, [Parameter(Mandatory=$False)] [ValidateSet('SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS', IgnoreCase=$True)] [string[]]$Platforms, [Parameter(Mandatory=$False)] [string[]]$Servers, [Parameter(Mandatory=$False)] [ValidateSet('AAEP','AAMS','medsvr','mgdsp','node','phone','SBC','sip','unknown', IgnoreCase=$True)] [string[]]$EndpointTypes, [Parameter(Mandatory=$False)] [ValidateSet('AAEP','AAMS','medsvr','mgdsp','node','phone','SBC','sip','unknown', IgnoreCase=$True)] [string[]]$CallerEndpointTypes, [Parameter(Mandatory=$False)] [ValidateSet('AAEP','AAMS','medsvr','mgdsp','node','phone','SBC','sip','unknown', IgnoreCase=$True)] [string[]]$CalleeEndpointTypes, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$Users, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$FromUsers, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ToUsers, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$UserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$FromUserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ToUserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ConfOrganizers, [Parameter(Mandatory=$False)] [ValidateSet('TRUE','FALSE','UNKNOWN', IgnoreCase=$True)] [string[]]$VPN, [Parameter(Mandatory=$False)] [ValidateSet('TRUE','FALSE','UNKNOWN', IgnoreCase=$True)] [string[]]$CallerVPN, [Parameter(Mandatory=$False)] [ValidateSet('TRUE','FALSE','UNKNOWN', IgnoreCase=$True)] [string[]]$CalleeVPN, [Parameter(Mandatory=$False)] [ValidateRange(0,99999)] [int]$ParticipantsMinCount, [Parameter(Mandatory=$False)] [ValidateRange(0,99999)] [int]$ParticipantsMaxCount, [Parameter(Mandatory=$False)] [ValidateSet('BAD','POOR','FAIR','GOOD','EXCELLENT', IgnoreCase=$False)] [string[]]$FeedbackRating, [Parameter(Mandatory=$False)] [ValidateSet('NORMAL_SESSION','VOICE_MAIL','HIGH_JITTER','HIGH_PACKET_LOSS','HIGH_ROUNDTRIP_DELAY', IgnoreCase=$False)] [string[]]$Insights, [switch]$ShowQualityDetails, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [ValidateSet('DEFAULT','USERS','ENDPOINT_CLIENT','CALL_DETAILS_HISTORIC','QUALITY_DETAILS', IgnoreCase=$True)] [string]$Scope = 'CALL_DETAILS_HISTORIC', [Parameter(Mandatory=$False)] [ValidateRange(1,100000)] [int]$PageSize = 1000, [Parameter(Mandatory=$False)] [ValidateRange(1,9999999)] [int]$ResultSize ) Process { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } $FilterSession = Set-NectarFilterParams @PsBoundParameters If ($TenantName) { $Params = @{'Tenant' = $TenantName} } Try { $JSON = Invoke-RestMethod -Method GET -uri "https://$Global:NectarCloud/dapi/quality/locations" -Headers $Global:NectarAuthHeader -Body $Params -WebSession $FilterSession $JSONGoodPoor = Invoke-RestMethod -Method GET -uri "https://$Global:NectarCloud/dapi/quality/map" -Headers $Global:NectarAuthHeader -Body $Params -WebSession $FilterSession ForEach ($Location in $JSON) { $LocGoodPoor = $JSONGoodPoor | Where-Object {$_.name -eq $Location.Name} $QualitySummary = [pscustomobject][ordered]@{ 'LocationName' = $Location.Name 'Sessions' = $Location.Data.Sessions 'CurrentSessions' = $Location.Data.currentSession 'GoodSessions' = $LocGoodPoor.Sessions.Good 'PoorSessions' = $LocGoodPoor.Sessions.Poor 'UnknownSessions' = $LocGoodPoor.Sessions.Unknown 'AvgMOS' = $Location.Data.avgMos 'MOSTrend' = $Location.Data.mos } If ($TenantName) { $QualitySummary | Add-Member -NotePropertyName 'TenantName' -NotePropertyValue $TenantName } $QualitySummary | Add-Member -TypeName 'Nectar.LocationQuality' If ($Location.Name -ne 'All') { $QualitySummary } } } Catch { Write-Error "No results. Try specifying a less-restrictive filter" Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarAgentVersion { <# .SYNOPSIS Returns a list of agent versions .DESCRIPTION Returns a list of agent versions .PARAMETER SearchQuery Allows filtering of the final results .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarAgentVersion -SearchQuery Sonus Returns all the agent versions that contain 'Sonus' in the name .NOTES Version 1.0 #> [Alias("gns")] Param ( [Parameter(Mandatory=$False)] [string]$SearchQuery, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/dapi/info/agent/version?tenant=$TenantName" $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader If ($SearchQuery) { $JSON = $JSON | Where-Object {$_ -like "*$SearchQuery*"} } If (!$JSON) { Write-Error "Agent version list could not be found." } Else { Return $JSON } } Catch { Write-Error "Unable to get agent version list." Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarServers { <# .SYNOPSIS Returns a list of platform servers .DESCRIPTION Returns a list of platform servers like datacenter names (TEAMS), servernames (CISCO etc) .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarServers .NOTES Version 1.0 #> [Alias("gns")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/dapi/info/platform/servers?tenant=$TenantName" $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader If (!$JSON.elements) { Write-Error "Server list could not be found." } Else { If ($TenantName) { $JSON.elements | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty } # Add the tenant name to the output which helps pipelining $JSON.elements | Add-Member -TypeName 'Nectar.LocationList' Return $JSON.elements } } Catch { Write-Error "Unable to get server details." Get-JSONErrorStream -JSONResponse $_ } } } ################################################################################################################################################# # # # Room/Device Functions # # # ################################################################################################################################################# Function Get-NectarRoom { <# .SYNOPSIS Return a list of Nectar monitored rooms .DESCRIPTION Return a list of Nectar monitored rooms .PARAMETER SearchQuery Limit the results to the specified search query. Will match against all fields. .PARAMETER OrderByField Order the resultset by the specified field. Choose from id, type, lastTime, displayName, deviceName, description, eventId, time, delay, source, location, sourceId .PARAMETER OrderDirection Sort order. Pick from ASC or DESC .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 1000 .EXAMPLE Get-NectarRoom Returns a list of all rooms .EXAMPLE Get-NectarRoom -OrderByField HealthStatus -OrderDirection Descending Returns a list of rooms sorted by Health Status .NOTES Version 1.0 #> Param ( [Parameter(Mandatory=$False)] [string]$SearchQuery, [Parameter(Mandatory=$False)] [ValidateSet('HealthStatus', 'Name', 'BusinessUnits', 'NetworkName', 'SiteName', 'City', 'Region', 'Country', 'State', 'DeviceCount', IgnoreCase=$False)] [string]$OrderByField = 'Name', [Parameter(Mandatory=$False)] [ValidateSet('asc', 'desc', IgnoreCase=$True)] [string]$OrderDirection, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [ValidateRange(1,100000)] [int]$PageSize = 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 !$TenantName) { $TenantName = $Global:NectarTenantName } If ($TenantName) { $NULL = $PSBoundParameters.Add('Tenant',$TenantName) $NULL = $PSBoundParameters.Remove('TenantName') } # Convert to camelCase if used If ($OrderByField) { $PSBoundParameters.OrderByField = $OrderByField -Replace "^$($OrderByField[0])", "$($OrderByField[0].ToString().ToLower())" } $PSBoundParameters.PageSize = $PageSize $URI = "https://$Global:NectarCloud/dapi/room-and-device/rooms" Write-Verbose $URI If ($PSBoundParameters['Verbose']) { ForEach ($boundParam in $PSBoundParameters.GetEnumerator()) { '{0} = {1}' -f $boundParam.Key, $boundParam.Value } } $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader -Body $PSBoundParameters $TotalPages = $JSON.totalPages If ($TenantName) {$JSON.elements | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty} $JSON.elements Write-Verbose "Page size: $PageSize" Write-Verbose "Total pages: $TotalPages" If ($TotalPages -gt 1) { $PageNum = 2 $PSBoundParameters.Add('pageNumber', 2) While ($PageNum -le $TotalPages) { Write-Verbose "Working on page $PageNum of $TotalPages" $PSBoundParameters.pageNumber = $PageNum $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader -Body $PSBoundParameters If ($TenantName) {$JSON.elements | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty} $JSON.elements $PageNum++ } } } Catch { Write-Error "Error pulling data." Get-JSONErrorStream -JSONResponse $_ } } } Function Set-NectarRoom { <# .SYNOPSIS Update an existing Nectar monitored room .DESCRIPTION Update an existing Nectar monitored room. Can change the name and the associated business units .PARAMETER ID The ID of the room to update. Can be obtained via Get-NectarRoom and will accept pipeline input .PARAMETER Name The new name of the room .PARAMETER LocationID The ID of a Nectar location to assign to the room. Location IDs can be obtained via Get-NectarLocation .PARAMETER BusinessUnitID One or more business unit IDs to assign to the room. Business Unit IDs can be obtained via Get-NectarBusinessUnit .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Set-NectarRoom -RoomID 1 -RoomName 'Secondary Boardroom' Changes the room name with ID 1 to 'Secondary Boardroom' .EXAMPLE Get-NectarRoom -SearchQuery 'Main Boardroom' | Set-NectarRoom -RoomName 'Secondary Boardroom' Changes the room name from 'Main Boardroom' to 'Secondary Boardroom' .EXAMPLE Get-NectarRoom -SearchQuery Dallas | Set-NectarRoom -BusinessUnitID 3 Changes all rooms that have Dallas in any field to BusinessUnitID 3 .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [int]$ID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$Name, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias('businessUnits')] [string[]]$BusinessUnitID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $Body = @{ roomName = $Name } If ($BusinessUnitID) { # If using BusinessUnitIDs from pipeline, it contains text. We have to strip it down to just the number for updating If ($BusinessUnitID[0] -like '*:*') { ForEach ($i in $BusinessUnitID) { $BusinessUnitID[$BusinessUnitID.IndexOf($i)] = $($i.Substring(0, $i.IndexOf(':'))) } } $Body.Add('bUnitIds', $BusinessUnitID) } $JSONBody = $Body | ConvertTo-Json $URI = "https://$Global:NectarCloud/dapi/room-and-device/room/$ID/?tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method PUT -uri $URI -Headers $Global:NectarAuthHeader -Body $JSONBody -ContentType 'application/json; charset=utf-8' Return $JSON } Catch { Write-Error "Error updating room." Get-JSONErrorStream -JSONResponse $_ } } } Function New-NectarRoom { <# .SYNOPSIS Create a new Nectar monitored room .DESCRIPTION Create a new Nectar monitored room .PARAMETER RoomName The name of the room to create .PARAMETER LocationID The ID of a Nectar location to assign to the room. Location IDs can be obtained via Get-NectarLocation .PARAMETER BusinessUnitID One or more business unit IDs to assign to the room. Business Unit IDs can be obtained via Get-NectarBusinessUnit .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE New-NectarRoom 'Main Boardroom' Creates a room called 'Main Boardroom' .EXAMPLE New-NectarRoom 'Main Boardroom' -BusinessUnitID 1,3 Creates a room called 'Main Boardroom' and assign business units 1 and 3 to the room .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$RoomName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [int[]]$BusinessUnitID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $Body = @{ roomName = $RoomName } If ($BusinessUnitID) { $Body.Add('bUnitIds', $BusinessUnitID) } $JSONBody = $Body | ConvertTo-Json $URI = "https://$Global:NectarCloud/dapi/room-and-device/room?tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method POST -uri $URI -Headers $Global:NectarAuthHeader -Body $JSONBody -ContentType 'application/json; charset=utf-8' Return $JSON } Catch { Write-Error "Error creating room." Get-JSONErrorStream -JSONResponse $_ } } } Function Remove-NectarRoom { <# .SYNOPSIS Deletes a Nectar monitored room .DESCRIPTION Deletes a new Nectar monitored room .PARAMETER ID The numeric ID of the room to delete. Accepts pipeline input .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarRoom -SearchQuery Boardroom | Remove-NectarRoom Removes all rooms with the name 'Boardroom' anywhere in the name .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [int]$ID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/dapi/room-and-device/room/$($ID)?tenant=$TenantName" Write-Verbose $URI $NULL = Invoke-RestMethod -Method DELETE -uri $URI -Headers $Global:NectarAuthHeader } Catch { Write-Error "Error deleting room with ID $ID." Get-JSONErrorStream -JSONResponse $_ } } } Function Connect-NectarRoomDevice { <# .SYNOPSIS Connects one or more devices to a Nectar Room .DESCRIPTION Connects one or more devices to a Nectar Room .PARAMETER RoomID The numeric ID of the room to connect the device to. .PARAMETER DeviceUnitedId The numeric ID of the device to connect to the room. Accepts pipeline input from Get-NectarDevice .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarDevice -SearchQuery Dallas | Connect-NectarRoomDevice -RoomName DallasBoardroom Connects all devices with 'Dallas' in the name to the DallasBoardroom .NOTES Version 1.0 #> Param ( [Parameter(Mandatory=$True)] [string]$RoomName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [int]$DeviceUnitedId, [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 !$TenantName) { $TenantName = $Global:NectarTenantName } $RoomID = (Get-NectarRoom -SearchQuery $RoomName).id If ($RoomID.Count -gt 1) { Throw "Room name must be unique enough to return a single value. Your query returned $($RoomID.Count) results for search '$RoomName'." } } Process { Try { $URI = "https://$Global:NectarCloud/dapi/room-and-device/device/connect?device_united_id=$DeviceUnitedID&room_id=$RoomID&tenant=$TenantName" Write-Verbose $URI $NULL = Invoke-RestMethod -Method POST -uri $URI -Headers $Global:NectarAuthHeader } Catch { Write-Error "Error adding device with ID $DeviceUnitedID to $RoomName. Is this device already associated with a room?" Get-JSONErrorStream -JSONResponse $_ } } } Function Disconnect-NectarRoomDevice { <# .SYNOPSIS Disonnects one or more devices from a Nectar Room .DESCRIPTION Disonnects one or more devices from a Nectar Room .PARAMETER DeviceUnitedId The numeric ID of the device to disconnect from a room. Accepts pipeline input from Get-NectarDevice .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarDevice -SearchQuery Dallas | Disconnect-NectarRoomDevice Disconnects all devices with 'Dallas' in any field from any associated room .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [int]$DeviceUnitedId, [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 !$TenantName) { $TenantName = $Global:NectarTenantName } } Process { Try { $URI = "https://$Global:NectarCloud/dapi/room-and-device/device/disconnect/$DeviceUnitedID/?tenant=$TenantName" Write-Verbose $URI $NULL = Invoke-RestMethod -Method DELETE -uri $URI -Headers $Global:NectarAuthHeader } Catch { Write-Error "Error disconnecting device with ID $DeviceUnitedID. The device may already be disconnected from a room." Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarDevice { <# .SYNOPSIS Return a list of Nectar monitored devices .DESCRIPTION Return a list of Nectar monitored devices .PARAMETER SearchQuery Limit the results to the specified search query. Will match against all fields. .PARAMETER OrderByField Order the resultset by the specified field. Choose from id, type, lastTime, displayName, deviceName, description, eventId, time, delay, source, location, sourceId .PARAMETER OrderDirection Sort order. Pick from ASC or DESC .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 1000 .EXAMPLE Get-NectarDevice Returns a list of all devices .EXAMPLE Get-NectarDevice -SearchQuery Dallas Returns a list of devices that has 'Dallas' in any field .EXAMPLE Get-NectarDevice -OrderByField HealthStatus -OrderDirection Descending Returns a list of devices sorted by Health Status .NOTES Version 1.0 #> Param ( [Parameter(Mandatory=$False)] [string]$SearchQuery, [Parameter(Mandatory=$False)] [ValidateSet('HealthStatus', 'DeviceDisplayName', 'DeviceType', 'Vendor', 'Model', 'Platform', 'ActivityState', 'RoomName', 'BusinessUnits', 'NetworkName', IgnoreCase=$False)] [string]$OrderByField, [Parameter(Mandatory=$False)] [ValidateSet('asc', 'desc', IgnoreCase=$True)] [string]$OrderDirection, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [ValidateRange(1,100000)] [int]$PageSize = 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 !$TenantName) { $TenantName = $Global:NectarTenantName } If ($TenantName) { $NULL = $PSBoundParameters.Add('Tenant',$TenantName) $NULL = $PSBoundParameters.Remove('TenantName') } # Convert to camelCase if used If ($OrderByField) { $PSBoundParameters.OrderByField = $OrderByField -Replace "^$($OrderByField[0])", "$($OrderByField[0].ToString().ToLower())" } $PSBoundParameters.PageSize = $PageSize $PSBoundParameters.Add('pageNumber', 1) $URI = "https://$Global:NectarCloud/dapi/room-and-device/devices" Write-Verbose $URI If ($PSBoundParameters['Verbose']) { ForEach ($boundParam in $PSBoundParameters.GetEnumerator()) { '{0} = {1}' -f $boundParam.Key, $boundParam.Value } } $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader -Body $PSBoundParameters $TotalPages = $JSON.totalPages If ($TenantName) {$JSON.elements | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty} $JSON.elements Write-Verbose "Page size: $PageSize" Write-Verbose "Total pages: $TotalPages" If ($TotalPages -gt 1) { $PageNum = 2 While ($PageNum -le $TotalPages) { Write-Verbose "Working on page $PageNum of $TotalPages" $PSBoundParameters.PageNumber = $PageNum $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader -Body $PSBoundParameters $JSON.elements $PageNum++ } } } Catch { Write-Error "Error pulling data." Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarBusinessUnit { <# .SYNOPSIS Return a list of Nectar business units .DESCRIPTION Return a list of Nectar business units .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 .EXAMPLE Get-NectarBusinessUnit Returns a list of business units .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [ValidateRange(1,100000)] [int]$PageSize = 10000 ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } If ($TenantName) { $NULL = $PSBoundParameters.Add('Tenant',$TenantName) $NULL = $PSBoundParameters.Remove('TenantName') } $PSBoundParameters.PageSize = $PageSize $PSBoundParameters.Add('pageNumber', 1) $URI = "https://$Global:NectarCloud/dapi/room-and-device/business-unit?pageSize=$PageSize&tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader If ($TenantName) {$JSON | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty} $JSON } Catch { Write-Error "Error pulling data." Get-JSONErrorStream -JSONResponse $_ } } } Function New-NectarBusinessUnit { <# .SYNOPSIS Create a new Nectar business unit to be assigned to a room .DESCRIPTION Create a new Nectar business unit to be assigned to a room .PARAMETER Name The name of the business unit to create .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE New-NectarBusinessUnit 'Sales' Creates a business unit called 'Sales' .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$Name, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $Body = @{ unitName = $Name } $JSONBody = $Body | ConvertTo-Json $URI = "https://$Global:NectarCloud/dapi/room-and-device/business-unit?tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method POST -uri $URI -Headers $Global:NectarAuthHeader -Body $JSONBody -ContentType 'application/json; charset=utf-8' Return $JSON } Catch { Write-Error "Error creating business unit. Does the business unit already exist?" Get-JSONErrorStream -JSONResponse $_ } } } ################################################################################################################################################# # # # Report/Analytics Functions # # # ################################################################################################################################################# Function Get-NectarReport { <# .SYNOPSIS Gets basic information about all available reports .DESCRIPTION Gets basic information about all available reports .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarReport Returns information about all reports .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$SearchQuery, [Parameter(Mandatory=$False)] [ValidateSet('name', 'status', 'createdDate', 'lastRun', 'scheduledRun', IgnoreCase=$True)] [string]$OrderByField = 'name', [Parameter(Mandatory=$False)] [ValidateSet('asc', 'desc', IgnoreCase=$True)] [string]$OrderDirection = 'asc', [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 } Process { Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } $Params = @{ 'pageNumber' = 1 'pageSize' = $PageSize 'orderByField' = $OrderByField 'orderDirection' = $OrderDirection } If ($SearchQuery) { $Params.Add('searchQuery',$SearchQuery) } If ($ResultSize) { $Params.pageSize = $ResultSize } $URI = "https://$Global:NectarCloud/rapi/client/report/config/list?tenant=$TenantName" Write-Verbose $URI Write-Verbose "** Body Parameters **" ForEach($k in $Params.Keys){Write-Verbose "$k`: $($Params[$k])"} $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 If ($TenantName) {$JSON.elements | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty} $JSON.elements $PageNum++ } } } Catch { Write-Error "Error getting report info" Get-JSONErrorStream -JSONResponse $_ } } } Function New-NectarReport { <# .SYNOPSIS Creates a new report .DESCRIPTION Creates a new report using previously defined widgets from New-NectarReportWidgetConfig .PARAMETER Name The name of the report .PARAMETER Description A description to go with the report .PARAMETER Type The report type (or scope). Select from User or Tenant. User reports are visible only to the creator. Tenant reports are visible to all users. .PARAMETER WidgetList An arraylist containing the definitions for all the widgets to put in the report. Widgets are defined using New-NectarReportWidget .PARAMETER ReportTimeZone The timezone used for report display .PARAMETER ScheduleMode How often to run the report when on a schedule .PARAMETER SchedDayOfMonth If report is run on a schedule, what day of the month to run the report. Only works with ScheduleMode MONTHLY .PARAMETER SchedWeekdays The days of the week to run the report. Only works with ScheduleMode WEEKLY .PARAMETER SchedStartDate When the report schedule should start .PARAMETER SchedEndDate When the report schedule should end .PARAMETER SchedStartTime The time of day that the report should start processing .PARAMETER SchedTimeZone The timezone the schedule should run on .PARAMETER SchedNotifyMethod How to notify users when the report is ready .PARAMETER SchedEmail One or more email addresses, separated by commas to notify upon report completion. Only usable when SchedNotifyMethod is EMAIL .PARAMETER SchedEmailSubject The subject line of the email. Only usable when SchedNotifyMethod is EMAIL .PARAMETER SchedEmailMessage The content of the email message. Only usable when SchedNotifyMethod is EMAIL .PARAMETER SchedRepeatEveryNumDays How often to repeat a daily schedule .PARAMETER SchedRepeatEveryNumWeeks How often to repeat a weekly schedule .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE New-NectarReport -Name 'Test Report -Description 'This is a test report' -WidgetList $WidgetList -Type User Creates a user-level report using a pre-defined set of widgets stored in the $WidgetList variable .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$Name, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$Description, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('User','Tenant', IgnoreCase=$True)] [string]$Type = 'User', [Parameter(Mandatory=$True)] [System.Collections.ArrayList]$WidgetList, [Parameter(Mandatory=$False)] [string]$ReportTimeZone = "Etc/GMT$((Get-TimeZone).BaseUTCOffset.hours)", [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('ONCE','DAILY','WEEKLY','MONTHLY', IgnoreCase=$False)] [string]$ScheduleMode, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateRange(1,31)] [nullable[int]]$SchedDayOfMonth, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('MONDAY','TUESDAY','WEDNESDAY','THURSDAY','FRIDAY','SATURDAY','SUNDAY', IgnoreCase=$False)] [string[]]$SchedWeekdays, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$SchedStartDate = ((Get-Date).AddDays(1)).ToShortDateString(), [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$SchedEndDate, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [datetime]$SchedStartTime = '00:00', [Parameter(Mandatory=$False)] [string]$SchedTimeZone = "Etc/GMT$((Get-TimeZone).BaseUTCOffset.hours)", [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('NONE','EMAIL', IgnoreCase=$False)] [string]$SchedNotifyMethod = 'NONE', [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$SchedEmail, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$SchedEmailSubject = $Name, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$SchedEmailMessage, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [AllowNull()] [nullable[int]]$SchedRepeatEveryNumDays, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [AllowNull()] [nullable[int]]$SchedRepeatEveryNumWeeks, [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 !$TenantName) { $TenantName = $Global:NectarTenantName } Switch ($Type) { 'User' { $ReportTypeID = 1 } 'Tenant' { $ReportTypeID = 2 } } If ($ScheduleMode) { If ($SchedNotifyMethod -eq 'EMAIL') { $EmailParams = @{ 'recipients' = $SchedEmail 'subject' = $SchedEmailSubject #If (!$SchedEmailSubject) { $Name } Else { $SchedEmailSubject } 'message' = $SchedEmailMessage } } $NotificationParams = @{ 'method' = $SchedNotifyMethod 'email' = $EmailParams } $ScheduleParams = @{ 'scheduleMode' = $ScheduleMode 'dayOfMonth' = $SchedDayOfMonth 'startDate' = $SchedStartDate 'startTime' = Get-Date $SchedStartTime -UFormat %R 'endDate' = If (!$SchedEndDate) { $Null } Else { $SchedEndDate } 'repeatEveryNumDays' = $SchedRepeatEveryNumDays 'repeatEveryNumWeeks' = $SchedRepeatEveryNumWeeks 'weekDayOfMonth' = $SchedWeekDayOfMonth 'weekDays' = $SchedWeekDays 'weekOfMonth' = $SchedWeekOfMonth 'timeZone' = $SchedTimeZone 'notification' = $NotificationParams } } $Params = @{ 'name' = $Name 'description' = $Description 'schedule' = $ScheduleParams 'timeZone' = $ReportTimeZone 'visibilityType' = $ReportTypeID 'widgets' = $WidgetList } $JSONParams = $Params | ConvertTo-Json -Depth 7 $URI = "https://$Global:NectarCloud/rapi/client/report/config?tenant=$TenantName" Write-Verbose $URI Write-Verbose $JSONParams Try { $Null = Invoke-RestMethod -Method POST -uri $URI -Headers $Global:NectarAuthHeader -Body $JSONParams -ContentType 'application/json; charset=utf-8' } Catch { Write-Error "Error creating report" Get-JSONErrorStream -JSONResponse $_ } } } Function New-NectarReportWidget { <# .SYNOPSIS Creates a report widget to be added to a report .DESCRIPTION Creates a report widget to be added to a report. One ore more widgets have to be defined before creating a report .PARAMETER WidgetType The type of widget to create .PARAMETER WidgetDescription A description to go with the widget .PARAMETER WidgetPosition The numeric position to place the widget in the report. Starts at 0 .PARAMETER WidgetGroupVarName The name of a arraylist variable to add the widget to. If the variable does not exist, it will be created. Makes creating reports a bit easier. .PARAMETER TimePeriod The time period to show session data from. Select from 'LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM'. CUSTOM requires using TimePeriodFrom and TimePeriodTo parameters. .PARAMETER TimePeriodFrom The earliest date/time to show session data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodTo parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. .PARAMETER TimePeriodTo The latest date/time to show session data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodFrom parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. .PARAMETER SessionQualities Show sessions that match a given quality rating. Case sensitive. Choose one or more from: 'GOOD','POOR_0_25','PARTIALLY_GOOD_25_50','PARTIALLY_GOOD_50_75','PARTIALLY_GOOD_75_100','UNAVAILABLE','UNKNOWN' .PARAMETER NectarScore Show sessions that match the given range of NectarScores. Use format aa-xx or aa.bb-xx.yy, where aa is less than xx, and xx is less than 100. Eg. 70-90 or 95.00-95.59 .PARAMETER DurationFrom The shortest call length (in seconds) to show session data. .PARAMETER DurationTo The longest call length (in seconds) to show session data. .PARAMETER Modalities Show sessions that match one or more modality types. Not case sensitive. Choose one or more from: 'AUDIO','VIDEO','APP_SHARING','IM','UNKNOWN','TOTAL','VBSS','REMOTE_ASSISTANCE','APP_INVITE','FOCUS','UNKNOWN' .PARAMETER Protocols Show sessions that match one or more network protocol types. Case sensitive. Choose one or more from: 'TCP','UDP','Unknown' .PARAMETER ResponseCodes Show sessions that match one or more SIP response codes. Accepts numbers from 200 to 699 .PARAMETER SessionScenarios Show sessions that match one or more session scenarios. Not case sensitive. Choose one or more from: 'External','Internal','Internal-External','External-Internal','Federated','Internal-Federated','External-Federated','Unknown' .PARAMETER SessionTypes Show sessions that match one or more session scenarios. Case sensitive. Choose one or more from: 'Conference','Peer To Peer','Peer To Peer (Multimedia)','PSTN/External' .PARAMETER Codecs Show sessions where the selected codec was used by either caller or callee. Can query for multiple codecs. Case sensitive. Use Get-NectarCodecs for a list of valid codecs. .PARAMETER CallerCodecs Show sessions where the selected codec was used by the caller. Can query for multiple codecs. Case sensitive. Use Get-NectarCodecs for a list of valid codecs. .PARAMETER CalleeCodecs Show sessions where the selected codec was used by the callee. Can query for multiple codecs. Case sensitive. Use Get-NectarCodecs for a list of valid codecs. .PARAMETER Devices Show sessions where the selected device was used by either caller or callee. Can query for multiple devices. Case sensitive. Use Get-NectarSupportedDevice for a list of valid devices. .PARAMETER CallerDevices Show sessions where the selected device was used by the caller. Can query for multiple devices. Case sensitive. Use Get-NectarSupportedDevice for a list of valid devices. .PARAMETER CalleeDevices Show sessions where the selected device was used by the callee. Can query for multiple devices. Case sensitive. Use Get-NectarSupportedDevice for a list of valid devices. .PARAMETER DeviceVersions Show sessions where the selected device version was used by either caller or callee. Can query for multiple devices. Case sensitive. Use Get-NectarClientVersion for a list of valid client versions. .PARAMETER CallerDeviceVersions Show sessions where the selected device version was used by the caller. Can query for multiple devices. Case sensitive. Use Get-NectarClientVersion for a list of valid client versions. .PARAMETER CalleeDeviceVersions Show sessions where the selected device version was used by the callee. Can query for multiple devices. Case sensitive. Use Get-NectarClientVersion for a list of valid client versions. .PARAMETER AgentVersions Show sessions where the selected agent version was used by either caller or callee. Can query for multiple agent. Case sensitive. Use Get-NectarAgentVersion for a list of valid agent versions. .PARAMETER CallerAgentVersions Show sessions where the selected agent version was used by either caller or callee. Can query for multiple agent. Case sensitive. Use Get-NectarAgentVersion for a list of valid agent versions. .PARAMETER CalleeAgentVersions Show sessions where the selected agent version was used by either caller or callee. Can query for multiple agent. Case sensitive. Use Get-NectarAgentVersion for a list of valid agent versions. .PARAMETER IPAddresses Show sessions where the selected IP address was used by either caller or callee. Can query for multiple IPs. .PARAMETER CallerIPAddresses Show sessions where the selected IP address was used by the caller. Can query for multiple IPs. .PARAMETER CalleeIPAddresses Show sessions where the selected IP address was used by the callee. Can query for multiple IPs. .PARAMETER Locations Show sessions where the selected location was used by either caller or callee. Can query for multiple locations. .PARAMETER CallerLocations Show sessions where the selected location was used by the caller. Can query for multiple locations. .PARAMETER CalleeLocations Show sessions where the selected location was used by the callee. Can query for multiple locations. .PARAMETER ExtCities Show sessions where the caller or callee was located in the selected city (as detected via geolocating the user's external IP address). Can query for multiple cities. .PARAMETER CallerExtCities Show sessions where the caller was located in the selected city (as detected via geolocating the user's external IP address). Can query for multiple cities. .PARAMETER CalleeExtCities Show sessions where the callee was located in the selected city (as detected via geolocating the user's external IP address). Can query for multiple cities. .PARAMETER ExtCountries Show sessions where the caller or callee was located in the selected country (as detected via geolocating the user's external IP address). Can query for multiple countries. .PARAMETER CallerExtCountries Show sessions where the caller was located in the selected country (as detected via geolocating the user's external IP address). Can query for multiple countries. .PARAMETER CalleeExtCountries Show sessions where the callee was located in the selected country (as detected via geolocating the user's external IP address). Can query for multiple countries. .PARAMETER ExtISPs Show sessions where the caller or callee was located in the selected ISP (as detected via geolocating the user's external IP address). Can query for multiple ISPs. .PARAMETER CallerExtISPs Show sessions where the caller was located in the selected ISP (as detected via geolocating the user's external IP address). Can query for multiple ISPs. .PARAMETER CalleeExtISPs Show sessions where the callee was located in the selected ISP (as detected via geolocating the user's external IP address). Can query for multiple ISPs. .PARAMETER ExtConnectionTypes Show sessions that match the caller or callee external connection type (as detected via geolocating the user's external IP address). Can query for multiple types. .PARAMETER CallerExtConnectionTypes Show sessions that match the caller's external connection type (as detected via geolocating the user's external IP address). Can query for multiple types. .PARAMETER CalleeExtConnectionTypes Show sessions that match the callee's external connection type (as detected via geolocating the user's external IP address). Can query for multiple types. .PARAMETER NetworkTypes Show sessions where the selected network type was used by either caller or callee. Can query for multiple network types. Case sensitive. Choose one or more from: 'CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED' .PARAMETER CallerNetworkTypes Show sessions where the selected network type was used by the caller. Can query for multiple network types. Case sensitive. Choose one or more from: 'CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED' .PARAMETER CalleeNetworkTypes Show sessions where the selected network type was used by the callee. Can query for multiple network types. Case sensitive. Choose one or more from: 'CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED' .PARAMETER Platforms Show sessions where the selected platform was used by either caller or callee. Can query for multiple platforms. Case sensitive. Choose one or more from: 'SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS' .PARAMETER EndpointTypes Show sessions where one or more selected Avaya endpoint types have been used. Choose one or more from: 'medsvr','mgdsp','node','phone','unknown' .PARAMETER CallerEndpointTypes Show sessions where one or more selected Avaya endpoint types have been used by the caller. Choose one or more from: 'medsvr','mgdsp','node','phone','unknown' .PARAMETER CalleeEndpointTypes Show sessions where one or more selected Avaya endpoint types have been used by the callee. Choose one or more from: 'medsvr','mgdsp','node','phone','unknown' .PARAMETER Users Show sessions where the selected user was either caller or callee. Can query for multiple users. .PARAMETER FromUsers Show sessions where the selected user was the caller. Can query for multiple users. .PARAMETER ToUsers Show sessions where the selected user was the callee. Can query for multiple users. .PARAMETER UserIDs Show sessions where the selected user ID was either caller or callee. Can query for multiple user IDs. .PARAMETER FromUserIDs Show sessions where the selected user ID was the caller. Can query for multiple user IDs. .PARAMETER ToUserIDs Show sessions where the selected user ID was the callee. Can query for multiple user IDs. .PARAMETER ConfOrganizers Show sessions hosted by a specified conference organizer. Can query for multiple organizers. .PARAMETER VPN Show sessions where the selected VPN was used by either caller or callee. .PARAMETER CallerVPN Show sessions where the selected VPN was used by the caller. .PARAMETER CalleeVPN Show sessions where the selected VPN was used by the callee. .PARAMETER ParticipantsMinCount Show sessions where the number of participants is greater than or equal to the entered value .PARAMETER ParticipantsMaxCount Show sessions where the number of participants is less than or equal to the entered value .PARAMETER FeedbackRating Show sessions where users provided specific feedback ratings from BAD to EXCELLENT. Allowed values are BAD, POOR, FAIR, GOOD, EXCELLENT. Corresponds to ratings from 1 to 5 stars. .PARAMETER Insights Show sessions that match one or more given insights. Choose from NORMAL_SESSION, VOICE_MAIL, HIGH_JITTER, HIGH_PACKET_LOSS, HIGH_ROUNDTRIP_DELAY .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE New-NectarReportWidget -WidgetType 'Call Details, Sessions' -WidgetDescription "Conference Session Summary for New York" -WidgetPosition 0 -WidgetGroupVarName WidgetList -TimePeriod LAST_MONTH -Modalities AUDIO -Platforms TEAMS -SessionTypes CONFERENCE_SESSION -ExtCities 'New York' -ExtCountries 'US' Creates a Call Details widget to be used later in New-NectarReport .NOTES Version 1.0 #> Param ( [Parameter(Mandatory=$True)] [ValidateSet('Call Details, Sessions','Call Details, Quality Audio','Call Details, Quality Video','Call Details, Quality App Sharing','Call Details, Session List','Quality Details, Sessions','Quality Details, Quality Summary','Quality Details, Session List', IgnoreCase=$True)] [string]$WidgetType, [Parameter(Mandatory=$False)] [string]$WidgetDescription, [Parameter(Mandatory=$False)] [int]$WidgetPosition, [Parameter(Mandatory=$False)] [string]$WidgetGroupVarName, [Parameter(Mandatory=$False)] [ValidateSet('YESTERDAY','LAST_WEEK','LAST_MONTH','LAST_03_MONTHS','LAST_12_MONTHS','MONTH_TO_DATE','CUSTOM', IgnoreCase=$True)] [string]$TimePeriod = 'LAST_MONTH', [Parameter(Mandatory=$False)] [Alias("StartDateFrom")] [DateTime]$TimePeriodFrom, [Parameter(Mandatory=$False)] [Alias("StartDateTo")] [DateTime]$TimePeriodTo, [Parameter(Mandatory=$False)] [ValidateSet('GOOD','POOR_0_25','PARTIALLY_GOOD_25_50','PARTIALLY_GOOD_50_75','PARTIALLY_GOOD_75_100','UNAVAILABLE','UNKNOWN', IgnoreCase=$True)] [string[]]$SessionQualities, [Parameter(Mandatory=$False)] [string]$NectarScore, [Parameter(Mandatory=$False)] [ValidateRange(0,99999999)] [int]$DurationFrom, [Parameter(Mandatory=$False)] [ValidateRange(0,99999999)] [int]$DurationTo, [Parameter(Mandatory=$False)] [ValidateSet('AUDIO','VIDEO','APP_SHARING','IM','UNKNOWN','TOTAL','VBSS','REMOTE_ASSISTANCE','APP_INVITE','FOCUS','UNKNOWN', IgnoreCase=$True)] [string[]]$Modalities, [Parameter(Mandatory=$False)] [ValidateSet('TCP','UDP','Unknown', IgnoreCase=$False)] [string[]]$Protocols, [Parameter(Mandatory=$False)] [ValidateRange(0,608)] [string[]]$ResponseCodes, [Parameter(Mandatory=$False)] [ValidateSet('INTERNAL','EXTERNAL','FEDERATED','INTERNAL_EXTERNAL','EXTERNAL_INTERNAL','FEDERATED_INTERNAL','INTERNAL_FEDERATED','FEDERATED_EXTERNAL','EXTERNAL_FEDERATED','UNKNOWN', IgnoreCase=$True)] [string[]]$SessionScenarios, [Parameter(Mandatory=$False)] [ValidateSet('CONFERENCE','CONFERENCE_SESSION','PEER2PEER','PEER2PEER_MULTIMEDIA','PSTN','WEBINAR', IgnoreCase=$False)] [string[]]$SessionTypes, [Parameter(Mandatory=$False)] [string[]]$Codecs, [Parameter(Mandatory=$False)] [string[]]$CallerCodecs, [Parameter(Mandatory=$False)] [string[]]$CalleeCodecs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$Devices, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerDevices, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeDevices, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$DeviceVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerDeviceVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeDeviceVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$AgentVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerAgentVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeAgentVersions, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$IPAddresses, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerIPAddresses, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeIPAddresses, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$Locations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerLocations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeLocations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtISPs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtISPs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtISPs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('Cable','Dialup','DSL','FTTx','ISDN','Unknown','Wireless', IgnoreCase=$False)] [string[]]$ExtConnectionTypes, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('Cable','Dialup','DSL','FTTx','ISDN','Unknown','Wireless', IgnoreCase=$False)] [string[]]$CallerExtConnectionTypes, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('Cable','Dialup','DSL','FTTx','ISDN','Unknown','Wireless', IgnoreCase=$False)] [string[]]$CalleeExtConnectionTypes, [Parameter(Mandatory=$False)] [ValidateSet('CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED', IgnoreCase=$False)] [string[]]$NetworkTypes, [Parameter(Mandatory=$False)] [ValidateSet('CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED', IgnoreCase=$False)] [string[]]$CallerNetworkTypes, [Parameter(Mandatory=$False)] [ValidateSet('CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED', IgnoreCase=$False)] [string[]]$CalleeNetworkTypes, [Parameter(Mandatory=$False)] [ValidateSet('SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS', IgnoreCase=$True)] [string]$Platform, [Parameter(Mandatory=$False)] [ValidateSet('SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS', IgnoreCase=$True)] [string[]]$Platforms, [Parameter(Mandatory=$False)] [string[]]$Servers, [Parameter(Mandatory=$False)] [ValidateSet('AAEP','AAMS','medsvr','mgdsp','node','phone','SBC','sip','unknown', IgnoreCase=$True)] [string[]]$EndpointTypes, [Parameter(Mandatory=$False)] [ValidateSet('AAEP','AAMS','medsvr','mgdsp','node','phone','SBC','sip','unknown', IgnoreCase=$True)] [string[]]$CallerEndpointTypes, [Parameter(Mandatory=$False)] [ValidateSet('AAEP','AAMS','medsvr','mgdsp','node','phone','SBC','sip','unknown', IgnoreCase=$True)] [string[]]$CalleeEndpointTypes, [Parameter(Mandatory=$False)] [ValidateSet('EXTERNAL','EXTERNAL_FEDERATED','EXTERNAL_INTERNAL','FEDERATED','FEDERATED_EXTERNAL','FEDERATED_INTERNAL','INTERNAL','INTERNAL_EXTERNAL','INTERNAL_FEDERATED','UNKNOWN', IgnoreCase=$True)] [string[]]$Scenarios, [Parameter(Mandatory=$False)] [ValidateSet('External','Internal','Federated','Unknown', IgnoreCase=$True)] [string[]]$CallerScenarios, [Parameter(Mandatory=$False)] [ValidateSet('External','Internal','Federated','Unknown', IgnoreCase=$True)] [string[]]$CalleeScenarios, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$Users, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$FromUsers, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ToUsers, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$UserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$FromUserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ToUserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ConfOrganizers, [Parameter(Mandatory=$False)] [ValidateSet('TRUE','FALSE','UNKNOWN', IgnoreCase=$True)] [string[]]$VPN, [Parameter(Mandatory=$False)] [ValidateSet('TRUE','FALSE','UNKNOWN', IgnoreCase=$True)] [string[]]$CallerVPN, [Parameter(Mandatory=$False)] [ValidateSet('TRUE','FALSE','UNKNOWN', IgnoreCase=$True)] [string[]]$CalleeVPN, [Parameter(Mandatory=$False)] [ValidateRange(0,99999)] [int]$ParticipantsMinCount, [Parameter(Mandatory=$False)] [ValidateRange(0,99999)] [int]$ParticipantsMaxCount, [Parameter(Mandatory=$False)] [ValidateSet('BAD','POOR','FAIR','GOOD','EXCELLENT', IgnoreCase=$False)] [string[]]$FeedbackRating, [Parameter(Mandatory=$False)] [ValidateSet('NORMAL_SESSION','VOICE_MAIL','HIGH_JITTER','HIGH_PACKET_LOSS','HIGH_ROUNDTRIP_DELAY', IgnoreCase=$False)] [string[]]$Insights, [Parameter(Mandatory=$False)] [ValidateSet('P2P','PING','AUDIO','VIDEO', IgnoreCase=$False)] [string[]]$TestTypes, [Parameter(Mandatory=$False)] [ValidateSet('PASSED','FAILED','UNKNOWN','INCOMPLETE','INCOMPLETE', IgnoreCase=$False)] [string[]]$TestResults, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [ValidateSet('DEFAULT','USERS','ENDPOINT_CLIENT','CALL_DETAILS_HISTORIC','QUALITY_DETAILS', IgnoreCase=$True)] [string]$Scope = 'DEFAULT', [switch]$ShowQualityDetails ) Begin { # Check for the existance of the global widget group variable. Create it if it doesn't exist. If ($WidgetGroupVarName) { If (Get-Variable $WidgetGroupVarName -Scope Global -ErrorAction SilentlyContinue) { } Else { [System.Collections.ArrayList]$TempVar = @() Set-Variable -Name $WidgetGroupVarName -Value (Get-Variable -Name TempVar -ValueOnly) -Scope Global Remove-Variable TempVar } } } Process { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } # Convert to all-caps If ($Scope) { $Scope = $Scope.ToUpper() } If ($TimePeriod) { $TimePeriod = $TimePeriod.ToUpper() } $FilterParams = @{ 'analyticsTimePeriod' = $TimePeriod } # Map widget names to their corresponding IDs Switch ($WidgetType) { 'Call Details, Sessions' { $WidgetID = 1 } 'Call Details, Quality Audio' { $WidgetID = 5 } 'Call Details, Quality Video' { $WidgetID = 6 } 'Call Details, Quality App Sharing' { $WidgetID = 7 } 'Call Details, Session List' { $WidgetID = 3 } 'Quality Details, Sessions' { $WidgetID = 2 } 'Quality Details, Quality Summary' { $WidgetID = 8 } 'Quality Details, Session List' { $WidgetID = 4 } } # Convert any PowerShell array objects to comma-separated strings to add to the GET querystring # Convert multi-string variables into a comma-delimited list and add to the FilterParams array. For variables that need to be in upper-case $MultiVarListUpper = 'SessionQualities','SessionScenarios','SessionTypes','Modalities','Platforms','Scenarios', 'VPN','CallerVPN','CalleeVPN', 'FeedbackRating','Insights','TestTypes','TestResults' ForEach ($MultiVar in $MultiVarListUpper) { If ((Get-Variable $MultiVar).Value) { # Make sure all variable names are in camel-case $FilterParams.Add($MultiVar.substring(0,1).ToLower()+$MultiVar.substring(1),(Get-Variable $MultiVar).Value) Write-Verbose "$($MultiVar.substring(0,1).ToLower()+$MultiVar.substring(1))`: $((Get-Variable $MultiVar).Value)" } } # Convert multi-string variables into a comma-delimited list and add to the FilterParams array. For variables that do not need to be in upper-case $MultiVarList = 'Protocols','ResponseCodes','Servers', 'Codecs','CallerCodecs','CalleeCodecs', 'Devices','CallerDevices','CalleeDevices', 'DeviceVersions','CallerDeviceVersions','CalleeDeviceVersions', 'Locations','CallerLocations','CalleeLocations', 'ExtCities','CallerExtCities','CalleeExtCities', 'ExtCountries','CallerExtCountries','CalleeExtCountries', 'ExtISPs','CallerExtISPs','CalleeExtISPs', 'extConnectionTypes','callerExtConnectionTypes','calleeExtConnectionTypes', 'NetworkTypes','CallerNetworkTypes','CalleeNetworkTypes', 'ipAddresses','callerIpAddresses','calleeIpAddresses', 'endpointTypes','callerEndpointTypes','calleeEndpointTypes' ForEach ($MultiVar in $MultiVarList) { If ((Get-Variable $MultiVar).Value) { $MultiVarValue = (Get-Variable $MultiVar).Value # Update name of parameters if necessary Switch ($MultiVar) { 'Servers' { $MultiVar = 'platformServersOrDataCenters' } } If ($MultiVar -like '*ISPs') { $MultiVar = $MultiVar.Replace('ISPs', 'Isps') } # Make sure all variable names are in camel-case $FilterParams.Add($MultiVar.substring(0,1).ToLower()+$MultiVar.substring(1),$MultiVarValue) Write-Verbose "$($MultiVar.substring(0,1).ToLower()+$MultiVar.substring(1))`: $MultiVarValue)" } } # Get user IDs and convert into a comma-delimited list and add to the FilterParams array. $UserVarList = 'Users','FromUsers','ToUsers' ForEach ($UserVar in $UserVarList) { If ((Get-Variable $UserVar).Value) { $UserList = (Get-Variable $UserVar).Value $UserIDList = @() $PlatformUserNames = @() ForEach ($User in $UserList) { Write-Verbose "UserSearch: $User" $UserInfo = Get-NectarUser $User -Scope $Scope -TenantName $TenantName -FilterSearch -ResultSize 10000 If ($UserInfo.id) { $UserIDList += $UserInfo.id } ElseIf ($UserInfo.platformUserName) { $PlatformUserNames += $UserInfo.platformUserName } } If ($UserIDList) { $UserIDList | ForEach-Object { $UserIDString += ($(if($UserIDString){","}) + $_) } $FilterParams.Add($UserVar.substring(0,1).ToLower()+$UserVar.substring(1),$UserIDString) Write-Verbose "UserID $UserVar`: $UserIDString" Remove-Variable UserIDString } If ($PlatformUserNames) { $PlatformUserNames | ForEach-Object { $PlatformUserNameString += ($(if($PlatformUserNameString){","}) + $_) } $FilterParams.Add($UserVar.Replace('Users','PlatformUserNames'),$PlatformUserNameString) Write-Verbose "Platform $UserVar`: $PlatformUserNameString" Remove-Variable PlatformUserNameString } If (!$UserIDList -And !$PlatformUserNames) { Write-Error 'No matching users found'; Return } } } # Do the same for UserIDs $UserIDVarList = 'UserIDs','FromUserIDs','ToUserIDs' ForEach ($UserIDVar in $UserIDVarList) { If ((Get-Variable $UserIDVar -ErrorAction SilentlyContinue).Value) { $UserIDList = (Get-Variable $UserIDVar).Value If ($UserIDList) { $UserIDList | ForEach-Object { $UserIDString += ($(if($UserIDString){","}) + $_) } $FilterParams.Add($UserIDVar.substring(0,1).ToLower()+$UserIDVar.substring(1).Replace('ID',''),$UserIDString) # Convert to camelCase Write-Verbose "UserID $($UserIDVar.substring(0,1).ToLower()+$UserIDVar.substring(1).Replace('ID',''))`: $UserIDString" Remove-Variable UserIDString } } } # Get agent versions and convert into a comma-delimited list and add to the FilterParams array. $AgentVarList = 'AgentVersions','CallerAgentVersions','CalleeAgentVersions' ForEach ($AgentVar in $AgentVarList) { If ((Get-Variable $AgentVar).Value) { $AgentList = (Get-Variable $AgentVar).Value # Parse through each entry and search for results $FinalAgentList = @() ForEach ($Agent in $AgentList) { Write-Verbose "AgentSearch: $Agent" $AgentInfo = Get-NectarAgentVersion -SearchQuery $Agent -TenantName $TenantName If ($AgentInfo) { $FinalAgentList += $AgentInfo } } # Convert to comma-delimited list and add to FilterParams array If ($FinalAgentList) { $FilterParams.Add($AgentVar.Substring(0,1).ToLower()+$AgentVar.Substring(1,$AgentVar.Length-2),$FinalAgentList) # Convert to camelCase Write-Verbose "Agent $AgentVar.Substring(0,1).ToLower()+$AgentVar.Substring(1,$AgentVar.Length-2)`: $FinalAgentList" } If (!$FinalAgentList) { Write-Error 'No matching agent versions found'; Return } } } # Add single parameter variables to the FilterParams array. $VarList = 'NectarScore','DurationFrom','DurationTo','ParticipantsMinCount','ParticipantsMaxCount' ForEach ($Var in $VarList) { If ((Get-Variable $Var).Value) { $FilterParams.Add($Var.Substring(0,1).ToLower()+$Var.Substring(1),(Get-Variable $Var).Value) # Convert to camelCase Write-Verbose "$Var`: (Get-Variable $Var).Value" } } # Add time-based parameter variables to the FilterParams array. This converts to UNIX timestamp $TimeVarList = 'TimePeriodFrom','TimePeriodTo' # Set TimePeriodTo to NOW if not explicitly set If ($TimePeriodFrom -And !$TimePeriodTo) { [String]$TimePeriodTo = Get-Date } ForEach ($TimeVar in $TimeVarList) { If ((Get-Variable $TimeVar).Value) { [decimal]$TimePeriodUnix = Get-Date -Date (Get-Variable $TimeVar).Value -UFormat %s [long]$TimePeriodUnix = $TimePeriodUnix * 1000 $FilterParams.Add($TimeVar.Replace('TimePeriod','startDate'),$TimePeriodUnix) Write-Verbose "$TimeVar`: $TimePeriodUnix" } } If ($ConfOrganizers) { $ConfOrganizerIDs = ForEach($Organizer in $ConfOrganizers) { (Get-NectarUser $Organizer -Scope $Scope -TenantName $TenantName -ErrorAction:Stop).Userid } $ConfOrganizerIDs | ForEach-Object { $ConfOrganizerIDsStr += ($(if($ConfOrganizerIDsStr){","}) + $_) } $FilterParams.Add('organizersOrSpaces',$ConfOrganizerIDsStr) } If ($ShowQualityDetails) { $FilterParams.Add('sessionQualitySources','CDS,CDR_CDS') $FilterParams.Add('excludeIncompleteRecords','true') } $SessionQualitySources = 'CDR','CDR_CDS' $FilterParams.Add('sessionQualitySources',$SessionQualitySources) $FilterParams.Add('scope', $Scope) $FilterParams.Add('excludeAvayaPpm', $True) $WidgetConfig = @{ description = $WidgetDescription exportFormat = 'pdf' filter = $FilterParams view = $Null } # Check for widget position. If not present, use the position from the WidgetGroupVar (if available) If (!$WidgetPosition) { # If the WidgetGroupVar exists, then set the widget position to be the last numeric value If ($WidgetGroupVarName) { $WidgetGroupData = (Get-Variable $WidgetGroupVarName).Value $WidgetPosition = $WidgetGroupData.Count } } $WidgetParams = @{ id = $NULL name = $WidgetType position = $WidgetPosition widgetId = $WidgetID configuration = $WidgetConfig } If ($WidgetGroupVarName) { (Get-Variable $WidgetGroupVarName).Value += $WidgetParams } Else { Return $WidgetParams } } } Function Remove-NectarReport { <# .SYNOPSIS Removes a report from Nectar DXP .DESCRIPTION Removes a report from Nectar DXP .PARAMETER ID The ID of the report to delete. Can be passed from Get-NectarReport .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarReport -SearchQuery 'Test' | Remove-NectarReport Removes all reports that contain the word 'Test' in the name/description etc .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [int]$ID, [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 !$TenantName) { $TenantName = $Global:NectarTenantName } Try { $URI = "https://$Global:NectarCloud/rapi/client/report/config/$($ID)?tenant=$TenantName" Write-Verbose $URI $Null = Invoke-RestMethod -Method DELETE -uri $URI -Headers $Global:NectarAuthHeader } Catch { Write-Error "Error deleting report #$($ID)" Get-JSONErrorStream -JSONResponse $_ } } } Function Start-NectarReport { <# .SYNOPSIS Triggers the processing of a report .DESCRIPTION Triggers the processing of a report .PARAMETER ID The numeric ID of the report to start .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Start-NectarReport -ID 31 Triggers the execution of report with ID 31 Get-NectarReport Test | Start-NectarReport Triggers the execution of any report with 'Test' in the name .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [int]$ID, [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 !$TenantName) { $TenantName = $Global:NectarTenantName } Try { $URI = "https://$Global:NectarCloud/rapi/client/report/runnow/$($ID)?tenant=$TenantName" Write-Verbose $URI $Null = Invoke-RestMethod -Method PUT -uri $URI -Headers $Global:NectarAuthHeader } Catch { Write-Error "Error running report #$($ID)" Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarReportPreview { <# .SYNOPSIS Gets information about the report widgets used for a given report .DESCRIPTION Gets information about the report widgets used for a given report .PARAMETER ID The numeric ID of the report to get information about .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarReportPreview -ID 31 Returns information about the report with ID 31 .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [int]$ID, [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 !$TenantName) { $TenantName = $Global:NectarTenantName } Try { $URI = "https://$Global:NectarCloud/rapi/client/report/config/detail/$($ID)?tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader If ($TenantName) {$JSON | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty} Return $JSON } Catch { Write-Error "Error getting report info for ID $ID. Does the report exist?" Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarReportDetail { <# .SYNOPSIS Returns detail about a given widget within a report .DESCRIPTION Returns detail about a given widget within a report .PARAMETER ReportID The numeric ID of the report that contains the desired widget .PARAMETER ReportWidgetID The numeric ID of the report widget to get details about .PARAMETER WidgetType The type of widget to report on. If not supplied, will be obtained by running Get-NectarReportPreview .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarReportDetail -ReportID 31 -ReportWidgetID 84 Returns details about the widget with ID 84 on report ID 31 .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [int]$ReportID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [int]$ReportWidgetID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('SESSIONS', 'QUALITY_AUDIO', 'QUALITY_VIDEO', 'QUALITY_APPSHARE', 'QUALITY_SUMMARY', 'SESSION_LIST', IgnoreCase=$False)] [string]$WidgetType, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$WidgetDescription, [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 !$TenantName) { $TenantName = $Global:NectarTenantName } # Get the widget type if it wasn't entered as a parameter If (!$WidgetType) { $WidgetType = ((Get-NectarReportPreview -ID $ReportID).Widgets | Where-Object {$_.ReportWidgetID -eq $ReportWidgetID}).widgetType } Switch ($WidgetType) { 'GRAPH' { $URLPath = 'graph' } 'SESSIONS' { $URLPath = 'graph' } 'QUALITY_AUDIO' { $URLPath = 'session/audio' } 'QUALITY_VIDEO' { $URLPath = 'session/video' } 'QUALITY_APPSHARE' { $URLPath = 'session/appshare' } 'QUALITY_SUMMARY' { $URLPath = 'quality/summary' } } $URI = "https://$Global:NectarCloud/rapi/client/report/data/$URLPath/$($ReportWidgetID)?tenant=$TenantName" Write-Verbose $URI Try { $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader If ($WidgetDescription) { $JSON | Add-Member -Name 'WidgetDescription' -Value $WidgetDescription -MemberType NoteProperty } Return $JSON } Catch { Write-Error "Error getting report widget detail for ID $ReportWidgetID. Does the report widget exist?" Get-JSONErrorStream -JSONResponse $_ } } } ################################################################################################################################################# # # # Event Functions # # # ################################################################################################################################################# Function Get-NectarEvent { <# .SYNOPSIS Return a list of current or historic Nectar monitored device events .DESCRIPTION Return a list of current or historic Nectar monitored device events .PARAMETER TimePeriod The time period to show event data from. Select from 'LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM'. CUSTOM requires using TimePeriodFrom and TimePeriodTo parameters. .PARAMETER TimePeriodFrom The earliest date/time to show event data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodTo parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. .PARAMETER TimePeriodTo The latest date/time to show event data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodFrom parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. .PARAMETER LastTimeAfter Only return results that occurred more recently than the entered value. Use date-time format as in 2020-04-20T17:46:37.554 .PARAMETER EventAlertLevels Return only events that meet the specified alert level. Choose one or more from CRITICAL, MAJOR, MINOR, WARNING, GOOD, NO_ACTIVITY .PARAMETER Locations Show alerts for one or more specified locations .PARAMETER SearchQuery Search for events that contain the specified string .PARAMETER OrderByField Order the resultset by the specified field. Choose from id, type, lastTime, displayName, deviceName, description, eventId, time, delay, source, location, sourceId .PARAMETER EventState Return either current events or previously acknowledged events .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 1000 .PARAMETER ResultSize The total number of results to return. Defaults to 1000. Maximum result size is 9,999,999 results .EXAMPLE Get-NectarEvent -EventAlertLevels CRITICAL,MAJOR Returns a list of current events in the last hour that are either critical or major .EXAMPLE Get-NectarEvent -SearchQuery BadServer -EventState Historic -TimePeriod LAST_WEEK Returns a list of historical events from the last week that include the word 'badserver' .NOTES Version 1.0 #> Param ( [Parameter(Mandatory=$False)] [ValidateSet('LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM', IgnoreCase=$True)] [string]$TimePeriod = 'LAST_HOUR', [Parameter(Mandatory=$False)] [Alias("StartDateFrom")] [DateTime]$TimePeriodFrom, [Parameter(Mandatory=$False)] [Alias("StartDateTo")] [DateTime]$TimePeriodTo, [Parameter(Mandatory=$False)] [string]$LastTimeAfter, [Parameter(Mandatory=$False)] [ValidateSet('CRITICAL', 'MAJOR', 'MINOR', 'WARNING', 'GOOD', 'NO_ACTIVITY', IgnoreCase=$True)] [string[]]$EventAlertLevels, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias("SiteName")] [string[]]$Locations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$SearchQuery, [Parameter(Mandatory=$False)] [ValidateSet('id', 'type', 'lastTime', 'displayName', 'deviceName', 'description', 'eventId', 'time', 'delay', 'source', 'location', 'sourceId', IgnoreCase=$True)] [string]$OrderByField, [Parameter(Mandatory=$False)] [ValidateSet('asc', 'desc', IgnoreCase=$True)] [string]$OrderDirection, [Parameter(Mandatory=$False)] [ValidateSet('Current', 'Historic', IgnoreCase=$True)] [string]$EventState = 'Current', [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 } Process { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } $Params = @{ 'TimePeriod' = $TimePeriod } # Convert any PowerShell array objects to comma-separated strings to add to the GET querystring If ($LastTimeAfter) { $Params.Add('LastTimeAfter',$LastTimeAfter) } If ($EventAlertLevels) { $EventAlertLevels | ForEach-Object { $EventAlertLevelsStr += ($(if($EventAlertLevelsStr){","}) + $_) }; $Params.Add('EventAlertLevels',$EventAlertLevelsStr) } If ($Locations) { $Locations | ForEach-Object { $LocationsStr += ($(if($LocationsStr){","}) + $_) }; $Params.Add('Locations',$LocationsStr) } If ($SearchQuery) { $Params.Add('q',$SearchQuery) } If ($OrderByField) { $Params.Add('OrderByField',$OrderByField) } If ($OrderDirection) { $Params.Add('OrderDirection',$OrderDirection) } If ($TenantName) { $Params.Add('Tenant',$TenantName) } # Convert date to UNIX timestamp If($TimePeriodFrom) { $TimePeriodFrom = (Get-Date -Date $TimePeriodFrom -UFormat %s) + '000' $Params.Add('StartDateFrom',$TimePeriodFrom) } If($TimePeriodTo) { $TimePeriodTo = (Get-Date -Date $TimePeriodTo -UFormat %s) + '000' $Params.Add('StartDateTo',$TimePeriodTo) } # 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) } If ($EventState -eq 'Current') { $URI = "https://$Global:NectarCloud/dapi/event/current" } Else { $URI = "https://$Global:NectarCloud/dapi/event/historic" } Write-Verbose $URI Try { $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" $PagedURI = $URI + "?pageNumber=$PageNum" $JSON = Invoke-RestMethod -Method GET -uri $PagedURI -Headers $Global:NectarAuthHeader -Body $Params If ($TenantName) {$JSON.elements | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty} $JSON.elements $PageNum++ } } } Catch { Write-Error "No results. Try specifying a less-restrictive filter" Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarEventCount { <# .SYNOPSIS Return a sampling of event counts for a given time period grouped by event severity. .DESCRIPTION Return a sampling of event counts for a given time period grouped by event severity. This is only a sample and is not intended to provide a true count of the number of events in a given period. Its useful for situations where you want to get a sense of event distribution without having to pull the entire list of events, which can take a lot of time. .PARAMETER TimePeriod The time period to show event data from. Select from 'LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM'. CUSTOM requires using TimePeriodFrom and TimePeriodTo parameters. .PARAMETER TimePeriodFrom The earliest date/time to show event data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodTo parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. .PARAMETER TimePeriodTo The latest date/time to show event data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodFrom parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarEventCount -TimePeriod LAST_WEEK Returns a count of events grouped by severity for the last week .NOTES Version 1.0 #> Param ( [Parameter(Mandatory=$False)] [ValidateSet('LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM', IgnoreCase=$True)] [string]$TimePeriod = 'LAST_DAY', [Parameter(Mandatory=$False)] [Alias("StartDateFrom")] [DateTime]$TimePeriodFrom, [Parameter(Mandatory=$False)] [Alias("StartDateTo")] [DateTime]$TimePeriodTo, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/dapi/event/alerts/summary?timePeriod=$TimePeriod&tenant=$TenantName" Write-Verbose $URI Try { $RawCount = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader } Catch { # DXP versions less than 1.15.0 use a trend, which is removed in 1.15.0. This allows this function to still work during the transition. $URI = "https://$Global:NectarCloud/dapi/event/alerts/trend?timePeriod=$TimePeriod&tenant=$TenantName" Write-Verbose $URI $RawCount = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader } $ResultSummary = [System.Collections.ArrayList]@() ForEach ($Event in $RawCount) { $EventSummary = [pscustomobject][ordered]@{ 'EventType' = $Event.Type 'Count' = $Event.data.count } $ResultSummary += $EventSummary } Return $ResultSummary } Catch { Write-Error "No results. Try specifying a less-restrictive filter" Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarEventDetail { <# .SYNOPSIS Return information about a specific event .DESCRIPTION Return information about a specific event .PARAMETER EventID The ID of the event to return details about .PARAMETER EventState Return either current events or previously acknowledged events .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-MSTeamsCallRecord -CallRecordID ed672235-5417-40ce-8425-12b8b702a505 .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$ID, [Parameter(Mandatory=$False)] [ValidateSet('Current', 'Historic', IgnoreCase=$True)] [string]$EventState = 'Current', [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $Params = @{ 'eventId' = $ID } If ($TenantName) { $Params.Add('Tenant',$TenantName) } If ($EventState -eq 'Current') { $URI = "https://$Global:NectarCloud/dapi/event/current/view" } Else { $URI = "https://$Global:NectarCloud/dapi/event/historic/view" } Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader -Body $Params If ($TenantName) {$JSON | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty} $JSON | Add-Member -TypeName 'Nectar.EventDetail' $JSON } Catch { Write-Error "Could not find event with ID $EventID" Get-JSONErrorStream -JSONResponse $_ } } } ################################################################################################################################################# # # # Cisco Platform Functions # # # ################################################################################################################################################# Function Get-NectarCiscoCluster { <# .SYNOPSIS Return base information about all Cisco clusters .DESCRIPTION Return base information about all Cisco clusters .PARAMETER Platform Show information about selected platform. Choose one or more from: 'AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','CISCO','CISCO_CMS','CISCO_VKM','SKYPE','SKYPE_ONLINE','TEAMS' .PARAMETER TimePeriod The time period to show event data from. Select from 'LAST_HOUR','LAST_DAY','LAST_WEEK'. .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarCiscoCluster .NOTES Version 1.0 #> Param ( [Parameter(Mandatory=$False)] [ValidateSet('LAST_HOUR','LAST_DAY','LAST_WEEK', IgnoreCase=$True)] [string]$TimePeriod = 'LAST_DAY', [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [ValidateRange(1,100000)] [int]$PageSize = 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $FilterSession = Set-NectarFilterParams -Scope DEFAULT -Platform CISCO -TenantName $TenantName -TimePeriod $TimePeriod $URI = "https://$Global:NectarCloud/dapi/platform/clusters?pageSize=$PageSize&platform=CISCO&tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader -WebSession $FilterSession If ($TenantName) {$JSON.elements | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty} $JSON.elements } Catch { Write-Error "Could not get platform items" Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarCiscoClusterInventory { <# .SYNOPSIS Return inventory information for a given Cisco cluster .DESCRIPTION Return inventory information for a given Cisco cluster .PARAMETER ClusterID The Nectar-defined ClusterID for the given cluster. Accepts pipelined results .PARAMETER InventorySet What specified inventory set to pull information about .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-NectarCiscoClusterInventory -ClusterID 39_1 -InventorySet CallManagers Returns information about the call managers for the specified clusterID .EXAMPLE Get-NectarCiscoCluster | Get-NectarCiscoClusterInventory -InventorySet Gateways Returns information about gateways from all Cisco clusters .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$ClusterID, [Parameter(Mandatory=$True)] [ValidateSet('Annunciators','ApplicationServers','AssignedPresenceServers','CallManagers','ConferenceBridges','DevicePools','Gatekeepers','Gateways','GatewayEndpoints','H323Gateways','HuntLists','HuntPilots','InteractiveVoiceResponses','LicenseUsers','Locations','Mtp','MusicOnholdServers','Phones','RecordingProfiles','Services','SipTrunk','Transcoders','Users','VoiceMailPilots','VoiceMailPorts', IgnoreCase=$True)] [string]$InventorySet, [Parameter(Mandatory=$False)] [string]$SearchQuery, [Parameter(Mandatory=$False)] [string]$OrderByField = 'idx', [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 !$TenantName) { $TenantName = $Global:NectarTenantName } # The URLs for each inventory set is usually split on capital letters # This little function takes care of that # EG: MySampleTests turns into /my/sample/tests $InventoryURL = ($InventorySet -CReplace '([A-Z])', '/$1').ToLower() # Take care of any cases that don't match the above rule Switch ($InventorySet) { 'HuntLists' { $InventoryURL = '/hunts'; 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 'platform' = 'CISCO' 'tenant' = $TenantName } If ($SearchQuery) { $Params.Add('q', $SearchQuery) } $URI = "https://$Global:NectarCloud/dapi/platform/cluster/$ClusterID/inventory$InventoryURL" 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 } If ($Name) { $JSON.elements | Add-Member -Name 'ClusterName' -Value $Name -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} If ($Name) { $JSON.elements | Add-Member -Name 'ClusterName' -Value $Name -MemberType NoteProperty } $JSON.elements $PageNum++ } } } } ################################################################################################################################################# # # # Platform Functions # # # ################################################################################################################################################# Function Get-NectarPlatformItems { <# .SYNOPSIS Return information about all the platforms installed .DESCRIPTION Return information about all the platforms installed .PARAMETER Platform Show information about selected platform. Choose one or more from: 'AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','CISCO','CISCO_CMS','CISCO_VKM','SKYPE','SKYPE_ONLINE','TEAMS' .PARAMETER TimePeriod The time period to show event data from. Select from 'LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM'. CUSTOM requires using TimePeriodFrom and TimePeriodTo parameters. .PARAMETER TimePeriodFrom The earliest date/time to show event data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodTo parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. Use date-time format as in 2020-04-20T17:46:37.554 .PARAMETER TimePeriodTo The latest date/time to show event data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodFrom parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. Use date-time format as in 2020-04-20T17:46:37.554 .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 1000 .PARAMETER ResultSize The total number of results to return. Defaults to 1000. Maximum result size is 9,999,999 results .EXAMPLE Get-NectarPlatformItems -Platform CISCO .NOTES Version 1.0 #> Param ( [Parameter(Mandatory=$True)] [ValidateSet('AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS', IgnoreCase=$True)] [string]$Platform, [Parameter(Mandatory=$False)] [ValidateSet('LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM', IgnoreCase=$True)] [string]$TimePeriod = 'LAST_HOUR', [Parameter(Mandatory=$False)] [Alias("StartDateFrom")] [DateTime]$TimePeriodFrom, [Parameter(Mandatory=$False)] [Alias("StartDateTo")] [DateTime]$TimePeriodTo, [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 } Process { $Params = @{ 'TimePeriod' = $TimePeriod 'Platform' = $Platform } # Convert date to UNIX timestamp If($TimePeriodFrom) { $TimePeriodFrom = (Get-Date -Date $TimePeriodFrom -UFormat %s) + '000' $Params.Add('StartDateFrom',$TimePeriodFrom) } If($TimePeriodTo) { $TimePeriodTo = (Get-Date -Date $TimePeriodTo -UFormat %s) + '000' $Params.Add('StartDateTo',$TimePeriodTo) } Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If ($TenantName) { $Params.Add('Tenant',$TenantName) } $URI = "https://$Global:NectarCloud/dapi/platform/clusters" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader -Body $Params If ($TenantName) {$JSON | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty} $JSON.elements } Catch { Write-Error "Could not get platform items" Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarPlatformItemSummary { <# .SYNOPSIS Return summary information about a specific platform item .DESCRIPTION Return summary information about a specific platform item .PARAMETER ClusterID The ID of a cluster to return summary information .PARAMETER Platform Show information about selected platform. Choose one or more from: 'AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','CISCO','CISCO_CMS','CISCO_VKM','SKYPE','SKYPE_ONLINE','TEAMS' .PARAMETER Source Show information about either events, current status or both .PARAMETER TimePeriod The time period to show event data from. Select from 'LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM'. CUSTOM requires using TimePeriodFrom and TimePeriodTo parameters. .PARAMETER TimePeriodFrom The earliest date/time to show event data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodTo parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. Use date-time format as in 2020-04-20T17:46:37.554 .PARAMETER TimePeriodTo The latest date/time to show event data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodFrom parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. Use date-time format as in 2020-04-20T17:46:37.554 .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 1000 .PARAMETER ResultSize The total number of results to return. Defaults to 1000. Maximum result size is 9,999,999 results .EXAMPLE Get-NectarPlatformItemSummary -Platform CISCO -ClusterID 3_1 .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$ClusterID, [Parameter(Mandatory=$True)] [ValidateSet('AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS', IgnoreCase=$True)] [string]$Platform, [Parameter(Mandatory=$False)] [ValidateSet('Events','Current','All', IgnoreCase=$True)] [string]$Source = 'All', [Parameter(Mandatory=$False)] [ValidateSet('LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM', IgnoreCase=$True)] [string]$TimePeriod = 'LAST_HOUR', [Parameter(Mandatory=$False)] [Alias("StartDateFrom")] [DateTime]$TimePeriodFrom, [Parameter(Mandatory=$False)] [Alias("StartDateTo")] [DateTime]$TimePeriodTo, [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 } Process { $Params = @{ 'TimePeriod' = $TimePeriod 'Platform' = $Platform } # Convert date to UNIX timestamp If($TimePeriodFrom) { $TimePeriodFrom = (Get-Date -Date $TimePeriodFrom -UFormat %s) + '000' $Params.Add('StartDateFrom',$TimePeriodFrom) } If($TimePeriodTo) { $TimePeriodTo = (Get-Date -Date $TimePeriodTo -UFormat %s) + '000' $Params.Add('StartDateTo',$TimePeriodTo) } Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If ($TenantName) { $Params.Add('Tenant',$TenantName) } $URI = "https://$Global:NectarCloud/dapi/platform/cluster/$ClusterID/summary" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader -Body $Params If ($TenantName) {$JSON | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty} If ($Source -ne 'All') { $JSON.$Source } Else { $JSON } } Catch { Write-Error "Could not get platform item summary" Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarPlatformItemResources { <# .SYNOPSIS Return resource information about a specific platform item .DESCRIPTION Return resource information about a specific platform item .PARAMETER ClusterID The ID of a cluster to return resource information .PARAMETER Platform Show information about selected platform. Choose one or more from: 'AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','CISCO','CISCO_CMS','CISCO_VKM','SKYPE','SKYPE_ONLINE','TEAMS' .PARAMETER TimePeriod The time period to show event data from. Select from 'LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM'. CUSTOM requires using TimePeriodFrom and TimePeriodTo parameters. .PARAMETER TimePeriodFrom The earliest date/time to show event data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodTo parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. Use date-time format as in 2020-04-20T17:46:37.554 .PARAMETER TimePeriodTo The latest date/time to show event data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodFrom parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. Use date-time format as in 2020-04-20T17:46:37.554 .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 1000 .PARAMETER ResultSize The total number of results to return. Defaults to 1000. Maximum result size is 9,999,999 results .EXAMPLE Get-NectarPlatformItemResources -Platform CISCO -ClusterID 3_1 .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$ClusterID, [Parameter(Mandatory=$True)] [ValidateSet('AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS', IgnoreCase=$True)] [string]$Platform, [Parameter(Mandatory=$False)] [ValidateSet('LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM', IgnoreCase=$True)] [string]$TimePeriod = 'LAST_HOUR', [Parameter(Mandatory=$False)] [Alias("StartDateFrom")] [DateTime]$TimePeriodFrom, [Parameter(Mandatory=$False)] [Alias("StartDateTo")] [DateTime]$TimePeriodTo, [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 } Process { $Params = @{ 'TimePeriod' = $TimePeriod 'Platform' = $Platform } # Convert date to UNIX timestamp If($TimePeriodFrom) { $TimePeriodFrom = (Get-Date -Date $TimePeriodFrom -UFormat %s) + '000' $Params.Add('StartDateFrom',$TimePeriodFrom) } If($TimePeriodTo) { $TimePeriodTo = (Get-Date -Date $TimePeriodTo -UFormat %s) + '000' $Params.Add('StartDateTo',$TimePeriodTo) } Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If ($TenantName) { $Params.Add('Tenant',$TenantName) } $URI = "https://$Global:NectarCloud/dapi/platform/cluster/$ClusterID/resources" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader -Body $Params If ($TenantName) {$JSON | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty} $JSON } Catch { Write-Error "Could not get platform item server resources" Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarPlatformItemServers { <# .SYNOPSIS Return information about a specific platform item's servers .DESCRIPTION Return information about a specific platform item's servers .PARAMETER ClusterID The ID of a cluster to return server information .PARAMETER Platform Show information about selected platform. Choose one or more from: 'AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','CISCO','CISCO_CMS','CISCO_VKM','SKYPE','SKYPE_ONLINE','TEAMS' .PARAMETER Type Show information about either publishers, subscribers or both .PARAMETER TimePeriod The time period to show event data from. Select from 'LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM'. CUSTOM requires using TimePeriodFrom and TimePeriodTo parameters. .PARAMETER TimePeriodFrom The earliest date/time to show event data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodTo parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. Use date-time format as in 2020-04-20T17:46:37.554 .PARAMETER TimePeriodTo The latest date/time to show event data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodFrom parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. Use date-time format as in 2020-04-20T17:46:37.554 .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 1000 .PARAMETER ResultSize The total number of results to return. Defaults to 1000. Maximum result size is 9,999,999 results .EXAMPLE Get-NectarPlatformItemServers -Platform CISCO -ClusterID 3_1 .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$ClusterID, [Parameter(Mandatory=$True)] [ValidateSet('AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS', IgnoreCase=$True)] [string]$Platform, [Parameter(Mandatory=$False)] [ValidateSet('Publisher','Subscribers','All', IgnoreCase=$True)] [string]$Type = 'All', [Parameter(Mandatory=$False)] [ValidateSet('LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM', IgnoreCase=$True)] [string]$TimePeriod = 'LAST_HOUR', [Parameter(Mandatory=$False)] [Alias("StartDateFrom")] [DateTime]$TimePeriodFrom, [Parameter(Mandatory=$False)] [Alias("StartDateTo")] [DateTime]$TimePeriodTo, [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 } Process { $Params = @{ 'TimePeriod' = $TimePeriod 'Platform' = $Platform } # Convert date to UNIX timestamp If($TimePeriodFrom) { $TimePeriodFrom = (Get-Date -Date $TimePeriodFrom -UFormat %s) + '000' $Params.Add('StartDateFrom',$TimePeriodFrom) } If($TimePeriodTo) { $TimePeriodTo = (Get-Date -Date $TimePeriodTo -UFormat %s) + '000' $Params.Add('StartDateTo',$TimePeriodTo) } Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If ($TenantName) { $Params.Add('Tenant',$TenantName) } $URI = "https://$Global:NectarCloud/dapi/platform/cluster/$ClusterID/servers" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader -Body $Params If ($TenantName) {$JSON | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty} If ($Type -ne 'All') { $JSON.$Type } Else { $JSON } } Catch { Write-Error "Could not get platform item servers" Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarPlatformServerServices { <# .SYNOPSIS Return service information about a specific platform item .DESCRIPTION Return service information about a specific platform item .PARAMETER ClusterID The ID of a cluster to return service information .PARAMETER ServerID The ID of a server within a cluster to return service information .PARAMETER Platform Show information about selected platform. Choose one or more from: 'AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','CISCO','CISCO_CMS','CISCO_VKM','SKYPE','SKYPE_ONLINE','TEAMS' .PARAMETER TimePeriod The time period to show event data from. Select from 'LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM'. CUSTOM requires using TimePeriodFrom and TimePeriodTo parameters. .PARAMETER TimePeriodFrom The earliest date/time to show event data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodTo parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. Use date-time format as in 2020-04-20T17:46:37.554 .PARAMETER TimePeriodTo The latest date/time to show event data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodFrom parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. Use date-time format as in 2020-04-20T17:46:37.554 .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 1000 .PARAMETER ResultSize The total number of results to return. Defaults to 1000. Maximum result size is 9,999,999 results .EXAMPLE Get-NectarPlatformServerServices -Platform CISCO -ClusterID 3_1 -ServerID 1 .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias("PlatformItemId")] [string]$ClusterID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias("Id")] [string]$ServerID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [ValidateSet('EDGE','FE','MEDIATION','GATEWAY','PUBLISHER','SUBSCRIBER','CUBE','IM_PRESENCE','VG224','CUCM','CMS','HW_CFB','CUCM_SW_CFB', IgnoreCase=$False)] [string]$Type, [Parameter(Mandatory=$True)] [ValidateSet('AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','JABRA','MISCELLANEOUS', IgnoreCase=$False)] [string]$Platform, [Parameter(Mandatory=$False)] [ValidateSet('LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM', IgnoreCase=$False)] [string]$TimePeriod = 'LAST_HOUR', [Parameter(Mandatory=$False)] [Alias("StartDateFrom")] [DateTime]$TimePeriodFrom, [Parameter(Mandatory=$False)] [Alias("StartDateTo")] [DateTime]$TimePeriodTo, [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 } Process { $Params = @{ 'TimePeriod' = $TimePeriod 'Platform' = $Platform 'type' = $Type } # Convert date to UNIX timestamp If($TimePeriodFrom) { $TimePeriodFrom = (Get-Date -Date $TimePeriodFrom -UFormat %s) + '000' $Params.Add('StartDateFrom',$TimePeriodFrom) } If($TimePeriodTo) { $TimePeriodTo = (Get-Date -Date $TimePeriodTo -UFormat %s) + '000' $Params.Add('StartDateTo',$TimePeriodTo) } Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If ($TenantName) { $Params.Add('Tenant',$TenantName) } $URI = "https://$Global:NectarCloud/dapi/platform/cluster/$ClusterID/server/$ServerID/services" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader -Body $Params If ($TenantName) {$JSON | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty} $JSON } Catch { Write-Error "Could not get platform item server services" Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarPlatformDashboard { <# .SYNOPSIS Return information about all custom Platform dashboards .DESCRIPTION Return information about all custom Platform dashboards .PARAMETER DashboardName The name of a dashboard to return data on .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarPlatformDashboards .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$DashboardName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/aapi/client/dashboards?tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader If ($TenantName) {$JSON | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty} If ($DashboardName) { Return $JSON | Where-Object {$_.name -eq $DashboardName} } Else { Return $JSON } } Catch { Write-Error "Could not get platform dashboards" Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarPlatformDashboardItemPollerSummary { <# .SYNOPSIS Return current poller information about a given device/item .DESCRIPTION Return current poller information about a given device/item .PARAMETER ItemID The ID of the item to return poller information about .PARAMETER PollerGroup The poller group to return poller information on. Choose from Application, Server or Network .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarPlatformDashboardItemPollerSummary -ItemID 320_51 .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias('ID')] [string]$ItemID, [Parameter(Mandatory=$True)] [ValidateSet('Application','Server','Network', IgnoreCase=$True)] [string]$PollerGroup, [Parameter(ValueFromPipelineByPropertyName, DontShow)] [Alias('Name')] [string]$ItemName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/dapi/platform/dashboard/item/$ItemID/summary?tenant=$TenantName&group=$($PollerGroup.ToUpper())" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader # If the command is called as part of a pipeline from Get-NectarPlatformDashboard then add the device name to the output If ($ItemName) {$JSON | Add-Member -Name 'ItemName' -Value $ItemName -MemberType NoteProperty} # Add the Item ID and PollerGroup to the output $JSON | Add-Member -Name 'ItemID' -Value $ItemID -MemberType NoteProperty $JSON | Add-Member -Name 'PollerGroup' -Value $PollerGroup -MemberType NoteProperty If ($TenantName) {$JSON | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty} Return $JSON } Catch { Write-Error "Could not get platform dashboards" Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarPlatformDashboardItemPollerDetail { <# .SYNOPSIS Return specific poller information over time about a given device/item .DESCRIPTION Return specific poller information over time about a given device/item .PARAMETER ItemID The ID of the item to return poller information about .PARAMETER Index The numeric index of the poller to return information on .PARAMETER Type The metric type assigned to the poller .PARAMETER PollerGroup The poller group to return poller information on. Choose from Application, Server or Network .PARAMETER ExpandHistogram Show a detailed table with the contents of the histogram output instead of a difficult to parse array .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarPlatformDashboardItemPollerDetail -ItemID 320_51 .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$ItemID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$Index, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$Type, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [ValidateSet('Application','Server','Network', IgnoreCase=$True)] [string]$PollerGroup, [Parameter(Mandatory=$False)] [ValidateSet('LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM', IgnoreCase=$False)] [string]$TimePeriod = 'LAST_DAY', [Parameter(Mandatory=$False)] [Alias("StartDateFrom")] [DateTime]$TimePeriodFrom, [Parameter(Mandatory=$False)] [Alias("StartDateTo")] [DateTime]$TimePeriodTo, [Parameter(Mandatory=$False)] [switch]$ExpandHistogram, [Parameter(ValueFromPipelineByPropertyName, DontShow)] [string]$ItemName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $Params = @{} # Convert date to UNIX timestamp If($TimePeriodFrom) { $TimePeriodFromUNIX = (Get-Date -Date $TimePeriodFrom -UFormat %s) + '000' $Params.Add('StartDateFrom',$TimePeriodFromUNIX) } If($TimePeriodTo) { $TimePeriodToUNIX = (Get-Date -Date $TimePeriodTo -UFormat %s) + '000' $Params.Add('StartDateTo',$TimePeriodToUNIX) } $URI = "https://$Global:NectarCloud/dapi/platform/dashboard/item/$ItemID/metric?type=$Type&group=$($PollerGroup.ToUpper())&timePeriod=$TimePeriod&index=$Index&tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader -Body $Params If ($ItemName) {$JSON | Add-Member -Name 'ItemName' -Value $ItemName -MemberType NoteProperty} If ($TenantName) {$JSON | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty} If ($ExpandHistogram) { $Histogram = $JSON.histogram $HistoFormatted = @() ForEach ($Element in $Histogram) { $RowData = New-Object PsObject $RowData | Add-Member -NotePropertyName 'Date' -NotePropertyValue $Element[0] $RowData | Add-Member -NotePropertyName 'Item' -NotePropertyValue $ItemName $RowData | Add-Member -NotePropertyName 'Poller' -NotePropertyValue $JSON.Name $RowData | Add-Member -NotePropertyName 'Value' -NotePropertyValue $Element[1] $HistoFormatted += $RowData } Return $HistoFormatted } Else { Return $JSON } } Catch { Write-Error "Could not get item poller detail" Get-JSONErrorStream -JSONResponse $_ } } } ################################################################################################################################################# # # # Endpoint Client Functions # # # ################################################################################################################################################# Function Get-NectarEndpoint { <# .SYNOPSIS Returns a list of Nectar DXP endpoints .DESCRIPTION Returns a list of Nectar DXP endpoints .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 1000 .PARAMETER ResultSize The total number of results to return. Defaults to 100000. Maximum result size is 9,999,999 results .EXAMPLE Get-NectarEndpoint Returns the first 100000 endpoints .EXAMPLE Get-NectarEndpoint -ResultSize 100 Returns the first 100 endpoints .EXAMPLE Get-NectarEndpoint -SearchQuery US Returns all endpoints that have US in any of the data fields .NOTES Version 1.0 #> [Alias("gne")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$SearchQuery, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('id','uuid','name','ipAddress','isHub','mapped','userName','userDisplayName','location', IgnoreCase=$False)] [string]$OrderByField = 'id', [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('asc','desc', IgnoreCase=$False)] [string]$OrderDirection = 'asc', [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [ValidateRange(1,100000)] [int]$PageSize = 1000, [Parameter(Mandatory=$False)] [ValidateRange(1,100000)] [int]$ResultSize = 100000 ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/aapi/testing/entities/" $Params = @{ 'orderByField' = $OrderByField 'orderDirection' = $OrderDirection } If ($SearchQuery) { $Params.Add('searchQuery', $SearchQuery) } If ($ResultSize) { $Params.Add('pageSize', $ResultSize) } Else { $Params.Add('pageSize', $PageSize) } If ($TenantName) { $Params.Add('tenant', $TenantName) } $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader -Body $Params If ($TenantName) { $JSON.elements | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty } # Add the tenant name to the output which helps pipelining $JSON.elements $TotalPages = $JSON.totalPages 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 If ($TenantName) { $JSON.elements | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty } $JSON.elements $PageNum++ } } } Catch { Write-Error "Unable to retrieve endpoint information" Get-JSONErrorStream -JSONResponse $_ } } } Function Set-NectarEndpoint { <# .SYNOPSIS Modify properties of a Nectar DXP endpoint .DESCRIPTION Modify properties of a Nectar DXP endpoint .PARAMETER SearchQuery A string to search for to return a specific endpoint. Will search for match against all fields .PARAMETER UserName The username to associate with the given endpoint. Set to $NULL to de-associate a user with an endpoint .PARAMETER IsHub Set the endpoint to be a hub endpoint or not. .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Set-NectarEndpoint -SearchQuery ff4bf84a-04d8-11ec-ad2e-17d4f8d1df89 -UserName tferguson@contoso.com -IsHub $False Sets a specific endpoint's associated username to 'tferguson' .NOTES Version 1.0 #> [Alias("gne")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias('UUID')] [string]$SearchQuery, [Parameter(Mandatory=$False)] [string]$UserName, [Parameter(Mandatory=$False)] [string]$DisplayName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [bool]$IsHub, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [ValidateRange(1,100000)] [int]$PageSize = 1000, [Parameter(Mandatory=$False)] [ValidateRange(1,100000)] [int]$ResultSize = 100000 ) Begin { Connect-NectarCloud # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } $ECSearchURI = "https://$Global:NectarCloud/aapi/testing/entities/" $ECUpdateURI = "https://$Global:NectarCloud/aapi/testing/entity/" $UserNameURI = "https://$Global:NectarCloud/aapi/testing/entities/usernames/" } Process { Try { # Search for a single valid endpoint. Throw error if zero or more than 1 endpoint returned $ECSearchParams = @{ 'searchQuery' = $SearchQuery } If ($TenantName) { $ECSearchParams.Add('tenant', $TenantName) } $ECSearchJSON = Invoke-RestMethod -Method GET -uri $ECSearchURI -Headers $Global:NectarAuthHeader -Body $ECSearchParams If ($ECSearchJSON.totalElements -gt 1) { Write-Error 'Too many endpoints returned. Please refine your search query to return only a single endpoint.' Return } ElseIf ($ECSearchJSON.totalElements -eq 0) { Write-Error "Could not find endpoint $SearchQuery" Return } $EndpointDetails = $ECSearchJSON.elements # Search for a single valid username. Throw error if zero or more than 1 username returned If ($UserName) { $UserNameParams = @{ 'searchQuery' = $UserName } If ($TenantName) { $UserNameParams.Add('tenant', $TenantName) } $UserSearchJSON = Invoke-RestMethod -Method GET -uri $UserNameURI -Headers $Global:NectarAuthHeader -Body $UserNameParams If ($UserSearchJSON.Count -gt 1) { Write-Error 'Too many usernames returned. Please refine your search query to return only a single username.' Return } ElseIf ($UserSearchJSON.Count -eq 0) { Write-Error "Could not find user with name $UserName" Return } $UserName = $UserSearchJSON.UserName } If ($PSBoundParameters.Keys.Contains('UserName')) { If ($UserName) { $EndpointDetails[0].userName = $UserName } Else { $EndpointDetails[0].userName = $NULL } } If ($DisplayName) { $EndpointDetails[0].userDisplayName = $DisplayName } If ($PSBoundParameters.Keys.Contains('IsHub')) { $EndpointDetails[0].IsHub = $IsHub $EndpointDetails[0].userName = $NULL } $EndpointJSON = $EndpointDetails | ConvertTo-Json Write-Verbose $EndpointJSON If ($TenantName) { $ECUpdateURI = $ECUpdateURI + "?tenant=$TenantName" } $NULL = Invoke-RestMethod -Method PUT -uri $ECUpdateURI -Headers $Global:NectarAuthHeader -Body $EndpointJSON -ContentType 'application/json; charset=utf-8' } Catch { Write-Error "Unable to set endpoint information" Get-JSONErrorStream -JSONResponse $_ } } } Function Remove-NectarEndpoint { <# .SYNOPSIS Remove a Nectar DXP endpoint .DESCRIPTION Remove a Nectar DXP endpoint .PARAMETER ID The ID of the endpoint to remove. Both ID and UUID are required for deletion. .PARAMETER UUID The UUID of the endpoint to remove. Both ID and UUID are required for deletion. .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarEndpoint -SearchQuery tferguson | Remove-NectarEndpoint Removes the endpoint associated with 'tferguson' .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$ID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$UUID, [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 !$TenantName) { $TenantName = $Global:NectarTenantName } } Process { Try { $URI = "https://$Global:NectarCloud/aapi/testing/entity?id=$ID&uuid=$UUID&tenant=$TenantName" $NULL = Invoke-RestMethod -Method DELETE -uri $URI -Headers $Global:NectarAuthHeader } Catch { Write-Error "Unable to delete endpoint information" Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarEndpointTest { <# .SYNOPSIS Returns information about Endpoint Client tests .DESCRIPTION Returns information about Endpoint Client tests UI_ELEMENT .PARAMETER TimePeriod The time period to show session data from. Select from 'LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM'. CUSTOM requires using TimePeriodFrom and TimePeriodTo parameters. .PARAMETER TimePeriodFrom The earliest date/time to show session data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodTo parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. .PARAMETER TimePeriodTo The latest date/time to show session data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodFrom parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. .PARAMETER TestTypes The types of EPC tests to return. Choose from one or more of 'P2P', 'PING', 'AUDIO', or 'VIDEO' .PARAMETER TestResults The result type to return. Choose from one or more of 'PASSED','FAILED', 'INCOMPLETE' or 'UNKNOWN' .PARAMETER ResponseCodes Show tests that match one or more SIP response codes. Accepts numbers from 0 to 699 .PARAMETER Locations Show sessions where the selected location was used by either caller or callee. Can query for multiple locations. .PARAMETER CallerLocations Show sessions where the selected location was used by the caller. Can query for multiple locations. .PARAMETER CalleeLocations Show sessions where the selected location was used by the callee. Can query for multiple locations. .PARAMETER ExtCities Show sessions where the caller or callee was located in the selected city (as detected via geolocating the user's external IP address). Can query for multiple cities. .PARAMETER CallerExtCities Show sessions where the caller was located in the selected city (as detected via geolocating the user's external IP address). Can query for multiple cities. .PARAMETER CalleeExtCities Show sessions where the callee was located in the selected city (as detected via geolocating the user's external IP address). Can query for multiple cities. .PARAMETER ExtCountries Show sessions where the caller or callee was located in the selected country (as detected via geolocating the user's external IP address). Can query for multiple countries. .PARAMETER CallerExtCountries Show sessions where the caller was located in the selected country (as detected via geolocating the user's external IP address). Can query for multiple countries. .PARAMETER CalleeExtCountries Show sessions where the callee was located in the selected country (as detected via geolocating the user's external IP address). Can query for multiple countries. .PARAMETER ExtISPs Show sessions where the caller or callee was located in the selected ISP (as detected via geolocating the user's external IP address). Can query for multiple ISPs. .PARAMETER CallerExtISPs Show sessions where the caller was located in the selected ISP (as detected via geolocating the user's external IP address). Can query for multiple ISPs. .PARAMETER CalleeExtISPs Show sessions where the callee was located in the selected ISP (as detected via geolocating the user's external IP address). Can query for multiple ISPs. .PARAMETER Users Show sessions where the selected user was either caller or callee. Can query for multiple users. .PARAMETER FromUsers Show sessions where the selected user was the caller. Can query for multiple users. .PARAMETER ToUsers Show sessions where the selected user was the callee. Can query for multiple users. .PARAMETER UserIDs Show sessions where the selected user ID was either caller or callee. Can query for multiple user IDs. .PARAMETER FromUserIDs Show sessions where the selected user ID was the caller. Can query for multiple user IDs. .PARAMETER ToUserIDs Show sessions where the selected user ID was the callee. Can query for multiple user IDs. .PARAMETER OrderByField Sort the output by the selected field .PARAMETER OrderDirection Sort direction. Use with OrderByField. Not case sensitive. Choose from: ASC, DESC .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 1000 .PARAMETER ResultSize The total number of results to return. Defaults to 1000. Maximum result size is 9,999,999 results .EXAMPLE Get-NectarEndpointTest -TimePeriod LAST_HOUR -TestTypes P2P -TestResults FAILED Returns a list of all Endpoint Client P2P tests that failed within the last hour .EXAMPLE Get-NectarEndpointTest -TimePeriod LAST_DAY -Locations 'Head Office', Warehouse Returns a list of all Endpoint Client tests where either the caller or callee was in the corporate head office or the warehouse .EXAMPLE Get-NectarEndpointTest -TimePeriod LAST_WEEK -CallerExtCities Chicago, Dallas Returns a list of all Endpoint Client tests from the last week where the caller was in either Chicago or Dallas .NOTES Version 1.0 #> [CmdletBinding(PositionalBinding=$False)] Param ( [Parameter(Mandatory=$False)] [ValidateSet('LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM', IgnoreCase=$True)] [string]$TimePeriod = 'LAST_HOUR', [Parameter(Mandatory=$False)] [Alias("StartDateFrom")] [DateTime]$TimePeriodFrom, [Parameter(Mandatory=$False)] [Alias("StartDateTo")] [DateTime]$TimePeriodTo, [Parameter(Mandatory=$False)] [ValidateSet('P2P','PING','AUDIO','VIDEO', IgnoreCase=$False)] [string[]]$TestTypes, [Parameter(Mandatory=$False)] [ValidateSet('PASSED','FAILED','UNKNOWN','INCOMPLETE', IgnoreCase=$False)] [string[]]$TestResults, [Parameter(Mandatory=$False)] [ValidateRange(0,699)] [string[]]$ResponseCodes, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$Users, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$FromUsers, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ToUsers, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$UserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$FromUserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ToUserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$Locations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerLocations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeLocations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtISPs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtISPs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtISPs, [Parameter(Mandatory=$False)] [string]$OrderByField, [Parameter(Mandatory=$False)] [ValidateSet('ASC','DESC', IgnoreCase=$True)] [string]$OrderDirection, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [ValidateRange(1,10000)] [int]$PageSize = 1000, [Parameter(Mandatory=$False)] [ValidateRange(1,50000)] [int]$ResultSize ) Process { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If ($PageSize) { $PSBoundParameters.Remove('PageSize') | Out-Null } If ($ResultSize) { $PSBoundParameters.Remove('ResultSize') | Out-Null } If ($OrderByField) { $PSBoundParameters.Remove('OrderByField') | Out-Null } If ($OrderDirection) { $PSBoundParameters.Remove('OrderDirection') | Out-Null } $FilterSession = Set-NectarFilterParams @PsBoundParameters -Platform ENDPOINT_CLIENT -Scope ENDPOINT_CLIENT $URI = "https://$Global:NectarCloud/dapi/testing/results" # 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) $Params = @{ 'Scope' = 'ENDPOINT_CLIENT' 'Platform' = 'ENDPOINT_CLIENT' } If ($ResultSize) { $Params.Add( 'pageSize',$ResultSize) } Else { $Params.Add('pageSize',$PageSize) } If($OrderByField) { $Params.Add('orderByField',$OrderByField) } If($OrderDirection) { $Params.Add('orderDirection',$OrderDirection) } If ($TenantName) { $Params.Add('tenant',$TenantName) } # Return results in pages Try { Write-Verbose $URI ForEach($k in $Params.Keys){Write-Verbose "$k $($Params[$k])"} $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader -Body $Params -WebSession $FilterSession $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" $PagedURI = $URI + "?pageNumber=$PageNum" $JSON = Invoke-RestMethod -Method GET -uri $PagedURI -Headers $Global:NectarAuthHeader -Body $Params -WebSession $FilterSession If ($TenantName) {$JSON.elements | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty} $JSON.elements $PageNum++ } } } Catch { Write-Error "No results. Try specifying a less-restrictive filter" Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarEndpointTestSummary { <# .SYNOPSIS Returns summary information about Endpoint Client tests .DESCRIPTION Returns summary information about Endpoint Client tests UI_ELEMENT .PARAMETER TimePeriod The time period to show session data from. Select from 'LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM'. CUSTOM requires using TimePeriodFrom and TimePeriodTo parameters. .PARAMETER TimePeriodFrom The earliest date/time to show session data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodTo parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. .PARAMETER TimePeriodTo The latest date/time to show session data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodFrom parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. .PARAMETER TestTypes The types of EPC tests to return. Choose from one or more of 'P2P', 'PING', 'AUDIO', or 'VIDEO' .PARAMETER TestResults The result type to return. Choose from one or more of 'PASSED','FAILED', 'INCOMPLETE' or 'UNKNOWN' .PARAMETER ResponseCodes Show tests that match one or more SIP response codes. Accepts numbers from 0 to 699 .PARAMETER Locations Show sessions where the selected location was used by either caller or callee. Can query for multiple locations. .PARAMETER CallerLocations Show sessions where the selected location was used by the caller. Can query for multiple locations. .PARAMETER CalleeLocations Show sessions where the selected location was used by the callee. Can query for multiple locations. .PARAMETER ExtCities Show sessions where the caller or callee was located in the selected city (as detected via geolocating the user's external IP address). Can query for multiple cities. .PARAMETER CallerExtCities Show sessions where the caller was located in the selected city (as detected via geolocating the user's external IP address). Can query for multiple cities. .PARAMETER CalleeExtCities Show sessions where the callee was located in the selected city (as detected via geolocating the user's external IP address). Can query for multiple cities. .PARAMETER ExtCountries Show sessions where the caller or callee was located in the selected country (as detected via geolocating the user's external IP address). Can query for multiple countries. .PARAMETER CallerExtCountries Show sessions where the caller was located in the selected country (as detected via geolocating the user's external IP address). Can query for multiple countries. .PARAMETER CalleeExtCountries Show sessions where the callee was located in the selected country (as detected via geolocating the user's external IP address). Can query for multiple countries. .PARAMETER ExtISPs Show sessions where the caller or callee was located in the selected ISP (as detected via geolocating the user's external IP address). Can query for multiple ISPs. .PARAMETER CallerExtISPs Show sessions where the caller was located in the selected ISP (as detected via geolocating the user's external IP address). Can query for multiple ISPs. .PARAMETER CalleeExtISPs Show sessions where the callee was located in the selected ISP (as detected via geolocating the user's external IP address). Can query for multiple ISPs. .PARAMETER Users Show sessions where the selected user was either caller or callee. Can query for multiple users. .PARAMETER FromUsers Show sessions where the selected user was the caller. Can query for multiple users. .PARAMETER ToUsers Show sessions where the selected user was the callee. Can query for multiple users. .PARAMETER UserIDs Show sessions where the selected user ID was either caller or callee. Can query for multiple user IDs. .PARAMETER FromUserIDs Show sessions where the selected user ID was the caller. Can query for multiple user IDs. .PARAMETER ToUserIDs Show sessions where the selected user ID was the callee. Can query for multiple user IDs. .PARAMETER OrderByField Sort the output by the selected field .PARAMETER OrderDirection Sort direction. Use with OrderByField. Not case sensitive. Choose from: ASC, DESC .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 1000 .PARAMETER ResultSize The total number of results to return. Defaults to 1000. Maximum result size is 9,999,999 results .EXAMPLE Get-NectarEndpointTestSummary -TimePeriod LAST_HOUR -TestTypes P2P -TestResults FAILED Returns a summary of all Endpoint Client P2P tests that failed within the last hour grouped by endpoint .EXAMPLE Get-NectarEndpointTestSummary -TimePeriod LAST_DAY -Locations 'Head Office', Warehouse Returns a summary of all Endpoint Client tests grouped by endpoint where either the caller or callee was in the corporate head office or the warehouse .EXAMPLE Get-NectarEndpointTestSummary -TimePeriod LAST_WEEK -CallerExtCities Chicago, Dallas Returns a list of all Endpoint Client tests from the last week grouped by endpoint where the caller was in either Chicago or Dallas .NOTES Version 1.0 #> [CmdletBinding(PositionalBinding=$False)] Param ( [Parameter(Mandatory=$False)] [ValidateSet('LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM', IgnoreCase=$True)] [string]$TimePeriod = 'LAST_HOUR', [Parameter(Mandatory=$False)] [Alias("StartDateFrom")] [DateTime]$TimePeriodFrom, [Parameter(Mandatory=$False)] [Alias("StartDateTo")] [DateTime]$TimePeriodTo, [Parameter(Mandatory=$False)] [ValidateSet('P2P','PING','AUDIO','VIDEO', IgnoreCase=$False)] [string[]]$TestTypes, [Parameter(Mandatory=$False)] [ValidateSet('PASSED','FAILED','UNKNOWN','INCOMPLETE','INCOMPLETE', IgnoreCase=$False)] [string[]]$TestResults, [Parameter(Mandatory=$False)] [ValidateSet('HUB','ENDPOINT', IgnoreCase=$False)] [string]$HubOrEndpoint = 'ENDPOINT', [Parameter(Mandatory=$False)] [ValidateRange(0,699)] [string[]]$ResponseCodes, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$Users, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$FromUsers, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ToUsers, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$UserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$FromUserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ToUserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$Locations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerLocations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeLocations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtISPs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtISPs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtISPs, [Parameter(Mandatory=$False)] [ValidateSet('TestSuccessRate','TestCount', IgnoreCase=$True)] [string]$OrderByField, [Parameter(Mandatory=$False)] [ValidateSet('ASC','DESC', IgnoreCase=$True)] [string]$OrderDirection, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [ValidateRange(1,10000)] [int]$PageSize = 1000, [Parameter(Mandatory=$False)] [ValidateRange(1,50000)] [int]$ResultSize ) Process { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If ($HubOrEndpoint) { $PSBoundParameters.Remove('HubOrEndpoint') | Out-Null } If ($PageSize) { $PSBoundParameters.Remove('PageSize') | Out-Null } If ($ResultSize) { $PSBoundParameters.Remove('ResultSize') | Out-Null } If ($OrderByField) { $PSBoundParameters.Remove('OrderByField') | Out-Null } If ($OrderDirection) { $PSBoundParameters.Remove('OrderDirection') | Out-Null } $FilterSession = Set-NectarFilterParams @PsBoundParameters -Platform ENDPOINT_CLIENT -Scope ENDPOINT_CLIENT $URI = "https://$Global:NectarCloud/dapi/testing/entities" # 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) $Params = @{ 'Platform' = 'ENDPOINT_CLIENT' 'type' = $HubOrEndpoint } If ($ResultSize) { $Params.Add( 'pageSize',$ResultSize) } Else { $Params.Add('pageSize',$PageSize) } If($OrderByField) { $Params.Add('orderByField',$OrderByField) } If($OrderDirection) { $Params.Add('orderDirection',$OrderDirection) } If ($TenantName) { $Params.Add('tenant',$TenantName) } # Return results in pages Try { Write-Verbose $URI foreach($k in $Params.Keys){Write-Verbose "$k $($Params[$k])"} $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader -Body $Params -WebSession $FilterSession $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" $PagedURI = $URI + "?pageNumber=$PageNum" $JSON = Invoke-RestMethod -Method GET -uri $PagedURI -Headers $Global:NectarAuthHeader -Body $Params -WebSession $FilterSession If ($TenantName) {$JSON.elements | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty} $JSON.elements $PageNum++ } } } Catch { Write-Error "No results. Try specifying a less-restrictive filter" Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarEndpointTestCount { <# .SYNOPSIS Returns summary information about Endpoint Client tests .DESCRIPTION Returns summary information about Endpoint Client tests UI_ELEMENT .PARAMETER TimePeriod The time period to show session data from. Select from 'LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM'. CUSTOM requires using TimePeriodFrom and TimePeriodTo parameters. .PARAMETER TimePeriodFrom The earliest date/time to show session data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodTo parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. .PARAMETER TimePeriodTo The latest date/time to show session data from. Must be used in conjunction with -TimePeriod CUSTOM and TimePeriodFrom parameters. Use format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'. All time/dates in UTC. .PARAMETER TestTypes The types of EPC tests to return. Choose from one or more of 'P2P', 'PING', 'AUDIO', or 'VIDEO' .PARAMETER TestResults The result type to return. Choose from one or more of 'PASSED','FAILED', 'INCOMPLETE' or 'UNKNOWN' .PARAMETER ResponseCodes Show tests that match one or more SIP response codes. Accepts numbers from 0 to 699 .PARAMETER Locations Show sessions where the selected location was used by either caller or callee. Can query for multiple locations. .PARAMETER CallerLocations Show sessions where the selected location was used by the caller. Can query for multiple locations. .PARAMETER CalleeLocations Show sessions where the selected location was used by the callee. Can query for multiple locations. .PARAMETER ExtCities Show sessions where the caller or callee was located in the selected city (as detected via geolocating the user's external IP address). Can query for multiple cities. .PARAMETER CallerExtCities Show sessions where the caller was located in the selected city (as detected via geolocating the user's external IP address). Can query for multiple cities. .PARAMETER CalleeExtCities Show sessions where the callee was located in the selected city (as detected via geolocating the user's external IP address). Can query for multiple cities. .PARAMETER ExtCountries Show sessions where the caller or callee was located in the selected country (as detected via geolocating the user's external IP address). Can query for multiple countries. .PARAMETER CallerExtCountries Show sessions where the caller was located in the selected country (as detected via geolocating the user's external IP address). Can query for multiple countries. .PARAMETER CalleeExtCountries Show sessions where the callee was located in the selected country (as detected via geolocating the user's external IP address). Can query for multiple countries. .PARAMETER ExtISPs Show sessions where the caller or callee was located in the selected ISP (as detected via geolocating the user's external IP address). Can query for multiple ISPs. .PARAMETER CallerExtISPs Show sessions where the caller was located in the selected ISP (as detected via geolocating the user's external IP address). Can query for multiple ISPs. .PARAMETER CalleeExtISPs Show sessions where the callee was located in the selected ISP (as detected via geolocating the user's external IP address). Can query for multiple ISPs. .PARAMETER Users Show sessions where the selected user was either caller or callee. Can query for multiple users. .PARAMETER FromUsers Show sessions where the selected user was the caller. Can query for multiple users. .PARAMETER ToUsers Show sessions where the selected user was the callee. Can query for multiple users. .PARAMETER UserIDs Show sessions where the selected user ID was either caller or callee. Can query for multiple user IDs. .PARAMETER FromUserIDs Show sessions where the selected user ID was the caller. Can query for multiple user IDs. .PARAMETER ToUserIDs Show sessions where the selected user ID was the callee. Can query for multiple user IDs. .PARAMETER OrderByField Sort the output by the selected field .PARAMETER OrderDirection Sort direction. Use with OrderByField. Not case sensitive. Choose from: ASC, DESC .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 1000 .PARAMETER ResultSize The total number of results to return. Defaults to 1000. Maximum result size is 9,999,999 results .EXAMPLE Get-NectarEndpointTestCount -TimePeriod LAST_HOUR -TestTypes P2P -TestResults FAILED Returns a count of all Endpoint Client P2P tests that failed within the last hour .EXAMPLE Get-NectarEndpointTestCount -TimePeriod LAST_DAY -Locations 'Head Office', Warehouse Returns a count of all Endpoint Client tests where either the caller or callee was in the corporate head office or the warehouse .EXAMPLE Get-NectarEndpointTestSummary -TimePeriod LAST_WEEK -CallerExtCities Chicago, Dallas Returns a count of all Endpoint Client tests from the last week where the caller was in either Chicago or Dallas .NOTES Version 1.0 #> [CmdletBinding(PositionalBinding=$False)] Param ( [Parameter(Mandatory=$False)] [ValidateSet('LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM', IgnoreCase=$True)] [string]$TimePeriod = 'LAST_HOUR', [Parameter(Mandatory=$False)] [Alias("StartDateFrom")] [DateTime]$TimePeriodFrom, [Parameter(Mandatory=$False)] [Alias("StartDateTo")] [DateTime]$TimePeriodTo, [Parameter(Mandatory=$False)] [ValidateSet('P2P','PING','AUDIO','VIDEO', IgnoreCase=$False)] [string[]]$TestTypes, [Parameter(Mandatory=$False)] [ValidateSet('PASSED','FAILED','UNKNOWN','INCOMPLETE','INCOMPLETE', IgnoreCase=$False)] [string[]]$TestResults, [Parameter(Mandatory=$False)] [ValidateSet('HUB','ENDPOINT', IgnoreCase=$False)] [string]$HubOrEndpoint = 'ENDPOINT', [Parameter(Mandatory=$False)] [ValidateRange(0,699)] [string[]]$ResponseCodes, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$Users, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$FromUsers, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ToUsers, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$UserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$FromUserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ToUserIDs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$Locations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerLocations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeLocations, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtCities, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtCountries, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$ExtISPs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallerExtISPs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CalleeExtISPs, [Parameter(Mandatory=$False)] [ValidateSet('ASC','DESC', IgnoreCase=$True)] [string]$OrderDirection, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) Process { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If ($HubOrEndpoint) { $PSBoundParameters.Remove('HubOrEndpoint') | Out-Null } $FilterSession = Set-NectarFilterParams @PsBoundParameters -Platform ENDPOINT_CLIENT -Scope ENDPOINT_CLIENT $URI = "https://$Global:NectarCloud/dapi/testing/counts" # 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) $Params = @{ 'platform' = 'ENDPOINT_CLIENT' 'tenant' = $TenantName } # Try { Write-Verbose $URI foreach($k in $Params.Keys){Write-Verbose "$k $($Params[$k])"} $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader -Body $Params -WebSession $FilterSession If ($TenantName) {$JSON | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty} Return $JSON # } # Catch { # Write-Error "No results. Try specifying a less-restrictive filter" # Get-JSONErrorStream -JSONResponse $_ # } } } ################################################################################################################################################# # # # MS Teams Functions # # # ################################################################################################################################################# Function Get-MSGraphAccessToken { <# .SYNOPSIS Get a Microsoft Graph access token for a given MS tenant. Needed to run other Graph API queries. .DESCRIPTION Get a Microsoft Graph access token for a given MS tenant. Needed to run other Graph API queries. .PARAMETER MSClientID The MS client ID for the application granted access to Azure AD. .PARAMETER MSClientSecret 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. .PARAMETER CertFriendlyName The friendly name of an installed certificate to be used for certificate authentication. Can be used instead of MSClientSecret .PARAMETER CertThumbprint The thumbprint of an installed certificate to be used for certificate authentication. Can be used instead of MSClientSecret .PARAMETER CertPath The path to a PFX certificate to be used for certificate authentication. Can be used instead of MSClientSecret .PARAMETER CertStore The certificate store to be used for certificate authentication. Select either LocalMachine or CurrentUser. Used in conjunction with CertThumbprint or CertFriendlyName Can be used instead of MSClientSecret. .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=$True)] [string]$MSClientID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSClientSecret, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$MSTenantID, [Parameter(Mandatory=$False)] [switch]$N10Cert, [Parameter(Mandatory=$False)] [string]$CertFriendlyName, [Parameter(Mandatory=$False)] [string]$CertThumbprint, [Parameter(Mandatory=$False)] [string]$CertPath, [Parameter(Mandatory=$False)] [ValidateSet('LocalMachine','CurrentUser', IgnoreCase=$True)] [string]$CertStore = 'CurrentUser' ) Begin { $Scope = 'https://graph.microsoft.com/.default' } Process { If ($MSClientSecret) { Try { # Get the Azure Graph API auth token $AuthBody = @{ grant_type = 'client_credentials' client_id = $MSClientID client_secret = $MSClientSecret scope = $Scope } $URI = "https://login.microsoftonline.com/$MSTenantID/oauth2/v2.0/token" Write-Verbose $URI $JSON_Auth = Invoke-RestMethod -Method POST -uri $URI -Body $AuthBody $AuthToken = $JSON_Auth.access_token Return $AuthToken } Catch { Write-Error "Failed to get access token. Ensure the values for MSTenantID, MSClientID and MSClientSecret are correct." Get-JSONErrorStream -JSONResponse $_ } } Else { # Needs access to the full certificate stored in Nectar DXP, and can be exported to PEM via Get-NectarMSTeamsSubscription (only if you have global admin privs) # Get-NectarMSTeamsSubscription -ExportCertificate # Need to create a certificate from the resulting PEM files using the following command: # openssl pkcs12 -export -in TeamsCert.pem -inkey TeamsPriv.key -CSP "Microsoft Enhanced RSA and AES Cryptographic Provider" -out FullCert.pfx # Requires that OpenSSL is installed # To convert a PFX to a Kubernetes secret in Base64 format, run the following: # $fileContentBytes = get-content FullCert.pfx -AsByteStream # [System.Convert]::ToBase64String($fileContentBytes) | Out-File pfx-encoded-bytes.txt # Then use the resulting certificate to obtain an access token: # $GraphToken = Get-MSGraphAccessToken -MSTenantID <tenantID> -MSClientID <Client/AppID> -CertPath .\FullCert.pfx # Then use the resulting token in other commands like: # Test-MSTeamsConnectivity -AuthToken $GraphToken # Get the certificate information via one of several methods If ($CertThumbprint) { $Certificate = Get-Item Cert:\$CertStore\My\$CertThumbprint } If ($CertFriendlyName) { $Certificate = Get-ChildItem Cert:\$CertStore\My | Where-Object {$_.FriendlyName -eq $CertFriendlyName} } If ($CertPath) { $Certificate = Get-PfxCertificate -FilePath $CertPath } If ($N10Cert) { # Get certificate BASE64 encoding from N10 $CertBlob = (Get-NectarMSTeamsSubscription).msClientCertificateDto.certificate $CertRaw = $CertBlob -replace "-----BEGIN CERTIFICATE-----", $NULL -replace "-----END CERTIFICATE-----", $NULL $Certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $Certificate.Import([Convert]::FromBase64String($CertRaw)) } If ($Certificate) { # Adapted from https://adamtheautomator.com/microsoft-graph-api-powershell/ # Create base64 hash of certificate $CertificateBase64Hash = [System.Convert]::ToBase64String($Certificate.GetCertHash()) # Create JWT timestamp for expiration $StartDate = (Get-Date "1970-01-01T00:00:00Z" ).ToUniversalTime() $JWTExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End (Get-Date).ToUniversalTime().AddMinutes(2)).TotalSeconds $JWTExpiration = [math]::Round($JWTExpirationTimeSpan,0) # Create JWT validity start timestamp $NotBeforeExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End ((Get-Date).ToUniversalTime())).TotalSeconds $NotBefore = [math]::Round($NotBeforeExpirationTimeSpan,0) # Create JWT header $JWTHeader = @{ alg = "RS256" typ = "JWT" # Use the CertificateBase64Hash and replace/strip to match web encoding of base64 x5t = $CertificateBase64Hash -replace '\+','-' -replace '/','_' -replace '=' } # Create JWT payload $JWTPayload = @{ # What endpoint is allowed to use this JWT aud = "https://login.microsoftonline.com/$MSTenantID/oauth2/token" # Expiration timestamp exp = $JWTExpiration # Issuer = your application iss = $MSClientID # JWT ID: random guid jti = [guid]::NewGuid() # Not to be used before nbf = $NotBefore # JWT Subject sub = $MSClientID } # Convert header and payload to base64 $JWTHeaderToByte = [System.Text.Encoding]::UTF8.GetBytes(($JWTHeader | ConvertTo-Json)) $EncodedHeader = [System.Convert]::ToBase64String($JWTHeaderToByte) $JWTPayloadToByte = [System.Text.Encoding]::UTF8.GetBytes(($JWTPayload | ConvertTo-Json)) $EncodedPayload = [System.Convert]::ToBase64String($JWTPayloadToByte) # Join header and Payload with "." to create a valid (unsigned) JWT $JWT = $EncodedHeader + "." + $EncodedPayload # Get the private key object of your certificate $PrivateKey = $Certificate.PrivateKey # Define RSA signature and hashing algorithm $RSAPadding = [Security.Cryptography.RSASignaturePadding]::Pkcs1 $HashAlgorithm = [Security.Cryptography.HashAlgorithmName]::SHA256 # Create a signature of the JWT ##### Breaks down here. Only works with full cert downloaded/installed ##### $Signature = [Convert]::ToBase64String($PrivateKey.SignData([System.Text.Encoding]::UTF8.GetBytes($JWT),$HashAlgorithm,$RSAPadding)) -replace '\+','-' -replace '/','_' -replace '=' # Join the signature to the JWT with "." $JWT = $JWT + "." + $Signature # Create a hash with body parameters $Body = @{ client_id = $MSClientID client_assertion = $JWT client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" scope = $Scope grant_type = "client_credentials" } $Uri = "https://login.microsoftonline.com/$MSTenantID/oauth2/v2.0/token" # Use the self-generated JWT as Authorization $Header = @{ Authorization = "Bearer $JWT" } # Splat the parameters for Invoke-Restmethod for cleaner code $PostSplat = @{ ContentType = 'application/x-www-form-urlencoded' Method = 'POST' Body = $Body Uri = $Uri Headers = $Header } $Request = Invoke-RestMethod @PostSplat $AuthToken = $Request.access_token Return $AuthToken } Else { Write-Error "Could not find certificate in $CertStore" } } } } Function Test-MSTeamsConnectivity { <# .SYNOPSIS Tests if we are able to retrieve Teams call data and Azure AD information from a O365 tenant. .DESCRIPTION Tests if we are able to retrieve Teams call data and Azure AD information from a O365 tenant. .PARAMETER MSClientID The MS client ID for the application granted access to Azure AD. .PARAMETER MSClientSecret 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. .PARAMETER SkipUserCount Skips the user count .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER AuthToken The authorization token used for this request. Normally obtained via Get-MSGraphAccessToken .EXAMPLE Get-NectarMSTeamsSubscription -TenantName contoso | Test-MSAzureADAccess .NOTES Version 1.1 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSClientID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSClientSecret, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSTenantID, [Parameter(Mandatory=$False)] [switch]$SkipUserCount, [Parameter(Mandatory=$False)] [switch]$HideOutput, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$AuthToken ) Process { If ($MSTenantID) { $AuthToken = Get-MSGraphAccessToken -MSClientID $MSClientID -MSClientSecret $MSClientSecret -MSTenantID $MSTenantID } ElseIf (!$AuthToken) { $AuthToken = Get-NectarMSTeamsSubscription -TenantName $TenantName | Get-MSGraphAccessToken } $Headers = @{ Authorization = "Bearer $AuthToken" } $AccessResults = [pscustomobject][ordered]@{ [string]'TeamsCallRecords' = 'FAIL' [string]'TeamsDevices' = 'FAIL' [string]'AzureUsers' = 'FAIL' [string]'AzureGroups' = 'FAIL' } # Test MS Teams call record access Try { $FromDateTime = (Get-Date -Format 'yyyy-MM-dd') $ToDateTime = ((Get-Date).AddDays(+1).ToString('yyyy-MM-dd')) $URI = "https://graph.microsoft.com/beta/communications/callRecords/getDirectRoutingCalls(fromDateTime=$FromDateTime,toDateTime=$ToDateTime)" If ($TenantName -And !$HideOutput) { Write-Host "TenantName: $TenantName - " -NoNewLine } If (!$HideOutput) { Write-Host 'Teams CR Status: ' -NoNewLine } $NULL = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers If (!$HideOutput) { Write-Host 'PASS' -ForegroundColor Green } $AccessResults.TeamsCallRecords = 'PASS' } Catch { If (!$HideOutput) { Write-Host 'FAIL' -ForegroundColor Red } } # Test MS Teams device access Try { $URI = 'https://graph.microsoft.com/beta/teamwork/devices' If ($TenantName -And !$HideOutput) { Write-Host "TenantName: $TenantName - " -NoNewLine } If (!$HideOutput) { Write-Host 'Teams Device Status: ' -NoNewLine } $NULL = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers If (!$HideOutput) { Write-Host 'PASS' -ForegroundColor Green } $AccessResults.TeamsDevices = 'PASS' } Catch { If (!$HideOutput) { Write-Host 'FAIL' -ForegroundColor Red } } # Test Azure AD user access Try { $URI = 'https://graph.microsoft.com/v1.0/users' If ($TenantName -And !$HideOutput) { Write-Host "TenantName: $TenantName - " -NoNewLine } If (!$HideOutput) { Write-Host 'Azure AD User Status: ' -NoNewLine } $NULL = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers If (!$HideOutput) { Write-Host 'PASS' -ForegroundColor Green } $AccessResults.AzureUsers = 'PASS' } Catch { If (!$HideOutput) { Write-Host 'FAIL' -ForegroundColor Red } Get-JSONErrorStream -JSONResponse $_ $SkipUserCount = $True } # Test Azure AD group access Try { $URI = 'https://graph.microsoft.com/v1.0/groups' If ($TenantName -And !$HideOutput) { Write-Host "TenantName: $TenantName - " -NoNewLine } If (!$HideOutput) { Write-Host 'Azure AD Group Status: ' -NoNewLine } $NULL = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers If (!$HideOutput) { Write-Host 'PASS' -ForegroundColor Green } $AccessResults.AzureGroups = 'PASS' } Catch { If (!$HideOutput) { Write-Host 'FAIL' -ForegroundColor Red } } If (!$SkipUserCount) { Get-MSTeamsUserLicenseCount -TenantName $TenantName } Clear-Variable AuthToken Return $AccessResults } } Function Get-MSAzureGraphSubscriptions { <# .SYNOPSIS Return data about the subscriptions for a given Azure tenant. .DESCRIPTION Return data about the subscriptions for a given Azure tenant. .PARAMETER MSClientID The MS client ID for the application granted access to Azure AD. .PARAMETER MSClientSecret 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. .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER AuthToken The authorization token used for this request. Normally obtained via Get-MSGraphAccessToken .EXAMPLE Get-MSAzureGraphSubscriptions .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSClientID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSClientSecret, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSTenantID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$AuthToken ) Process { Try { If ($MSTenantID) { $AuthToken = Get-MSGraphAccessToken -MSClientID $MSClientID -MSClientSecret $MSClientSecret -MSTenantID $MSTenantID } ElseIf (!$AuthToken) { $AuthToken = Get-NectarMSTeamsSubscription -TenantName $TenantName | Get-MSGraphAccessToken } # Test Azure AD access $Headers = @{ Authorization = "Bearer $AuthToken" } $URI = "https://graph.microsoft.com/v1.0/subscriptions" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers Return $JSON.value } Catch { Write-Error "Could not obtain subscription information." Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarMSTeamsSubscription { <# .SYNOPSIS Return data about the MSTeams subscription for a given tenant. .DESCRIPTION Return data about the MSTeams subscription for a given tenant. Requires a global admin account. Not available to tenant-level admins. .EXAMPLE Get-NectarMSTeamsSubscription .NOTES Version 1.0 #> [Alias("gnmtai","Get-MSTeamsAppInfo")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [switch]$ExportCertificate, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) Begin { Connect-NectarCloud } Process { Try { If (!$TenantName) { $TenantName = Get-NectarDefaultTenantName } $URI = "https://$Global:NectarCloud/aapi/clouddatasources/configuration/msteams/subscription?tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader If ($ExportCertificate) { Try { $JSON.data.msClientCertificateDto.certificate | Out-File TeamsCert.pem -Encoding ASCII $JSON.data.msClientCertificateDto.privateKey | Out-File TeamsPriv.key -Encoding ASCII Write-Host 'Successfully exported certificate to .\TeamsCert.pem and .\TeamsPriv.key' } Catch { Get-JSONErrorStream -JSONResponse $_ } } Else { Return $JSON.data } } Catch { Write-Error "TenantName: $TenantName - No MS Teams information found or insufficient permissions." Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarAzureADSubscription { <# .SYNOPSIS Return data about the Azure AD subscription for a given tenant. .DESCRIPTION Return data about the Azure AD subscription for a given tenant. Requires a global admin account. Not available to tenant-level admins. .EXAMPLE Get-NectarAzureADSubscription .NOTES Version 1.0 #> [Alias("gnads")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [switch]$ExportCertificate, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) Begin { Connect-NectarCloud } Process { Try { If (!$TenantName) { $TenantName = Get-NectarDefaultTenantName } $URI = "https://$Global:NectarCloud/aapi/clouddatasources/configuration/azuread/subscription?tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader If ($ExportCertificate) { Try { $JSON.data.msClientCertificateDto.certificate | Out-File TeamsCert.pem $JSON.data.msClientCertificateDto.privateKey | Out-File TeamsPriv.key Write-Host 'Successfully exported certificate to .\TeamsCert.pem and .\TeamsPriv.key' } Catch { Get-JSONErrorStream -JSONResponse $_ } } Else { Return $JSON.data } } Catch { Write-Error "TenantName: $TenantName - No Azure AD information found or insufficient permissions." Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarMSTeamsConfig { <# .SYNOPSIS Return information about an existing MS Teams call data integration with Nectar DXP .DESCRIPTION Return information about an existing MS Teams call data integration with Nectar DXP. Requires a global admin account. Not available to tenant-level admins. .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarMSTeamsConfig .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 !$TenantName) { $TenantName = $Global:NectarTenantName } # Get the existing MS Teams configurations $TeamsURI = "https://$Global:NectarCloud/aapi/clouddatasources/configuration/msteams/cdr?tenant=$TenantName" $TeamsBody = (Invoke-RestMethod -Method GET -uri $TeamsURI -Headers $Global:NectarAuthHeader).data If ($TenantName) { $TeamsBody | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty } # Add the tenant name to the output which helps pipelining Return $TeamsBody } } Function Get-NectarMSAzureConfig { <# .SYNOPSIS Return information about an existing MS Azure AD integration with Nectar DXP .DESCRIPTION Return information about an existing MS Azure AD integration with Nectar DXP. Requires a global admin account. Not available to tenant-level admins. .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarMSAzureConfig .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 !$TenantName) { $TenantName = $Global:NectarTenantName } # Get the existing MS Teams configurations $AzureURI = "https://$Global:NectarCloud/aapi/clouddatasources/configuration/azuread?tenant=$TenantName" $AzureBody = (Invoke-RestMethod -Method GET -uri $AzureURI -Headers $Global:NectarAuthHeader).data If ($TenantName) { $AzureBody | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty } # Add the tenant name to the output which helps pipelining Return $AzureBody } } Function Set-NectarMSTeamsConfig { <# .SYNOPSIS Modify an existing MS Teams call data integration with Nectar DXP .DESCRIPTION Modify an existing MS Teams call data integration with Nectar DXP. Requires a global admin account. Not available to tenant-level admins. .PARAMETER MSClientID The MS client ID for the application granted access to Azure AD. .PARAMETER MSClientSecret The MS client secret for the application granted access to Azure AD. Use either this or CertID, but not both. .PARAMETER CertID The Nectar DXP certificate ID. Use either this or MSClientSecret but not both. Obtain the cert ID by running Get-NectarMSTeamsCertificate. .PARAMETER MSTenantID The MS tenant ID for the O365 customer granted access to Azure AD. .PARAMETER MTRMonitoring Enable/disable Microsoft Teams Room device monitoring .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Set-NectarMSTeamsConfig -CertID .NOTES Version 1.0 #> [cmdletbinding(SupportsShouldProcess, ConfirmImpact = 'High')] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSClientID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSClientSecret, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [int]$CertID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSTenantID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [bool]$MTRMonitoring, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [int]$ConfigID ) Begin { Connect-NectarCloud } Process { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } # Get the existing MS Teams configurations $TeamsURI = "https://$Global:NectarCloud/aapi/clouddatasources/configuration/msteams/cdr?tenant=$TenantName" $AzureURI = "https://$Global:NectarCloud/aapi/clouddatasources/configuration/azuread?tenant=$TenantName" $TeamsBody = (Invoke-RestMethod -Method GET -uri $TeamsURI -Headers $Global:NectarAuthHeader).data $AzureBody = (Invoke-RestMethod -Method GET -uri $AzureURI -Headers $Global:NectarAuthHeader).data If ($ConfigID) { $TeamsBody = $TeamsBody | Where-Object {$_.ID -eq $ConfigID} $AzureBody = $AzureBody | Where-Object {$_.ID -eq $ConfigID} } If ($TeamsBody.Count -gt 1) { Throw "Multiple Teams configurations detected. Please specify config ID. Use Get-NectarMSTeamsConfig to locate." } # Build body for updating If (!$MSTenantID) { $MSTenantID = $TeamsBody.MSTenantID } If (!$MSClientID) { $MSClientID = $TeamsBody.msClientID } If (!$MSClientSecret -and !$CertID) { $MSClientSecret = $TeamsBody.msClientSecret; $CertID = $TeamsBody.msClientCertificateId } $TeamsUpdateBody = @{ tenant = $TenantName cloudAgentName = 'cloudconnector_agent' displayName = $TeamsBody.displayName msTenantId = $MSTenantID msClientId = $MSClientID msClientSecret = $MSClientSecret msClientCertificateId = $CertID kafkaTopic = $TeamsBody.kafkaTopic sourceId = $TeamsBody.sourceId loadDevices = $MTRMonitoring } $AzureUpdateBody = @{ tenant = $TenantName cloudAgentName = 'cloudconnector_agent' displayName = $AzureBody.displayName msTenantId = $MSTenantID msClientId = $MSClientID msClientSecret = $MSClientSecret msClientCertificateId = $CertID kafkaTopic = $AzureBody.kafkaTopic sourceId = $AzureBody.sourceId primaryAD = $AzureBody.isPrimary } If ($CertID -and !$MSClientSecret) { $TeamsUpdateBody.msClientSecret = $NULL $AzureUpdateBody.msClientSecret = $NULL } ElseIf ($MSClientSecret -and !$CertID) { $TeamsUpdateBody.msClientCertificateId = $NULL $AzureUpdateBody.msClientCertificateId = $NULL } $TeamsJSONBody = $TeamsUpdateBody | ConvertTo-Json $AzureJSONBody = $AzureUpdateBody | ConvertTo-Json $TeamsUpdateURI = "https://$Global:NectarCloud/aapi/clouddatasources/configuration/msteams/cdr/$($TeamsBody.id)?tenant=$TenantName" $AzureUpdateURI = "https://$Global:NectarCloud/aapi/clouddatasources/configuration/azuread/$($AzureBody.id)?tenant=$TenantName" Write-Verbose $TeamsUpdateURI Write-Verbose $TeamsJSONBody Write-Verbose $AzureUpdateURI Write-Verbose $AzureJSONBody If ($PSCmdlet.ShouldProcess(("Updating Nectar DXP MS Teams config on tenant {0}" -f $TenantName), ("Update Nectar DXP MS Teams config on tenant {0}?" -f $TenantName), 'Nectar DXP MS Teams Config Update')) { Try { Invoke-RestMethod -Method PUT -uri $TeamsUpdateURI -Headers $Global:NectarAuthHeader -Body $TeamsJSONBody -ContentType 'application/json; charset=utf-8' } Catch { Get-JSONErrorStream -JSONResponse $_ } Try { Invoke-RestMethod -Method PUT -uri $AzureUpdateURI -Headers $Global:NectarAuthHeader -Body $AzureJSONBody -ContentType 'application/json; charset=utf-8' } Catch { Get-JSONErrorStream -JSONResponse $_ } } } } Function New-NectarMSTeamsConfig { <# .SYNOPSIS Enables MS Teams call data and Azure AD integration with Nectar DXP .DESCRIPTION Enables MS Teams call data and Azure AD integration with Nectar DXP. Requires a global admin account. Not available to tenant-level admins. .PARAMETER MSClientID The MS client ID for the application granted access to Azure AD. .PARAMETER MSClientSecret The MS client secret for the application granted access to Azure AD. Use either this or CertID, but not both. .PARAMETER CertID The Nectar DXP certificate ID. Use either this or MSClientSecret but not both. Obtain the cert ID by running Get-NectarMSTeamsCertificate. .PARAMETER MSTenantID The MS tenant ID for the O365 customer granted access to Azure AD. .PARAMETER MTRMonitoring Enable/disable Microsoft Teams Room device monitoring .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE New-NectarMSTeamsConfig .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSClientID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSClientSecret, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [int]$CertID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSTenantID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [bool]$MTRMonitoring, [Parameter(Mandatory=$False)] [switch]$AzureOnly, [Parameter(Mandatory=$False)] [switch]$TeamsOnly ) Begin { Connect-NectarCloud } Process { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } $TeamsURI = "https://$Global:NectarCloud/aapi/clouddatasources/configuration/msteams/cdr" $AzureURI = "https://$Global:NectarCloud/aapi/clouddatasources/configuration/azuread" # Get the cloud agent name $CloudAgentURI = "https://$Global:NectarCloud/aapi/cloudagents" $CloudAgentName = (Invoke-RestMethod -Method GET -uri $CloudAgentURI -Headers $Global:NectarAuthHeader)[0].cloudAgentName # Check for existing Teams/Azure config # Will increment the sourceID if one already exists [int]$TeamsSourceID = ((Get-NectarMSTeamsConfig).sourceID | Measure-Object -Maximum).Maximum + 1 [int]$AzureSourceID = ((Get-NectarMSAzureConfig).sourceID | Measure-Object -Maximum).Maximum + 1 # Build the JSON body for creating the config $TeamsBody = @{ tenant = $TenantName cloudAgentName = $CloudAgentName displayName = "$TenantName MS Teams" msTenantId = $MSTenantID msClientId = $MSClientID kafkaTopic = 'msteams' sourceId = $TeamsSourceID loadDevices = $MTRMonitoring } $AzureBody = @{ tenant = $TenantName cloudAgentName = $CloudAgentName displayName = "$TenantName Azure AD" msTenantId = $MSTenantID msClientId = $MSClientID kafkaTopic = 'azuread' sourceId = $AzureSourceID primaryAD = $TRUE } If ($CertID) { $TeamsBody.Add('msClientCertificateId',$CertID) $AzureBody.Add('msClientCertificateId',$CertID) } Else { $TeamsBody.Add('msClientSecret',$MSClientSecret) $AzureBody.Add('msClientSecret',$MSClientSecret) } $TeamsJSONBody = $TeamsBody | ConvertTo-Json $AzureJSONBody = $AzureBody | ConvertTo-Json Write-Verbose $TeamsURI Write-Verbose $TeamsJSONBody Write-Verbose $AzureURI Write-Verbose $AzureJSONBody If (!$AzureOnly) { $NULL = Invoke-RestMethod -Method POST -uri $TeamsURI -Headers $Global:NectarAuthHeader -Body $TeamsJSONBody -ContentType 'application/json; charset=utf-8' } If (!$TeamsOnly) { $NULL = Invoke-RestMethod -Method POST -uri $AzureURI -Headers $Global:NectarAuthHeader -Body $AzureJSONBody -ContentType 'application/json; charset=utf-8' } } } Function Get-NectarMSTeamsCertificate { <# .SYNOPSIS Returns information about a public/private key pair for Azure app certificate-based authentication .DESCRIPTION MS Azure allows for either client secret or certificate-based authentication for Azure applications. This shows the certificate details for a given tenant. Requires a global admin account. Not available to tenant-level admins. .PARAMETER CertID The Nectar DXP certificate ID. Typically starts at 1 and increments from there .PARAMETER CertFilePath Exports the certificate to a .cer file at the listed path .EXAMPLE Get-NectarMSTeamsCertificate -CertID 1 .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [int]$CertID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$CertFilePath ) Begin { Connect-NectarCloud } Process { Try { $URI = "https://$Global:NectarCloud/aapi/client/certificate?id=$CertID" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader If ($CertFilePath) { $JSON.data.certificate | Out-File -FilePath $CertFilePath Write-Host "Certificate file written to $CertFilePath" } Return $JSON.data } Catch { Get-JSONErrorStream -JSONResponse $_ } } } Function New-NectarMSTeamsCertificate { <# .SYNOPSIS Creates a new public/private key pair for Azure app certificate-based authentication .DESCRIPTION MS Azure allows for either client secret or certificate-based authentication for Azure applications. This command will create a public/private key pair to use for this purpose. It will create the private key, associate it with the Nectar DXP tenant and present the public portion to be added to the MS Azure app. Requires a global admin account. Not available to tenant-level admins. .PARAMETER CertDisplayName The display name to assign to the certificate .PARAMETER ValidityPeriod The number of days the certificate should be valid for. Defaults to 365 days (1 year) .EXAMPLE New-NectarMSTeamsCertificate -CertDisplayName 'CompanyName Certificate for Nectar DXP' -ValidityPeriod 730 .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$CertDisplayName = 'Nectar DXP Certificate for Azure App', [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [int]$ValidityPeriod = 365 ) Begin { Connect-NectarCloud } Process { Try { If (!$CertDisplayName) { } $URI = "https://$Global:NectarCloud/aapi/client/certificate?name=$CertDisplayName&validityDays=$ValidityPeriod" Write-Verbose $URI $JSON = Invoke-RestMethod -Method POST -uri $URI -Headers $Global:NectarAuthHeader Return $JSON.data } Catch { Get-JSONErrorStream -JSONResponse $_ } } } Function Remove-NectarMSTeamsCertificate { <# .SYNOPSIS Removes a previously created public/private key pair used for MS Teams Azure app authentication .DESCRIPTION MS Azure allows for either client secret or certificate-based authentication for Azure applications. This command will delete a previously created public/private key pair. Requires a global admin account. Not available to tenant-level admins. .PARAMETER CertID The Nectar DXP certificate ID. Typically starts at 1 and increments from there .EXAMPLE Remove-NectarMSTeamsCertificate -CertID 1 .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [int]$CertID ) Begin { Connect-NectarCloud } Process { Try { $URI = "https://$Global:NectarCloud/aapi/client/certificate?id=$CertID" Write-Verbose $URI $NULL = Invoke-RestMethod -Method DELETE -uri $URI -Headers $Global:NectarAuthHeader } Catch { Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarMSTeamsPartialMonitoringUsers { <# .SYNOPSIS Return a list of users who are currently manually assigned to Teams Partial User Monitoring .DESCRIPTION Return a list of users who are currently manually assigned to Teams Partial User Monitoring. Requires a global admin account. Not available to tenant-level admins. .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 1000 .EXAMPLE Get-NectarMSTeamsPartialMonitoringUsers .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [ValidateRange(1,100000)] [int]$PageSize = 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/aapi/client/teams/partial-monitoring?tenant=$TenantName" Write-Verbose $URI $Params = @{ 'pageSize' = $PageSize } $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) { $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 If ($TenantName) {$JSON.elements | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty} $JSON.elements $PageNum++ } } } Catch { Write-Error "No results." } } } Function Add-NectarMSTeamsPartialMonitoringUser { <# .SYNOPSIS Adds a user account to the Teams Partial User Monitoring list .DESCRIPTION Adds a user account to the Teams Partial User Monitoring list .PARAMETER UserPrincipalName The UPN of the user account(s) to add. Separate multiple UPNs with comma .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Add-NectarMSTeamsPartialMonitoringUser tferguson@contoso.com Adds tferguson@nectarcorp.com to the Teams Partial Monitoring User list .EXAMPLE Add-NectarMSTeamsPartialMonitoringUser tferguson@contoso.com,tjones@contoso.com Adds tferguson@nectarcorp.com and tjones@contoso.com to the Teams Partial Monitoring User list .EXAMPLE Import-Csv PartialUserList.csv | Add-NectarMSTeamsPartialMonitoringUser Adds the contents of a CSV file called PartialUserList.csv to the Teams Partial Monitoring User list. The CSV to import must have UserPrincipalName as the column header and include a list of UPNs .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string[]]$UserPrincipalName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) Begin { Connect-NectarCloud $SourceID = (Get-NectarMSTeamsConfig -Tenant $TenantName).SourceID } Process { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/aapi/client/teams/partial-monitoring?tenant=$TenantName" Write-Verbose $URI ForEach ($UPN in $UserPrincipalName) { $Body = @{ userPrincipalName = $UPN sourceId = $SourceID } $JSONBody = $Body | ConvertTo-Json Try { $NULL = Invoke-RestMethod -Method POST -uri $URI -Headers $Global:NectarAuthHeader -Body $JSONBody -ContentType 'application/json; charset=utf-8' Write-Verbose $JSONBody } Catch { Write-Error "Unable to add user $UPN." Get-JSONErrorStream -JSONResponse $_ # Check to see if the user exists in Azure. Make sure the secret exists and is accessible to the user before checking. If ((Get-NectarMSTeamsConfig).msClientSecret) { $AzureUserInfo = Get-MSAzureUser -UPN $UPN -ErrorAction SilentlyContinue If ($AzureUserInfo) { Write-Warning "$UPN exists in Azure AD. This means there is an issue with the Azure AD sync within Nectar DXP." } Else { Write-Warning "$UPN does not exist in Azure AD. Please check the name." } } } } } } Function Remove-NectarMSTeamsPartialMonitoringUser { <# .SYNOPSIS Removes a user account from the Teams Partial User Monitoring list .DESCRIPTION Removes a user account from the Teams Partial User Monitoring list .PARAMETER UserPrincipalName The UPN of the user account(s) to remove. Separate multiple UPNs with comma .PARAMETER UserID The GUID of the user account(s) to remove. Separate multiple GUIDs with comma .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Remove-NectarMSTeamsPartialMonitoringUser tferguson@contoso.com,tjones@contoso.com Removes tferguson@contoso.com and tjones@contoso.com from the Partial User Monitoring list .EXAMPLE Get-NectarMSTeamsPartialMonitoringUser | Remove-NectarMSTeamsPartialMonitoringUser Removes all users from the Teams Partial User Monitoring list .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$UserPrincipalName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$UserID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) Begin { Connect-NectarCloud $UserList = Get-NectarMSTeamsPartialMonitoringUsers } Process { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If ($UserID) { ForEach ($ID in $UserID) { $URI = "https://$Global:NectarCloud/aapi/client/teams/partial-monitoring/$($ID)?sourceId=$($UserList[0].SourceID)&tenant=$TenantName" Write-Verbose $URI $NULL = Invoke-RestMethod -Method DELETE -uri $URI -Headers $Global:NectarAuthHeader } } ElseIf ($UserPrincipalName) { ForEach ($UPN in $UserPrincipalName) { $UserDeleteList = $UserList | Where-Object {$_.UserPrincipalName -eq $UPN} If (!$UserDeleteList) { Throw "Could not find a user with UPN $UPN" } Try { ForEach ($UserDelete in $UserDeleteList) { $URI = "https://$Global:NectarCloud/aapi/client/teams/partial-monitoring/$($UserDelete.UserId)?sourceId=$($UserDelete.SourceID)&tenant=$TenantName" Write-Verbose $URI $NULL = Invoke-RestMethod -Method DELETE -uri $URI -Headers $Global:NectarAuthHeader } } Catch { Write-Error "Unable to remove user $UPN." Get-JSONErrorStream -JSONResponse $_ } } } } } Function Set-NectarMSTeamsPartialMonitoringLimit { <# .SYNOPSIS Configure a limit to the number of users that are monitored in Nectar DXP .DESCRIPTION Configure a limit to the number of users that are monitored in Nectar DXP. Uses membership in a specified Azure AD group to determine the users we pull call records. Requires a global admin account. Not available to tenant-level admins. .PARAMETER MaxUsers The maximum number of users that will be monitored. Any group members above MaxUsers will not be monitored. .PARAMETER GroupName The display name of the group that contains the users who will be monitored. If the group contains spaces, enclose the name in quotes .PARAMETER SkipValidation Does not attempt to validate the existence of the Azure AD group before enabling. Use with caution. .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Set-NectarMSTeamsUserMonitoringLimit -MaxUsers 1000 -GroupName N10Users -TenantName contoso Sets the maximum number of monitored users on the Contoso tenant to 1000 and uses the members of the N10Users group as the list of users to be monitored .NOTES Version 1.0 #> [Alias("Set-NectarMSTeamsUserMonitoringLimit")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [ValidateRange(1,999999)] [int]$MaxUsers, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$GroupName, [Parameter(Mandatory=$False)] [bool]$Enabled = $True, [Parameter(Mandatory=$False)] [switch]$SkipValidation, [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 !$TenantName) { $TenantName = $Global:NectarTenantName } If ($Enabled -And $GroupName -And $GroupName -ne 'Unknown' -And !$SkipValidation) { # Validate the group exists in Azure AD $AuthToken = Get-NectarMSTeamsSubscription -TenantName $TenantName | Get-MSGraphAccessToken $Headers = @{ Authorization = "Bearer $AuthToken" } $URI = "https://graph.microsoft.com/v1.0/groups?`$filter=displayName eq '$GroupName'" Write-Verbose $URI Try { Write-Host 'Validating group...' $GroupData = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers If ($GroupData.Value.Count -eq 1) { Write-Host "Found Azure AD group " -NoNewLine Write-Host $GroupName -ForegroundColor Green $CountURI = "https://graph.microsoft.com/v1.0/groups/$($GroupData.Value.id)/transitivemembers/microsoft.graph.user/$count" # Add header $Headers.Add('ConsistencyLevel', 'eventual') Write-Verbose $CountURI $MemberCount = Invoke-RestMethod -Method GET -uri $CountURI -Headers $Headers $TotalCount = $MemberCount.value.count While ($MemberCount.'@odata.nextLink') { $NextURI = $MemberCount.'@odata.nextLink' $MemberCount = Invoke-RestMethod -Method GET -uri $NextURI -Headers $Headers $TotalCount += $MemberCount.value.count } Write-Host "Group has $TotalCount members" } Else { Write-Host "No group called '$GroupName' could be found in Azure AD" -ForegroundColor Red Break } } Catch { Write-Host "There was an error while attempting to locate the group $GroupName in Azure AD" -ForegroundColor Red Break } } ElseIf ($Enabled -And ($GroupName -eq '' -Or $GroupName -eq 'Unknown')) { $GroupName = 'Unknown' } # Get the MS Teams Config ID for constructing the UrlEncode $MSTeamsConfigID = (Get-NectarMSTeamsSubscription).msTeamsConfigId $URI = "https://$Global:NectarCloud/aapi/clouddatasources/configuration/msteams/cdr/partialmonitoring?id=$MSTeamsConfigID" Write-Verbose $URI $Body = @{ tenant = $TenantName enabled = $Enabled azureAdGroup = $GroupName maxUsers = $MaxUsers } $JSONBody = $Body | ConvertTo-Json Write-Verbose $JSONBody Try { $NULL = Invoke-RestMethod -Method PUT -uri $URI -Headers $Global:NectarAuthHeader -Body $JSONBody -ContentType 'application/json; charset=utf-8' If ($Enabled) { Write-Host "Successfully enabled Teams partial user monitoring on " -NoNewLine Write-Host $TenantName -NoNewLine -ForegroundColor Green Write-Host " for up to " -NoNewLine Write-Host $MaxUsers -NoNewLine -ForegroundColor Green If ($GroupName -ne 'Unknown') { Write-Host " users using group named " -NoNewLine Write-Host $GroupName -NoNewLine -ForegroundColor Green } Else { Write-Host " manually managed users." } } Else { Write-Host "Successfully disabled Teams partial user monitoring on " -NoNewLine Write-Host $TenantName -ForegroundColor Green } } Catch { Write-Error "Error while setting parameters" Get-JSONErrorStream -JSONResponse $_ } } } Function Get-MSTeamsCallRecord { <# .SYNOPSIS Return a specific call record details directly from MS Azure tenant. .DESCRIPTION Return a specific call record details directly from MS Azure tenant. .PARAMETER CallRecordID The MS call record ID .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER AuthToken The authorization token used for this request. Normally obtained via Get-MSGraphAccessToken .EXAMPLE Get-MSTeamsCallRecord -CallRecordID ed672235-5417-40ce-8425-12b8b702a505 -AuthToken $AuthToken Returns the given MS Teams call record using a previously-obtained authtoken .EXAMPLE Get-MSTeamsCallRecord -CallRecordID ed672235-5417-40ce-8425-12b8b702a505 -AuthToken $AuthToken -TextOutput | Out-File Call.json Returns the given MS Teams call record using a previously-obtained authtoken and saves the results to a .JSON file. .NOTES Version 1.3 #> [Alias("gmtcr")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias("calldCallRecordId")] [string]$CallRecordID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$AuthToken, [Parameter(Mandatory=$False)] [switch]$TextOutput, [Parameter(Mandatory=$False)] [switch]$BetaCallRecord ) Begin { Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If (!$AuthToken) { $AuthToken = Get-NectarMSTeamsSubscription -TenantName $TenantName | Where-Object {$_.SubscriptionStatus -eq 'ACTIVE'} | Get-MSGraphAccessToken } } Catch { Write-Error "Could not obtain authorization token." Get-JSONErrorStream -JSONResponse $_ } } Process { Try { $Headers = @{ Authorization = "Bearer $AuthToken" } # Change case to lower $CallRecordID = $CallRecordID.ToLower() If ($BetaCallRecord) { $URI = "https://graph.microsoft.com/beta/communications/callRecords/$CallRecordID/?`$expand=sessions(`$expand=segments)" } Else { $URI = "https://graph.microsoft.com/v1.0/communications/callRecords/$CallRecordID/?`$expand=sessions(`$expand=segments)" } Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers If ($JSON.'sessions@odata.nextLink') { $NextURI = $JSON.'sessions@odata.nextLink' Do { Write-Verbose "Getting next session page: $NextURI" $NextJSON = Invoke-RestMethod -Method GET -uri $NextURI -Headers $Headers $JSON.sessions += $NextJSON.value Write-Verbose "Session count: $($NextJSON.value.count)" $NextURI = $NextJSON.'@odata.nextLink' } Until (!$NextURI) } If ($TextOutput) { $JSON = $JSON | ConvertTo-Json -Depth 8 } Return $JSON } Catch { Write-Error "Could not get call record." Write-Error $_ } } } Function Get-MSTeamsPSTNCalls { <# .SYNOPSIS Return a list of MS Teams PSTN calls .DESCRIPTION Return a list of MS Teams PSTN calls .PARAMETER FromDateTime Start of time range to query. UTC, inclusive. Time range is based on the call start time. Defaults to today .PARAMETER ToDateTime End of time range to query. UTC, inclusive. Defaults to today .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER MSClientID The MS client ID for the application granted access to Azure AD. .PARAMETER MSClientSecret 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. .PARAMETER AuthToken The authorization token used for this request. Normally obtained via Get-MSGraphAccessToken .PARAMETER ResultSize The number of results to return. Defaults to 1000. .EXAMPLE Get-MSTeamsPSTNCalls -MSClientID 41a228ad-db6c-4e4e-4184-6d8a1175a35f -MSClientSecret 43Rk5Xl3K349w-pFf0i_Rt45Qd~ArqkE32. -MSTenantID 17e1e614-8119-48ab-8ba1-6ff1d94a6930 Returns all Teams PSTN calls for the specified tenant/clientID/secret combination .EXAMPLE Get-MSTeamsPSTNCalls -AuthToken $AuthToken Returns all Teams PSTN calls using a previously obtained authtoken .NOTES Version 1.0 #> Param ( [Parameter(Mandatory=$False)] [datetime]$FromDateTime = (Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ'), [Parameter(Mandatory=$False)] [datetime]$ToDateTime = ((Get-Date).AddDays(+1).ToString('yyyy-MM-ddTHH:mm:ssZ')), [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSClientID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSClientSecret, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSTenantID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$AuthToken, [Parameter(Mandatory=$False)] [ValidateRange(1,500000)] [int]$ResultSize ) Process { $Body = @{} $Params = @{} Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If ($MSTenantID) { $AuthToken = Get-MSGraphAccessToken -MSClientID $MSClientID -MSClientSecret $MSClientSecret -MSTenantID $MSTenantID } ElseIf (!$AuthToken) { $AuthToken = Get-NectarMSTeamsSubscription -TenantName $TenantName | Get-MSGraphAccessToken } } Catch { Write-Error "Could not obtain authorization token." Get-JSONErrorStream -JSONResponse $_ } If ($FromDateTime) { [string]$FromDateTimeString = $FromDateTime.ToString('yyyy-MM-ddTHH:mm:ssZ') } If ($ToDateTime) { [string]$ToDateTimeString = $ToDateTime.ToString('yyyy-MM-ddTHH:mm:ssZ') } If ($AuthToken) { $Headers = @{ Authorization = "Bearer $AuthToken" } $URI = "https://graph.microsoft.com/v1.0/communications/callRecords/getPstnCalls(fromDateTime=$FromDateTimeString,toDateTime=$ToDateTimeString)" Write-Verbose $URI $Params = @{} If ($AuthToken) { $Params.Add('AuthToken',$AuthToken) } If ($TenantName) { $Params.Add('Tenant',$TenantName) } $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers -Body $Body $JSON.value $PageSize = $JSON.value.count While (($JSON.'@odata.nextLink') -And ($PageSize -lt $ResultSize)) { $NextURI = $JSON.'@odata.nextLink' $JSON = Invoke-RestMethod -Method GET -uri $NextURI -Headers $Headers $JSON.value $PageSize += $JSON.value.count } Clear-Variable -Name AuthToken } } } Function Get-MSTeamsDRCalls { <# .SYNOPSIS Return a list of MS Teams Direct Routing calls .DESCRIPTION Return a list of MS Teams Direct Routing calls .PARAMETER FromDateTime Start of time range to query. UTC, inclusive. Time range is based on the call start time. Defaults to today .PARAMETER ToDateTime End of time range to query. UTC, inclusive. Defaults to today .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER MSClientID The MS client ID for the application granted access to Azure AD. .PARAMETER MSClientSecret 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. .PARAMETER AuthToken The authorization token used for this request. Normally obtained via Get-MSGraphAccessToken .PARAMETER ResultSize The number of results to return. Defaults to 1000. .EXAMPLE Get-MSTeamsDRCalls -MSClientID 41a228ad-db6c-4e4e-4184-6d8a1175a35f -MSClientSecret 43Rk5Xl3K349w-pFf0i_Rt45Qd~ArqkE32. -MSTenantID 17e1e614-8119-48ab-8ba1-6ff1d94a6930 Returns all Teams DR calls for the specified tenant/clientID/secret combination .EXAMPLE Get-MSTeamsDRCalls -AuthToken $AuthToken Returns all Teams DR calls using a previously obtained authtoken .NOTES Version 1.0 #> Param ( [Parameter(Mandatory=$False)] [datetime]$FromDateTime = (Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ'), [Parameter(Mandatory=$False)] [datetime]$ToDateTime = ((Get-Date).AddDays(+1).ToString('yyyy-MM-ddTHH:mm:ssZ')), [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSClientID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSClientSecret, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSTenantID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$AuthToken, [Parameter(Mandatory=$False)] [ValidateRange(1,500000)] [int]$ResultSize ) Process { $Body = @{} $Params = @{} Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If ($MSTenantID) { $AuthToken = Get-MSGraphAccessToken -MSClientID $MSClientID -MSClientSecret $MSClientSecret -MSTenantID $MSTenantID } ElseIf (!$AuthToken) { $AuthToken = Get-NectarMSTeamsSubscription -TenantName $TenantName | Get-MSGraphAccessToken } } Catch { Write-Error "Could not obtain authorization token." Get-JSONErrorStream -JSONResponse $_ } If ($FromDateTime) { [string]$FromDateTimeString = $FromDateTime.ToString('yyyy-MM-ddTHH:mm:ssZ') } If ($ToDateTime) { [string]$ToDateTimeString = $ToDateTime.ToString('yyyy-MM-ddTHH:mm:ssZ') } If ($AuthToken) { $Headers = @{ Authorization = "Bearer $AuthToken" } $URI = "https://graph.microsoft.com/v1.0/communications/callRecords/getDirectRoutingCalls(fromDateTime=$FromDateTimeString,toDateTime=$ToDateTimeString)" Write-Verbose $URI $Params = @{} If ($AuthToken) { $Params.Add('AuthToken',$AuthToken) } If ($TenantName) { $Params.Add('Tenant',$TenantName) } $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers -Body $Body $JSON.value $PageSize = $JSON.value.count While (($JSON.'@odata.nextLink') -And ($PageSize -lt $ResultSize)) { $NextURI = $JSON.'@odata.nextLink' $JSON = Invoke-RestMethod -Method GET -uri $NextURI -Headers $Headers $JSON.value $PageSize += $JSON.value.count } Clear-Variable -Name AuthToken } } } Function Import-MSTeamsCallRecord { <# .SYNOPSIS Import one or more MS Teams call records into Nectar DXP .DESCRIPTION Import one or more MS Teams call records into Nectar DXP .PARAMETER CallRecordID An array of Microsoft call record IDs to import .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Import-MSTeamsCallRecord -CallRecordID aac8d44c-e5f6-4d50-91a0-235d4a9631c9 Imports a single call record into Nectar DXP .EXAMPLE Import-MSTeamsCallRecord -CallRecordID $CallRecordList Imports an array of MS Teams call records into Nectar DXP .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory=$False)] [string[]]$CallRecordID, [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 !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/aapi/clouddatasources/configuration/msteams/command/cdr/reload-by-id" # Get the cloud agent name $CloudAgentURI = "https://$Global:NectarCloud/aapi/cloudagents" $CloudAgentName = (Invoke-RestMethod -Method GET -uri $CloudAgentURI -Headers $Global:NectarAuthHeader)[0].cloudAgentName # Get the sourceID $SourceID = (Get-NectarMSTeamsConfig).sourceID $Body = @{ tenant = $TenantName cloudAgentName = $CloudAgentName commandName = 'ReloadMsTeamsCdrByIdCommand' sourceId = $SourceID uuids = $CallRecordID } $JSONBody = $Body | ConvertTo-Json Write-Verbose $URI Write-Verbose $JSONBody Try { $JSON = Invoke-RestMethod -Method POST -uri $URI -Headers $Global:NectarAuthHeader -Body $JSONBody -ContentType 'application/json; charset=utf-8' Return $JSON.data } Catch { Get-JSONErrorStream -JSONResponse $_ } } } Function Get-MSAzureUser { <# .SYNOPSIS Return a list of users from Azure AD. .DESCRIPTION Return a list of users from Azure AD. .PARAMETER Properties A comma-delimited list of properties to return in the results Format as per https://docs.microsoft.com/en-us/graph/query-parameters?view=graph-rest-1.0#select-parameter Available properties can be found at https://docs.microsoft.com/en-us/graph/api/resources/user?view=graph-rest-1.0#properties .PARAMETER Filter Add filter parameters as per https://docs.microsoft.com/en-us/graph/query-parameters?context=graph%2Fapi%2F1.0&view=graph-rest-1.0#filter-parameter Available properties can be found at https://docs.microsoft.com/en-us/graph/api/resources/user?view=graph-rest-1.0#properties .PARAMETER UPN Return user details for the given UserPrincipalName .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER HideProgressBar Don't show the progress bar. Cleans up logs when running on Docker. .PARAMETER AuthToken The authorization token used for this request. Normally obtained via Get-MSGraphAccessToken .PARAMETER TotalCount Only return a total count of objects .PARAMETER ResultSize The number of results to return. Defaults to 1000. .EXAMPLE Get-MSAzureUser Returns all MS Azure user accounts .EXAMPLE Get-MSAzureUser -Properties 'id,userPrincipalName' -AuthToken $AuthToken Returns a list of Azure users' ID and userPrincipalNames using a previously-obtained authtoken .EXAMPLE Get-MSAzureUser -Filter "startswith(displayName,'Meeting Room')" Returns a list of Azure users whose display names start with 'Meeting Room' .NOTES Version 1.3 #> Param ( [Parameter(Mandatory=$False)] [string]$Properties, [Parameter(Mandatory=$False)] [string]$Filter, [Parameter(Mandatory=$False)] [string]$UPN, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [switch]$TotalCount, [Parameter(Mandatory=$False)] [switch]$HideProgressBar, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$AuthToken, [Parameter(Mandatory=$False)] [ValidateRange(1,500000)] [int]$ResultSize = 200000, [Parameter(Mandatory=$False)] [ValidateRange(1,99999)] [int]$ProgressUpdateFreq = 1 ) Process { $Body = @{'$count' = 'true'} Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If (!$AuthToken) { $AuthToken = Get-NectarMSTeamsSubscription -TenantName $TenantName | Get-MSGraphAccessToken } } Catch { Write-Error "Could not obtain authorization token." Get-JSONErrorStream -JSONResponse $_ } If ($AuthToken) { Try { $Headers = @{ Authorization = "Bearer $AuthToken" ConsistencyLevel = 'eventual' } # If UPN is entered, just look for that user and exit. If ($UPN) { $URI = "https://graph.microsoft.com/v1.0/users/$UPN" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers Return $JSON } If ($Properties) { $Body.Add('$select',$Properties) } If ($Filter) { $Body.Add('$filter',$Filter) } If ($TotalCount) { $URI = 'https://graph.microsoft.com/v1.0/users/$count' $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers -Body $Body $UserCount = New-Object PsObject $UserCount | Add-Member -NotePropertyName 'UserCount' -NotePropertyValue $JSON If ($TenantName) { $UserCount | Add-Member -NotePropertyName 'TenantName' -NotePropertyValue $TenantName } Clear-Variable -Name AuthToken Return $UserCount } Else { $URI = "https://graph.microsoft.com/v1.0/users" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers -Body $Body $TotalUsers = $JSON.'@odata.count' $JSON.value $PageSize = $JSON.value.count $Message = "Getting $TotalUsers Azure AD users for tenant $TenantName." If ($HideProgressBar) { Write-Host $Message } $UserCount = 0 $Stopwatch = [System.Diagnostics.Stopwatch]::StartNew() While (($JSON.'@odata.nextLink') -And ($PageSize -lt $ResultSize)) { $NextURI = $JSON.'@odata.nextLink' $JSON = Invoke-RestMethod -Method GET -uri $NextURI -Headers $Headers $JSON.value $PageSize += $JSON.value.count If ($Stopwatch.Elapsed.TotalSeconds -ge $ProgressUpdateFreq) { $Percentage = ($PageSize / $TotalUsers) * 100 If ($HideProgressBar) { $Percentage = [math]::Round($Percentage,1) Write-Host "Retrieving $Percentage`% of $($TotalUsers) users on tenant $TenantName..." } Else { Write-Progress -Activity $Message -PercentComplete $Percentage -Status 'Retrieving...' } $Stopwatch.Reset() $Stopwatch.Start() } } } Clear-Variable -Name AuthToken } Catch { Write-Error "Could not get user data." Write-Error $_ Clear-Variable -Name AuthToken } } } } Function Get-MSAzureUserGroupMembership { <# .SYNOPSIS Return the groups that a user is a member of from Azure AD. .DESCRIPTION Return the groups that a user is a member of from Azure AD. .PARAMETER ID The Azure AD GUID of the user .PARAMETER Transitive Show groups where the user is a member of a group that is a member of another group .PARAMETER Properties A comma-delimited list of properties to return in the results Format as per https://docs.microsoft.com/en-us/graph/query-parameters?view=graph-rest-1.0#select-parameter Available properties can be found at https://docs.microsoft.com/en-us/graph/api/resources/group?view=graph-rest-1.0#properties .PARAMETER Filter Add filter parameters as per https://docs.microsoft.com/en-us/graph/query-parameters?context=graph%2Fapi%2F1.0&view=graph-rest-1.0#filter-parameter Available properties can be found at https://docs.microsoft.com/en-us/graph/api/resources/group?view=graph-rest-1.0#properties .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER AuthToken The authorization token used for this request. Normally obtained via Get-MSGraphAccessToken .PARAMETER TotalCount Only return a total count of objects .PARAMETER ResultSize The number of results to return. Defaults to 10000. .EXAMPLE Get-MSAzureUserGroupMembership -ID abcdefab-1234-1234-1234-abcdabcdabcd Returns the groups that the selected user is a member of .EXAMPLE Get-MSAzureUser -Properties 'id,userPrincipalName' -AuthToken $AuthToken Returns a list of Azure users' ID and userPrincipalNames using a previously-obtained authtoken .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$ID, [Parameter(Mandatory=$False)] [switch]$Transitive, [string]$Properties, [Parameter(Mandatory=$False)] [string]$Filter, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [switch]$TotalCount, [Parameter(Mandatory=$False)] [switch]$HideProgressBar, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$AuthToken, [Parameter(Mandatory=$False)] [ValidateRange(1,500000)] [int]$ResultSize = 10000, [Parameter(Mandatory=$False)] [ValidateRange(1,99999)] [int]$ProgressUpdateFreq = 1 ) Begin { If ($Transitive) { $MemberOfScope = 'transitiveMemberOf' } Else { $MemberOfScope = 'MemberOf' } } Process { $Body = @{} $Params = @{} Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If (!$AuthToken) { $AuthToken = Get-NectarMSTeamsSubscription -TenantName $TenantName | Get-MSGraphAccessToken } } Catch { Write-Error "Could not obtain authorization token." Get-JSONErrorStream -JSONResponse $_ } If ($AuthToken) { Try { $Headers = @{ Authorization = "Bearer $AuthToken" } If ($Properties) { $Body.Add('$select',$Properties) $Params.Add('Properties',$Properties) } If ($Filter) { $Body.Add('$filter',$Filter) $Params.Add('Filter',$Filter) } If ($TotalCount) { $Body.Add('ConsistencyLevel','eventual') $URI = "https://graph.microsoft.com/v1.0/users/$ID/$MemberOfScope/`$count" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers -Body $Body $GroupCount = New-Object PsObject $GroupCount | Add-Member -NotePropertyName 'UserCount' -NotePropertyValue $JSON If ($TenantName) { $GroupCount | Add-Member -NotePropertyName 'TenantName' -NotePropertyValue $TenantName } Clear-Variable -Name AuthToken Return $GroupCount } Else { $URI = "https://graph.microsoft.com/v1.0/users/$ID/$MemberOfScope" Write-Verbose $URI $Params = @{} If ($AuthToken) { $Params.Add('AuthToken',$AuthToken) } If ($TenantName) { $Params.Add('Tenant',$TenantName) } $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers -Body $Body $JSON.value $PageSize = $JSON.value.count While (($JSON.'@odata.nextLink') -And ($PageSize -lt $ResultSize)) { $NextURI = $JSON.'@odata.nextLink' $JSON = Invoke-RestMethod -Method GET -uri $NextURI -Headers $Headers $JSON.value $PageSize += $JSON.value.count } } Clear-Variable -Name AuthToken } Catch { Write-Error "Could not get group membership data." Write-Error $_ Clear-Variable -Name AuthToken } } } } Function Get-MSAzureGroup { <# .SYNOPSIS Return a list of groups from Azure AD. .DESCRIPTION Return a list of groups from Azure AD. .PARAMETER Properties A comma-delimited list of properties to return in the results Format as per https://docs.microsoft.com/en-us/graph/query-parameters?view=graph-rest-1.0#select-parameter Available properties can be found at https://docs.microsoft.com/en-us/graph/api/resources/group?view=graph-rest-1.0#properties .PARAMETER Filter Add filter parameters as per https://docs.microsoft.com/en-us/graph/query-parameters?context=graph%2Fapi%2F1.0&view=graph-rest-1.0#filter-parameter Available properties can be found at https://docs.microsoft.com/en-us/graph/api/resources/group?view=graph-rest-1.0#properties .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER HideProgressBar Don't show the progress bar. Cleans up logs when running on Docker. .PARAMETER AuthToken The authorization token used for this request. Normally obtained via Get-MSGraphAccessToken .PARAMETER TotalCount Only return a total count of objects .PARAMETER ResultSize The number of results to return. Defaults to 1000. .EXAMPLE Get-MSAzureGroup Returns all MS Azure groups .EXAMPLE Get-MSAzureGroup -Filter "startsWith(displayName,'Global-')" Returns all MS Azure groups whose display names start with Global- .EXAMPLE Get-MSAzureGroup -Properties 'id,DisplayName' -AuthToken $AuthToken Returns a list of Azure group ID and display names using a previously-obtained authtoken .NOTES Version 1.0 #> Param ( [Parameter(Mandatory=$False)] [string]$Properties, [Parameter(Mandatory=$False)] [string]$Filter, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [switch]$TotalCount, [Parameter(Mandatory=$False)] [switch]$HideProgressBar, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$AuthToken, [Parameter(Mandatory=$False)] [ValidateRange(1,500000)] [int]$ResultSize, [Parameter(Mandatory=$False)] [ValidateRange(1,99999)] [int]$ProgressUpdateFreq = 1 ) Process { $Body = @{} $Params = @{} Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If (!$AuthToken) { $AuthToken = Get-NectarMSTeamsSubscription -TenantName $TenantName | Get-MSGraphAccessToken } } Catch { Write-Error "Could not obtain authorization token." Get-JSONErrorStream -JSONResponse $_ } If ($AuthToken) { Try { $Headers = @{ Authorization = "Bearer $AuthToken" } If ($Properties) { $Body.Add('$select',$Properties) $Params.Add('Properties',$Properties) } If ($Filter) { $Body.Add('$filter',$Filter) $Params.Add('Filter',$Filter) } If ($TotalCount) { $Body.Add('ConsistencyLevel','eventual') $URI = 'https://graph.microsoft.com/v1.0/groups/$count' $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers -Body $Body $GroupCount = New-Object PsObject $GroupCount | Add-Member -NotePropertyName 'GroupCount' -NotePropertyValue $JSON If ($TenantName) { $GroupCount | Add-Member -NotePropertyName 'TenantName' -NotePropertyValue $TenantName } Clear-Variable -Name AuthToken Return $GroupCount } Else { $URI = "https://graph.microsoft.com/v1.0/groups" Write-Verbose $URI $Params = @{} If ($AuthToken) { $Params.Add('AuthToken',$AuthToken) } If ($TenantName) { $Params.Add('Tenant',$TenantName) } $TotalGroups = Get-MSAzureGroup @Params -TotalCount $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers -Body $Body $JSON.value $PageSize = $JSON.value.count $Message = "Getting Azure AD groups for tenant $TenantName." If ($HideProgressBar) { Write-Host $Message } $GroupCount = 0 $Stopwatch = [System.Diagnostics.Stopwatch]::StartNew() While (($JSON.'@odata.nextLink') -And ($PageSize -lt $ResultSize)) { $NextURI = $JSON.'@odata.nextLink' $JSON = Invoke-RestMethod -Method GET -uri $NextURI -Headers $Headers $JSON.value $PageSize += $JSON.value.count If ($Stopwatch.Elapsed.TotalSeconds -ge $ProgressUpdateFreq) { $Percentage = ($PageSize / $TotalGroups.GroupCount) * 100 If ($HideProgressBar) { $Percentage = [math]::Round($Percentage,1) Write-Host "Retrieving $Percentage`% of $($TotalGroups.GroupCount) users on tenant $TenantName..." } Else { Write-Progress -Activity $Message -PercentComplete $Percentage -Status 'Retrieving...' } $Stopwatch.Reset() $Stopwatch.Start() } } } Clear-Variable -Name AuthToken } Catch { Write-Error "Could not get group data." Write-Error $_ Clear-Variable -Name AuthToken } } } } Function Get-MSAzureGroupMembers { <# .SYNOPSIS Return a list of group members from Azure AD. .DESCRIPTION Return a list of group members from Azure AD. .PARAMETER ID The GUID of the group to return membership info .PARAMETER Transitive Show members where the members are members of a group that is a member of another group .PARAMETER Properties A comma-delimited list of properties to return in the results Format as per https://docs.microsoft.com/en-us/graph/query-parameters?view=graph-rest-1.0#select-parameter Available properties can be found at https://docs.microsoft.com/en-us/graph/api/resources/group?view=graph-rest-1.0#properties .PARAMETER Filter Add filter parameters as per https://docs.microsoft.com/en-us/graph/query-parameters?context=graph%2Fapi%2F1.0&view=graph-rest-1.0#filter-parameter Available properties can be found at https://docs.microsoft.com/en-us/graph/api/resources/group?view=graph-rest-1.0#properties .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER AuthToken The authorization token used for this request. Normally obtained via Get-MSGraphAccessToken .PARAMETER ResultSize The number of results to return. Defaults to 10000. .EXAMPLE Get-MSAzureGroup -Filter "startsWith(displayName,'Global-Sales')" | Get-MSAzureGroupMembers -TotalOnly Returns all MS Azure groups whose display names start with Global-Sales .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$ID, [Parameter(Mandatory=$False)] [switch]$Transitive, [Parameter(Mandatory=$False)] [string]$Properties, [Parameter(Mandatory=$False)] [string]$Filter, [Parameter(Mandatory=$False)] [switch]$TotalCount, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$AuthToken, [Parameter(Mandatory=$False)] [ValidateRange(1,500000)] [int]$ResultSize = 10000 ) Begin { If ($Transitive) { $MemberScope = 'transitiveMembers' } Else { $MemberScope = 'members' } Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If (!$AuthToken) { $AuthToken = Get-NectarMSTeamsSubscription -TenantName $TenantName | Get-MSGraphAccessToken } } Catch { Write-Error "Could not obtain authorization token." Get-JSONErrorStream -JSONResponse $_ } } Process { $Body = @{} $Params = @{} If ($AuthToken) { Try { $Headers = @{ Authorization = "Bearer $AuthToken" } If ($Properties) { $Body.Add('$select',$Properties) $Params.Add('Properties',$Properties) } If ($Filter) { $Body.Add('$filter',$Filter) $Params.Add('Filter',$Filter) } If ($TotalCount) { $Body.Add('ConsistencyLevel','eventual') $URI = "https://graph.microsoft.com/v1.0/groups/$ID/$MemberScope/`$count" $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers -Body $Body $MemberCount = New-Object PsObject $MemberCount | Add-Member -NotePropertyName 'MemberCount' -NotePropertyValue $JSON If ($TenantName) { $MemberCount | Add-Member -NotePropertyName 'TenantName' -NotePropertyValue $TenantName } Clear-Variable -Name AuthToken Return $MemberCount } Else { $URI = "https://graph.microsoft.com/v1.0/groups/$ID/$MemberScope" Write-Verbose $URI $Params = @{} If ($AuthToken) { $Params.Add('AuthToken',$AuthToken) } If ($TenantName) { $Params.Add('Tenant',$TenantName) } $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers -Body $Body $JSON.value $PageSize = $JSON.value.count While (($JSON.'@odata.nextLink') -And ($PageSize -lt $ResultSize)) { $NextURI = $JSON.'@odata.nextLink' $JSON = Invoke-RestMethod -Method GET -uri $NextURI -Headers $Headers $JSON.value $PageSize += $JSON.value.count } } Clear-Variable -Name AuthToken } Catch { Write-Error "Could not get group member data." Write-Error $_ Clear-Variable -Name AuthToken } } } } Function Get-MSTeamsServiceStatus { <# .SYNOPSIS Return the current MS Teams service status from Office 365 .DESCRIPTION Return the current MS Teams service status from Office 365. Requires Graph API permission ServiceHealth.Read.All .PARAMETER CallRecordID The MS call record ID .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-MSTeamsServiceStatus .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) Begin { Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If (!$AuthToken) { $AuthToken = Get-NectarMSTeamsSubscription -TenantName $TenantName | Get-MSGraphAccessToken } } Catch { Write-Error "Could not obtain authorization token." Get-JSONErrorStream -JSONResponse $_ } } Process { If ($AuthToken) { Try { $Headers = @{ Authorization = "Bearer $AuthToken" } $URI = "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews/Microsoft Teams" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers Clear-Variable -Name AuthToken Return $JSON } Catch { Write-Error $_.Exception.Response.StatusDescription Clear-Variable -Name AuthToken } } } } Function Get-MSTeamsServiceMessage { <# .SYNOPSIS Return any current service status messages regarding MS Teams cloud issues .DESCRIPTION Return any current service status messages regarding MS Teams cloud issues. Requires Graph API permission ServiceHealth.Read.All .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-MSTeamsServiceMessage .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [ValidateSet('advisory','incident','unknownFutureValue', IgnoreCase=$True)] [string]$Classification, [Parameter(Mandatory=$False)] [ValidateSet('serviceOperational','investigating','restoringService','verifyingService','serviceRestored','postIncidentReviewPublished','serviceDegradation','serviceInterruption','extendedRecovery','falsePositive','investigationSuspended','resolved','mitigatedExternal','mitigated','resolvedExternal','confirmed','reported','unknownFutureValue', IgnoreCase=$True)] [string]$Status, [Parameter(Mandatory=$False)] [switch]$Current ) Begin { Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If (!$AuthToken) { $AuthToken = Get-NectarMSTeamsSubscription -TenantName $TenantName | Get-MSGraphAccessToken } } Catch { Write-Error "Could not obtain authorization token." Get-JSONErrorStream -JSONResponse $_ } } Process { If ($AuthToken) { Try { $Headers = @{ Authorization = "Bearer $AuthToken" } $URI = "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/issues?`$filter=Service eq 'Microsoft Teams'" If ($Current) { $URI = $URI + " and isResolved eq false" } If ($Classification) { $URI = $URI + " and Classification eq '$Classification'" } If ($Status) { $URI = $URI + " and Status eq '$Status'" } Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers Clear-Variable -Name AuthToken Return $JSON.value } Catch { Write-Error $_.Exception.Response.StatusDescription Clear-Variable -Name AuthToken } } } } Function Get-MSTeamsLicenseStatus { <# .SYNOPSIS Return the total number of SKUs for a company that have Teams in it .DESCRIPTION Return the total number of SKUs for a company that have Teams in it. Requires Organization.Read.All permission on graph.microsoft.com .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER AuthToken The authorization token used for this request. Normally obtained via Get-MSGraphAccessToken .EXAMPLE Get-MSTeamsLicenseStatus .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$AuthToken ) Process { Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If (!$AuthToken) { $AuthToken = Get-NectarMSTeamsSubscription -TenantName $TenantName | Get-MSGraphAccessToken } } Catch { Write-Error "Could not obtain authorization token for tenant $TenantName." Get-JSONErrorStream -JSONResponse $_ } If ($AuthToken) { Try { $Headers = @{ Authorization = "Bearer $AuthToken" } $URI = "https://graph.microsoft.com/v1.0/subscribedSkus" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers $TotalSKUs = $JSON.value $LicenseCount = $TotalSKUs | Where-Object {$_.servicePlans.ServicePlanName -eq 'TEAMS1'} | Select-Object skuPartNumber, ConsumedUnits If ($TotalOnly) { $LicenseCount = $LicenseCount | Measure-Object -Property consumedUnits -Sum | Select-Object Sum } If ($TenantName) { $LicenseCount | Add-Member -NotePropertyName 'TenantName' -NotePropertyValue $TenantName } Clear-Variable -Name AuthToken Return $LicenseCount } Catch { Write-Error "Could not get license data for tenant $TenantName." Write-Error $_ Clear-Variable -Name AuthToken } } } } Function Get-MSTeamsUserLicenseCount { <# .SYNOPSIS Return the total number of Teams licensed users for a given tenant. .DESCRIPTION Return the total number of Teams licensed users for a given tenant. Excludes disabled accounts. .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER HideProgressBar Don't show the progress bar. Cleans up logs when running on Docker. .PARAMETER ProgressUpdateFreq How frequently to show progress updates, in seconds. Defaults to 1 second .PARAMETER UserList Use a previously obtained list of Azure user IDs. If this isn't specified, then a list will be downloaded using Get-MSAzureUser .PARAMETER MSClientID The MS client ID for the application granted access to Azure AD. .PARAMETER MSClientSecret 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. .PARAMETER AuthToken The authorization token used for this request. Normally obtained via Get-MSGraphAccessToken .PARAMETER TestMode Sets the mode to run the tests under. Choose from Fast, Basic or Extended. Defaults to Fast mode Extended mode checks user O365 Service Plans for both Teams and Enterprise Voice functionality. Most accurate, but slower because it requires pulling down about 10x the data. The process ends up taking about twice as long as Basic mode. Basic mode looks at the assigned license SKUs, and also checks to see if TEAMS1 has been disabled. Faster because it requires about 10x less data than Extended mode, but can't obtain information about Enterprise Voice functionality. It's twice as fast as Extended mode, but only returns a total Teams count. It doesn't return the Enterprise Voice users. Fast mode just returns a total count of assigned licenses. It doesn't check for users with a license but has Teams explicitly disabled. This may over-count, but historically it amounts to a miniscule fraction of total users. .PARAMETER ExportCSV Exports the list of users and the assigned Teams SKU to a CSV file. Requires Basic or Extended test mode .EXAMPLE Get-MSTeamsUserLicenseCount -TenantName contoso .NOTES Version 1.3 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [switch]$HideProgressBar, [Parameter(Mandatory=$False)] [ValidateRange(1,99999)] [int]$ProgressUpdateFreq = 1, [Parameter(Mandatory=$False)] $UserList, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSClientID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSClientSecret, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSTenantID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$AuthToken, [Parameter(Mandatory=$False)] [ValidateSet('Fast','Basic','Extended', IgnoreCase=$True)] [string]$TestMode = 'Fast', [Parameter(Mandatory=$False)] [string]$ExportCSV ) Begin { $TeamsSKUList = @( [PSCustomObject]@{SKU = 'e4654015-5daf-4a48-9b37-4f309dddd88b'; Name = 'Microsoft Teams Enterprise'; ShortCode = 'TEAMS_ADVCOMMS' } [PSCustomObject]@{SKU = '4b590615-0888-425a-a965-b3bf7789848d'; Name = 'Microsoft 365 Education A3 for Faculty'; ShortCode = 'M365EDU_A3_FACULTY' } [PSCustomObject]@{SKU = '7cfd9a2b-e110-4c39-bf20-c6a3f36a3121'; Name = 'Microsoft 365 Education A3 for Students'; ShortCode = 'M365EDU_A3_STUDENT' } [PSCustomObject]@{SKU = '18250162-5d87-4436-a834-d795c15c80f3'; Name = 'Microsoft 365 Education A3 for Students Use Benefit'; ShortCode = 'M365EDU_A3_STUUSEBNFT' } [PSCustomObject]@{SKU = '1aa94593-ca12-4254-a738-81a5972958e8'; Name = 'Microsoft 365 A3 - Unattended License for students use benefit'; ShortCode = 'M365EDU_A3_STUUSEBNFT_RPA1' } [PSCustomObject]@{SKU = 'e97c048c-37a4-45fb-ab50-922fbf07a370'; Name = 'Microsoft 365 Education A5 for Faculty'; ShortCode = 'M365EDU_A5_FACULTY' } [PSCustomObject]@{SKU = '46c119d4-0379-4a9d-85e4-97c66d3f909e'; Name = 'Microsoft 365 Education A5 for Students'; ShortCode = 'M365EDU_A5_STUDENT' } [PSCustomObject]@{SKU = '31d57bc7-3a05-4867-ab53-97a17835a411'; Name = 'Microsoft 365 A5 for students use benefit'; ShortCode = 'M365EDU_A5_STUUSEBNFT' } [PSCustomObject]@{SKU = '81441ae1-0b31-4185-a6c0-32b6b84d419f'; Name = 'Microsoft 365 A5 without Audio Conferencing for students use benefit'; ShortCode = 'M365EDU_A5_NOPSTNCONF_STUUSEBNFT' } [PSCustomObject]@{SKU = '3b555118-da6a-4418-894f-7df1e2096870'; Name = 'Microsoft 365 Business Basic'; ShortCode = 'O365_BUSINESS_ESSENTIALS' } [PSCustomObject]@{SKU = 'dab7782a-93b1-4074-8bb1-0e61318bea0b'; Name = 'Microsoft 365 Business Basic'; ShortCode = 'SMB_BUSINESS_ESSENTIALS' } [PSCustomObject]@{SKU = 'f245ecc8-75af-4f8e-b61f-27d8114de5f3'; Name = 'Microsoft 365 Business Standard'; ShortCode = 'O365_BUSINESS_PREMIUM' } [PSCustomObject]@{SKU = 'ac5cef5d-921b-4f97-9ef3-c99076e5470f'; Name = 'Microsoft 365 Business Standard'; ShortCode = 'SMB_BUSINESS_PREMIUM' } [PSCustomObject]@{SKU = 'cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46'; Name = 'Microsoft 365 Business Premium'; ShortCode = 'SPB' } [PSCustomObject]@{SKU = '05e9a617-0261-4cee-bb44-138d3ef5d965'; Name = 'Microsoft 365 E3'; ShortCode = 'SPB_E3' } [PSCustomObject]@{SKU = 'c2ac2ee4-9bb1-47e4-8541-d689c7e83371'; Name = 'Microsoft 365 E3 - Unattended License'; ShortCode = 'SPE_E3_RPA1' } [PSCustomObject]@{SKU = '0c21030a-7e60-4ec7-9a0f-0042e0e0211a'; Name = 'Microsoft 365 E3 (500 seats min)_HUB'; ShortCode = 'Microsoft_365_E3' } [PSCustomObject]@{SKU = '06ebc4ee-1bb5-47dd-8120-11324bc54e06'; Name = 'Microsoft 365 E5'; ShortCode = 'SPB_E5' } [PSCustomObject]@{SKU = 'a91fc4e0-65e5-4266-aa76-4037509c1626'; Name = 'Microsoft 365 E5 with Calling Minutes'; ShortCode = 'SPE_E5_CALLINGMINUTES' } [PSCustomObject]@{SKU = 'c42b9cae-ea4f-4ab7-9717-81576235ccac'; Name = 'Microsoft 365 E5 Developer (without Windows and Audio Conferencing)'; ShortCode = 'DEVELOPERPACK_E5' } [PSCustomObject]@{SKU = 'cd2925a3-5076-4233-8931-638a8c94f773'; Name = 'Microsoft 365 E5 without Audio Conferencing'; ShortCode = 'SPE_E5_NOPSTNCONF' } [PSCustomObject]@{SKU = '44575883-256e-4a79-9da4-ebe9acabe2b2'; Name = 'Microsoft 365 F1'; ShortCode = 'M365_F1' } [PSCustomObject]@{SKU = '50f60901-3181-4b75-8a2c-4c8e4c1d5a72'; Name = 'Microsoft 365 F1'; ShortCode = 'M365_F1_COMM' } [PSCustomObject]@{SKU = '66b55226-6b4f-492c-910c-a3b7a3c9d993'; Name = 'Microsoft 365 F3'; ShortCode = 'SPE_F1' } [PSCustomObject]@{SKU = '2a914830-d700-444a-b73c-e3f31980d833'; Name = 'Microsoft 365 F3 GCC'; ShortCode = 'M365_F1_GOV' } [PSCustomObject]@{SKU = 'e823ca47-49c4-46b3-b38d-ca11d5abe3d2'; Name = 'Microsoft 365 GCC G3'; ShortCode = 'M365_G3_GCC' } [PSCustomObject]@{SKU = 'e2be619b-b125-455f-8660-fb503e431a5d'; Name = 'Microsoft 365 GCC G5'; ShortCode = 'M365_G5_GCC' } [PSCustomObject]@{SKU = '94763226-9b3c-4e75-a931-5c89701abe66'; Name = 'Office 365 A1 for faculty'; ShortCode = 'STANDARDWOFFPACK_FACULTY' } [PSCustomObject]@{SKU = '78e66a63-337a-4a9a-8959-41c6654dfb56'; Name = 'Office 365 A1 Plus for faculty'; ShortCode = 'STANDARDWOFFPACK_IW_FACULTY' } [PSCustomObject]@{SKU = '314c4481-f395-4525-be8b-2ec4bb1e9d91'; Name = 'Office 365 A1 for students'; ShortCode = 'STANDARDWOFFPACK_STUDENT' } [PSCustomObject]@{SKU = 'e82ae690-a2d5-4d76-8d30-7c6e01e6022e'; Name = 'Office 365 A1 Plus for students'; ShortCode = 'STANDARDWOFFPACK_IW_STUDENT' } [PSCustomObject]@{SKU = 'e578b273-6db4-4691-bba0-8d691f4da603'; Name = 'Office 365 A3 for faculty'; ShortCode = 'ENTERPRISEPACKPLUS_FACULTY' } [PSCustomObject]@{SKU = '98b6e773-24d4-4c0d-a968-6e787a1f8204'; Name = 'Office 365 A3 for students'; ShortCode = 'ENTERPRISEPACKPLUS_STUDENT' } [PSCustomObject]@{SKU = 'a4585165-0533-458a-97e3-c400570268c4'; Name = 'Office 365 A5 for Faculty'; ShortCode = 'ENTERPRISEPREMIUM_FACULTY' } [PSCustomObject]@{SKU = 'ee656612-49fa-43e5-b67e-cb1fdf7699df'; Name = 'Office 365 A5 for Students'; ShortCode = 'ENTERPRISEPREMIUM_STUDENT' } [PSCustomObject]@{SKU = '18181a46-0d4e-45cd-891e-60aabd171b4e'; Name = 'Office 365 E1'; ShortCode = 'STANDARDPACK' } [PSCustomObject]@{SKU = '6634e0ce-1a9f-428c-a498-f84ec7b8aa2e'; Name = 'Office 365 E2'; ShortCode = 'STANDARDWOFFPACK' } [PSCustomObject]@{SKU = '6fd2c87f-b296-42f0-b197-1e91e994b900'; Name = 'Office 365 E3'; ShortCode = 'ENTERPRISEPACK' } [PSCustomObject]@{SKU = '189a915c-fe4f-4ffa-bde4-85b9628d07a0'; Name = 'Office 365 Developer'; ShortCode = 'DEVELOPERPACK' } [PSCustomObject]@{SKU = '1392051d-0cb9-4b7a-88d5-621fee5e8711'; Name = 'Office 365 E4'; ShortCode = 'ENTERPRISEWITHSCAL' } [PSCustomObject]@{SKU = 'c7df2760-2c81-4ef7-b578-5b5392b571df'; Name = 'Office 365 E5'; ShortCode = 'ENTERPRISEPREMIUM' } [PSCustomObject]@{SKU = '26d45bd9-adf1-46cd-a9e1-51e9a5524128'; Name = 'Office 365 E5 without Audio Conferencing'; ShortCode = 'ENTERPRISEPREMIUM_NOPSTNCONF' } [PSCustomObject]@{SKU = '4b585984-651b-448a-9e53-3b10f069cf7f'; Name = 'Office 365 F1/3'; ShortCode = 'DESKLESSPACK' } [PSCustomObject]@{SKU = '3f4babde-90ec-47c6-995d-d223749065d1'; Name = 'Office 365 G1 GCC'; ShortCode = 'STANDARDPACK_GOV' } [PSCustomObject]@{SKU = '535a3a29-c5f0-42fe-8215-d3b9e1f38c4a'; Name = 'Office 365 G3 GCC'; ShortCode = 'ENTERPRISEPACK_GOV' } [PSCustomObject]@{SKU = '8900a2c0-edba-4079-bdf3-b276e293b6a8'; Name = 'Office 365 G5 GCC'; ShortCode = 'ENTERPRISEPREMIUM_GOV' } [PSCustomObject]@{SKU = 'b75fa366-5b88-4b3c-9faa-86f44849c1e5'; Name = 'Office 365 F1 GCC'; ShortCode = '' } [PSCustomObject]@{SKU = '6af4b3d6-14bb-4a2a-960c-6c902aad34f3'; Name = 'Microsoft Teams Rooms Basic'; ShortCode = 'Microsoft_Teams_Rooms_Basic' } [PSCustomObject]@{SKU = 'a4e376bd-c61e-4618-9901-3fc0cb1b88bb'; Name = 'Microsoft Teams Rooms Basic for EDU'; ShortCode = 'Microsoft_Teams_Rooms_Basic_FAC' } [PSCustomObject]@{SKU = '50509a35-f0bd-4c5e-89ac-22f0e16a00f8'; Name = 'Microsoft Teams Rooms Basic without Audio Conferencing'; ShortCode = 'Microsoft_Teams_Rooms_Basic_without_Audio_Conferencing' } [PSCustomObject]@{SKU = '4cde982a-ede4-4409-9ae6-b003453c8ea6'; Name = 'Microsoft Teams Rooms Pro'; ShortCode = 'Microsoft_Teams_Rooms_Pro' } [PSCustomObject]@{SKU = 'c25e2b36-e161-4946-bef2-69239729f690'; Name = 'Microsoft Teams Rooms Pro for EDU'; ShortCode = 'Microsoft_Teams_Rooms_Pro_FAC' } [PSCustomObject]@{SKU = '31ecb341-2a17-483e-9140-c473006d1e1a'; Name = 'Microsoft Teams Rooms Pro for GCC'; ShortCode = 'Microsoft_Teams_Rooms_Pro_GCC' } [PSCustomObject]@{SKU = '21943e3a-2429-4f83-84c1-02735cd49e78'; Name = 'Microsoft Teams Rooms Pro without Audio Conferencing'; ShortCode = 'Microsoft_Teams_Rooms_Pro_without_Audio_Conferencing' } [PSCustomObject]@{SKU = '6070a4c8-34c6-4937-8dfb-39bbc6397a60'; Name = 'Microsoft Teams Rooms Standard'; ShortCode = 'MEETING_ROOM' } [PSCustomObject]@{SKU = '61bec411-e46a-4dab-8f46-8b58ec845ffe'; Name = 'Microsoft Teams Rooms Standard without Audio Conferencing'; ShortCode = 'MEETING_ROOM_NOAUDIOCONF' } [PSCustomObject]@{SKU = '9571e9ac-2741-4b63-95fd-a79696f0d0ac'; Name = 'Microsoft Teams Rooms Standard for GCC'; ShortCode = 'MEETING_ROOM_GOV' } [PSCustomObject]@{SKU = 'b4348f75-a776-4061-ac6c-36b9016b01d1'; Name = 'Microsoft Teams Rooms Standard for GCC without Audio Conferencing'; ShortCode = 'MEETING_ROOM_GOV_NOAUDIOCONF' } [PSCustomObject]@{SKU = '4fb214cb-a430-4a91-9c91-4976763aa78f'; Name = 'Teams Rooms Premium'; ShortCode = 'MTR_PREM' } [PSCustomObject]@{SKU = '710779e8-3d4a-4c88-adb9-386c958d1fdf'; Name = 'Teams Exploratory'; ShortCode = 'TEAMS_EXPLORATORY' } [PSCustomObject]@{SKU = '29a2f828-8f39-4837-b8ff-c957e86abe3c'; Name = 'Microsoft Teams Commercial Cloud'; ShortCode = 'TEAMS_COMMERCIAL_TRIAL' } [PSCustomObject]@{SKU = '74fbf1bb-47c6-4796-9623-77dc7371723b'; Name = 'Microsoft Teams Trial'; ShortCode = 'MS_TEAMS_IW' } [PSCustomObject]@{SKU = '295a8eb0-f78d-45c7-8b5b-1eed5ed02dff'; Name = 'Microsoft Teams Shared Devices'; ShortCode = 'MCOCAP' } [PSCustomObject]@{SKU = 'b1511558-69bd-4e1b-8270-59ca96dba0f3'; Name = 'Microsoft Teams Shared Devices for GCC'; ShortCode = 'MCOCAP_GOV' } [PSCustomObject]@{SKU = 'ea126fc5-a19e-42e2-a731-da9d437bffcf'; Name = 'Dynamics 365 Plan 1 Enterprise Edition'; ShortCode = 'DYN365_ENTERPRISE_PLAN1' } [PSCustomObject]@{SKU = '8e7a3d30-d97d-43ab-837c-d7701cef83dc'; Name = 'Dynamics 365 for Team Members Enterprise Edition'; ShortCode = 'DYN365_ENTERPRISE_TEAM_MEMBERS' } [PSCustomObject]@{SKU = '7a551360-26c4-4f61-84e6-ef715673e083'; Name = 'Dynamics 365 Remote Assist'; ShortCode = 'MICROSOFT_REMOTE_ASSIST' } [PSCustomObject]@{SKU = 'e48328a2-8e98-4484-a70f-a99f8ac9ec89'; Name = 'Dynamics 365 Remote Assist HoloLens'; ShortCode = 'MICROSOFT_REMOTE_ASSIST_HOLOLENS' } [PSCustomObject]@{SKU = '7ac9fe77-66b7-4e5e-9e46-10eed1cff547'; Name = 'Dynamics 365 Team Members_wDynamicsRetail'; ShortCode = 'DYN365_TEAM_MEMBERS' } [PSCustomObject]@{SKU = '7e74bd05-2c47-404e-829a-ba95c66fe8e5'; Name = 'Microsoft Teams EEA'; ShortCode = 'Microsoft_Teams_EEA_New' } [PSCustomObject]@{SKU = '3ab6abff-666f-4424-bfb7-f0bc274ec7bc'; Name = 'Microsoft Teams Essentials'; ShortCode = 'TEAMS_ESSENTIALS_AAD' } ) $TeamsPlanID = '57ff2da0-773e-42df-b2af-ffb7a2317929' # TEAMS1 $TeamsGOVPlanID = '304767db-7d23-49e8-a945-4a7eb65f9f28' # TEAMS_GOV $TeamsPhoneSystemID = '4828c8ec-dc2e-4779-b502-87ac9ce28ab7' # MCOEV $TeamsGOVPhoneSystemID = 'db23fce2-a974-42ef-9002-d78dd42a0f22' # MCOEV_GOV # Not currently used, but may be handy at some point $TeamsCallingPlanList = '4828c8ec-dc2e-4779-b502-87ac9ce28ab7', # MCOEV 'db23fce2-a974-42ef-9002-d78dd42a0f22', # MCOEV_GOV '4ed3ff63-69d7-4fb7-b984-5aec7f605ca8', # MCOPSTN1 '5a10155d-f5c1-411a-a8ec-e99aae125390', # MCOPSTN2 '54a152dc-90de-4996-93d2-bc47e670fc06' # MCOPSTN5 } Process { Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If ($MSTenantID -And !$AuthToken) { $AuthToken = Get-MSGraphAccessToken -MSClientID $MSClientID -MSClientSecret $MSClientSecret -MSTenantID $MSTenantID } ElseIf (!$AuthToken) { $AuthToken = Get-NectarMSTeamsSubscription -TenantName $TenantName | Get-MSGraphAccessToken } } Catch { Write-Error "Could not obtain authorization token for tenant $TenantName." Get-JSONErrorStream -JSONResponse $_ } $TeamsCount = 0 $TeamsEVCount = 0 If ($AuthToken) { If ($TestMode -eq 'Basic') { $Properties = 'id,assignedLicenses' } Else { $Properties = 'id,assignedPlans' } If ($ExportCSV) { $Properties += ',userPrincipalName,mail,displayName' [System.Collections.ArrayList]$CSVList = @() $TestMode = 'Basic' } $Params = @{ Properties = $Properties Filter = 'accountEnabled eq true and assignedLicenses/$count ne 0' AuthToken = $AuthToken ResultSize = 300000 } If ($TenantName) { $Params.Add('TenantName',$TenantName) } If ($HideProgressBar) { $Params.Add('HideProgressBar', $HideProgressBar) } If ($ProgressUpdateFreq) { $Params.Add('ProgressUpdateFreq', $ProgressUpdateFreq) } Switch ($TestMode) { # Fastest approach. Just counts the licenses directly. Preferred for when we only need a total count. # May slightly increase counts compared to other methods because there's no way that I know to exclude users where Teams is explicitly disabled # We're talking about at most a tiny fraction of total users. Not big enough to worry about. 'Fast' { # Build the filter. Its simply appending each SKU as an 'or' option on the filter. [String]$Filter = '' ForEach ($SKU In $TeamsSKUList) { $Filter += "assignedLicenses/any(s:s/skuId eq $($SKU.SKU)) or " } $Filter = $Filter.TrimEnd(' or ') $Filter = "accountEnabled eq true and ($Filter)" $TeamsCount = (Get-MSAzureUser -TenantName $TenantName -Filter $Filter -AuthToken $AuthToken -TotalCount).UserCount } # Pulls the user list and parses it for license counts 'Basic'{ If (!$UserList) { $UserList = Get-MSAzureUser @Params } # $UserList = $UserList | Where-Object {$_.assignedLicenses -ne ''} $TotalUsers = $UserList.Count $UserCount = 0 $LastCount = 0 $Stopwatch = [System.Diagnostics.Stopwatch]::StartNew() ForEach ($User in $UserList) { $LicenseSKUs = $User.assignedLicenses $TeamsSKUs = $LicenseSKUs | Where-Object {$_.skuId -in $TeamsSKUList.SKU} ForEach ($SKU in $TeamsSKUs) { If (!($SKU | Where-Object {$_.disabledPlans -eq $TeamsPlanID -Or $_.disabledPlans -eq $TeamsGOVPlanID})) { #Teams isn't disabled for this user. Increase the Teams user count and break out If ($ExportCSV) { $Item = [PSCustomObject][Ordered]@{ DisplayName = $User.DisplayName UserPrincipalName = $User.UserPrincipalName Email = $User.Mail TeamsLicense = ($TeamsSKUList | Where-Object {$_.SKU -eq $SKU.SkuID} | Select-Object Name).Name } $CSVList += $Item } $TeamsCount++ Break } } If ($Stopwatch.Elapsed.TotalSeconds -ge $ProgressUpdateFreq) { $Percentage = $UserCount / $TotalUsers $Rate = ($UserCount - $LastCount) / $ProgressUpdateFreq $LastCount = $UserCount $Message = "Checking Teams license status for user {0} of {1} ({4} Teams users so far) on tenant {2} at a rate of {3}/sec." -f $UserCount, $TotalUsers, $TenantName, $Rate, $TeamsCount If ($HideProgressBar) { Write-Host $Message } Else { Write-Progress -Activity $Message -PercentComplete ($Percentage * 100) -Status 'Calculating...' } $Stopwatch.Reset() $Stopwatch.Start() } $UserCount++ } If ($ExportCSV) { $CSVList | Export-CSV $ExportCSV -NoTypeInformation -Force } } 'Extended' { If (!$UserList) { $UserList = Get-MSAzureUser @Params } $UserList = $UserList | Where-Object {$_.assignedPlans -ne ''} $TeamsCount = ($UserList | Select-Object -ExpandProperty assignedPlans | Where-Object {$_.capabilityStatus -eq 'Enabled' -and ($_.servicePlanId -eq $TeamsPlanID -Or $_.servicePlanId -eq $TeamsGOVPlanID)}).Count $TeamsEVCount = ($UserList | Select-Object -ExpandProperty assignedPlans | Where-Object {$_.capabilityStatus -eq 'Enabled' -and ($_.servicePlanId -eq $TeamsPhoneSystemID -Or $_.servicePlanId -eq $TeamsGOVPhoneSystemID)}).Count If ($ExportCSV) { $UserList | Select-Object id,userPrincipalName,mail,displayName -ExpandProperty assignedPlans | Where-Object {$_.capabilityStatus -eq 'Enabled' -and ($_.servicePlanId -eq $TeamsPlanID -Or $_.servicePlanId -eq $TeamsGOVPlanID)} | Export-Csv $ExportCSV -NoTypeInformation -Force } } } $TeamsLicense = New-Object PsObject $TeamsLicense | Add-Member -NotePropertyName 'TeamsCount' -NotePropertyValue $TeamsCount If ($TestMode -eq 'Extended') { $TeamsLicense | Add-Member -NotePropertyName 'EVCount' -NotePropertyValue $TeamsEVCount } If ($TenantName) { $TeamsLicense | Add-Member -NotePropertyName 'TenantName' -NotePropertyValue $TenantName } Clear-Variable -Name AuthToken Return $TeamsLicense } } } Function Get-MSTeamsDevice { <# .SYNOPSIS Return a list of MS Teams Devices .DESCRIPTION Return a list of MS Teams Devices .PARAMETER ID The GUID of a specific device to query .PARAMETER DeviceType Return all devices that match the given device type. Choose from 'unknown', 'ipPhone', 'TeamsRoom', 'SurfaceHub', 'CollaborationBar', 'TeamsDisplay', 'TouchConsole', 'LowCostPhone', 'TeamsPanel', 'SIP', 'UnknownFutureValue' .PARAMETER CurrentUser Show devices that are currently logged in by the given user. Use either the user's GUID or UPN .PARAMETER HardwareUniqueID Show device that matches the given unique hardware ID. Different from the GUID formatted ID. .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER MSClientID The MS client ID for the application granted access to Azure AD. .PARAMETER MSClientSecret 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. .PARAMETER AuthToken The authorization token used for this request. Normally obtained via Get-MSGraphAccessToken .PARAMETER ResultSize The number of results to return. Defaults to 1000. .EXAMPLE Get-MSTeamsDRCalls -MSClientID 41a228ad-db6c-4e4e-4184-6d8a1175a35f -MSClientSecret 43Rk5Xl3K349w-pFf0i_Rt45Qd~ArqkE32. -MSTenantID 17e1e614-8119-48ab-8ba1-6ff1d94a6930 Returns all Teams DR calls for the specified tenant/clientID/secret combination .EXAMPLE Get-MSTeamsDRCalls -AuthToken $AuthToken Returns all Teams DR calls using a previously obtained authtoken .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$ID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('unknown', 'ipPhone', 'TeamsRoom', 'SurfaceHub', 'CollaborationBar', 'TeamsDisplay', 'TouchConsole', 'LowCostPhone', 'TeamsPanel', 'SIP', 'UnknownFutureValue', IgnoreCase=$True)] [string]$DeviceType, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$CurrentUser, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$HardwareUniqueID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSClientID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSClientSecret, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSTenantID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$AuthToken, [Parameter(Mandatory=$False)] [ValidateRange(1,500000)] [int]$ResultSize = 10000 ) Process { Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If ($MSTenantID) { $AuthToken = Get-MSGraphAccessToken -MSClientID $MSClientID -MSClientSecret $MSClientSecret -MSTenantID $MSTenantID } ElseIf (!$AuthToken) { $AuthToken = Get-NectarMSTeamsSubscription -TenantName $TenantName | Get-MSGraphAccessToken } } Catch { Write-Error "Could not obtain authorization token." Get-JSONErrorStream -JSONResponse $_ } If ($AuthToken) { $Headers = @{ Authorization = "Bearer $AuthToken" } If ($ID) { $URI = "https://graph.microsoft.com/beta/teamwork/devices/$ID" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers Return $JSON } If ($DeviceType) { $Filter | ForEach-Object { $Filter += ($(if($Filter){" and "}) + "deviceType eq '$DeviceType'") } } If ($UniqueID) { $Filter | ForEach-Object { $Filter += ($(if($Filter){" and "}) + "hardwareDetail/uniqueId eq '$UniqueID'") } } If ($CurrentUser) { If ([guid]::TryParse($CurrentUser, $([ref][guid]::Empty))) { # Check if the CurrentUser param is already set to a GUID $Filter | ForEach-Object { $Filter += ($(if($Filter){" and "}) + "currentUser/id eq '$CurrentUser'") } } else { # Attempt to get the GUID of the user if we entered a username/email address $UserGUID = (Get-MSAzureUser -UPN $CurrentUser).id If ($UserGUID -ne '') { $Filter | ForEach-Object { $Filter += ($(if($Filter){" and "}) + "currentUser/id eq '$UserGUID'") } } } } $URI = "https://graph.microsoft.com/beta/teamwork/devices" If ($Filter) { $URI = $URI + "?`$filter=$Filter" } Write-Verbose $URI $Params = @{} If ($AuthToken) { $Params.Add('AuthToken',$AuthToken) } If ($TenantName) { $Params.Add('Tenant',$TenantName) } $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers -Body $Body $JSON.value $PageSize = $JSON.value.count While (($JSON.'@odata.nextLink') -And ($PageSize -lt $ResultSize)) { $NextURI = $JSON.'@odata.nextLink' $JSON = Invoke-RestMethod -Method GET -uri $NextURI -Headers $Headers $JSON.value $PageSize += $JSON.value.count } Clear-Variable -Name AuthToken } } } Function Get-MSTeamsDeviceActivity { <# .SYNOPSIS Returns activity details for a Microsoft Teams-enabled device, including the active peripheral devices attached to the device. .DESCRIPTION Returns activity details for a Microsoft Teams-enabled device, including the active peripheral devices attached to the device. .PARAMETER ID The GUID of a specific device to query .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER MSClientID The MS client ID for the application granted access to Azure AD. .PARAMETER MSClientSecret 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. .PARAMETER AuthToken The authorization token used for this request. Normally obtained via Get-MSGraphAccessToken .PARAMETER ResultSize The number of results to return. Defaults to 1000. .EXAMPLE Get-MSTeamsDRCalls -MSClientID 41a228ad-db6c-4e4e-4184-6d8a1175a35f -MSClientSecret 43Rk5Xl3K349w-pFf0i_Rt45Qd~ArqkE32. -MSTenantID 17e1e614-8119-48ab-8ba1-6ff1d94a6930 Returns all Teams DR calls for the specified tenant/clientID/secret combination .EXAMPLE Get-MSTeamsDRCalls -AuthToken $AuthToken Returns all Teams DR calls using a previously obtained authtoken .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [ValidateScript ({ Try { [System.Guid]::Parse($_) | Out-Null; $True } Catch { Throw 'Invalid GUID format.' } })] [string]$ID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSClientID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSClientSecret, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSTenantID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$AuthToken, [Parameter(Mandatory=$False)] [ValidateRange(1,500000)] [int]$ResultSize ) Process { Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If ($MSTenantID) { $AuthToken = Get-MSGraphAccessToken -MSClientID $MSClientID -MSClientSecret $MSClientSecret -MSTenantID $MSTenantID } ElseIf (!$AuthToken) { $AuthToken = Get-NectarMSTeamsSubscription -TenantName $TenantName | Get-MSGraphAccessToken } } Catch { Write-Error "Could not obtain authorization token." Get-JSONErrorStream -JSONResponse $_ } If ($AuthToken) { $Headers = @{ Authorization = "Bearer $AuthToken" } $URI = "https://graph.microsoft.com/beta/teamwork/devices/$ID/activity" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers Return $JSON } } } Function Get-MSTeamsDeviceConfiguration { <# .SYNOPSIS Returns configuration details for a Microsoft Teams-enabled device. .DESCRIPTION Returns configuration details for a Microsoft Teams-enabled device. .PARAMETER ID The GUID of a specific device to query .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER MSClientID The MS client ID for the application granted access to Azure AD. .PARAMETER MSClientSecret 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. .PARAMETER AuthToken The authorization token used for this request. Normally obtained via Get-MSGraphAccessToken .PARAMETER ResultSize The number of results to return. Defaults to 1000. .EXAMPLE Get-MSTeamsDRCalls -MSClientID 41a228ad-db6c-4e4e-4184-6d8a1175a35f -MSClientSecret 43Rk5Xl3K349w-pFf0i_Rt45Qd~ArqkE32. -MSTenantID 17e1e614-8119-48ab-8ba1-6ff1d94a6930 Returns all Teams DR calls for the specified tenant/clientID/secret combination .EXAMPLE Get-MSTeamsDRCalls -AuthToken $AuthToken Returns all Teams DR calls using a previously obtained authtoken .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [ValidateScript ({ Try { [System.Guid]::Parse($_) | Out-Null; $True } Catch { Throw 'Invalid GUID format.' } })] [string]$ID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSClientID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSClientSecret, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSTenantID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$AuthToken, [Parameter(Mandatory=$False)] [ValidateRange(1,500000)] [int]$ResultSize ) Process { Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If ($MSTenantID) { $AuthToken = Get-MSGraphAccessToken -MSClientID $MSClientID -MSClientSecret $MSClientSecret -MSTenantID $MSTenantID } ElseIf (!$AuthToken) { $AuthToken = Get-NectarMSTeamsSubscription -TenantName $TenantName | Get-MSGraphAccessToken } } Catch { Write-Error "Could not obtain authorization token." Get-JSONErrorStream -JSONResponse $_ } If ($AuthToken) { $Headers = @{ Authorization = "Bearer $AuthToken" } $URI = "https://graph.microsoft.com/beta/teamwork/devices/$ID/configuration" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers Return $JSON } } } Function Get-MSTeamsDeviceHealth { <# .SYNOPSIS Returns health details for a Microsoft Teams-enabled device. .DESCRIPTION Returns health details for a Microsoft Teams-enabled device. .PARAMETER ID The GUID of a specific device to query .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER MSClientID The MS client ID for the application granted access to Azure AD. .PARAMETER MSClientSecret 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. .PARAMETER AuthToken The authorization token used for this request. Normally obtained via Get-MSGraphAccessToken .PARAMETER ResultSize The number of results to return. Defaults to 1000. .EXAMPLE Get-MSTeamsDRCalls -MSClientID 41a228ad-db6c-4e4e-4184-6d8a1175a35f -MSClientSecret 43Rk5Xl3K349w-pFf0i_Rt45Qd~ArqkE32. -MSTenantID 17e1e614-8119-48ab-8ba1-6ff1d94a6930 Returns all Teams DR calls for the specified tenant/clientID/secret combination .EXAMPLE Get-MSTeamsDRCalls -AuthToken $AuthToken Returns all Teams DR calls using a previously obtained authtoken .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [ValidateScript ({ Try { [System.Guid]::Parse($_) | Out-Null; $True } Catch { Throw 'Invalid GUID format.' } })] [string]$ID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSClientID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSClientSecret, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSTenantID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$AuthToken ) Process { Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If ($MSTenantID) { $AuthToken = Get-MSGraphAccessToken -MSClientID $MSClientID -MSClientSecret $MSClientSecret -MSTenantID $MSTenantID } ElseIf (!$AuthToken) { $AuthToken = Get-NectarMSTeamsSubscription -TenantName $TenantName | Get-MSGraphAccessToken } } Catch { Write-Error "Could not obtain authorization token." Get-JSONErrorStream -JSONResponse $_ } If ($AuthToken) { $Headers = @{ Authorization = "Bearer $AuthToken" } $URI = "https://graph.microsoft.com/beta/teamwork/devices/$ID/health" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers Return $JSON } } } Function Get-MSTeamsDeviceOperations { <# .SYNOPSIS Returns a list of operations that are currently running on a Microsoft Teams-enabled device. .DESCRIPTION Returns a list of operations that are currently running on a Microsoft Teams-enabled device. .PARAMETER ID The GUID of a specific device to query .PARAMETER OperationID The GUID of a specific operation ID for a given device to query .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER MSClientID The MS client ID for the application granted access to Azure AD. .PARAMETER MSClientSecret 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. .PARAMETER AuthToken The authorization token used for this request. Normally obtained via Get-MSGraphAccessToken .PARAMETER ResultSize The number of results to return. Defaults to 1000. .EXAMPLE Get-MSTeamsDRCalls -MSClientID 41a228ad-db6c-4e4e-4184-6d8a1175a35f -MSClientSecret 43Rk5Xl3K349w-pFf0i_Rt45Qd~ArqkE32. -MSTenantID 17e1e614-8119-48ab-8ba1-6ff1d94a6930 Returns all Teams DR calls for the specified tenant/clientID/secret combination .EXAMPLE Get-MSTeamsDRCalls -AuthToken $AuthToken Returns all Teams DR calls using a previously obtained authtoken .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [ValidateScript ({ Try { [System.Guid]::Parse($_) | Out-Null; $True} Catch { Throw 'Invalid GUID format.' } })] [string]$ID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateScript ({ Try { [System.Guid]::Parse($_) | Out-Null; $True } Catch { Throw 'Invalid GUID format.' } })] [string]$OperationID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSClientID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSClientSecret, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$MSTenantID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$AuthToken, [Parameter(Mandatory=$False)] [ValidateRange(1,500000)] [int]$ResultSize ) Process { Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } If ($MSTenantID) { $AuthToken = Get-MSGraphAccessToken -MSClientID $MSClientID -MSClientSecret $MSClientSecret -MSTenantID $MSTenantID } ElseIf (!$AuthToken) { $AuthToken = Get-NectarMSTeamsSubscription -TenantName $TenantName | Get-MSGraphAccessToken } } Catch { Write-Error "Could not obtain authorization token." Get-JSONErrorStream -JSONResponse $_ } If ($AuthToken) { $Headers = @{ Authorization = "Bearer $AuthToken" } $URI = "https://graph.microsoft.com/beta/teamwork/devices/$ID/operations" If ($OperationID) { $URI += "/$OperationID" } Write-Verbose $URI $Params = @{} If ($AuthToken) { $Params.Add('AuthToken',$AuthToken) } If ($TenantName) { $Params.Add('Tenant',$TenantName) } $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Headers -Body $Body $JSON.value $PageSize = $JSON.value.count While (($JSON.'@odata.nextLink') -And ($PageSize -lt $ResultSize)) { $NextURI = $JSON.'@odata.nextLink' $JSON = Invoke-RestMethod -Method GET -uri $NextURI -Headers $Headers $JSON.value $PageSize += $JSON.value.count } Clear-Variable -Name AuthToken } } } ################################################################################################################################################# # # # Zoom 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." 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" $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader Return $JSON.data } Catch { Write-Error 'No tenant Zoom data found or insufficient permissions.' 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" 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.' 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" $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.' 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" $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.' 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" $NULL = Invoke-RestMethod -Method DELETE -uri $URI -Headers $Global:NectarAuthHeader } Catch { Write-Error 'Cound not remove Zoom OAuth application.' 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.' 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 !$TenantName) { $TenantName = $Global:NectarTenantName } If ($APIKey) { $AuthToken = Get-ZoomAccessToken -APIKey $APIKey -APISecret $APISecret } ElseIf (!$AuthToken) { $AuthToken = Get-NectarMSTeamsSubscription -TenantName $TenantName | Get-MSGraphAccessToken } } Catch { Write-Error "Could not obtain authorization token." 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 !$TenantName) { $TenantName = $Global:NectarTenantName } If ($APIKey) { $AuthToken = Get-ZoomAccessToken -APIKey $APIKey -APISecret $APISecret } ElseIf (!$AuthToken) { $AuthToken = Get-NectarMSTeamsSubscription -TenantName $TenantName | Get-MSGraphAccessToken } } Catch { Write-Error "Could not obtain authorization token." 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 !$TenantName) { $TenantName = $Global:NectarTenantName } If ($APIKey) { $AuthToken = Get-ZoomAccessToken -APIKey $APIKey -APISecret $APISecret } ElseIf (!$AuthToken) { $AuthToken = Get-NectarMSTeamsSubscription -TenantName $TenantName | Get-MSGraphAccessToken } } Catch { Write-Error "Could not obtain authorization token." 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 # # # ################################################################################################################################################# Function Get-NectarCodecs { <# .SYNOPSIS Returns a list of Nectar DXP codecs used in calls .DESCRIPTION Returns a list of Nectar DXP codecs used in calls .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarCodecs Returns all codecs .NOTES Version 1.1 #> [Alias("gnc")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/dapi/info/codecs?tenant=$TenantName" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader If (!$JSON) { Write-Error 'Codec not found.' } Else { Return $JSON } } Catch { Write-Error 'Unable to get codecs.' Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarExtCities { <# .SYNOPSIS Returns a list of cities found via IP geolocation .DESCRIPTION Most call records include the user's external IP address. Nectar DXP does a geo-IP lookup of the external IP address and stores the geographic information for later use. This command will return all the cities where Nectar DXP was able to successfully geolocate an external IP address. .PARAMETER SearchQuery The name of the city to locate. Can be a partial match, and may return more than one entry. .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-NectarExtCities Returns the first 1000 cities sorted alphabetically. .EXAMPLE Get-NectarExtCities -ResultSize 5000 Returns the first 5000 cities sorted alphabetically. .EXAMPLE Get-NectarExtCities -SearchQuery Gu Returns all cities that contain the letters 'gu' .NOTES Version 1.0 #> [Alias("gneci")] 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 { Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/dapi/info/external/cities" $Params = @{ 'pageSize' = $ResultSize } If ($SearchQuery) { $Params.Add('searchQuery',$SearchQuery) } If ($TenantName) { $Params.Add('Tenant',$TenantName) } $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 } Catch { Write-Error "No results. Try specifying a less-restrictive filter" Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarExtCountries { <# .SYNOPSIS Returns a list of 2-letter country codes found via IP geolocation .DESCRIPTION Most call records include the user's external IP address. Nectar DXP does a geo-IP lookup of the external IP address and stores the geographic information for later use. This command will return all the countries where Nectar DXP was able to successfully geolocate an external IP address. .PARAMETER SearchQuery The 2-letter country code to locate. Can be a partial match, and may return more than one entry. .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-NectarExtCountries Returns all country codes sorted alphabetically. .EXAMPLE Get-NectarExtCountries -SearchQuery US Returns all country codes that contain the letters 'US' .NOTES Version 1.0 #> [Alias("gneco")] 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 !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/dapi/info/external/countries" $Params = @{ 'pageSize' = $ResultSize } If ($SearchQuery) { $Params.Add('searchQuery',$SearchQuery) } If ($TenantName) { $Params.Add('Tenant',$TenantName) } $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 } Catch { Write-Error "No results. Try specifying a less-restrictive filter" Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NectarExtISPs { <# .SYNOPSIS Returns a list of ISPs found via IP geolocation .DESCRIPTION Most call records include the user's external IP address. Nectar DXP does a geo-IP lookup of the external IP address and stores the geographic information for later use. This command will return all the ISPs where Nectar DXP was able to successfully geolocate an external IP address. .PARAMETER SearchQuery The name of the city to locate. Can be a partial match, and may return more than one entry. .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-NectarExtISPs Returns the first 1000 ISPs sorted alphabetically. .EXAMPLE Get-NectarExtISPs -ResultSize 5000 Returns the first 5000 ISPs sorted alphabetically. .EXAMPLE Get-NectarExtISPs -SearchQuery Be Returns all ISPs that contain the letters 'be' .NOTES Version 1.0 #> [Alias("gneci")] 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 { Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/dapi/info/external/isps" $Params = @{ 'pageSize' = $ResultSize } If ($SearchQuery) { $Params.Add('searchQuery',$SearchQuery) } If ($TenantName) { $Params.Add('Tenant',$TenantName) } $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 } Catch { Write-Error "No results. Try specifying a less-restrictive filter" Get-JSONErrorStream -JSONResponse $_ } } } ################################################################################################################################################# ################################################################################################################################################# ## ## ## Endpoint Client Functions ## ## ## ################################################################################################################################################# ################################################################################################################################################# ################################################################################################################################################# # # # Controller Connection Functions # # # ################################################################################################################################################# Function Connect-EPCController { <# .SYNOPSIS Connects to EPC Controller and store the credentials for later use. .DESCRIPTION Connects to an Endpoint Client Controller and store the credentials for later use. .PARAMETER ControllerFQDN The FQDN of the Endpoint Client Controller .PARAMETER Credential The credentials used to access the EPC Controller. .PARAMETER StoredCredentialTarget Use stored credentials saved via New-StoredCredential. Requires prior installation of CredentialManager module via Install-Module CredentialManager, and running: Get-Credential | New-StoredCredential -Target MyEPCCreds -Persist LocalMachine .PARAMETER EnvFromFile Use a CSV file called EPCEnvList.csv located in the user's default Documents folder to show a list of environments to select from. Run [Environment]::GetFolderPath("MyDocuments") to find your default document folder. This parameter is only available if EPCEnvList.csv is found in the user's default Documents folder (ie: C:\Users\username\Documents) Also sets the default stored credential target to use for the selected environment. Requires prior installation and configuration of CredentialManager PS add-in. EPCEnvList.csv must have a header with two columns defined as "Environment, StoredCredentialTarget". Each environment and StoredCredentialTarget (if used) should be on their own separate lines .EXAMPLE $Cred = Get-Credential Connect-EPControllerFQDN -Credential $cred -ControllerFQDN contoso.nectar.services Connects to the contoso.nectar.services EPC Controller using the credentials supplied to the Get-Credential command .EXAMPLE Connect-EPControllerFQDN -ControllerFQDN contoso.nectar.services -StoredCredentialTarget MyEPCCreds Connects to contoso.nectar.services EPC Controller using previously stored credentials called MyEPCCreds .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipeline, Mandatory=$False)] # [ValidateScript ({ # If ($_ -Match "^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$") { # $True # } # Else { # Throw "ERROR: Endpoint Client Controller name must be in FQDN format." # } # })] [string] $ControllerFQDN, [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.Credential()] [PSCredential] $Credential, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$StoredCredentialTarget, [switch]$UseXML ) DynamicParam { $DefaultDocPath = [Environment]::GetFolderPath("MyDocuments") $EnvPath = "$DefaultDocPath\EPCEnvList.csv" If (Test-Path $EnvPath -PathType Leaf) { # Set the dynamic parameters' name $ParameterName = 'EnvFromFile' # Create the dictionary $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary # Create the collection of attributes $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] # Create and set the parameters' attributes $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute $ParameterAttribute.Mandatory = $False $ParameterAttribute.Position = 1 # Add the attributes to the attributes collection $AttributeCollection.Add($ParameterAttribute) # Generate and set the ValidateSet $EnvSet = Import-Csv -Path $EnvPath $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($EnvSet.Environment) # Add the ValidateSet to the attributes collection $AttributeCollection.Add($ValidateSetAttribute) # Create and return the dynamic parameter $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection) $RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter) Return $RuntimeParameterDictionary } } Begin { # Bind the dynamic parameter to a friendly variable If (Test-Path $EnvPath -PathType Leaf) { If ($PsBoundParameters[$ParameterName]) { $ControllerFQDN = $PsBoundParameters[$ParameterName] Write-Verbose "ControllerFQDN: $ControllerFQDN" # Get the array position of the selected environment $EnvPos = $EnvSet.Environment.IndexOf($ControllerFQDN) # Check for stored credential target in EPCEnvList.csv and use if available $StoredCredentialTarget = $EnvSet[$EnvPos].StoredCredentialTarget Write-Verbose "StoredCredentialTarget: $StoredCredentialTarget" } } <# The New-WebServiceProxy command is not supported on PS versions higher than 5.1. On PS 6.0+, we have to use Invoke-WebRequest, which works, but is more verbose and doesn't utilize the WSDL files which creates specific objects for the results. Do a check and set a global variable which will determine if the function will use New-WebServiceProxy or Invoke-WebRequest #> If ($PSVersionTable.PSVersion.Major -gt 5) { $Global:EPC_UseWSDL = $FALSE } Else { $Global:EPC_UseWSDL = $TRUE } Write-Verbose "Setting global UseWSDL variable to $Global:EPC_UseWSDL" } Process { # Need to force TLS 1.2, if not already set If ([Net.ServicePointManager]::SecurityProtocol -ne 'Tls12') { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } # Ask for the tenant name if global EPCController tenant variable not available and not entered on command line If ((-not $Global:EPControllerFQDN) -And (-not $ControllerFQDN)) { $ControllerFQDN = Read-Host "Enter the Endpoint Client Controller FQDN" } ElseIf (($Global:EPControllerFQDN) -And (-not $ControllerFQDN)) { $ControllerFQDN = $Global:EPControllerFQDN } $RegEx = "^(?:http(s)?:\/\/)?([\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+)$" $FQDNMatch = Select-String -Pattern $Regex -InputObject $ControllerFQDN $EPControllerFQDN = $FQDNMatch.Matches.Groups[2].Value # Ask for credentials if global EPCController creds aren't available If (((-not $Global:EPControllerCred) -And (-not $Credential)) -Or (($Global:EPControllerFQDN -ne $EPControllerFQDN) -And (-Not $Credential)) -And (-Not $StoredCredentialTarget)) { $Credential = Get-Credential } ElseIf ($Global:EPControllerCred -And (-not $Credential)) { $Credential = $Global:EPControllerCred } # Pull stored credentials if specified If ($StoredCredentialTarget) { Try { $Credential = Get-StoredCredential -Target $StoredCredentialTarget } Catch { Write-Error "Cannot find stored credential for target: $StoredCredentialTarget" } } # Get the WSDL If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { Write-Verbose 'Using WSDL' If ($Global:EPCWSDL_ActiveCtrl -and $Global:EPControllerFQDN -eq $EPControllerFQDN) { Write-Verbose 'Using WSDL from global variable' $EPC_ActiveCtrl = $Global:EPCWSDL_ActiveCtrl $EPC_TestGrpMgmt = $Global:EPCWSDL_TestGrpMgmt $EPC_ResGrpMgmt = $Global:EPCWSDL_ResGrpMgmt $EPC_SvcMgmt = $Global:EPCWSDL_SvcMgmt $EPC_BulkExport = $Global:EPCWSDL_BulkExport $EPC_GetData = $Global:EPCWSDL_GetData } ElseIf ((!$Global:EPCWSDL_ActiveCtrl -and $Global:EPControllerFQDN -eq $EPControllerFQDN) -Or !$Global:EPCWSDL_ActiveCtrl) { Write-Verbose "Loading WSDL from $EPControllerFQDN" $EPC_ActiveCtrl = New-WebServiceProxy "https://$EPControllerFQDN/telchemywebservices/services/telchemyActiveCtrlService?wsdl" -Namespace EPC.ActiveCtrl -Class ActiveCtrl $EPC_TestGrpMgmt = New-WebServiceProxy "https://$EPControllerFQDN/telchemywebservices/services/telchemyTestGroupManagementService?wsdl" -Namespace EPC.TestGrpMgmt -Class TestGrpMgmt $EPC_ResGrpMgmt = New-WebServiceProxy "https://$EPControllerFQDN/telchemywebservices/services/telchemyRsrcGroupMgmtService?wsdl" -Namespace EPC.ResGrpMgmt -Class ResGrpMgmt $EPC_SvcMgmt = New-WebServiceProxy "https://$EPControllerFQDN/telchemywebservices/services/telchemySvcMgmtService?wsdl" -Namespace EPC.SvcMgmt -Class SvcMgmt $EPC_BulkExport = New-WebServiceProxy "https://$EPControllerFQDN/telchemywebservices/services/telchemyBulkExportService?wsdl" -Namespace EPC.BulkExport -Class BulkExport $EPC_GetData = New-WebServiceProxy "https://$EPControllerFQDN/telchemywebservices/services/telchemyGetDataService?wsdl" -Namespace EPC.GetData -Class GetData } Else { Write-Error "There is already an active connection to $($Global:EPControllerFQDN). Please open a new PowerShell window to connect to a new controller." Return } } If ((-not $Global:EPControllerCred) -Or (-not $Global:EPControllerFQDN) -Or ($Global:EPControllerFQDN -ne $EPControllerFQDN)) { # Validate login credentials by running a simple API query If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { # Store creds in EPC credential object $EPCCred = New-Object -TypeName EPC.ActiveCtrl.CredentialsType $EPCCred.username = $Credential.UserName $EPCCred.password = $Credential.GetNetworkCredential().Password Write-Verbose 'Using WSDL to test access' $EPCAPIVersionParams = New-Object -TypeName EPC.ActiveCtrl.GetAPIVersionParametersType $EPCAPIVersionParams.credentials = $EPCCred $EPCAPIVersion = $EPC_ActiveCtrl.getAPIVersion($EPCAPIVersionParams) $LoginResult = $EPCAPIVersion.result } Else { $ProgressPreference = 'SilentlyContinue' Write-Verbose 'Using WebXML to test access' [xml]$SOAPReq = "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' xmlns:urn='urn:telchemyActiveCtrl'> <soapenv:Header/> <soapenv:Body> <urn:getAPIVersionParameters> <credentials> <username>$($Credential.UserName)</username> <password>$($Credential.GetNetworkCredential().Password)</password> </credentials> </urn:getAPIVersionParameters> </soapenv:Body> </soapenv:Envelope>" Write-Verbose $SOAPReq.OuterXML $SOAPFQDN = "https://$EPControllerFQDN/telchemywebservices/services/telchemyActiveCtrlService" Write-Verbose $SOAPFQDN [xml]$XMLResponse = (Invoke-WebRequest -Method POST -URI $SOAPFQDN -Body $SOAPReq -ContentType 'text/xml').Content $LoginResult = $XMLResponse.Envelope.Body.getAPIVersionResults.result } If ($LoginResult -ne 'success') { Write-Error "Could not connect to $EPControllerFQDN using $($Credential.UserName)" Throw $LoginResult } Else { Write-Host -ForegroundColor Green "Successful API connection to " -NoNewLine Write-Host -ForegroundColor Yellow "https://$EPControllerFQDN" -NoNewLine Write-Host -ForegroundColor Green " using " -NoNewLine Write-Host -ForegroundColor Yellow ($Credential).UserName $Global:EPControllerFQDN = $EPControllerFQDN $Global:EPControllerCred = $Credential $Global:EPCWSDL_ActiveCtrl = $EPC_ActiveCtrl $Global:EPCWSDL_TestGrpMgmt = $EPC_TestGrpMgmt $Global:EPCWSDL_ResGrpMgmt = $EPC_ResGrpMgmt $Global:EPCWSDL_SvcMgmt = $EPC_SvcMgmt $Global:EPCWSDL_BulkExport = $EPC_BulkExport $Global:EPCWSDL_GetData = $EPC_GetData If ($PSVersionTable.PSVersion.Major -gt 5) { # Check for the PowerHTML module and install if not present. Needed for parsing web pageSize If (!(Get-InstalledModule -Name PowerHTML)) { $Null = Install-Module -Name PowerHTML -Scope CurrentUser -Force } } Write-Verbose "Setting global UseWSDL variable to $Global:EPC_UseWSDL" } } } } Function Get-EPCConnectedController { <# .SYNOPSIS Returns the FQDN of the connected controller .DESCRIPTION Returns the FQDN of the connected controller .NOTES Version 1.0 #> If ($Global:EPControllerFQDN) { Return $Global:EPControllerFQDN } Else { Return 'Not connected to a controller' } } Function Get-EPCWebSessionCookie { <# .SYNOPSIS Creates a web session cookie for use with commands that can't use the EPC API .DESCRIPTION Connects to an Endpoint Client Controller and store the credentials for later use. Must have already successfully connected via Connect-EPCController .NOTES Version 1.0 #> [cmdletbinding()] param () Connect-EPCController $ProgressPreference = 'SilentlyContinue' #URLEncode the password $EncodedPassword = [System.Web.HttpUtility]::UrlEncode($Global:EPControllerCred.GetNetworkCredential().Password) # Check for existing global session cookie. Connect if not available and save the web session variable in a global variable If (!$Global:EPCSessionCookie) { Write-Verbose 'Global session cookie does not exist' $Dashboard = Invoke-WebRequest -UseBasicParsing -Uri "https://$EPControllerFQDN/dashboard.htm" -SessionVariable 'EPCSession' $NULL = Invoke-WebRequest -Uri "https://$EPControllerFQDN/j_security_check" -Method POST -Body "j_username=$($Global:EPControllerCred.UserName)&j_password=$EncodedPassword" -WebSession $EPCSession # Verify that session is active. We do this by looking for the existence of a single link on the dashboard page. If there is, this means the session expired and the base login page is being shown. # If there are numerous links, this indicates a successful login. $Dashboard = Invoke-WebRequest -UseBasicParsing -Uri "https://$EPControllerFQDN/dashboard.htm" -WebSession $EPCSession If ($Dashboard.Links.Count -gt 1) { Write-Host -ForegroundColor Green "Successful web session connection to " -NoNewLine Write-Host -ForegroundColor Yellow "https://$EPControllerFQDN" -NoNewLine Write-Host -ForegroundColor Green " using " -NoNewLine Write-Host -ForegroundColor Yellow ($Global:EPControllerCred).UserName $Global:EPCSessionCookie = $EPCSession } Else { Throw "Could not connect to $EPControllerFQDN web session using $($Credential.UserName)" } } Else { # Verify that session is active. We do this by looking for the existence of a single link on the dashboard page. If there is, this means the session expired and the base login page is being shown. # If there are numerous links, this indicates the session is still active $Dashboard = Invoke-WebRequest -UseBasicParsing -Uri "https://$EPControllerFQDN/dashboard.htm" -WebSession $Global:EPCSessionCookie Write-Verbose "Session verify against $EPControllerFQDN" If ($Dashboard.Links.Count -eq 1) { # Re-authenticate and update global session cookie Write-Verbose 'Global session cookie expired' $Dashboard = Invoke-WebRequest -UseBasicParsing -Uri "https://$EPControllerFQDN/dashboard.htm" -SessionVariable 'EPCSession' $NULL = Invoke-WebRequest -Uri "https://$EPControllerFQDN/j_security_check" -Method POST -Body "j_username=$($Global:EPControllerCred.UserName)&j_password=$EncodedPassword" -WebSession $EPCSession $Dashboard = Invoke-WebRequest -UseBasicParsing -Uri "https://$EPControllerFQDN/dashboard.htm" -WebSession $EPCSession If ($Dashboard.Links.Count -gt 1) { Write-Verbose "Successful auth cookie regeneration against $EPControllerFQDN" $Global:EPCSessionCookie = $EPCSession } Else { Throw "Could not regenerate auth cookie against $EPControllerFQDN using $($Credential.UserName)" } } Else { Write-Verbose "Session verified against $EPControllerFQDN" } } } Function Disconnect-EPCController { <# .SYNOPSIS Disconnects from any active Endpoint Client Controller .DESCRIPTION Essentially deletes any stored credentials and FQDN from global variables. If PS version is less than 6, this doesn't currently work because there apparently isn't a way to remove the WSDL namespace from memory. Thiss means that connecting to a new EPC controller will still try to use the WSDL associated with the original controller. Any API commands will fail. Therefore, there is no useful reason to disconnect. .EXAMPLE Disconnect-EPCController Disconnects from all active connections to an EPC Controller .NOTES Version 1.0 #> [cmdletbinding()] param () $VariableNames = 'EPControllerCred','EPControllerFQDN','EPCSessionCookie','EPCWSDL_ActiveCtrl','EPCWSDL_ResGrpMgmt','EPCWSDL_SvcMgmt', 'EPC_UseWSDL' ForEach ($Variable in $VariableNames) { Remove-Variable $Variable -Scope Global -ErrorAction:SilentlyContinue } } ################################################################################################################################################# # # # EPC Test Point Functions # # # ################################################################################################################################################# Function Get-EPCTestPoint { <# .SYNOPSIS Return information about a given EPC test point .DESCRIPTION Return information about a given EPC test point .PARAMETER Name The name of a specific test point to retrieve information about. Will do partial matches. .PARAMETER Status Only return information about test points with a specified status .PARAMETER OrgID Only return information about test points within a specified organization .PARAMETER Version Only return information about test points that match the given version .PARAMETER RGName Only return information about test points that are within a given resource group Can specify either RG name or ID .PARAMETER RGID Only return information about test points that are within a given resource group Can specify either RG name or ID .PARAMETER ResultSize The number of results to return. Defaults to 1000. .EXAMPLE Get-EPCTestPoint Returns information about the first 10000 test points .EXAMPLE Get-EPCTestPoint -Name 'TFerguson Laptop' Returns information about a specific test point using the test point name .EXAMPLE Get-EPCTestPoint -Status Connected -OrgID Contoso Returns information about connected test points in the Contoso organization .NOTES Version 1.0 #> [cmdletbinding()] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias('EndpointName')] [string]$Name, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('Connected','NotConnected','Reachable','Unknown','Unlicensed', IgnoreCase=$True)] [string]$Status, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$OrgID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$Version, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$RGName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [int[]]$RGID, [Parameter(Mandatory=$False)] [int]$ResultSize = 10000, [switch]$UseXML ) Begin { Connect-EPCController If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $EPCCred = New-Object -TypeName EPC.ActiveCtrl.CredentialsType $EPCCred.username = $Global:EPControllerCred.UserName $EPCCred.password = $Global:EPControllerCred.GetNetworkCredential().Password } Else { $ProgressPreference = 'SilentlyContinue' } } Process { If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { Write-Verbose 'Using WSDL' $EPCListTestPointParams = New-Object -TypeName EPC.ActiveCtrl.ListTestPointsParametersType $EPCListTestPointParams.credentials = $EPCCred $EPCListTestPointParams.maxNumber = $ResultSize # If any of the below parameters are specified, apply a filter If ($Name -Or $Status -Or $OrgID -Or $Version -Or $RGName -Or $RGID) { $EPCListTestPointFilterParams = New-Object -TypeName EPC.ActiveCtrl.ListTestPointsParametersTypeFilter If ($Name) { $EPCListTestPointFilterParams.namesubstring = $Name } If ($OrgID) { $EPCListTestPointFilterParams.orgID = $OrgID } If ($Version) { $EPCListTestPointFilterParams.version = $Version } If ($Status) { $EPCListTestPointFilterParams.status = $Status; $EPCListTestPointFilterParams.statusSpecified = $TRUE } If ($RGName) { $RGID = (Get-EPCResourceGroup | Where-Object {$_.RGName -eq $RGName}).RGID } If ($RGID) { $EPCListTestPointFilterParams.RGList = $RGID } $EPCListTestPointParams.Filter = $EPCListTestPointFilterParams } $TestPointResults = $Global:EPCWSDL_ActiveCtrl.listTestPoints($EPCListTestPointParams) Write-Verbose $TestPointResults.result If ($TestPointResults.result -eq 'success') { Return $TestPointResults.testptList } Else { Throw $TestPointResults.result } } Else { Write-Verbose 'Using WebXML' [xml]$SOAPReq = "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' xmlns:urn='urn:telchemyActiveCtrl'> <soapenv:Header/> <soapenv:Body> <urn:listTestPointsParameters> <credentials> <username>$($Global:EPControllerCred.UserName)</username> <password>$($Global:EPControllerCred.GetNetworkCredential().Password)</password> </credentials> <maxNumber>$ResultSize</maxNumber> </urn:listTestPointsParameters> </soapenv:Body> </soapenv:Envelope>" # If any of the below parameters are specified, apply a filter If ($Name -Or $Status -Or $OrgID -Or $Version -Or $OSArch -Or $RGName -Or $RGID) { $FilterXMLElement = $SOAPReq.Envelope.Body.listTestPointsParameters.AppendChild($SOAPReq.CreateElement('filter')) If ($Name) { $NewXMLElement = $FilterXMLElement.AppendChild($SOAPReq.CreateElement('name-substring')) $NULL = $NewXMLElement.AppendChild($SOAPReq.CreateTextNode($Name)) } If ($Status) { If ($Status -eq 'NotConnected') { $StatusFormatted = 'not connected' } Else { $StatusFormatted = $Status } $NewXMLElement = $FilterXMLElement.AppendChild($SOAPReq.CreateElement('status')) $NULL = $NewXMLElement.AppendChild($SOAPReq.CreateTextNode($StatusFormatted.ToLower())) } If ($OrgID) { $NewXMLElement = $FilterXMLElement.AppendChild($SOAPReq.CreateElement('orgID')) $NULL = $NewXMLElement.AppendChild($SOAPReq.CreateTextNode($OrgID.ToLower())) } If ($Version) { $NewXMLElement = $FilterXMLElement.AppendChild($SOAPReq.CreateElement('version')) $NULL = $NewXMLElement.AppendChild($SOAPReq.CreateTextNode($Version)) } If ($RGName) { $RGID = (Get-EPCResourceGroup | Where-Object {$_.RGName -eq $RGName}).RGID } If ($RGID) { $RGXMLElement = $FilterXMLElement.AppendChild($SOAPReq.CreateElement('RGList')) $NewXMLElement = $RGXMLElement.AppendChild($SOAPReq.CreateElement('rg')) $NULL = $NewXMLElement.AppendChild($SOAPReq.CreateTextNode($RGID)) } } Write-Verbose $SOAPReq.OuterXML $SOAPFQDN = "https://$Global:EPControllerFQDN/telchemywebservices/services/telchemyActiveCtrlService" Write-Verbose $SOAPFQDN [xml]$XMLResponse = (Invoke-WebRequest -Method POST -URI $SOAPFQDN -Body $SOAPReq -ContentType 'text/xml').Content $TestPointResults = $XMLResponse.Envelope.Body.listTestPointsResults If ($TestPointResults.result -eq 'success') { Return $TestPointResults.testptList.testpt } Else { Throw $TestPointResults.result } } } } Function Set-EPCTestPoint { <# .SYNOPSIS Update the name and/or description for a given EPC test point .DESCRIPTION Update the name and/or description for a given EPC test point .PARAMETER UUID The UUID of the test point to update .PARAMETER Name The display name to set on the test point .PARAMETER Description The description to set on the test point .EXAMPLE Set-EPCTestPoint -UUID d4a1437f-1f18-11ec-cf33-0daedf04882a -Name 'New Name' -Description 'Updated description' Updates the selected test point's name and description .NOTES Version 1.0 #> [cmdletbinding()] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias("ID")] [string]$UUID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias("UserDisplayName")] [string]$DisplayName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$Description ) Begin { Get-EPCWebSessionCookie $ProgressPreference = 'SilentlyContinue' } Process { $TPFQDN = "https://$EPControllerFQDN/test/testPoint.do?action=2" # If name isn't set, pull the name from the existing record, otherwise it will replace the name with its IP address If (!$DisplayName) { $DisplayName = (Get-EPCTestPoint -UUID $UUID).Name } $TPBody = @{ tptype = 1 uuid = $UUID name = $DisplayName } If ($Description) { $TPBody.Add('description',$Description -Replace "[^\sa-zA-Z0-9.-]") } Write-Verbose "UUID: $UUID, Name: $DisplayName" $NULL = Invoke-WebRequest -Method POST -Uri $TPFQDN -Body $TPBody -WebSession $Global:EPCSessionCookie } } Function Get-EPCTestPointInterface { <# .SYNOPSIS Return network interface inforemation for a given EPC test point .DESCRIPTION Return network interface inforemation for a given EPC test point .PARAMETER ID The ID of the test point to retrieve information about .EXAMPLE Get-EPCTestPointInterface -ID d4a1437f-1f18-11ec-cf33-0daedf04882a Returns network interface information about a specific test point .NOTES Version 1.0 #> [cmdletbinding()] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias('testptID', 'EndpointID')] [string]$UUID, [switch]$UseXML ) Begin { Connect-EPCController If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $EPCCred = New-Object -TypeName EPC.ActiveCtrl.CredentialsType $EPCCred.username = $Global:EPControllerCred.UserName $EPCCred.password = $Global:EPControllerCred.GetNetworkCredential().Password } Else { $ProgressPreference = 'SilentlyContinue' } } Process { If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { Write-Verbose 'Using WSDL to pull interface list' $EPCListTestPointInterfaceParams = New-Object -TypeName EPC.ActiveCtrl.ListTestPointInterfaceParametersType $EPCListTestPointInterfaceParams.credentials = $EPCCred $EPCListTestPointInterfaceParams.testptID = $UUID $TestPointInterfaceResults = $Global:EPCWSDL_ActiveCtrl.listTestPointInterfaces($EPCListTestPointInterfaceParams) Write-Verbose $TestPointInterfaceResults.result If ($TestPointInterfaceResults.result -eq 'success') { Return $TestPointInterfaceResults.InterfaceList } Else { Throw $TestPointInterfaceResults.result } } Else { Write-Verbose 'Using XML to pull interface list' [xml]$SOAPReq = "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' xmlns:urn='urn:telchemyActiveCtrl'> <soapenv:Header/> <soapenv:Body> <urn:listTestPointInterfaceParameters> <credentials> <username>$($Global:EPControllerCred.UserName)</username> <password>$($Global:EPControllerCred.GetNetworkCredential().Password)</password> </credentials> <testptID>$UUID</testptID> </urn:listTestPointInterfaceParameters> </soapenv:Body> </soapenv:Envelope>" Write-Verbose $SOAPReq $SOAPFQDN = "https://$Global:EPControllerFQDN/telchemywebservices/services/telchemyActiveCtrlService" Write-Verbose $SOAPFQDN [xml]$XMLResponse = (Invoke-WebRequest -Method POST -URI $SOAPFQDN -Body $SOAPReq -ContentType 'text/xml').Content $TestPointInterfaceResults = $XMLResponse.Envelope.Body.listTestPointInterfaceResults If ($TestPointInterfaceResults.result -eq 'success') { [psobject]$TestPointInterfaces = $TestPointInterfaceResults.InterfaceList.interface | ConvertFrom-XMLElement Return $TestPointInterfaces } Else { Throw $TestPointInterfaceResults.result } } } } Function Get-EPCTestPointEvent { <# .SYNOPSIS Returns a list of EPC test groups and their associated IDs .DESCRIPTION Returns a list of EPC test groups and their associated IDs .PARAMETER ResultSize The number of results to return. Defaults to 1000. .EXAMPLE Get-EPCTestPointEvent .NOTES Version 1.0 #> [cmdletbinding()] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias("testptID")] [string]$TestPointID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TestPointName, [Parameter(Mandatory=$False)] [ValidateSet('LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','ALL','CUSTOM', IgnoreCase=$True)] [string]$TimePeriod = 'LAST_DAY', [Parameter(Mandatory=$False)] [Alias("StartDateFrom")] [datetimeoffset]$TimePeriodFrom, [Parameter(Mandatory=$False)] [Alias("StartDateTo")] [datetimeoffset]$TimePeriodTo, [Parameter(Mandatory=$False)] [ValidateSet('Any','Fatal','Error','Warn','Info','Debug', IgnoreCase=$True)] [string]$EventLevel = 'Any', [Parameter(Mandatory=$False)] [ValidateSet('Any','Reporter','Controller','Agent', IgnoreCase=$True)] [string]$AppType = 'Any', [Parameter(Mandatory=$False)] [string]$SortColumn = 'evt_date', [Parameter(Mandatory=$False)] [int]$ResultSize = 1000 ) Begin { Get-EPCWebSessionCookie $ProgressPreference = 'SilentlyContinue' } Process { $Body = @{ action = 3 filter = $EventLevel.ToLower() } If ($AppType -ne 'Any') { Switch ($AppType) { 'Reporter' { $AppTypeID = 2 } 'Controller' { $AppTypeID = 3 } 'Agent' { $AppTypeID = 4 } } $Body.Add('apptype', $AppTypeID) } If ($TimePeriod -ne 'ALL') { Switch ($TimePeriod) { 'LAST_HOUR' { $TimePeriodFrom = [DateTimeOffset]::Now.AddHours(-1); $TimePeriodTo = [DateTimeOffset]::Now } 'LAST_DAY' { $TimePeriodFrom = [DateTimeOffset]::Now.AddDays(-1); $TimePeriodTo = [DateTimeOffset]::Now } 'LAST_WEEK' { $TimePeriodFrom = [DateTimeOffset]::Now.AddDays(-7); $TimePeriodTo = [DateTimeOffset]::Now } 'LAST_MONTH' { $TimePeriodFrom = [DateTimeOffset]::Now.AddMonths(-1); $TimePeriodTo = [DateTimeOffset]::Now } } If (!$TimePeriodTo) { $TimePeriodTo = [DateTimeOffset]::Now } $Body.Add('beginsecs', $TimePeriodFrom.ToUnixTimeSeconds()) $Body.Add('endsecs', $TimePeriodTo.ToUnixTimeSeconds()) } If ($TestPointID) { $Body.Add('hostuuid', $TestPointID) } If ($TestPointName) { $TestPointID = (Get-EPCTestPoint -Name $TestPointName -ResultSize 1).testptID If (!$TestPointID) { Throw "Could not find test point with name $TestPointName" } $Body.Add('hostuuid', $TestPointID) } $TPEFQDN = "https://$Global:EPControllerFQDN/admin/eventLogs.do" $TPEResult = Invoke-RestMethod -Method GET -Uri $TPEFQDN -WebSession $Global:EPCSessionCookie -Body $Body $FormattedResults = $TPEResult.events | Select-Object application, host, appCode, @{Name='time';Expression={(([System.DateTimeOffset]::FromUnixTimeMilliSeconds($_.Time)).LocalDateTime).ToString("yyyy-MM-dd HH:mm:ss.fff")}}, priority, message Return $FormattedResults } } ################################################################################################################################################# # # # EPC Test Group Functions # # # ################################################################################################################################################# Function Get-EPCTestGroup { <# .SYNOPSIS Returns a list of EPC test groups and their associated IDs .DESCRIPTION Returns a list of EPC test groups and their associated IDs .PARAMETER SearchString Filter the results to only show test groups that match the given search string .EXAMPLE Get-EPCTestGroup -SearchString Contoso Returns test groups that contain the name Contoso .NOTES Version 1.0 #> [cmdletbinding()] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$SearchString, [switch]$UseXML ) Begin { Connect-EPCController If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $EPCCred = New-Object -TypeName EPC.TestGrpMgmt.CredentialsType $EPCCred.username = $Global:EPControllerCred.UserName $EPCCred.password = $Global:EPControllerCred.GetNetworkCredential().Password } Else { $ProgressPreference = 'SilentlyContinue' } } Process { If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { Write-Verbose 'Using WSDL to pull test group list' $EPCListGroupsParams = New-Object -TypeName EPC.TestGrpMgmt.ListGroupsParametersType $EPCListGroupsParams.credentials = $EPCCred $EPCListGroupsParams.matchsubstring = $SearchString $EPCListGroupsResults = $Global:EPCWSDL_TestGrpMgmt.ListGroups($EPCListGroupsParams) Write-Verbose $EPCListGroupsResults.ListGroupsResponse If ($EPCListGroupsResults.ListGroupsResponse -eq 'success') { Return $EPCListGroupsResults.ListGroupsGroupList } Else { Throw $EPCListGroupsResults.ListGroupsResponse } } Else { Write-Verbose 'Using XML to pull test group list' [xml]$SOAPReq = "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' xmlns:urn='urn:telchemyTestGroupManagement'> <soapenv:Header/> <soapenv:Body> <urn:ListGroupsMatchingString> <credentials> <username>$($Global:EPControllerCred.UserName)</username> <password>$($Global:EPControllerCred.GetNetworkCredential().Password)</password> </credentials> <matchsubstring>$SearchString</matchsubstring> </urn:ListGroupsMatchingString> </soapenv:Body> </soapenv:Envelope>" Write-Verbose $SOAPReq $SOAPFQDN = "https://$Global:EPControllerFQDN/telchemywebservices/services/telchemyTestGroupManagementService" Write-Verbose $SOAPFQDN [xml]$XMLResponse = (Invoke-WebRequest -Method POST -URI $SOAPFQDN -Body $SOAPReq -ContentType 'text/xml').Content $EPCListGroupsResults = $XMLResponse.Envelope.Body.ListGroupsResults If ($EPCListGroupsResults.ListGroupsResponse -eq 'success') { If ($EPCListGroupsResults.ListGroupsGroupList) { [psobject]$ListGroups = $EPCListGroupsResults.ListGroupsGroupList | ConvertFrom-XMLElement Return $ListGroups } Else { Write-Error "Could not find a group called $SearchString" } } Else { Throw $EPCListGroupsResults.ListGroupsResponse } } } } Function Set-EPCTestGroup { <# .SYNOPSIS Update the name of an existing test group .DESCRIPTION Update the name of an existing test group .PARAMETER Name The name of the test group to update .PARAMETER NewName The new name of the test group .PARAMETER TGID The ID of a test group. If both Name and TGID are specified, Name will take priority. .EXAMPLE Set-EPCTestGroup -Name 'Existing Name' -NewName 'New Name' .NOTES Version 1.0 #> [cmdletbinding()] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$GroupName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [int]$GroupID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$NewGroupName, [switch]$UseXML ) Begin { Connect-EPCController If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $EPCCred = New-Object -TypeName EPC.TestGrpMgmt.CredentialsType $EPCCred.username = $Global:EPControllerCred.UserName $EPCCred.password = $Global:EPControllerCred.GetNetworkCredential().Password } Else { $ProgressPreference = 'SilentlyContinue' } } Process { If ($GroupName -And !$GroupID) { $GroupIDList = (Get-EPCTestGroup -SearchString $GroupName).GroupID If ($GroupIDList.Count -gt 1) { Throw 'Too many results returned for specified group name. Please refine your search to return a single group or use GroupID parameter.' } Else { $GroupID = $GroupIDList } } If (!$GroupID) { Throw 'Test Group ID not specified or could not be found' } If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { Write-Verbose 'Using WSDL to set test group parameters' $EPCModifyGroupParams = New-Object -TypeName EPC.TestGrpMgmt.ModifyGroupParametersType $EPCModifyGroupParams.credentials = $EPCCred $EPCModifyGroupParams.ModifyGroupID = $GroupID $EPCModifyGroupParams.ModifyGroupUpdatedName = $NewGroupName $EPCModifyGroupResults = $Global:EPCWSDL_TestGrpMgmt.ModifyGroup($EPCModifyGroupParams) Write-Verbose $EPCModifyGroupResults.ModifyGroupResponse } Else { Write-Verbose 'Using XML to set test group parameters' [xml]$SOAPReq = "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' xmlns:urn='urn:telchemyTestGroupManagement'> <soapenv:Header/> <soapenv:Body> <urn:ModifyGroupParameters> <credentials> <username>$($Global:EPControllerCred.UserName)</username> <password>$($Global:EPControllerCred.GetNetworkCredential().Password)</password> </credentials> <ModifyGroupID>$GroupID</ModifyGroupID> <ModifyGroupUpdatedName>$NewGroupName</ModifyGroupUpdatedName> </urn:ModifyGroupParameters> </soapenv:Body> </soapenv:Envelope>" Write-Verbose $SOAPReq $SOAPFQDN = "https://$Global:EPControllerFQDN/telchemywebservices/services/telchemyTestGroupManagementService" Write-Verbose $SOAPFQDN [xml]$XMLResponse = (Invoke-WebRequest -Method POST -URI $SOAPFQDN -Body $SOAPReq -ContentType 'text/xml').Content $EPCModifyGroupResults = $XMLResponse.Envelope.Body.ModifyGroupResults } If ($EPCModifyGroupResults.ModifyGroupResponse -eq 'success') { Return $EPCModifyGroupResults.ModifyGroupResponse } Else { Throw $EPCModifyGroupResults.ModifyGroupResponse } } } Function New-EPCTestGroup { <# .SYNOPSIS Create a new EPC test group .DESCRIPTION Create a new EPC test group .PARAMETER GroupName The name of the test group .PARAMETER GroupType The type to assign to the test group .EXAMPLE New-EPCTestGroup -GroupName MyGroup -GroupType Agent Creates an agent test group called MyGroup .NOTES Version 1.0 #> [cmdletbinding()] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$GroupName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('Agent','Network Entity','DHCP','DNS','HTTP','POP3','SIP Endpoint','SMTP', IgnoreCase=$True)] [string]$GroupType = 'Agent', [switch]$UseXML ) Begin { Connect-EPCController If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $EPCCred = New-Object -TypeName EPC.TestGrpMgmt.CredentialsType $EPCCred.username = $Global:EPControllerCred.UserName $EPCCred.password = $Global:EPControllerCred.GetNetworkCredential().Password } Else { $ProgressPreference = 'SilentlyContinue' } } Process { If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { Write-Verbose 'Using WSDL to create test group' $EPCAddGroupParams = New-Object -TypeName EPC.TestGrpMgmt.AddGroupParametersType $EPCAddGroupParams.credentials = $EPCCred $EPCAddGroupParams.AddGroupGroupName = $GroupName $EPCAddGroupParams.GroupType = $GroupType $EPCAddGroupResults = $Global:EPCWSDL_TestGrpMgmt.AddGroup($EPCAddGroupParams) Write-Verbose $EPCAddGroupResults.AddGroupResponse } Else { Write-Verbose 'Using XML to create test group' [xml]$SOAPReq = "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' xmlns:urn='urn:telchemyTestGroupManagement'> <soapenv:Header/> <soapenv:Body> <urn:AddGroupParameters> <credentials> <username>$($Global:EPControllerCred.UserName)</username> <password>$($Global:EPControllerCred.GetNetworkCredential().Password)</password> </credentials> <AddGroupGroupName>$GroupName</AddGroupGroupName> <GroupType>$($GroupType.ToLower())</GroupType> </urn:AddGroupParameters> </soapenv:Body> </soapenv:Envelope>" Write-Verbose $SOAPReq $SOAPFQDN = "https://$Global:EPControllerFQDN/telchemywebservices/services/telchemyTestGroupManagementService" Write-Verbose $SOAPFQDN [xml]$XMLResponse = (Invoke-WebRequest -Method POST -URI $SOAPFQDN -Body $SOAPReq -ContentType 'text/xml').Content $EPCAddGroupResults = $XMLResponse.Envelope.Body.AddGroupResults } If ($EPCAddGroupResults.AddGroupResponse -eq 'success') { Return $EPCAddGroupResults } Else { Throw $EPCAddGroupResults.AddGroupResponse } } } Function Remove-EPCTestGroup { <# .SYNOPSIS Remove an EPC test group .DESCRIPTION Remove an EPC test group .PARAMETER GroupName The name of the test group to remove. Use either this or GroupID. .PARAMETER GroupID The ID of the test group to remove. Use either this or GroupName. .EXAMPLE Remove-EPCTestGroup -GroupName MyGroup Removes the test group called MyGroup .NOTES Version 1.0 #> [cmdletbinding()] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$GroupName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [int]$GroupID, [switch]$UseXML ) Begin { Connect-EPCController If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $EPCCred = New-Object -TypeName EPC.TestGrpMgmt.CredentialsType $EPCCred.username = $Global:EPControllerCred.UserName $EPCCred.password = $Global:EPControllerCred.GetNetworkCredential().Password } Else { $ProgressPreference = 'SilentlyContinue' } } Process { If ($GroupName -And !$GroupID) { $GroupIDList = (Get-EPCTestGroup -SearchString $GroupName).GroupID If ($GroupIDList.Count -gt 1) { Throw 'Too many results returned for specified group name. Please refine your search to return a single group or use GroupID parameter.' } Else { $GroupID = $GroupIDList } } If (!$GroupID) { Throw 'Test Group ID not specified or could not be found' } If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { Write-Verbose 'Using WSDL to create test group' $EPCDeleteGroupParams = New-Object -TypeName EPC.TestGrpMgmt.DeleteGroupParametersType $EPCDeleteGroupParams.credentials = $EPCCred $EPCDeleteGroupParams.DeleteGroupID = $GroupID $EPCDeleteGroupResults = $Global:EPCWSDL_TestGrpMgmt.DeleteGroup($EPCDeleteGroupParams) Write-Verbose $EPCDeleteGroupResults.DeleteGroupResponse } Else { Write-Verbose 'Using XML to create test group' [xml]$SOAPReq = "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' xmlns:urn='urn:telchemyTestGroupManagement'> <soapenv:Header/> <soapenv:Body> <urn:DeleteGroupWithID> <credentials> <username>$($Global:EPControllerCred.UserName)</username> <password>$($Global:EPControllerCred.GetNetworkCredential().Password)</password> </credentials> <DeleteGroupID>$GroupName</DeleteGroupID> </urn:DeleteGroupWithID> </soapenv:Body> </soapenv:Envelope>" Write-Verbose $SOAPReq $SOAPFQDN = "https://$Global:EPControllerFQDN/telchemywebservices/services/telchemyTestGroupManagementService" Write-Verbose $SOAPFQDN [xml]$XMLResponse = (Invoke-WebRequest -Method POST -URI $SOAPFQDN -Body $SOAPReq -ContentType 'text/xml').Content $EPCDeleteGroupResults = $XMLResponse.Envelope.Body.AddGroupResults } If ($EPCDeleteGroupResults.DeleteGroupResponse -eq 'success') { Return $EPCDeleteGroupResults } Else { Throw $EPCDeleteGroupResults.DeleteGroupResponse } } } Function Get-EPCTestGroupMembers { <# .SYNOPSIS Returns a list of test points associated with a given test group .DESCRIPTION Returns a list of test points associated with a given test group .EXAMPLE Get-EPCTestGroupMembers -GroupID 4 .NOTES Version 1.0 #> [cmdletbinding()] Param ( [Parameter(Mandatory=$False)] [string]$GroupName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [int]$GroupID, [switch]$UseXML ) Begin { Connect-EPCController If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $EPCCred = New-Object -TypeName EPC.TestGrpMgmt.CredentialsType $EPCCred.username = $Global:EPControllerCred.UserName $EPCCred.password = $Global:EPControllerCred.GetNetworkCredential().Password } Else { $ProgressPreference = 'SilentlyContinue' } } Process { If ($GroupName -And !$GroupID) { $GroupIDList = (Get-EPCTestGroup -SearchString $GroupName).GroupID If ($GroupIDList.Count -gt 1) { Throw 'Too many results returned for specified GroupName. Please refine your search to return a single group or use GroupID parameter.' } Else { $GroupID = $GroupIDList } } If (!$GroupID) { Throw 'Test Group ID not specified or could not be found' } If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { Write-Verbose 'Using WSDL to pull test group member list' $EPCListEndpointsInGroupParams = New-Object -TypeName EPC.TestGrpMgmt.ListEndpointsInGroupParametersType $EPCListEndpointsInGroupParams.credentials = $EPCCred $EPCListEndpointsInGroupParams.GroupID = $GroupID $EPCListEndpointsInGroupResults = $Global:EPCWSDL_TestGrpMgmt.ListEndpointsInGroup($EPCListEndpointsInGroupParams) Write-Verbose $EPCListEndpointsInGroupResults.ListEndpointsGroupResponse If ($EPCListEndpointsInGroupResults.ListEndpointsGroupResponse -eq 'success') { Return $EPCListEndpointsInGroupResults.EndpointIDList } Else { Throw $EPCListEndpointsInGroupResults.ListEndpointsGroupResponse } } Else { Write-Verbose 'Using XML to pull test group member list' [xml]$SOAPReq = "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' xmlns:urn='urn:telchemyTestGroupManagement'> <soapenv:Header/> <soapenv:Body> <urn:ListEndpointsInGroupWithID> <credentials> <username>$($Global:EPControllerCred.UserName)</username> <password>$($Global:EPControllerCred.GetNetworkCredential().Password)</password> </credentials> <GroupID>$GroupID</GroupID> </urn:ListEndpointsInGroupWithID> </soapenv:Body> </soapenv:Envelope>" Write-Verbose $SOAPReq $SOAPFQDN = "https://$Global:EPControllerFQDN/telchemywebservices/services/telchemyTestGroupManagementService" Write-Verbose $SOAPFQDN [xml]$XMLResponse = (Invoke-WebRequest -Method POST -URI $SOAPFQDN -Body $SOAPReq -ContentType 'text/xml').Content $ListGroupMembersResults = $XMLResponse.Envelope.Body.ListEndpointsInGroupResults If ($ListGroupMembersResults.ListEndpointsGroupResponse -eq 'success') { If ($ListGroupMembersResults.EndpointIDList) { # Only return results if the list exists [psobject]$ListGroupMembers = $ListGroupMembersResults.EndPointIDList | ConvertFrom-XMLElement Return $ListGroupMembers } } Else { Throw $ListGroupMembersResults.ListEndpointsGroupResponse } } } } Function Add-EPCTestGroupMember { <# .SYNOPSIS Adds an EPC test point to an EPC test group .DESCRIPTION Adds an EPC test point to an EPC test group .PARAMETER TestGroupName The name of the test group to add the member to. Use either this or TestGroupID .PARAMETER TestGroupID The ID of the test group to add the member to. Use either this or TestGroupName .PARAMETER ResourceGroupName The name of the resource group associated with this test group .PARAMETER TestPointName The name of the test point to add to the test group .PARAMETER InterfaceID The interface ID of the test point .PARAMETER IPAddress The IP address associated with the given interface .EXAMPLE Add-EPCTestGroupMember -TestGroupName MyTestGroup -ResourceGroupName MyRG -TestPointName 'TFerguson laptop' -InterfaceID 'Primary Network Adapter' -IPAddress '192.168.1.20' Adds the given test point to the MyTestGroup test group .NOTES Version 1.0 #> [cmdletbinding()] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TestGroupName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [int]$TestGroupID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$ResourceGroupName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [int]$ResourceGroupID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias('name')] [string]$TestPointName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias('TestPtID')] [string]$TestPointID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$InterfaceID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$IPAddress, [switch]$UseXML ) Begin { Connect-EPCController If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $EPCCred = New-Object -TypeName EPC.TestGrpMgmt.CredentialsType $EPCCred.username = $Global:EPControllerCred.UserName $EPCCred.password = $Global:EPControllerCred.GetNetworkCredential().Password } Else { $ProgressPreference = 'SilentlyContinue' } } Process { If ($TestGroupName -And !$TestGroupID) { $TestGroupIDList = (Get-EPCTestGroup -SearchString $TestGroupName).GroupID If ($TestGroupIDList.Count -gt 1) { Throw 'Too many results returned for specified test group name. Please refine your search to return a single test group or use TestGroupID parameter.' } Else { [int]$TestGroupID = $TestGroupIDList } } If (!$TestGroupID) { Throw 'Test Group ID not specified or could not be found' } If ($TestPointName -And !$TestPointID) { $TestPointIDList = (Get-EPCTestPoint -Name $TestPointName).TestPtID If ($TestPointIDList.Count -gt 1) { Throw 'Too many results returned for specified TestPoint name. Please refine your search to return a single testpoint or use TestPointID parameter.' } Else { $TestPointID = $TestPointIDList } } If (!$TestPointID) { Throw 'TestPoint ID not specified or could not be found' } If ($ResourceGroupName -And !$ResourcegroupID) { $ResourceGroupID = (Get-EPCResourceGroup | Where-Object {$_.RGName -eq $ResourceGroupName}).RGID } If (!$ResourceGroupID) { Throw 'Resource Group ID not specified or could not be found' } If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { Write-Verbose 'Using WSDL to add test point to group' # Put the endpoint details into an object $AgentDetails = New-Object -TypeName EPC.TestGrpMgmt.AgentType $AgentDetails.AgentID = $TestPointID $AgentDetails.AgentIF = $InterfaceID $AgentDetails.AgentIP = $IPAddress $AgentDetails.AgentRG = $ResourceGroupID $EPCAddEndpointToGroupParams = New-Object -TypeName EPC.TestGrpMgmt.AddEndpointToGroupParametersType $EPCAddEndpointToGroupParams.credentials = $EPCCred $EPCAddEndpointToGroupParams.ToGroupID = $TestGroupID $EPCAddEndpointToGroupParams.Item = $AgentDetails $EPCAddEndpointToGroupResults = $Global:EPCWSDL_TestGrpMgmt.AddEndpointToGroup($EPCAddEndpointToGroupParams) Write-Verbose $EPCAddEndpointToGroupResults.AddEndpointToGroupResponse } Else { Write-Verbose 'Using XML to add test point to group' [xml]$SOAPReq = "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' xmlns:urn='urn:telchemyTestGroupManagement'> <soapenv:Header/> <soapenv:Body> <urn:AddEndpointToGroupParameters> <credentials> <username>$($Global:EPControllerCred.UserName)</username> <password>$($Global:EPControllerCred.GetNetworkCredential().Password)</password> </credentials> <ToGroupID>$TestGroupID</ToGroupID> <Agent> <AgentID>$TestPointID</AgentID> <AgentRG>$ResourceGroupID</AgentRG> <AgentIF>$InterfaceID</AgentIF> <AgentIP>$IPAddress</AgentIP> </Agent> </urn:AddEndpointToGroupParameters> </soapenv:Body> </soapenv:Envelope>" Write-Verbose $SOAPReq $SOAPFQDN = "https://$Global:EPControllerFQDN/telchemywebservices/services/telchemyTestGroupManagementService" Write-Verbose $SOAPFQDN [xml]$XMLResponse = (Invoke-WebRequest -Method POST -URI $SOAPFQDN -Body $SOAPReq -ContentType 'text/xml').Content $EPCAddEndpointToGroupResults = $XMLResponse.Envelope.Body.AddEndpointToGroupResults } If ($EPCAddEndpointToGroupResults.AddEndpointToGroupResponse -eq 'success') { Return $EPCAddEndpointToGroupResults.AddEndpointToGroupResponse } Else { Throw $EPCAddEndpointToGroupResults.AddEndpointToGroupResponse } } } Function Remove-EPCTestGroupMember { <# .SYNOPSIS Removes an EPC test point from an EPC test group .DESCRIPTION Removes an EPC test point from an EPC test group .PARAMETER GroupName The name of the test group to remove the member from .PARAMETER GroupID The ID of the test group to remove the member from .PARAMETER TestPointName The name of the test point to remove from the test group .PARAMETER TestPointID The ID of the test point to remove from the test group .EXAMPLE Remove-EPCTestGroupMember -GroupName MyGroup -TestPointName TPTest Removes TPTest from the MyGroup group .NOTES Version 1.0 #> [cmdletbinding()] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$GroupName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [int]$GroupID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias('Name','EndpointName')] [string]$TestPointName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias('TestPtID','EndpointID')] [string]$TestPointID, [switch]$UseXML ) Begin { Connect-EPCController If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $EPCCred = New-Object -TypeName EPC.TestGrpMgmt.CredentialsType $EPCCred.username = $Global:EPControllerCred.UserName $EPCCred.password = $Global:EPControllerCred.GetNetworkCredential().Password } Else { $ProgressPreference = 'SilentlyContinue' } } Process { If ($GroupName -And !$GroupID) { $GroupIDList = (Get-EPCTestGroup -SearchString $GroupName).GroupID If ($GroupIDList.Count -gt 1) { Throw 'Too many results returned for specified test group name. Please refine your search to return a single test group or use TestGroupID parameter.' } Else { $GroupID = $GroupIDList } } If (!$GroupID) { Throw 'Test Group ID not specified or could not be found' } If ($TestPointName -And !$TestPointID) { $TestPointIDList = (Get-EPCTestPoint -Name $TestPointName).TestPtID If ($TestPointIDList.Count -gt 1) { Throw 'Too many results returned for specified TestPoint name. Please refine your search to return a single testpoint or use TestPointID parameter.' } Else { $TestPointID = $TestPointIDList } } If (!$TestPointID) { Throw 'TestPoint ID not specified or could not be found' } If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { Write-Verbose 'Using WSDL to remove test point from group' $EPCDeleteEndpointFromGroupParams = New-Object -TypeName EPC.TestGrpMgmt.DeleteEndpointFromGroupParametersType $EPCDeleteEndpointFromGroupParams.credentials = $EPCCred $EPCDeleteEndpointFromGroupParams.FromGroupID = $GroupID $EPCDeleteEndpointFromGroupParams.DeleteEndpointID = $TestPointID $EPCDeleteEndpointFromGroupResults = $Global:EPCWSDL_TestGrpMgmt.DeleteEndpointFromGroup($EPCDeleteEndpointFromGroupParams) Write-Verbose $EPCDeleteEndpointFromGroupResults.DeleteEndpointFromGroupResponse } Else { Write-Verbose 'Using XML to remove test point from group' [xml]$SOAPReq = "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' xmlns:urn='urn:telchemyTestGroupManagement'> <soapenv:Header/> <soapenv:Body> <urn:DeleteEndpointFromGroupParameters> <credentials> <username>$($Global:EPControllerCred.UserName)</username> <password>$($Global:EPControllerCred.GetNetworkCredential().Password)</password> </credentials> <DeleteEndpointID>$TestPointID</DeleteEndpointID> <FromGroupID>$GroupID</FromGroupID> </urn:DeleteEndpointFromGroupParameters> </soapenv:Body> </soapenv:Envelope>" Write-Verbose $SOAPReq $SOAPFQDN = "https://$Global:EPControllerFQDN/telchemywebservices/services/telchemyTestGroupManagementService" Write-Verbose $SOAPFQDN [xml]$XMLResponse = (Invoke-WebRequest -Method POST -URI $SOAPFQDN -Body $SOAPReq -ContentType 'text/xml').Content $EPCDeleteEndpointFromGroupResults = $XMLResponse.Envelope.Body.DeleteEndpointFromGroupResults } If ($EPCDeleteEndpointFromGroupResults.DeleteEndpointFromGroupResponse -eq 'success') { Return $EPCDeleteEndpointFromGroupResults.DeleteEndpointFromGroupResponse } Else { Throw $EPCDeleteEndpointFromGroupResults.DeleteEndpointFromGroupResponse } } } Function Find-EPCTestGroupMember { <# .SYNOPSIS Returns the EPC test group that a test point is a member of .DESCRIPTION Returns the EPC test group that a test point is a member of .PARAMETER GroupNamePrefix The starting text of the group names used for the customer .PARAMETER TestPointName The name of the test point to remove from the test group .EXAMPLE Find-EPCTestGroupMember -GroupNamePrefix Contoso -TestPointName TPTest Returns the group name the TPTest testpoint is a member of .NOTES Version 1.0 #> [cmdletbinding()] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$GroupNamePrefix, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias('Name','EndpointName')] [string]$TestPointName ) Begin { $TestGroupList = Get-EPCTestGroup -SearchString $GroupNamePrefix $TGResults = @() } Process { ForEach ($TG in $TestGroupList) { $TGMembers = Get-EPCTestGroupMembers -GroupID $TG.GroupID If ($TestPointName -in $TGMembers.EndpointName) { $TGResults += [pscustomobject][ordered]@{ 'TestPointName' = $TestPointName; 'GroupName' = $TG.GroupName } Break } } } End { Return $TGResults } } ################################################################################################################################################# # # # EPC Test Plan Functions # # # ################################################################################################################################################# Function Get-EPCTestPlan { <# .SYNOPSIS Return a list of EPC test plans .DESCRIPTION Return a list of EPC test plans .EXAMPLE Get-EPCTestPlan .NOTES Version 1.0 #> [cmdletbinding()] Param ( [switch]$UseXML ) Begin { Connect-EPCController If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $EPCCred = New-Object -TypeName EPC.ActiveCtrl.CredentialsType $EPCCred.username = $Global:EPControllerCred.UserName $EPCCred.password = $Global:EPControllerCred.GetNetworkCredential().Password } Else { $ProgressPreference = 'SilentlyContinue' } } Process { If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $EPCListTestPlansParams = New-Object -TypeName EPC.ActiveCtrl.ListTestPlansParametersType $EPCListTestPlansParams.credentials = $EPCCred $TestPlanResults = $Global:EPCWSDL_ActiveCtrl.listTestPlans($EPCListTestPlansParams) Write-Verbose $TestPlanResults.result If ($TestPlanResults.result -eq 'success') { Return $TestPlanResults.TestPlanList } Else { Throw $TestPlanResults.result } } Else { [xml]$SOAPReq = "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' xmlns:urn='urn:telchemyActiveCtrl'> <soapenv:Header/> <soapenv:Body> <urn:listTestPlansParameters> <credentials> <username>$($Global:EPControllerCred.UserName)</username> <password>$($Global:EPControllerCred.GetNetworkCredential().Password)</password> </credentials> </urn:listTestPlansParameters> </soapenv:Body> </soapenv:Envelope>" $SOAPFQDN = "https://$Global:EPControllerFQDN/telchemywebservices/services/telchemyActiveCtrlService" [xml]$XMLResponse = (Invoke-WebRequest -Method POST -URI $SOAPFQDN -Body $SOAPReq -ContentType 'text/xml').Content $TestPlanResults = $XMLResponse.Envelope.Body.listTestPlansResults If ($TestPlanResults.result -eq 'success') { [psobject]$TestPlans = $TestPlanResults.TestPlanList.testplan | ConvertFrom-XMLElement Return $TestPlans } Else { Throw $TestPlanResults.result } } } } ################################################################################################################################################# # # # EPC Service Functions # # # ################################################################################################################################################# Function Get-EPCService { <# .SYNOPSIS Return list of EPC services .DESCRIPTION Return list of EPC services .PARAMETER ServiceClass Limit results to only a specific service class .EXAMPLE Get-EPCService -ServiceClass VOIP Returns service information about all VOIP services .NOTES Version 1.0 #> [cmdletbinding()] Param ( [Parameter(Mandatory=$False)] [ValidateSet('Composite','Interface','IPSec','IPTV','NetApp','NetSVC','NetTrans','Network','Other','System','TCP','VidConf','VOIP', IgnoreCase=$True)] [string]$ServiceClass ) Begin { Connect-EPCController $EPCCred = New-Object -TypeName EPC.SvcMgmt.CredentialsType $EPCCred.username = $Global:EPControllerCred.UserName $EPCCred.password = $Global:EPControllerCred.GetNetworkCredential().Password } Process { $EPCListServicesParams = New-Object -TypeName EPC.SvcMgmt.ListServicesParametersType $EPCListServicesParams.credentials = $EPCCred If ($ServiceClass) { $EPCListServicesParams.serviceClass = $ServiceClass $EPCListServicesParams.serviceClassSpecified = $True } $ServiceResults = $Global:EPCWSDL_SvcMgmt.listServices($EPCListServicesParams) Write-Verbose $ServiceResults.result If ($ServiceResults.result -eq 'success') { Return $ServiceResults.ServiceList } Else { Throw $ServiceResults.result } } } ################################################################################################################################################# # # # EPC Resource Group Functions # # # ################################################################################################################################################# Function Get-EPCResourceGroup { <# .SYNOPSIS Return list of EPC resource groups .DESCRIPTION Return list of EPC resource groups .PARAMETER ParentRGID Limit results to only a specific resource group .EXAMPLE Get-EPCResourceGroup .NOTES Version 1.0 #> [cmdletbinding()] Param ( [Parameter(Mandatory=$False)] [int]$ParentRGID, [Parameter(Mandatory=$False)] [int]$ResultSize = 1000, [switch]$UseXML ) Begin { Connect-EPCController If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $EPCCred = New-Object -TypeName EPC.ResGrpMgmt.CredentialsType $EPCCred.username = $Global:EPControllerCred.UserName $EPCCred.password = $Global:EPControllerCred.GetNetworkCredential().Password } Else { $ProgressPreference = 'SilentlyContinue' } } Process { If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $EPCListResourceGroupParams = New-Object -TypeName EPC.ResGrpMgmt.ListResourceGroupParametersType $EPCListResourceGroupParams.credentials = $EPCCred $EPCListResourceGroupParams.maxNumber = $ResultSize If ($ParentRGID) { $RGRelation = New-Object -TypeName EPC.ResGrpMgmt.ResourceGroupRelationshipMapType $RGRelation.RGID = $ParentRGID $FilterType = New-Object -TypeName EPC.ResGrpMgmt.ResourceGroupFilterType $FilterType.ItemElementName = 'RGrelation' $FilterType.Item = $RGRelation $EPCListResourceGroupParams.filter = $FilterType } $ResourceGroupResults = $Global:EPCWSDL_ResGrpMgmt.listResourceGroups($EPCListResourceGroupParams) Write-Verbose $ResourceGroupResults.result If ($ResourceGroupResults.result -eq 'success') { Return $ResourceGroupResults.ResourceGroupList } Else { Throw $ResourceGroupResults.result } } Else { [xml]$SOAPReq = "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' xmlns:urn='urn:telchemyRsrcGroupMgmt'> <soapenv:Header/> <soapenv:Body> <urn:listResourceGroupParameters> <credentials> <username>$($Global:EPControllerCred.UserName)</username> <password>$($Global:EPControllerCred.GetNetworkCredential().Password)</password> </credentials> </urn:listResourceGroupParameters> </soapenv:Body> </soapenv:Envelope>" # Add filter options if entered If ($ParentRGID) { $FilterXMLElement = $SOAPReq.Envelope.Body.listResourceGroupParameters.AppendChild($SOAPReq.CreateElement('filter')) $RGRelationXMLElement = $FilterXMLElement.AppendChild($SOAPReq.CreateElement('RGrelation')) $RGRelationshipXMLElement = $RGRelationXMLElement.AppendChild($SOAPReq.CreateElement('relationship')) $NULL = $RGRelationshipXMLElement.AppendChild($SOAPReq.CreateTextNode('all-children')) $RGIDXMLElement = $RGRelationXMLElement.AppendChild($SOAPReq.CreateElement('RGID')) $NULL = $RGIDXMLElement.AppendChild($SOAPReq.CreateTextNode($ParentRGID)) } $SOAPFQDN = "https://$Global:EPControllerFQDN/telchemywebservices/services/telchemyRsrcGroupMgmtService" Write-Verbose $SOAPFQDN Write-Verbose $SOAPReq.OuterXML [xml]$XMLResponse = (Invoke-WebRequest -Method POST -URI $SOAPFQDN -Body $SOAPReq -ContentType 'text/xml').Content $ResourceGroupResults = $XMLResponse.Envelope.Body.listResourceGroupResults If ($ResourceGroupResults.result -eq 'success') { [psobject]$ResourceGroups = $ResourceGroupResults.ResourceGroupList.RG | ConvertFrom-XMLElement Return $ResourceGroups } Else { Throw $ResourceGroupResults.result } } } } ################################################################################################################################################# # # # EPC Test Instance Functions # # # ################################################################################################################################################# Function Get-EPCTestPointTestInstance { <# .SYNOPSIS Returns information about test point tests .DESCRIPTION Returns information about test point tests. Can be used to show any running tests .PARAMETER UUID The UUID of the test point to retrieve test information about .PARAMETER ExecMode Filter results by either normal or ad hoc tests .PARAMETER Status Filter results by the given status of a test. .EXAMPLE Get-EPCTestPointTestInstance -UUID d4a1437f-1f18-11ec-cf33-0daedf04882a -Status Running Shows running tests associated with the given test point UUID .NOTES Version 1.0 #> [cmdletbinding()] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$UUID, [Parameter(Mandatory=$False)] [ValidateSet('Normal','AdHoc', IgnoreCase=$True)] [string]$ExecMode, [Parameter(Mandatory=$False)] [ValidateSet('Aborted','Cancelling','Cancelled','Completed','Pending','Running','Unknown', IgnoreCase=$True)] [string]$Status, [switch]$UseXML ) Begin { Connect-EPCController If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $EPCCred = New-Object -TypeName EPC.ActiveCtrl.CredentialsType $EPCCred.username = $Global:EPControllerCred.UserName $EPCCred.password = $Global:EPControllerCred.GetNetworkCredential().Password } Else { $ProgressPreference = 'SilentlyContinue' } } Process { If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $EPCListTestPointInstancesParams = New-Object -TypeName EPC.ActiveCtrl.ListTestPointTestInstancesParametersType $EPCListTestPointInstancesParams.credentials = $EPCCred If ($UUID) { $EPCListTestPointInstancesParams.TestPTID = $UUID } If ($ExecMode) { $EPCListTestPointInstancesParams.ExecModeFilter = $ExecMode $EPCListTestPointInstancesParams.ExecModeFilterSpecified = $True } If ($Status) { $EPCListTestPointInstancesParams.StatusFilter = $Status $EPCListTestPointInstancesParams.StatusFilterSpecified = $True } $TestPointInstancesResults = $Global:EPCWSDL_ActiveCtrl.listTestPointTestInstances($EPCListTestPointInstancesParams) Write-Verbose $TestPointInstancesResults.result If ($TestPointInstancesResults.result -eq 'success') { Return $TestPointInstancesResults.TestInstanceList } Else { Throw $TestPointInstancesResults.result } } Else { [xml]$SOAPReq = "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' xmlns:urn='urn:telchemyActiveCtrl'> <soapenv:Header/> <soapenv:Body> <urn:listTestPointTestInstancesParameters> <credentials> <username>$($Global:EPControllerCred.UserName)</username> <password>$($Global:EPControllerCred.GetNetworkCredential().Password)</password> </credentials> </urn:listTestPointTestInstancesParameters> </soapenv:Body> </soapenv:Envelope>" # Add the testpoint ID to the search if entered If ($UUID) { $NewXMLElement = $SOAPReq.Envelope.Body.listTestPointTestInstancesParameters.AppendChild($SOAPReq.CreateElement('testptID')) $NULL = $NewXMLElement.AppendChild($SOAPReq.CreateTextNode($UUID)) } If ($ExecMode) { # WSDL uses adhoc, XML uses ad-hoc If ($ExecMode -eq 'AdHoc') { $ExecModeLower = 'ad-hoc' } Else { $ExecModeLower = 'normal' } $NewXMLElement = $SOAPReq.Envelope.Body.listTestPointTestInstancesParameters.AppendChild($SOAPReq.CreateElement('execModeFilter')) $NULL = $NewXMLElement.AppendChild($SOAPReq.CreateTextNode($ExecModeLower)) } If ($Status) { $NewXMLElement = $SOAPReq.Envelope.Body.listTestPointTestInstancesParameters.AppendChild($SOAPReq.CreateElement('statusFilter')) $NULL = $NewXMLElement.AppendChild($SOAPReq.CreateTextNode($Status.ToLower())) } Write-Verbose $SOAPReq.OuterXML $SOAPFQDN = "https://$Global:EPControllerFQDN/telchemywebservices/services/telchemyActiveCtrlService" Write-Verbose $SOAPFQDN [xml]$XMLResponse = (Invoke-WebRequest -Method POST -URI $SOAPFQDN -Body $SOAPReq -ContentType 'text/xml').Content $TPTestResults = $XMLResponse.Envelope.Body.listTestPointTestInstancesResults If ($TPTestResults.result -eq 'success') { Return $TPTestResults.TestInstanceList.testInstance } Else { Throw $TPTestResults.result } } } } Function Start-EPCTestInstance { <# .SYNOPSIS Starts an EPC test .DESCRIPTION Starts an EPC test .PARAMETER SrcTestPointID The UUID of the originating test point .PARAMETER SrcInterface The name of the originating network interface .PARAMETER SrcIPAddress The orginating test point's IP address .PARAMETER SrcResourceGroup The originating test point's resource group .PARAMETER DstTestPointID The UUID of the target test point .PARAMETER DstInterface The name of the target network interface .PARAMETER DstIPAddress The orginating test point's IP address .PARAMETER DstResourceGroup The target test point's resource group .PARAMETER Service The service name that contains the test to run .PARAMETER TestPlan The test plan name of the test to run .EXAMPLE Start-EPCTestInstance -SrcTestPointID d4a1437f-1f18-11ec-cf33-0daedf04882a -SrcInterface Ethernet -SrcIPAddress 192.168.1.100 -DstTestPointID b4a4437f-4f48-440c-cf33-0ba0bf04882a -DstInterface Wifi -DstIPAddress 192.168.0.43 -Service Contoso -TestPlan P2P_short Stops the given EPC test .NOTES Version 1.0 #> [cmdletbinding()] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$SrcTestPointID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$SrcTestPointName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$DstTestPointID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$DstTestPointName, [switch]$UseXML ) DynamicParam { # Create the dictionary $RuntimeParamDict = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary $ParamList = @() $Params = [pscustomobject][ordered]@{'ParamName' = 'SrcInterface'; 'Prefix' = 'SIF'; 'ParamRequired' = $FALSE; 'Position' = 2} $ParamList += $Params $Params = [pscustomobject][ordered]@{'ParamName' = 'SrcIPAddress'; 'Prefix' = 'SIP'; 'ParamRequired' = $FALSE; 'Position' = 3} $ParamList += $Params $Params = [pscustomobject][ordered]@{'ParamName' = 'SrcResourceGroup'; 'Prefix' = 'SRG'; 'ParamRequired' = $FALSE; 'Position' = 4} $ParamList += $Params $Params = [pscustomobject][ordered]@{'ParamName' = 'DstInterface'; 'Prefix' = 'DIF'; 'ParamRequired' = $FALSE; 'Position' = 5} $ParamList += $Params $Params = [pscustomobject][ordered]@{'ParamName' = 'DstIPAddress'; 'Prefix' = 'DIP'; 'ParamRequired' = $FALSE; 'Position' = 6} $ParamList += $Params $Params = [pscustomobject][ordered]@{'ParamName' = 'DstResourceGroup'; 'Prefix' = 'DRG'; 'ParamRequired' = $FALSE; 'Position' = 7} $ParamList += $Params $Params = [pscustomobject][ordered]@{'ParamName' = 'TestPlan'; 'Prefix' = 'TP'; 'ParamRequired' = $FALSE; 'Position' = 8} $ParamList += $Params $Params = [pscustomobject][ordered]@{'ParamName' = 'Service'; 'Prefix' = 'SRV'; 'ParamRequired' = $FALSE; 'Position' = 9} $ParamList += $Params # Initialize parameter list ForEach ($Param In $ParamList) { New-DynamicParam -ParamName $Param.ParamName -Prefix $Param.Prefix -ParamRequired $Param.ParamRequired -Position $Param.Position } If ($SrcTestPointID) { # Generate and set the ValidateSet $SrcTestPointIFList = Get-EPCTestPointInterface -UUID $SrcTestPointID | Where-Object {$_.Status -Contains 'ready' -And ($_.Status -Contains 'hasroute' -Or $_.Status -Contains 'has route')} $ValidateSetAttrib_SIF = New-Object System.Management.Automation.ValidateSetAttribute($SrcTestPointIFList.name) If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $SrcTestPointIPList = Get-EPCTestPointInterface -UUID $SrcTestPointID | Where-Object {$_.Status -Contains 'ready' -And ($_.Status -Contains 'hasroute' -Or $_.Status -Contains 'has route')} | ForEach-Object { $_.IFDetail | Select-Object -ExpandProperty Item } } Else { $SrcTestPointIPList = Get-EPCTestPointInterface -UUID $SrcTestPointID | Where-Object {$_.Status -Contains 'ready' -And ($_.Status -Contains 'hasroute' -Or $_.Status -Contains 'has route')} | ForEach-Object { ($_.IFDetail | Select-Object IPAddress).IPAddress } } $ValidateSetAttrib_SIP = New-Object System.Management.Automation.ValidateSetAttribute($SrcTestPointIPList) $SrcTestPointRGList = (Get-EPCTestPoint | Where-Object {$_.testptID -eq $SrcTestPointID} | Select-Object RGList).RGList If ($SrcTestPointRGList.GetType().FullName -eq 'System.Xml.XmlElement') { $SrcTestPointRGList = $SrcTestPointRGList.rg } # Account for differences in XML vs WSDL $ValidateSetAttrib_SRG = New-Object System.Management.Automation.ValidateSetAttribute($SrcTestPointRGList) } If ($DstTestPointID) { # Generate and set the ValidateSet $DstTestPointIFList = Get-EPCTestPointInterface -UUID $DstTestPointID | Where-Object {$_.Status -Contains 'ready' -And ($_.Status -Contains 'hasroute' -Or $_.Status -Contains 'has route')} $ValidateSetAttrib_DIF = New-Object System.Management.Automation.ValidateSetAttribute($DstTestPointIFList.name) If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $DstTestPointIPList = Get-EPCTestPointInterface -UUID $DstTestPointID | Where-Object {$_.Status -Contains 'ready' -And ($_.Status -Contains 'hasroute' -Or $_.Status -Contains 'has route')} | ForEach-Object { $_.IFDetail | Select-Object -ExpandProperty Item } } Else { $DstTestPointIPList = Get-EPCTestPointInterface -UUID $DstTestPointID | Where-Object {$_.Status -Contains 'ready' -And ($_.Status -Contains 'hasroute' -Or $_.Status -Contains 'has route')} | ForEach-Object { ($_.IFDetail | Select-Object IPAddress).IPAddress } } $ValidateSetAttrib_DIP = New-Object System.Management.Automation.ValidateSetAttribute($DstTestPointIPList) $DstTestPointRGList = (Get-EPCTestPoint | Where-Object {$_.testptID -eq $DstTestPointID} | Select-Object RGList).RGList If ($DstTestPointRGList.GetType().FullName -eq 'System.Xml.XmlElement') { $DstTestPointRGList = $DstTestPointRGList.rg } # Account for differences in XML vs WSDL $ValidateSetAttrib_DRG = New-Object System.Management.Automation.ValidateSetAttribute($DstTestPointRGList) $TestPlanList = Get-EPCTestPlan $ValidateSetAttrib_PT = New-Object System.Management.Automation.ValidateSetAttribute($TestPlanList.name) $ServiceList = Get-EPCService $ValidateSetAttrib_SRV = New-Object System.Management.Automation.ValidateSetAttribute($ServiceList.name) } # Add the ValidateSet to the attributes collection $AttribColl_SIF.Add($ValidateSetAttrib_SIF) $AttribColl_SIP.Add($ValidateSetAttrib_SIP) $AttribColl_SRG.Add($ValidateSetAttrib_SRG) $AttribColl_DIF.Add($ValidateSetAttrib_DIF) $AttribColl_DIP.Add($ValidateSetAttrib_DIP) $AttribColl_DRG.Add($ValidateSetAttrib_DRG) $AttribColl_TP.Add($ValidateSetAttrib_PT) $AttribColl_SRV.Add($ValidateSetAttrib_SRV) # Create and return the dynamic parameter $RunTimeParam_SIF = New-Object System.Management.Automation.RuntimeDefinedParameter($ParamName_SIF, [string], $AttribColl_SIF) $RunTimeParam_SIP = New-Object System.Management.Automation.RuntimeDefinedParameter($ParamName_SIP, [string], $AttribColl_SIP) $RunTimeParam_SRG = New-Object System.Management.Automation.RuntimeDefinedParameter($ParamName_SRG, [string], $AttribColl_SRG) $RunTimeParam_DIF = New-Object System.Management.Automation.RuntimeDefinedParameter($ParamName_DIF, [string], $AttribColl_DIF) $RunTimeParam_DIP = New-Object System.Management.Automation.RuntimeDefinedParameter($ParamName_DIP, [string], $AttribColl_DIP) $RunTimeParam_DRG = New-Object System.Management.Automation.RuntimeDefinedParameter($ParamName_DRG, [string], $AttribColl_DRG) $RunTimeParam_TP = New-Object System.Management.Automation.RuntimeDefinedParameter($ParamName_TP, [string], $AttribColl_TP) $RunTimeParam_SRV = New-Object System.Management.Automation.RuntimeDefinedParameter($ParamName_SRV, [string], $AttribColl_SRV) $RuntimeParamDict.Add($ParamName_SIF, $RunTimeParam_SIF) $RuntimeParamDict.Add($ParamName_SIP, $RunTimeParam_SIP) $RuntimeParamDict.Add($ParamName_SRG, $RunTimeParam_SRG) $RuntimeParamDict.Add($ParamName_DIF, $RunTimeParam_DIF) $RuntimeParamDict.Add($ParamName_DIP, $RunTimeParam_DIP) $RuntimeParamDict.Add($ParamName_DRG, $RunTimeParam_DRG) $RuntimeParamDict.Add($ParamName_TP, $RunTimeParam_TP) $RuntimeParamDict.Add($ParamName_SRV, $RunTimeParam_SRV) Return $RuntimeParamDict } Begin { Connect-EPCController # If no parameters were entered, then bring up the UI If ($PSBoundParameters.Count -eq 0 -Or $SrcTestPointName -Or $DstTestPointName) { [bool]$Script:SubmitClicked = $False $Script:SrcIPAddress = $NULL $Script:DstIPAddress = $NULL # Check to see if the module was installed to determine the location of the gui.xaml file $N10Path = (Get-Module -Name Nectar10 -ErrorAction:SilentlyContinue).Path If ($N10Path) { $N10Path = (Get-Item $N10Path).DirectoryName } Else { $N10Path = '.' } $inputXML = Get-Content "$N10Path\epc_starttest_gui.xaml" $inputXML = $inputXML -replace 'mc:Ignorable="d"','' -replace "x:N",'N' -replace '^<Win.*', '<Window' [void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework') [xml]$XAML = $inputXML #Read XAML $Reader=(New-Object System.Xml.XmlNodeReader $xaml) Try { $Form=[Windows.Markup.XamlReader]::Load( $Reader ) } Catch { Throw "Unable to parse XML, with error: $($Error[0])`n Ensure that there are NO SelectionChanged or TextChanged properties in your textboxes (PowerShell cannot process them)" } #=========================================================================== # Load XAML Objects In PowerShell #=========================================================================== $xaml.SelectNodes("//*[@Name]") | ForEach-Object { Try { Set-Variable -Name "$($_.Name)" -Value $Form.FindName($_.Name) -ErrorAction Stop } Catch { Throw } } # Populate the Test Plan List $TestPlanList = Get-EPCTestPlan | Select-Object Name, testPlanID | Sort-Object Name ForEach ($TestPlan in $TestPlanList) { [void] $TP_DropBox.Items.Add($TestPlan.name) } # Grab the default value $Script:TestPlanID = $TestPlanList[0].testPlanID # Populate the Resource Group List Try { $RGList = Get-EPCResourceGroup | Select-Object RGName, RGID | Sort-Object RGName ForEach ($RG in $RGList) { [void] $RG_DropBox.Items.Add($RG.RGName) } } Catch { # Hide the RG list dropdown if user doesn't have permission to view $RG_Label.Visibility = 'Hidden' $RG_DropBox.Visibility = 'Hidden' } # Grab the default value $Script:TestPlanID = $TestPlanList[0].testPlanID $IPRegEx = "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$" # Populate the Testpoint list $Script:TPList = Get-EPCTestPoint | Where-Object {$_.status -eq 'Connected'} | Select-Object Name, testptID, RGList | Sort-Object -Property Name ForEach ($TestPoint in $Script:TPList) { [void] $SrcEndpoint_ListBox.Items.Add($TestPoint.name) [void] $DstEndpoint_ListBox.Items.Add($TestPoint.name) } ######################## #Add Event Handlers ######################## $TP_DropBox.add_SelectionChanged({ # Get the selected Test Plan $SelectedTPIndex = $TP_DropBox.SelectedIndex $Script:TestPlanID = $TestPlanList[$SelectedTPIndex].testPlanID }) $RG_DropBox.add_SelectionChanged({ # Get the selected Resource Group and set the test point list $SelectedRGIndex = $RG_DropBox.SelectedIndex $Script:ResourceGroupID = $RGList[$SelectedRGIndex].RGID $Script:TPList = Get-EPCTestPoint -RGID $Script:ResourceGroupID | Where-Object {$_.status -eq 'Connected'} | Select-Object Name, testptID, RGList | Sort-Object -Property Name $SrcEndpoint_ListBox.Items.Clear() $DstEndpoint_ListBox.Items.Clear() $SrcInterface_ListBox.Items.Clear() $DstInterface_ListBox.Items.Clear() $SrcIPAddress_ListBox.Items.Clear() $DstIPAddress_ListBox.Items.Clear() ForEach ($TestPoint in $Script:TPList) { [void] $SrcEndpoint_ListBox.Items.Add($TestPoint.name) [void] $DstEndpoint_ListBox.Items.Add($TestPoint.name) } }) $SrcEndpoint_TextBox.add_LostFocus({ $Script:TPList = Get-EPCTestPoint -RGID $Script:ResourceGroupID -Name $($SrcEndpoint_TextBox.text) | Where-Object {$_.status -eq 'Connected'} | Select-Object Name, testptID, RGList | Sort-Object -Property Name $SrcEndpoint_ListBox.Items.Clear() $SrcInterface_ListBox.Items.Clear() $SrcIPAddress_ListBox.Items.Clear() ForEach ($TestPoint in $Script:TPList) { [void] $SrcEndpoint_ListBox.Items.Add($TestPoint.name) } }) $SrcEndpoint_ListBox.add_SelectionChanged({ # Get the selected endpoint $SelectedEPIndex = $SrcEndpoint_ListBox.SelectedIndex $Script:SrcTestPointID = $Script:TPList[$SelectedEPIndex].testPtID $Script:SrcResourceGroupList = $Script:TPList[$SelectedEPIndex].RGList # Ensure RGList object is a PSObject instead of XML If ($Global:EPC_UseWSDL -eq $False -Or $UseXML) { $Script:SrcResourceGroupList = ForEach($Item in $Script:SrcResourceGroupList.RG) { $Item } } Try { # Populate interface list $SrcInterface_ListBox.Items.Clear() $SrcIPAddress_ListBox.Items.Clear() $SrcInterface_ListBox.IsEnabled = $True $Script:SrcTestPointIFList = Get-EPCTestPointInterface -UUID $Script:SrcTestPointID | Where-Object {$_.Status -Contains 'ready' -And ($_.Status -Contains 'hasroute' -Or $_.Status -Contains 'has route') -And $_.ifID -notlike 'Teredo*'} ForEach ($Interface in $Script:SrcTestPointIFList) { $SrcInterface_ListBox.Items.Add($Interface.name) } # Auto-select the interface if there is only one If (($Script:SrcTestPointIFList | Measure-Object).Count -eq 1) { $SrcInterface_ListBox.SelectedIndex = 0 } # Auto-select the 'Any' interface if available If ($Script:SrcTestPointIFList.Name.Contains('Any')) { $SrcInterface_ListBox.SelectedIndex = $Script:SrcTestPointIFList.Name.IndexOf('Any') } } Catch { $SrcInterface_ListBox.Items.Clear() $SrcIPAddress_ListBox.Items.Clear() $SrcInterface_ListBox.Items.Add("ERROR CONNECTING TO ENDPOINT") $SrcInterface_ListBox.IsEnabled = $False } }) $SrcInterface_ListBox.add_SelectionChanged({ # Get the selected interface $SelectedIFIndex = $SrcInterface_ListBox.SelectedIndex If ($SelectedIFIndex -ge 0) { $Script:SrcInterfaceID = $Script:SrcTestPointIFList[$SelectedIFIndex].IFID # Populate the IP address list If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $SrcTestPointIPList = Get-EPCTestPointInterface -UUID $Script:SrcTestPointID | Where-Object {$_.IFID -eq $Script:SrcInterfaceID} | ForEach-Object { $_.IFDetail } | Select-Object Item | Where-Object {$_.Item -match $IPRegEx -Or $_.Item -like 'Any*'} } Else { $SrcTestPointIPList = Get-EPCTestPointInterface -UUID $Script:SrcTestPointID | Where-Object {$_.IFID -eq $Script:SrcInterfaceID} | ForEach-Object { $_.IFDetail } | Select-Object @{Name='Item';Expression={$_.IPAddress}} | Where-Object {$_.Item -match $IPRegEx -Or $_.Item -like 'Any*'} } $SrcIPAddress_ListBox.Items.Clear() ForEach ($IPAddress in $SrcTestPointIPList) { $SrcIPAddress_ListBox.Items.Add($IPAddress.Item) } # Auto-select the IP address if there is only one (usual case) If (($SrcTestPointIPList | Measure-Object).Count -eq 1 -Or $Script:SrcInterfaceID -eq 'Any') { $SrcIPAddress_ListBox.SelectedIndex = 0 } } }) $SrcIPAddress_ListBox.add_SelectionChanged({ # Get the selected IP address $Script:SrcIPAddress = $SrcIPAddress_ListBox.SelectedValue If ($Script:SrcIPAddress -and $Script:DstIPAddress) { $bSubmit.IsEnabled = $True } Else { $bSubmit.IsEnabled = $False } }) $DstEndpoint_TextBox.add_LostFocus({ $Script:TPList = Get-EPCTestPoint -RGID $Script:ResourceGroupID -Name $($DstEndpoint_TextBox.text) | Where-Object {$_.status -eq 'Connected'} | Select-Object Name, testptID, RGList | Sort-Object -Property Name $DstEndpoint_ListBox.Items.Clear() $DstInterface_ListBox.Items.Clear() $DstIPAddress_ListBox.Items.Clear() ForEach ($TestPoint in $Script:TPList) { [void] $DstEndpoint_ListBox.Items.Add($TestPoint.name) } }) $DstEndpoint_ListBox.add_SelectionChanged({ # Get the selected endpoint $SelectedEPIndex = $DstEndpoint_ListBox.SelectedIndex $Script:DstTestPointID = $Script:TPList[$SelectedEPIndex].testPtID $Script:DstResourceGroupList = $Script:TPList[$SelectedEPIndex].RGList # Ensure RGList object is a PSObject instead of XML If ($Global:EPC_UseWSDL -eq $False -Or $UseXML) { $Script:DstResourceGroupList = ForEach($Item in $Script:DstResourceGroupList.RG) { $Item } } # Populate interface list Try { $DstInterface_ListBox.Items.Clear() $DstIPAddress_ListBox.Items.Clear() $DstInterface_ListBox.IsEnabled = $True $Script:DstTestPointIFList = Get-EPCTestPointInterface -UUID $Script:DstTestPointID | Where-Object {$_.Status -Contains 'ready' -And ($_.Status -Contains 'hasroute' -Or $_.Status -Contains 'has route') -And $_.ifID -notlike 'Teredo*'} ForEach ($Interface in $Script:DstTestPointIFList) { $DstInterface_ListBox.Items.Add($Interface.name) } # Auto-select the interface if there is only one If (($Script:DstTestPointIFList | Measure-Object).Count -eq 1) { $DstInterface_ListBox.SelectedIndex = 0 } # Auto-select the 'Any' interface if available If ($Script:DstTestPointIFList.Name.Contains('Any')) { $DstInterface_ListBox.SelectedIndex = $Script:DstTestPointIFList.Name.IndexOf('Any') } } Catch { $DstInterface_ListBox.Items.Clear() $DstIPAddress_ListBox.Items.Clear() $DstInterface_ListBox.Items.Add("ERROR CONNECTING TO ENDPOINT") $DstInterface_ListBox.IsEnabled = $False } }) $DstInterface_ListBox.add_SelectionChanged({ # Get the selected interface $SelectedIFIndex = $DstInterface_ListBox.SelectedIndex If ($SelectedIFIndex -ge 0) { $Script:DstInterfaceID = $Script:DstTestPointIFList[$SelectedIFIndex].IFID #Populate the IP address list If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $DstTestPointIPList = Get-EPCTestPointInterface -UUID $Script:DstTestPointID | Where-Object {$_.IFID -eq $Script:DstInterfaceID} | ForEach-Object { $_.IFDetail } | Select-Object Item | Where-Object {$_.Item -match $IPRegEx -Or $_.Item -like 'Any*'} } Else { $DstTestPointIPList = Get-EPCTestPointInterface -UUID $Script:DstTestPointID | Where-Object {$_.IFID -eq $Script:DstInterfaceID} | ForEach-Object { $_.IFDetail } | Select-Object @{Name='Item';Expression={$_.IPAddress}} | Where-Object {$_.Item -match $IPRegEx -Or $_.Item -like 'Any*'} } $DstIPAddress_ListBox.Items.Clear() ForEach ($IPAddress in $DstTestPointIPList) { $DstIPAddress_ListBox.Items.Add($IPAddress.Item) } # Auto-select the IP address if there is only one (usual case) If (($DstTestPointIPList | Measure-Object).Count -eq 1 -Or $Script:DstInterfaceID -eq 'Any') { $DstIPAddress_ListBox.SelectedIndex = 0 } } }) $DstIPAddress_ListBox.add_SelectionChanged({ # Get the selected IP address $Script:DstIPAddress = $DstIPAddress_ListBox.SelectedValue If ($Script:DstIPAddress -and $Script:SrcIPAddress) { $bSubmit.IsEnabled = $True $bSubmit.Background = 'PaleGreen' } Else { $bSubmit.IsEnabled = $False } }) $bSubmit.Add_Click({ [bool]$Script:SubmitClicked = $True $form.Close() }) # Select endpoint if it was entered on the command line If ($SrcTestPointName) { $SrcEndpoint_TextBox.Text = $SrcTestPointName # Populate the search box with the name # Limit the listbox to show only entries that match the above text $Script:TPList = Get-EPCTestPoint -RGID $Script:ResourceGroupID -Name $($SrcEndpoint_TextBox.text) | Where-Object {$_.status -eq 'Connected'} | Select-Object Name, testptID, RGList | Sort-Object -Property Name $SrcEndpoint_ListBox.Items.Clear() $SrcInterface_ListBox.Items.Clear() $SrcIPAddress_ListBox.Items.Clear() ForEach ($TestPoint in $Script:TPList) { [void] $SrcEndpoint_ListBox.Items.Add($TestPoint.name) } Try { $SrcEndpoint_ListBox.SelectedIndex = $SrcEndpoint_ListBox.Items.IndexOf($SrcTestPointName) $SrcEndpoint_ListBox.SelectionChanged $SrcEndpoint_ListBox.ScrollIntoView($SrcEndpoint_ListBox.Items.GetItemAt($SrcEndpoint_ListBox.SelectedIndex)) } Catch {} } If ($DstTestPointName) { $DstEndpoint_TextBox.Text = $DstTestPointName # Populate the search box with the name # Limit the listbox to show only entries that match the above text $Script:TPList = Get-EPCTestPoint -RGID $Script:ResourceGroupID -Name $($DstEndpoint_TextBox.text) | Where-Object {$_.status -eq 'Connected'} | Select-Object Name, testptID, RGList | Sort-Object -Property Name $DstEndpoint_ListBox.Items.Clear() $DstInterface_ListBox.Items.Clear() $DstIPAddress_ListBox.Items.Clear() ForEach ($TestPoint in $Script:TPList) { [void] $DstEndpoint_ListBox.Items.Add($TestPoint.name) } Try { $DstEndpoint_ListBox.SelectedIndex = $DstEndpoint_ListBox.Items.IndexOf($DstTestPointName) $DstEndpoint_ListBox.SelectionChanged $DstEndpoint_ListBox.ScrollIntoView($DstEndpoint_ListBox.Items.GetItemAt($DstEndpoint_ListBox.SelectedIndex)) } Catch {} } #Show the Form $form.ShowDialog() | Out-Null If ($Script:SubmitClicked) { $SrcTestPointID = $Script:SrcTestPointID $DstTestPointID = $Script:DstTestPointID # Set the Resource Group for source and target if it was specified If ($Script:ResourceGroupID) { $SrcRG = $Script:ResourceGroupID $DstRG = $Script:ResourceGroupID } Else { # Find a common resource group and use that for the test $CommonRG = Compare-Object -IncludeEqual -ExcludeDifferent $Script:SrcResourceGroupList $Script:DstResourceGroupList $SrcRG = $CommonRG[0].InputObject $DstRG = $CommonRG[0].InputObject } } Else { Break } } # Write-Host "TestPlanID: $Script:TestPlanID" # Write-Host "SrcTPID: $Script:SrcTestPointID" # Write-Host "SrcIFID: $Script:SrcInterfaceID" # Write-Host "SrcIPID: $Script:SrcIPAddress" # Write-Host "DstTPID: $Script:DstTestPointID" # Write-Host "DstIFID: $Script:DstInterfaceID" # Write-Host "DstIPID: $Script:DstIPAddress" # Write-Host "SrcRG: $SrcRG" # Write-Host "DstRG: $DstRG" If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $EPCCred = New-Object -TypeName EPC.ActiveCtrl.CredentialsType $EPCCred.username = $Global:EPControllerCred.UserName $EPCCred.password = $Global:EPControllerCred.GetNetworkCredential().Password } Else { $ProgressPreference = 'SilentlyContinue' } If ($PSBoundParameters.Count -gt 0 -And -Not ($SrcTestPointName -Or $DstTestPointName)) { # Get the array position of the selected environment $SrcIFPos = $SrcTestPointIFList.Name.IndexOf($PsBoundParameters[$ParamName_SIF]) $DstIFPos = $DstTestPointIFList.Name.IndexOf($PsBoundParameters[$ParamName_DIF]) $TPPos = $TestPlanList.Name.IndexOf($PsBoundParameters[$ParamName_TP]) $SRVPos = $ServiceList.Name.IndexOf($PsBoundParameters[$ParamName_SRV]) $SrcInterfaceID = $SrcTestPointIFList[$SrcIFPos].IFID Write-Verbose "Src InterfaceID: $SrcInterfaceID" $SrcIPAddress = $PsBoundParameters[$ParamName_SIP] Write-Verbose "Src IPAddress: $SrcIPAddress" $SrcRG = $PsBoundParameters[$ParamName_SRG] Write-Verbose "Src RG: $SrcRG" $DstInterfaceID = $DstTestPointIFList[$DstIFPos].IFID Write-Verbose "Dst InterfaceID: $DstInterfaceID" $DstIPAddress = $PsBoundParameters[$ParamName_DIP] Write-Verbose "Dst IPAddress: $DstIPAddress" $DstRG = $PsBoundParameters[$ParamName_DRG] Write-Verbose "Dst RG: $DstRG" $TestPlanID = $TestPlanList[$TPPos].TestPlanID Write-Verbose "TestPlanID: $TestPlanID" $ServiceID = $ServiceList[$SRVPos].ServiceID Write-Verbose "ServiceID: $ServiceID" } } Process { If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $EPCSource = New-Object -TypeName EPC.ActiveCtrl.TestPointTargetType $EPCTarget = New-Object -TypeName EPC.ActiveCtrl.TestPointTargetType $EPCSource.TestPointID = $SrcTestPointID $EPCSource.ifID = $SrcInterfaceID $EPCSource.IPAddress = $SrcIPAddress $EPCSource.RG = $SrcRG $EPCTarget.TestPointID = $DstTestPointID $EPCTarget.ifID = $DstInterfaceID $EPCTarget.IPAddress = $DstIPAddress $EPCTarget.RG = $DstRG $EPCStartTestPlanTPtoTPParams = New-Object -TypeName EPC.ActiveCtrl.startTestPlanParamsTPtoTPType $EPCStartTestPlanTPtoTPParams.TestPlanID = $TestPlanID $EPCStartTestPlanTPtoTPParams.Originator = $EPCSource $EPCStartTestPlanTPtoTPParams.TPTarget = $EPCTarget If ($Service) { Write-Verbose "Selected Service: $ServiceID"; $EPCStartTestPlanTPtoTPParams.ServiceID = $ServiceID } $EPCStartTestPlanParams = New-Object -TypeName EPC.ActiveCtrl.startTestPlanParametersType $EPCStartTestPlanParams.credentials = $EPCCred $EPCStartTestPlanParams.Items = $EPCStartTestPlanTPtoTPParams $EPCStartTestPlanParams.ItemsElementName = 'tp2tptest' $StartTestPlanResults = $Global:EPCWSDL_ActiveCtrl.startTestPlan($EPCStartTestPlanParams) Write-Verbose $StartTestPlanResults.result } Else { [xml]$SOAPReq = "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' xmlns:urn='urn:telchemyActiveCtrl'> <soapenv:Header/> <soapenv:Body> <urn:startTestPlanParameters> <credentials> <username>$($Global:EPControllerCred.UserName)</username> <password>$($Global:EPControllerCred.GetNetworkCredential().Password)</password> </credentials> <tp2tpTest> <testplanID>$TestPlanID</testplanID> <originator> <testpointID>$SrcTestPointID</testpointID> <ifID>$SrcInterfaceID</ifID> <ipAddress>$SrcIPAddress</ipAddress> <RG>$SrcRG</RG> </originator> <tpTarget> <testpointID>$DstTestPointID</testpointID> <ifID>$DstInterfaceID</ifID> <ipAddress>$DstIPAddress</ipAddress> <RG>$DstRG</RG> </tpTarget> </tp2tpTest> </urn:startTestPlanParameters> </soapenv:Body> </soapenv:Envelope>" Write-Verbose $SOAPReq.OuterXML $SOAPFQDN = "https://$Global:EPControllerFQDN/telchemywebservices/services/telchemyActiveCtrlService" Write-Verbose $SOAPFQDN [xml]$XMLResponse = (Invoke-WebRequest -Method POST -URI $SOAPFQDN -Body $SOAPReq -ContentType 'text/xml').Content $StartTestPlanResults = $XMLResponse.Envelope.Body.StartTestPlanResults } If ($StartTestPlanResults.result -eq 'success') { Return "Successfully started test`n`rTestPointID: $SrcTestPointID`n`rInstance ID: $($StartTestPlanResults.instanceID)" } Else { Return $StartTestPlanResults.result } } } Function Stop-EPCTestInstance { <# .SYNOPSIS Stops a running EPC test .DESCRIPTION Stops a running EPC test .PARAMETER InstanceID The InstanceID of the test to stop .EXAMPLE Stop-EPCTestInstance -InstanceID MDNkMGQxZGQ4YzVjY2VlYmRkZTBlYjA2MWRlLTg3MjI=< Stops the given EPC test .NOTES Version 1.0 #> [cmdletbinding()] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$InstanceID, [switch]$UseXML ) Begin { Connect-EPCController If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $EPCCred = New-Object -TypeName EPC.ActiveCtrl.CredentialsType $EPCCred.username = $Global:EPControllerCred.UserName $EPCCred.password = $Global:EPControllerCred.GetNetworkCredential().Password } Else { $ProgressPreference = 'SilentlyContinue' } } Process { If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $EPCStopTestPlanParams = New-Object -TypeName EPC.ActiveCtrl.StopTestPlanParametersType $EPCStopTestPlanParams.credentials = $EPCCred $EPCStopTestPlanParams.instanceID = $InstanceID $StopTestPlanResult = $Global:EPCWSDL_ActiveCtrl.stopTestPlan($EPCStopTestPlanParams) } Else { [xml]$SOAPReq = "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' xmlns:urn='urn:telchemyActiveCtrl'> <soapenv:Header/> <soapenv:Body> <urn:stopTestPlanParameters> <credentials> <username>$($Global:EPControllerCred.UserName)</username> <password>$($Global:EPControllerCred.GetNetworkCredential().Password)</password> </credentials> <instanceID>$InstanceID</instanceID> </urn:stopTestPlanParameters> </soapenv:Body> </soapenv:Envelope>" Write-Verbose $SOAPReq.OuterXML $SOAPFQDN = "https://$Global:EPControllerFQDN/telchemywebservices/services/telchemyActiveCtrlService" Write-Verbose $SOAPFQDN [xml]$XMLResponse = (Invoke-WebRequest -Method POST -URI $SOAPFQDN -Body $SOAPReq -ContentType 'text/xml').Content $StopTestPlanResult = $XMLResponse.Envelope.Body.stopTestPlanResults } If ($StopTestPlanResult.result -eq 'success') { Return 'Test stopped successfully' } Else { Throw $StopTestPlanResult.result } } } ################################################################################################################################################# # # # EPC Performance Functions # # # ################################################################################################################################################# Function Get-EPCControllerUUID { [cmdletbinding()] Param ( [switch]$UseXML ) Begin { Get-EPCWebSessionCookie $ProgressPreference = 'SilentlyContinue' } Process { $FQDN = "https://$Global:EPControllerFQDN/components.do?action=4&type=3" $Result = Invoke-RestMethod -Method GET -Uri $FQDN -WebSession $Global:EPCSessionCookie $ControllerUUID = $Result.data.uuid If ($ControllerUUID) { Return $ControllerUUID } Else { Write-Error "Could not obtain controller UUID" } } } Function Get-EPCBulkExportJobs { <# .SYNOPSIS Return list of EPC bulk export jobs .DESCRIPTION Return list of EPC bulk export jobs .EXAMPLE Get-EPCBulkExportJobs Returns a list of all EPC bulk export jobs .NOTES Version 1.0 #> [cmdletbinding()] Param ( [switch]$UseXML ) Begin { Connect-EPCController If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $EPCCred = New-Object -TypeName EPC.BulkExport.CredentialsType $EPCCred.username = $Global:EPControllerCred.UserName $EPCCred.password = $Global:EPControllerCred.GetNetworkCredential().Password } Else { #$ProgressPreference = 'SilentlyContinue' } } Process { If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $EPCListExportJobsParams = New-Object -TypeName EPC.BulkExport.ListExportJobsType $EPCListExportJobsParams.credentials = $EPCCred $ExportJobResults = $Global:EPCWSDL_BulkExport.listExportJobs($EPCListExportJobsParams) Write-Verbose $ExportJobResults.result If ($ExportJobResults.result -eq 'success') { Return $ExportJobResults.jobList } Else { Throw $ExportJobResults.result } } Else { [xml]$SOAPReq = "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' xmlns:urn='urn:telchemyBulkExport'> <soapenv:Header/> <soapenv:Body> <urn:listExportJobsParameters>> <credentials> <username>$($Global:EPControllerCred.UserName)</username> <password>$($Global:EPControllerCred.GetNetworkCredential().Password)</password> </credentials> </urn:listExportJobsParameters> </soapenv:Body> </soapenv:Envelope>" $SOAPFQDN = "https://$Global:EPControllerFQDN/telchemywebservices/services/telchemyBulkExportService" [xml]$XMLResponse = (Invoke-WebRequest -Method POST -URI $SOAPFQDN -Body $SOAPReq -ContentType 'text/xml').Content $ExportJobResults = $XMLResponse.Envelope.Body.listExportJobsResults If ($ExportJobResults.result -eq 'success') { #[psobject]$JobList = $ExportJobResults.jobList.job | ConvertFrom-XMLElement Return $ExportJobResults.jobList.job } Else { Throw $ExportJobResults.result } } } } Function Get-EPCBulkExportJobStatus { <# .SYNOPSIS Return the status of a given EPC bulk export job .DESCRIPTION Return the status of a given EPC bulk export job .PARAMETER JobID The JobID of the bulk export job to retrieve status .EXAMPLE Get-EPCBulkExportJobStatus -JobID 1 Returns the export job status of bulk export job ID 1 .NOTES Version 1.0 #> [cmdletbinding()] Param ( [Parameter(Mandatory=$True)] [int]$JobID, [switch]$UseXML ) Begin { Connect-EPCController If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $EPCCred = New-Object -TypeName EPC.SvcMgmt.CredentialsType $EPCCred.username = $Global:EPControllerCred.UserName $EPCCred.password = $Global:EPControllerCred.GetNetworkCredential().Password } Else { $ProgressPreference = 'SilentlyContinue' } } Process { If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { $EPCBulkExportStatusParams = New-Object -TypeName EPC.SvcMgmt.statusExportJobParameters> $EPCBulkExportStatusParams.credentials = $EPCCred $EPCBulkExportStatusParams.jobID = $JobID $ServiceResults = $Global:EPCWSDL_SvcMgmt.listServices($EPCListServicesParams) Write-Verbose $ServiceResults.result If ($ServiceResults.result -eq 'success') { Return $ServiceResults.ServiceList } Else { Throw $ServiceResults.result } } Else { [xml]$SOAPReq = "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' xmlns:urn='urn:telchemySvcMgmt'> <soapenv:Header/> <soapenv:Body> <urn:listServicesParameters> <credentials> <username>$($Global:EPControllerCred.UserName)</username> <password>$($Global:EPControllerCred.GetNetworkCredential().Password)</password> </credentials> </urn:listServicesParameters> </soapenv:Body> </soapenv:Envelope>" # Add the testpoint ID to the search if entered If ($ServiceClass) { $NewXMLElement = $SOAPReq.Envelope.Body.listServicesParameters.AppendChild($SOAPReq.CreateElement('serviceClass')) $NULL = $NewXMLElement.AppendChild($SOAPReq.CreateTextNode($ServiceClass.ToLower())) } $SOAPFQDN = "https://$Global:EPControllerFQDN/telchemywebservices/services/telchemySvcMgmtService" [xml]$XMLResponse = (Invoke-WebRequest -Method POST -URI $SOAPFQDN -Body $SOAPReq -ContentType 'text/xml').Content $ServiceResults = $XMLResponse.Envelope.Body.listServicesResults If ($ServiceResults.result -eq 'success') { [psobject]$Services = $ServiceResults.ServiceList.service | ConvertFrom-XMLElement Return $Services } Else { Throw $ServiceResults.result } } } } Function Get-EPCControllerPerfData { <# .SYNOPSIS Returns a list of performance metrics for a given EPC controller .DESCRIPTION Returns a list of performance metrics for a given EPC controller .PARAMETER ResultSize The number of results to return. Defaults to 1000. .EXAMPLE Get-EPCControllerPerfData .NOTES Version 1.0 #> [cmdletbinding()] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias("testptID")] [string]$TestPointID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TestPointName, [Parameter(Mandatory=$False)] [ValidateSet('LAST_HOUR','LAST_DAY','LAST_WEEK','LAST_MONTH','CUSTOM', IgnoreCase=$True)] [string]$TimePeriod = 'LAST_DAY', [Parameter(Mandatory=$False)] [Alias("StartDateFrom")] [datetimeoffset]$TimePeriodFrom, [Parameter(Mandatory=$False)] [Alias("StartDateTo")] [datetimeoffset]$TimePeriodTo, [Parameter(Mandatory=$False)] [ValidateSet('Reporter','Controller','Agent', IgnoreCase=$True)] [string]$AppType, [Parameter(Mandatory=$False)] [string]$SortColumn = 'evt_date', [Parameter(Mandatory=$False)] [int]$ResultSize = 1000 ) Begin { Get-EPCWebSessionCookie $ProgressPreference = 'SilentlyContinue' } Process { # Get the time period to return stats for Switch ($TimePeriod) { 'LAST_HOUR' { $TimePeriodFrom = [DateTimeOffset]::Now.AddHours(-1); $TimePeriodTo = [DateTimeOffset]::Now } 'LAST_DAY' { $TimePeriodFrom = [DateTimeOffset]::Now.AddDays(-1); $TimePeriodTo = [DateTimeOffset]::Now } 'LAST_WEEK' { $TimePeriodFrom = [DateTimeOffset]::Now.AddDays(-7); $TimePeriodTo = [DateTimeOffset]::Now } 'LAST_MONTH' { $TimePeriodFrom = [DateTimeOffset]::Now.AddMonths(-1); $TimePeriodTo = [DateTimeOffset]::Now } } If (!$TimePeriodTo) { $TimePeriodTo = [DateTimeOffset]::Now } $Body.Add('beginsecs', $TimePeriodFrom.ToUnixTimeSeconds()) $Body.Add('endsecs', $TimePeriodTo.ToUnixTimeSeconds()) # If Testpoint name/id not provided, assume we're getting controller stats If (!$TestPointID -and !$TestPointName) { # Get the UUID of the controller $FQDN = "https://$Global:EPControllerFQDN/components.do?action=4&type=3" $Result = Invoke-RestMethod -Method GET -Uri $FQDN -WebSession $Global:EPCSessionCookie $ControllerUUID = $Result.data.uuid # Get the database response time } } } Function Get-EPCLicense { <# .SYNOPSIS Returns a list of EPC licenses .DESCRIPTION Returns a list of EPC licenses .EXAMPLE Get-EPCLicense .NOTES Version 1.0 #> [cmdletbinding()] Param ( [switch]$UseXML ) Begin { Get-EPCWebSessionCookie $ProgressPreference = 'SilentlyContinue' } Process { $LicFQDN = "https://$Global:EPControllerFQDN/admin/licensecfg.do?action=12" $LicResult = Invoke-RestMethod -Method GET -Uri $LicFQDN -WebSession $Global:EPCSessionCookie $FormattedResults = $LicResult.pts | Select-Object ` @{Name='Product';Expression={$_.product}},` @{Name='LicenseKey';Expression={$_.lk}},` @{Name='LastUpdate';Expression={(([System.DateTimeOffset]::FromUnixTimeSeconds($_.update)).LocalDateTime).ToString("yyyy-MM-dd HH:mm:ss")}},` @{Name='Expiry';Expression={If ($_.expiry -gt 0) { (([System.DateTimeOffset]::FromUnixTimeSeconds($_.expiry)).LocalDateTime).ToString("yyyy-MM-dd HH:mm:ss")}}},` status, host, id, ` @{Name='AutoAssign';Expression={[regex]::Match($_.aV, 'autoAssign=(true|false)').captures.Groups[1].value }} } End { Return $FormattedResults } } Function Get-EPCLicenseDetails { <# .SYNOPSIS Returns details about a given EPC license .DESCRIPTION Returns details about a given EPC license. Accepts pipeline input from Get-EPCLicense .PARAMETER LicenseKey The license key of the license to retrieve .PARAMETER ID The id of the license to retrieve .EXAMPLE Get-EPCLicenseDetails -LicenseKey abcdef -ID 1 Returns EPC license details about license key abcdef .EXAMPLE Get-EPCLicense | Where {$_.Product -like 'DVQ*'} | Get-EPCLicenseDetails Returns detailed license information about any license that starts with DVQ .NOTES Version 1.0 #> [cmdletbinding()] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$LicenseKey, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$ID, [switch]$UseXML ) Begin { Get-EPCWebSessionCookie $ProgressPreference = 'SilentlyContinue' [System.Collections.ArrayList]$LicMembers = @() } Process { Write-Verbose "LicenseKey: $LicenseKey" Write-Verbose "ID: $ID" $LicFQDN = "https://$Global:EPControllerFQDN/admin/viewlic.htm?licensekey=$LicenseKey&lkeyid=$ID" If ($PSItem.AutoAssign) { $LicFQDN = $LicFQDN + "&autoAssign=$($PSItem.AutoAssign)" } # For whatever reason, the value of the AutoAssign parameter is passed to the next page via the first page. Write-Verbose $LicFQDN $LicResult = Invoke-WebRequest -Method GET -Uri $LicFQDN -WebSession $Global:EPCSessionCookie $Item = [PSCustomObject][Ordered]@{} # Parse out tables and focus on the table which contains all the data we want If ($Global:EPC_UseWSDL -And $UseXML -eq $False) { Write-Verbose 'Using WSDL method' $Tables = $LicResult.ParsedHtml.body.getElementsByTagName('Table') $LicMemberRows = $Tables[0].rows | Select-Object InnerHTML # Get the row data innerHTML, which contains the data we want } Else { # PS v.6+ doesn't have ParsedHtml in Invoke-WebRequest, so we have to use different method $Tables = $LicResult.Content | ConvertFrom-Html $Tables = $Tables.SelectNodes("//table") $LicMemberRows = $Tables[0].SelectNodes("//tr") } $LicMemberRows = $LicMemberRows[1..($LicMemberRows.count - 2)] # Ignore the first row and the last 2 rows Write-Verbose "RowCount: $($LicMemberRows.Count)" # Parse each row and pull out the data which is in <TD></TD> blocks into a custom object ForEach ($Row in $LicMemberRows) { $RowData = $Row.InnerHTML $RowMatch = [regex]::Match($RowData, '^\s*<(TD|td) [\w=":;% ]+>([\w\ ]+)</(TD|td)>\s+<(TD|td)[\w=":;% ]*>([\w\s\-\.,<>:;&/]+)</(TD|td)>').captures $ItemName = $RowMatch.groups[2].value $ItemValue = $RowMatch.groups[5].value If ($ItemValue -eq ' ') { # If the item value is empty, it might be a date value provided via inline javascript. Try to get the value from there $RowMatchDate = [regex]::Match($RowData, 'var _(expire|update) = (\d+)').captures Try { If ($NULL -ne $RowMatchDate) { $RowMatchDate = $RowMatchDate.groups[2].value If ($RowMatchDate -gt 0) { $ItemValue = (([System.DateTimeOffset]::FromUnixTimeSeconds($RowMatchDate)).LocalDateTime).ToString("yyyy-MM-dd HH:mm:ss") } } } Catch { $ItemValue = '' } } $Item | Add-Member -NotePropertyName $ItemName -NotePropertyValue $ItemValue } $LicMembers += $Item } End { Return $LicMembers } } ################################################################################################################################################# ################################################################################################################################################# ## ## ## Nectar CX Functions ## ## ## ################################################################################################################################################# ################################################################################################################################################# ################################################################################################################################################# # # # CX Connection Functions # # # ################################################################################################################################################# Function Connect-NCXCloud { <# .SYNOPSIS Connects to Nectar CX cloud and store the credentials for later use. .DESCRIPTION Connects to Nectar CX cloud and store the credentials for later use. .PARAMETER CloudFQDN The FQDN of the Nectar CX cloud. .PARAMETER TenantName The name of a Nectar DXP cloud tenant to connect to and use for subsequent commands. Only useful for multi-tenant deployments .PARAMETER Credential The credentials used to access the Nectar DXP UI. Normally in username@domain.com format .PARAMETER StoredCredentialTarget Use stored credentials saved via New-StoredCredential. Requires prior installation of CredentialManager module via Install-Module CredentialManager, and running: Get-Credential | New-StoredCredential -Target MyCXCreds -Persist LocalMachine .PARAMETER EnvFromFile Use a CSV file called NCXEnvList.csv located in the user's default Documents folder to show a list of environments to select from. Run [Environment]::GetFolderPath("MyDocuments") to find your default document folder. This parameter is only available if N10EnvList.csv is found in the user's default Documents folder (ie: C:\Users\username\Documents) Also sets the default stored credential target to use for the selected environment. Requires prior installation and configuration of CredentialManager PS add-in. N10EnvList.csv must have a header with three columns defined as "Environment, DefaultTenant, StoredCredentialTarget". Each environment and StoredCredentialTarget (if used) should be on their own separate lines .EXAMPLE $Cred = Get-Credential Connect-NCX -Credential $cred -CloudFQDN contoso.nectar.services Connects to the contoso.nectar.services Nectar CX cloud using the credentials supplied to the Get-Credential command .EXAMPLE Connect-NCX-CloudFQDN contoso.nectar.services -StoredCredentialTarget MyCXCreds Connects to contoso.nectar.services Nectar CX cloud using previously stored credentials called MyCXCreds .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipeline, Mandatory=$False)] [ValidateScript ({ If ($_ -Match "^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$") { $True } Else { Throw "ERROR: Nectar CX cloud name must be in FQDN format." } })] [string]$CloudFQDN, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.Credential()] [PSCredential]$Credential, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$StoredCredentialTarget ) DynamicParam { $DefaultDocPath = [Environment]::GetFolderPath("MyDocuments") $EnvPath = "$DefaultDocPath\CXEnvList.csv" If (Test-Path $EnvPath -PathType Leaf) { # Set the dynamic parameters' name $ParameterName = 'EnvFromFile' # Create the dictionary $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary # Create the collection of attributes $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] # Create and set the parameters' attributes $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute $ParameterAttribute.Mandatory = $False $ParameterAttribute.Position = 1 # Add the attributes to the attributes collection $AttributeCollection.Add($ParameterAttribute) # Generate and set the ValidateSet $EnvSet = Import-Csv -Path $EnvPath $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($EnvSet.Environment) # Add the ValidateSet to the attributes collection $AttributeCollection.Add($ValidateSetAttribute) # Create and return the dynamic parameter $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection) $RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter) Return $RuntimeParameterDictionary } } Begin { # Bind the dynamic parameter to a friendly variable If (Test-Path $EnvPath -PathType Leaf) { If ($PsBoundParameters[$ParameterName]) { $CloudFQDN = $PsBoundParameters[$ParameterName] Write-Verbose "CloudFQDN: $CloudFQDN" # Get the array position of the selected environment $EnvPos = $EnvSet.Environment.IndexOf($CloudFQDN) # Check for default tenant in N10EnvList.csv and use if available, but don't override if user explicitly set the TenantName If (!$PsBoundParameters['TenantName']) { $TenantName = $EnvSet[$EnvPos].DefaultTenant Write-Verbose "DefaultTenant: $TenantName" } # Check for stored credential target in N10EnvList.csv and use if available $StoredCredentialTarget = $EnvSet[$EnvPos].StoredCredentialTarget Write-Verbose "StoredCredentialTarget: $StoredCredentialTarget" } } } Process { # Need to force TLS 1.2, if not already set If ([Net.ServicePointManager]::SecurityProtocol -ne 'Tls12') { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } # Ask for the tenant name if global Nectar tenant variable not available and not entered on command line If ((-not $Global:NCXCloud) -And (-not $CloudFQDN)) { $CloudFQDN = Read-Host "Enter the Nectar DXP cloud FQDN" } ElseIf (($Global:NCXCloud) -And (-not $CloudFQDN)) { $CloudFQDN = $Global:NCXCloud } $RegEx = "^(?:http(s)?:\/\/)?([\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+)$" $FQDNMatch = Select-String -Pattern $Regex -InputObject $CloudFQDN $CloudFQDN = $FQDNMatch.Matches.Groups[2].Value # Ask for credentials if global Nectar creds aren't available If (((-not $Global:NCXCred) -And (-not $Credential)) -Or (($Global:NCXCloud -ne $CloudFQDN) -And (-Not $Credential)) -And (-Not $StoredCredentialTarget)) { $Credential = Get-Credential } ElseIf ($Global:NCXCred -And (-not $Credential)) { $Credential = $Global:NCXCred } # Pull stored credentials if specified If ($StoredCredentialTarget) { Try { $Credential = Get-StoredCredential -Target $StoredCredentialTarget } Catch { Write-Error "Cannot find stored credential for target: $StoredCredentialTarget" } } If ((-not $Global:NCXCred) -Or (-not $Global:NCXCloud) -Or ($Global:NCXCloud -ne $CloudFQDN)) { # First check and notify if updated Nectar PS module available [string]$InstalledNectarPSVer = (Get-InstalledModule -Name Nectar10 -ErrorAction SilentlyContinue).Version If ($InstalledNectarPSVer -gt 0) { [string]$LatestNectarPSVer = (Find-Module Nectar10).Version If ($LatestNectarPSVer -gt $InstalledNectarPSVer) { Write-Host "=============== Nectar PowerShell module version $LatestN10Ver available ===============" -ForegroundColor Yellow Write-Host "You are running version $InstalledNectarPSVer. Type " -ForegroundColor Yellow -NoNewLine Write-Host 'Update-Module Nectar10' -ForegroundColor Green -NoNewLine Write-Host ' to update.' -ForegroundColor Yellow } } # Attempt connection to tenant $URI = "https://$CloudFQDN/cyclone-portlet/api/organisation/" Write-Verbose $URI $WebRequest = Invoke-WebRequest -Uri $URI -Method GET -Credential $Credential -UseBasicParsing -SessionVariable NectarSession If ($WebRequest.StatusCode -ne 200) { Write-Error "Could not connect to $CloudFQDN using $($Credential.UserName)" } Else { Write-Host -ForegroundColor Green "Successful connection to " -NoNewLine Write-Host -ForegroundColor Yellow "https://$CloudFQDN" -NoNewLine Write-Host -ForegroundColor Green " using " -NoNewLine Write-Host -ForegroundColor Yellow ($Credential).UserName $Global:NCXCloud = $CloudFQDN $Global:NCXCred = $Credential $Global:NCXSession = $NectarSession # If there is only one available tenant, assign that to the NCXTenantName global variable $TenantList = $WebRequest | ConvertFrom-Json If ($TenantList.Count -eq 1) { $Global:NCXTenantName = $TenantList.name $Global:NCXOrgID = $TenantList.ID } } } # Check to see if tenant name was entered and set global variable, if valid. If ($TenantName) { $URI = "https://$Global:NCXCloud/cyclone-portlet/api/organisation/" Write-Verbose $URI $TenantList = Invoke-RestMethod -Method GET -Credential $Global:NCXCred -uri $URI Try { If ($TenantList.name -Contains $TenantName) { $Global:NCXTenantName = ($TenantList | Where-Object {$_.name -eq $TenantName}).name $Global:NCXOrgID = ($TenantList | Where-Object {$_.name -eq $TenantName}).id Write-Host -ForegroundColor Green "Successsfully set the tenant name to " -NoNewLine Write-Host -ForegroundColor Yellow $Global:NCXTenantName -NoNewLine Write-Host -ForegroundColor Green " (OrgID=" -NoNewLine Write-Host -ForegroundColor Yellow $Global:NCXOrgID -NoNewLine Write-Host -ForegroundColor Green "). This tenantname will be used in all subsequent commands." } Else { $TenantList | ForEach-Object{ $TList += ($(If($TList){", "}) + $_.name) } Write-Error "Could not find a tenant with the name $TenantName on https://$Global:NCXCloud. Select one of $TList" } } Catch { Write-Error "Invalid tenant name on https://$Global:NCXCloud" } } ElseIf ($PSBoundParameters.ContainsKey('TenantName')) { # Remove the NCXTenantName global variable only if TenantName is explicitly set to NULL Remove-Variable NCXTenantName -Scope Global -ErrorAction:SilentlyContinue Remove-Variable NCXOrgID -Scope Global -ErrorAction:SilentlyContinue } } } Function Disconnect-NCXCloud { <# .SYNOPSIS Disconnects from any active Nectar CX connection .DESCRIPTION Essentially deletes any stored credentials and FQDN from global variables .EXAMPLE Disconnect-NCXCloud Disconnects from all active connections to Nectar DXP tenants .NOTES Version 1.0 #> [Alias("dnc")] [cmdletbinding()] param () Remove-Variable NCXCred -Scope Global -ErrorAction:SilentlyContinue Remove-Variable NCXCloud -Scope Global -ErrorAction:SilentlyContinue Remove-Variable NCXSession -Scope Global -ErrorAction:SilentlyContinue Remove-Variable NCXTenantName -Scope Global -ErrorAction:SilentlyContinue Remove-Variable NCXOrgID -Scope Global -ErrorAction:SilentlyContinue Write-Verbose "Successfully disconnected from Nectar CX cloud" } Function Get-NCXCloudInfo { <# .SYNOPSIS Shows information about the active Nectar CX connection .DESCRIPTION Shows information about the active Nectar CX connection .EXAMPLE Get-NCXCloud .NOTES Version 1.0 #> [cmdletbinding()] param () $CloudInfo = "" | Select-Object -Property CloudFQDN, Credential $CloudInfo.CloudFQDN = $Global:NCXCloud $CloudInfo.Credential = ($Global:NCXCred).UserName $CloudInfo | Add-Member -TypeName 'Nectar.CloudInfo' Try { $TenantCount = Get-NCXTenantNames If ($TenantCount.Count -gt 1) { If ($Global:NCXTenantName) { $CloudInfo | Add-Member -NotePropertyName 'TenantName' -NotePropertyValue $Global:NCXTenantName $CloudInfo | Add-Member -NotePropertyName 'OrgID' -NotePropertyValue $Global:NCXOrgID } Else { $CloudInfo | Add-Member -NotePropertyName 'TenantName' -NotePropertyValue '<Not Set>' $CloudInfo | Add-Member -NotePropertyName 'OrgID' -NotePropertyValue '<Not Set>' } } } Catch { } Return $CloudInfo } ################################################################################################################################################# # # # Other CX Functions # # # ################################################################################################################################################# Function Get-NCXTenantNames { <# .SYNOPSIS Shows all the available Nectar CX tenants on the cloud host. .DESCRIPTION Shows all the available Nectar CX tenants on the cloud host. Only available for multi-tenant deployments. .EXAMPLE Get-NCXTenantNames .NOTES Version 1.0 #> [cmdletbinding()] [alias('Get-NCXOrganization')] param () Begin { Connect-NCXCloud } Process { Try { $URI = "https://$Global:NCXCloud/cyclone-portlet/api/organisation/" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -WebSession $Global:NCXSession -uri $URI Return $JSON } Catch { Write-Error 'No data found or insufficient permissions.' Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NCXCampaign { <# .SYNOPSIS Shows all Nectar CX campaigns .DESCRIPTION Shows all Nectar CX campaigns .EXAMPLE Get-NCXCampaign .NOTES Version 1.0 #> [cmdletbinding()] param ( [Parameter(Mandatory=$True)] [ValidateSet('RADAR', 'EXPRESS', 'VORTEX', 'CYCLONE', 'INBOUND', IgnoreCase=$True)] [string]$PlanType ) Begin { Connect-NCXCloud } Process { Try { $URI = "https://$Global:NCXCloud/cyclone-portlet/api/campaigns/getCampaigns/$Global:NCXOrgID/$PlanType" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -WebSession $Global:NCXSession -uri $URI Return $JSON } Catch { Write-Error 'No data or insufficient permissions.' Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NCXTestCase { <# .SYNOPSIS Shows all Nectar CX test cases within the tenant .DESCRIPTION Shows all Nectar CX test cases within the tenant .EXAMPLE Get-NCXTestCase .NOTES Version 1.0 #> [cmdletbinding()] param ( [Parameter(Mandatory=$True)] [ValidateSet('RADAR', 'EXPRESS', 'VORTEX', 'CYCLONE', 'INBOUND', IgnoreCase=$True)] [string]$PlanType ) Begin { Connect-NCXCloud } Process { Try { $URI = "https://$Global:NCXCloud/cyclone-portlet/api/testCases/getTestCases/$Global:NCXOrgID/$PlanType" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -WebSession $Global:NCXSession -uri $URI Return $JSON } Catch { Write-Error 'No data or insufficient permissions.' Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NCXAlarm { <# .SYNOPSIS Shows NCX alarms .DESCRIPTION Shows NCX alarms .EXAMPLE Get-NCXAlarm .NOTES Version 1.0 #> [CmdletBinding(PositionalBinding=$False, DefaultParameterSetName = 'Summary')] Param ( [Parameter(Mandatory=$True, ParameterSetName = 'Summary', Position = 0)] [Parameter(Mandatory=$True, ParameterSetName = 'SummaryTime', Position = 0)] [ValidateSet('Organization', 'TestCase', 'CalledNumber', 'TestSuite', IgnoreCase=$True)] [string]$AlarmType, [Parameter(Mandatory=$False, ParameterSetName = 'Summary')] [ValidateSet('LAST_1HR', 'LAST_4HR', 'LAST_12HR', 'LAST_24HR', 'CURRENT_WEEK', 'CURRENT_MONTH', 'LAST_30DAYS', IgnoreCase=$True)] [string]$TimePeriod, [Parameter(Mandatory=$True, ParameterSetName = 'SummaryTime')] [DateTime]$TimePeriodFrom, [Parameter(Mandatory=$True, ParameterSetName = 'SummaryTime')] [DateTime]$TimePeriodTo ) DynamicParam { $ParamDictionary = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary Switch ($AlarmType) { {$_ -in 'Organization', 'CalledNumber'} { # Define parameter attributes for PlanType attribute $ParamAttributes = New-Object -Type System.Management.Automation.ParameterAttribute $ParamAttributes.Mandatory = $True $ParamAttributes.Position = 1 $ValidateSetAttributes = New-Object System.Management.Automation.ValidateSetAttribute('RADAR', 'EXPRESS', 'VORTEX', 'CYCLONE', 'INBOUND') $ParamAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute] $ParamAttributesCollect.Add($ParamAttributes) $ParamAttributesCollect.Add($ValidateSetAttributes) $DynParam1 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter('PlanType', [string], $ParamAttributesCollect) $ParamDictionary.Add('PlanType', $DynParam1) } 'TestCase' { # Define parameter attributes for TestCaseID attribute $ParamAttributes = New-Object -Type System.Management.Automation.ParameterAttribute $ParamAttributes.Mandatory = $True $ParamAttributes.Position = 1 $ParamAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute] $ParamAttributesCollect.Add($ParamAttributes) $DynParam1 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter('TestCaseID', [string], $ParamAttributesCollect) $ParamDictionary.Add('TestCaseID', $DynParam1) } 'CalledNumber' { # Define parameter attributes for CalledNumber attribute $ParamAttributes = New-Object -Type System.Management.Automation.ParameterAttribute $ParamAttributes.Mandatory = $True $ParamAttributes.Position = 2 $ParamAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute] $ParamAttributesCollect.Add($ParamAttributes) $DynParam1 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter('CalledNumber', [string], $ParamAttributesCollect) $ParamDictionary.Add('CalledNumber', $DynParam1) } 'TestSuite' { # Define parameter attributes for TestSuiteID attribute $ParamAttributes = New-Object -Type System.Management.Automation.ParameterAttribute $ParamAttributes.Mandatory = $True $ParamAttributes.Position = 2 $ParamAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute] $ParamAttributesCollect.Add($ParamAttributes) $DynParam1 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter('TestSuiteID', [string], $ParamAttributesCollect) $ParamDictionary.Add('TestSuiteID', $DynParam1) } } Return $ParamDictionary } Begin { Connect-NCXCloud } Process { $Body = @{} Try { Switch ($AlarmType) { 'Organization' { $URI = "https://$Global:NCXCloud/cyclone-portlet/api/email-notification/result/organization/$Global:NCXOrgID/$($PSBoundParameters['PlanType'])"; Break } 'TestCase' { $URI = "https://$Global:NCXCloud/cyclone-portlet/api/email-notification/result/test-case/$($PSBoundParameters['TestCaseID'])"; Break } 'CalledNumber' { $URI = "https://$Global:NCXCloud/cyclone-portlet/api/email-notification/result/called-number/$($PSBoundParameters['CalledNumber'])/$($PSBoundParameters['PlanType'])"; Break } 'TestSuite' { $URI = "https://$Global:NCXCloud/cyclone-portlet/api/email-notification/result/test-suite/$($PSBoundParameters['TestSuiteID'])" } } Write-Verbose $URI If ($TimePeriod) { $Body.Add('duration', $TimePeriod) } If ($TimePeriodFrom) { $Body.Add('startDate', $TimePeriodFrom.ToString('dd-MM-yyyy')) $Body.Add('endDate', $TimePeriodTo.ToString('dd-MM-yyyy')) } $JSON = Invoke-RestMethod -Method GET -WebSession $Global:NCXSession -uri $URI -Body $Body Return $JSON } Catch { Write-Error 'No data or insufficient permissions.' Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NCXHistoricalReport { <# .SYNOPSIS Shows NCX historical reports .DESCRIPTION Shows NCX historical reports .EXAMPLE Get-NCXHistoricalReports .NOTES Version 1.0 #> [CmdletBinding(PositionalBinding=$False, DefaultParameterSetName = 'Summary')] Param ( [Parameter(Mandatory=$True, ParameterSetName = 'Summary', Position = 0)] [Parameter(Mandatory=$True, ParameterSetName = 'SummaryTime', Position = 0)] [ValidateSet('Organization', 'SingleCampaignRun', 'AllCampaignRun', 'Campaign', IgnoreCase=$True)] [string]$ReportType, [Parameter(Mandatory=$False, ParameterSetName = 'Summary')] [ValidateSet('LAST_1HR', 'LAST_4HR', 'LAST_12HR', 'LAST_24HR', 'CURRENT_WEEK', 'CURRENT_MONTH', 'LAST_30DAYS', IgnoreCase=$True)] [string]$TimePeriod, [Parameter(Mandatory=$True, ParameterSetName = 'SummaryTime')] [DateTime]$TimePeriodFrom, [Parameter(Mandatory=$True, ParameterSetName = 'SummaryTime')] [DateTime]$TimePeriodTo ) DynamicParam { $ParamDictionary = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary Switch ($ReportType) { 'Organization' { # Define parameter attributes for PlanType attribute $ParamAttributes = New-Object -Type System.Management.Automation.ParameterAttribute $ParamAttributes.Mandatory = $True $ParamAttributes.Position = 1 $ValidateSetAttributes = New-Object System.Management.Automation.ValidateSetAttribute('RADAR', 'EXPRESS', 'VORTEX', 'CYCLONE', 'INBOUND') $ParamAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute] $ParamAttributesCollect.Add($ParamAttributes) $ParamAttributesCollect.Add($ValidateSetAttributes) $DynParam1 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter('PlanType', [string], $ParamAttributesCollect) $ParamDictionary.Add('PlanType', $DynParam1) Break } {$_ -like '*CampaignRun'} { # Define parameter attributes for TestCaseID attribute $ParamAttributes = New-Object -Type System.Management.Automation.ParameterAttribute $ParamAttributes.Mandatory = $True $ParamAttributes.Position = 1 $ParamAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute] $ParamAttributesCollect.Add($ParamAttributes) $DynParam1 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter('TestSuiteRunResultID', [string], $ParamAttributesCollect) $ParamDictionary.Add('TestSuiteRunResultID', $DynParam1) Break } 'Campaign' { # Define parameter attributes for CalledNumber attribute $ParamAttributes = New-Object -Type System.Management.Automation.ParameterAttribute $ParamAttributes.Mandatory = $True $ParamAttributes.Position = 2 $ParamAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute] $ParamAttributesCollect.Add($ParamAttributes) $DynParam1 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter('CampaignID', [string], $ParamAttributesCollect) $ParamDictionary.Add('CampaignID', $DynParam1) } } Return $ParamDictionary } Begin { Connect-NCXCloud } Process { $Body = @{} Try { Switch ($ReportType) { 'Organization' { $URI = "https://$Global:NCXCloud/cyclone-portlet/api/test-suite-run-result/organisation/$Global:NCXOrgID/$($PSBoundParameters['PlanType'])"; Break } 'SingleCampaignRun' { $URI = "https://$Global:NCXCloud/cyclone-portlet/api/test-suite-run-result/summary/$($PSBoundParameters['TestSuiteRunResultID'])"; Break } 'AllCampaignRun' { $URI = "https://$Global:NCXCloud/cyclone-portlet/api/test-suite-run-result/test-case-result/$($PSBoundParameters['TestSuiteRunResultID'])"; Break } 'Campaign' { $URI = "https://$Global:NCXCloud/cyclone-portlet/api/test-suite-run-result/test-case-run-result/$($PSBoundParameters['CampaignID'])" } } Write-Verbose $URI If ($TimePeriod) { $Body.Add('duration', $TimePeriod) } If ($TimePeriodFrom) { $Body.Add('startDate', $TimePeriodFrom.ToString('dd-MM-yyyy')) $Body.Add('endDate', $TimePeriodTo.ToString('dd-MM-yyyy')) } $JSON = Invoke-RestMethod -Method GET -WebSession $Global:NCXSession -uri $URI -Body $Body Return $JSON.data } Catch { Write-Error 'No data or insufficient permissions.' Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NCXLiveReport { <# .SYNOPSIS Shows CX live report information. .DESCRIPTION Shows CX live report information. .EXAMPLE Get-NCXLiveReport .NOTES Version 1.0 #> [CmdletBinding(PositionalBinding=$False, DefaultParameterSetName = 'Summary')] Param ( [Parameter(Mandatory=$True, ParameterSetName = 'Summary')] [ValidateSet('RADAR', 'EXPRESS', 'VORTEX', 'CYCLONE', 'INBOUND', IgnoreCase=$True)] [string]$PlanType, [Parameter(Mandatory=$True, ParameterSetName = 'Detail')] [ValidateSet('Voice', 'PESQ', 'MOS', IgnoreCase=$True)] [string]$ReportType, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True, ParameterSetName = 'Detail')] [Alias("id")] [string]$DashboardID ) Begin { Connect-NCXCloud } Process { Try { Switch ($ReportType) { 'Voice' { $URI = "https://$Global:NCXCloud/cyclone-portlet/api/dashboard/realtime-voice-channel/$DashboardID"; Break } 'PESQ' { $URI = "https://$Global:NCXCloud/cyclone-portlet/api/dashboard/realtime-pesq/$DashboardID"; Break } 'MOS' { $URI = "https://$Global:NCXCloud/cyclone-portlet/api/dashboard/realtime-mos/$DashboardID"; Break } default { $URI = "https://$Global:NCXCloud/cyclone-portlet/api/dashboard/$Global:NCXOrgID/$PlanType"} } Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -WebSession $Global:NCXSession -uri $URI Return $JSON } Catch { Write-Error 'No data or insufficient permissions.' Get-JSONErrorStream -JSONResponse $_ } } } Function Get-NCXAlarmConfig { <# .SYNOPSIS Shows CX email alarm configuration. .DESCRIPTION Shows CX email alarm configuration. .EXAMPLE Get-NCXAlarmConfig .NOTES Version 1.0 #> [CmdletBinding(PositionalBinding=$False, DefaultParameterSetName = 'Summary')] Param ( [Parameter(Mandatory=$True, ParameterSetName = 'Summary')] [ValidateSet('RADAR', 'EXPRESS', 'VORTEX', 'CYCLONE', 'INBOUND', IgnoreCase=$True)] [string]$PlanType, [Parameter(Mandatory=$True, ParameterSetName = 'Detail')] [ValidateSet('TestCase', 'CalledNumber', 'TestSuite', IgnoreCase=$True)] [string]$AlarmType, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True, ParameterSetName = 'Detail')] [Alias('TestCaseID')] [string]$ID ) Begin { Connect-NCXCloud } Process { Try { Switch ($ReportType) { 'TestCase' { $URI = "https://$Global:NCXCloud/cyclone-portlet/api/email-alarm/test-case/$ID"; Break } 'CalledNumber' { $URI = "https://$Global:NCXCloud/cyclone-portlet/api/email-alarm/called-number/$ID"; Break } 'TestSuite' { $URI = "https://$Global:NCXCloud/cyclone-portlet/api/email-alarm/test-suite/$ID"; Break } default { $URI = "https://$Global:NCXCloud/cyclone-portlet/api/email-alarm/organization/$Global:NCXOrgID/$PlanType"} } Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -WebSession $Global:NCXSession -uri $URI Return $JSON } Catch { Write-Error 'No data or insufficient permissions.' Get-JSONErrorStream -JSONResponse $_ } } } ################################################################################################################################################# # # # WebRTC Functions # # # ################################################################################################################################################# Function Get-NectarWebRTCConfig { <# .SYNOPSIS Return information about an existing WebRTC call data integration with Nectar DXP .DESCRIPTION Return information about an existing WebRTC call data integration with Nectar DXP. Requires a global admin account. Not available to tenant-level admins. .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Get-NectarWebRTCConfig .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 !$TenantName) { $TenantName = $Global:NectarTenantName } # Get the existing WebRTC configuration $URI = "https://$Global:NectarCloud/aapi/configuration/webrtc?tenant=$TenantName" Write-Verbose $URI $Body = (Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader).data Return $Body } } Function Set-NectarWebRTCConfig { <# .SYNOPSIS Modify an existing WebRTC call data integration with Nectar DXP .DESCRIPTION Modify an existing WebRTC call data integration with Nectar DXP. Requires a global admin account. Not available to tenant-level admins. .PARAMETER Platforms One or more platforms to pull WebRTC call data from .PARAMETER DisplayName The internal display name of the WebRTC configuration .PARAMETER RTCCollectionIntervalSecs How often (in seconds) to sample call quality data. Defaults to 60 .PARAMETER LoginRefreshIntervalSecs How often (in seconds) to refresh login credentials for client WebRTC connections. Defaults to 86400 (1 day). .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Set-NectarWebRTCConfig -CertID .NOTES Version 1.0 #> [cmdletbinding(SupportsShouldProcess, ConfirmImpact = 'High')] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [ValidateSet('Teams','Zoom','AirBnB','CiscoWebEx', IgnoreCase=$False)] [string[]]$Platforms, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$DisplayName, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [int]$RTCCollectionIntervalSecs, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [int]$LoginRefreshIntervalSecs, [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 !$TenantName) { $TenantName = $Global:NectarTenantName } # Get the existing WebRTC config details $Body = (Get-NectarWebRTCConfig)[0] $URI = "https://$Global:NectarCloud/aapi/configuration/webrtc/$($Body.id)" # Remove extraneous details $Body.psobject.Properties.Remove('Id') $Body.psobject.Properties.Remove('stateId') $Body.psobject.Properties.Remove('lastUpdatedTime') $Body.psobject.Properties.Remove('clientId') $Body.psobject.Properties.Remove('kafkaTopic') $Body.psobject.Properties.Remove('sourceId') $Body | Add-Member -MemberType NoteProperty -Name 'tenant' -Value $TenantName # Update with entered parameters ForEach ($Param in $PSBoundParameters.GetEnumerator()) { $Body.($Param.key) = $Param.value } $JSONBody = $Body | ConvertTo-Json Write-Verbose $URI Write-Verbose $JSONBody If ($PSCmdlet.ShouldProcess(("Updating Nectar DXP WebRTC config on tenant {0}" -f $TenantName), ("Update Nectar DXP WebRTC config on tenant {0}?" -f $TenantName), 'Nectar DXP WebRTC Config Update')) { Try { $NULL = Invoke-RestMethod -Method PUT -uri $URI -Headers $Global:NectarAuthHeader -Body $JSONBody -ContentType 'application/json; charset=utf-8' } Catch { Get-JSONErrorStream -JSONResponse $_ } } } } Function New-NectarWebRTCConfig { <# .SYNOPSIS Enables WebRTC call data integration with Nectar DXP .DESCRIPTION Enables WebRTC call data integration with Nectar DXP. Requires a global admin account. Not available to tenant-level admins. .PARAMETER Platforms One or more platforms to pull WebRTC call data from .PARAMETER DisplayName The internal display name of the WebRTC configuration .PARAMETER RTCCollectionIntervalSecs How often (in seconds) to sample call quality data. Defaults to 60 .PARAMETER LoginRefreshIntervalSecs How often (in seconds) to refresh login credentials for client WebRTC connections. Defaults to 86400 (1 day). .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE New-NectarWebRTCConfig .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [ValidateSet('Teams','Zoom','AirBnB','CiscoWebEx', IgnoreCase=$False)] [string[]]$Platforms, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$DisplayName = 'WebRTC', [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [int]$RTCCollectionIntervalSecs = 60, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [int]$LoginRefreshIntervalSecs = 86400, [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 !$TenantName) { $TenantName = $Global:NectarTenantName } $URI = "https://$Global:NectarCloud/aapi/configuration/webrtc" # Check for existing Teams/Azure config # Will increment the sourceID if one already exists [int]$WebRTCSourceID = ((Get-NectarWebRTCConfig -ErrorAction:SilentlyContinue).sourceID | Measure-Object -Maximum).Maximum + 1 # Build the JSON body for creating the config $Body = @{ tenant = $TenantName platforms = $Platforms displayName = $DisplayName rtcCollectionIntervalSecs = $RTCCollectionIntervalSecs loginRefreshIntervalSecs = $LoginRefreshIntervalSecs sourceId = $WebRTCSourceID } $JSONBody = $Body | ConvertTo-Json Write-Verbose $URI Write-Verbose $JSONBody Try { $NULL = Invoke-RestMethod -Method POST -uri $URI -Headers $Global:NectarAuthHeader -Body $JSONBody -ContentType 'application/json; charset=utf-8' } Catch { Get-JSONErrorStream -JSONResponse $_ } } } Function Remove-NectarWebRTCConfig { <# .SYNOPSIS Removes an existing WebRTC call data integration from Nectar DXP .DESCRIPTION Removes an existing WebRTC call data integration from Nectar DXP. Requires a global admin account. Not available to tenant-level admins. .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Remove-NectarWebRTCConfig -TenantName contoso .NOTES Version 1.0 #> [cmdletbinding(SupportsShouldProcess, ConfirmImpact = 'High')] 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 !$TenantName) { $TenantName = $Global:NectarTenantName } # Get the existing WebRTC config details $ExistingConfig = (Get-NectarWebRTCConfig)[0] $URI = "https://$Global:NectarCloud/aapi/configuration/webrtc/$($ExistingConfig.id)" $Body = @{ tenant = $TenantName } $JSONBody = $Body | ConvertTo-JSON Write-Verbose $URI If ($PSCmdlet.ShouldProcess(("Deleting Nectar DXP WebRTC config on tenant {0}" -f $TenantName), ("Delete Nectar DXP WebRTC config on tenant {0}?" -f $TenantName), 'Nectar DXP WebRTC Config Deletion')) { Try { $NULL = Invoke-RestMethod -Method DELETE -uri $URI -Headers $Global:NectarAuthHeader -Body $JSONBody -ContentType 'application/json; charset=utf-8' } Catch { Get-JSONErrorStream -JSONResponse $_ } } } } ################################################################################################################################################# # # # Supporting Functions # # # ################################################################################################################################################# Function Convert-NectarNumToTelURI { <# .SYNOPSIS Converts a Nectar formatted number "+12223334444 x200" into a valid TEL uri "+12223334444;ext=200" .DESCRIPTION Converts a Nectar formatted number "+12223334444 x200" into a valid TEL uri "+12223334444;ext=200" .PARAMETER PhoneNumber The phone number to convert to a TEL URI .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Convert-NectarNumToTelsURI "+12224243344 x3344" Converts the above number to a TEL URI .EXAMPLE Get-NectarUnallocatedNumber -LocationName Jericho | Convert-NectarNumToTelURI Returns the next available phone number in the Jericho location in Tel URI format .NOTES Version 1.1 #> Param ( [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName, Mandatory=$true)] [Alias("number")] [string]$PhoneNumber ) $PhoneNumber = "tel:" + $PhoneNumber.Replace(" x", ";ext=") Return $PhoneNumber } Function Get-NectarDefaultTenantName { <# .SYNOPSIS Set the default tenant name for use with other commands. .DESCRIPTION Set the default tenant name for use with other commands. .NOTES Version 1.0 #> [Alias("stne")] Param () If ($Global:NectarTenantName) { # Use globally set tenant name, if one was set and not explicitly included in the command Return $Global:NectarTenantName } ElseIf (!$TenantName -And !$Global:NectarTenantName) { # If a tenant name wasn't set (normal for most connections, set the TenantName variable if only one available $URI = "https://$Global:NectarCloud/aapi/tenant" Write-Verbose $URI $TenantList = Invoke-RestMethod -Method GET -uri $URI -Headers $Global:NectarAuthHeader If ($TenantList.Count -eq 1) { Return $TenantList } Else { $TenantList | ForEach-Object { $TList += ($(if($TList){", "}) + $_) } Write-Error "TenantName was not specified. Select one of $TList" $PSCmdlet.ThrowTerminatingError() Return } } } Function Get-LatLong { <# .SYNOPSIS Returns the geographical coordinates for an address. .DESCRIPTION Returns the geographical coordinates for an address. .PARAMETER Address The address of the location to return information on. Include as much detail as possible. .EXAMPLE Get-LatLong -Address "33 Main Street, Jonestown, NY, USA" Retrieves the latitude/longitude for the selected location .NOTES Version 1.0 #> Param( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [String]$Address ) Begin { $GoogleGeoAPIKey = [System.Environment]::GetEnvironmentVariable('GoogleGeocode_API_Key','user') If(!$GoogleGeoAPIKey) { Write-Host -ForegroundColor Red "You need to register for an API key and save it as persistent environment variable called GoogleGeocode_API_Key on this machine. Follow this link to get an API Key - https://developers.google.com/maps/documentation/geocoding/get-api-key " $GoogleGeoAPIKey = Read-Host "Enter a valid Google API key to be saved as environment variable GoogleGeocode_API_Key" [System.Environment]::SetEnvironmentVariable('GoogleGeocode_API_Key', $GoogleGeoAPIKey,[System.EnvironmentVariableTarget]::User) } } Process { Try { $URI = "https://maps.googleapis.com/maps/api/geocode/json?address=$Address&key=$GoogleGeoAPIKey" Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -Uri $URI -ErrorVariable EV $Status = $JSON.Status [double]$Lat = $JSON.results.geometry.location.lat [double]$Lng = $JSON.results.geometry.location.lng $LatLong = New-Object PSObject If($Status -eq "OK") { $LatLong | Add-Member -type NoteProperty -Name 'Latitude' -Value $Lat $LatLong | Add-Member -type NoteProperty -Name 'Longitude' -Value $Lng } ElseIf ($Status -eq 'ZERO_RESULTS') { $LatLong | Add-Member -type NoteProperty -Name 'Latitude' -Value 0 $LatLong | Add-Member -type NoteProperty -Name 'Longitude' -Value 0 } Else { $ErrorMessage = $JSON.error_message Write-Host -ForegroundColor Yellow "WARNING: Address geolocation failed for the following reason: $Status - $ErrorMessage" $LatLong | Add-Member -type NoteProperty -Name 'Latitude' -Value 0 $LatLong | Add-Member -type NoteProperty -Name 'Longitude' -Value 0 } } Catch { "Something went wrong. Please try again." $ev.message } Return $LatLong } } Function New-DynamicParam { [cmdletbinding()] Param ( [Parameter(Mandatory=$True)] [string]$ParamName, [Parameter(Mandatory=$True)] [string]$Prefix, [Parameter(Mandatory=$True)] [bool]$ParamRequired, [Parameter(Mandatory=$True)] [int]$Position ) Process { # Set the dynamic parameters' name New-Variable -Name "ParamName_$Prefix" -Value $ParamName -Scope Script -Force # Create the collection of attributes $AttribColl_XXX = New-Object System.Collections.ObjectModel.Collection[System.Attribute] # Create and set the parameters' attributes $ParamAttrib_XXX = New-Object System.Management.Automation.ParameterAttribute $ParamAttrib_XXX.Mandatory = $ParamRequired $ParamAttrib_XXX.Position = $Position # Add the attributes to the attributes collection $AttribColl_XXX.Add($ParamAttrib_XXX) New-Variable -Name "AttribColl_$Prefix" -Value $AttribColl_XXX -Scope Script -Force } } Function ConvertFrom-XMLElement { <# .Synopsis Converts named nodes of an element to properties of a PSObject, recursively. .Parameter Element The element to convert to a PSObject. .Parameter SelectXmlInfo Output from the Select-Xml cmdlet. .Inputs Microsoft.PowerShell.Commands.SelectXmlInfo output from Select-Xml. .Outputs System.Management.Automation.PSCustomObject object created from selected XML. .Link Select-Xml .Example Select-Xml /configuration/appSettings/add web.config |ConvertFrom-XmlElement key value --- ----- webPages:Enabled false #> #Requires -Version 3 [CmdletBinding()][OutputType([psobject])] Param( [Parameter(ParameterSetName='Element',Position=0,Mandatory=$true,ValueFromPipeline=$true)][Xml.XmlElement] $Element, [Parameter(ParameterSetName='SelectXmlInfo',Position=0,Mandatory=$true,ValueFromPipeline=$true)] [Microsoft.PowerShell.Commands.SelectXmlInfo]$SelectXmlInfo ) Process { switch($PSCmdlet.ParameterSetName) { SelectXmlInfo { @($SelectXmlInfo | ForEach-Object { [Xml.XmlElement]$_.Node } | ConvertFrom-XmlElement) } Element { if(($Element.SelectNodes('*') | Group-Object Name | Measure-Object).Count -eq 1) { @($Element.SelectNodes('*') |ConvertFrom-XmlElement) } else { $properties = @{} $Element.Attributes | ForEach-Object { [void]$properties.Add($_.Name,$_.Value) } foreach ($node in $Element.ChildNodes | Where-Object { $_.Name -and $_.Name -ne '#whitespace' } ) { $subelements = $node.SelectNodes('*') | Group-Object Name $value = if($node.InnerText -and !$subelements) { $node.InnerText } elseif(($subelements | Measure-Object).Count -eq 1) { @($node.SelectNodes('*') | ConvertFrom-XmlElement) } else { ConvertFrom-XmlElement $node } if(!$properties.Contains($node.Name)) { # new property [void]$properties.Add($node.Name,$value) } else { # property name collision! if($properties[$node.Name] -isnot [Collections.Generic.List[object]]) { $properties[$node.Name] = ([Collections.Generic.List[object]]@($properties[$node.Name],$value)) } else { $properties[$node.Name].Add($value) } } } New-Object PSObject -Property $properties } } } } } Function New-EPCTestPointXML { [cmdletbinding()] Param ( [Parameter(Mandatory=$True)] $Data ) $xmlData = '<testPointList>' ForEach ($Obj in $Data) { $Properties = $Obj | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name $xmlData += '<testPoint>' foreach ($Property in $Properties) { $xmlData += "<$Property>$($Obj.$Property)</$Property>" } $xmlData += '</testPoint>' } $xmlData += '</testPointList>' Return $xmlData } Function Show-GroupAndStats { <# .SYNOPSIS Groups a set of data by one parameter, and shows sum, average, min, max for another numeric parameter (such as duration) .DESCRIPTION Groups a set of data by one parameter, and shows sum, average, min, max for another numeric parameter (such as duration) .PARAMETER InputObject The data to group and sum. Can be pipelined .PARAMETER GroupBy The parameter to group on .PARAMETER SumBy The parameter to calculate numerical statistics. Must be a numeric field .PARAMETER ShowGroupMembers The field to show the members of the field used in the current grouping .PARAMETER ShowSumByAsTimeFormat If the SumBy parameter is in seconds (Duration is an example), format the output as dd.hh:mm:ss instead of seconds .EXAMPLE Get-NectarSession | Show-GroupAndStats -GroupBy CallerLocation -SumBy Duration Will group all calls by caller location and show sum, avg, min, max for the Duration column .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipeline, Mandatory=$True)] [Alias('Input')] [pscustomobject[]]$InputObject, [Parameter(Mandatory=$True)] [string[]]$GroupBy, [Parameter(Mandatory=$True)] [string]$SumBy, [Parameter(Mandatory=$False)] [string]$ShowGroupMembers, [switch]$ShowSumByAsTimeFormat ) # Validate the parameters exist and are the proper format ForEach ($Item in $GroupBy) { If ($NULL -eq ($InputObject | Get-Member $Item)) { Write-Error "$Item is not a valid parameter for the source data" Break } } If ($NULL -eq ($InputObject | Get-Member $SumBy)) { Write-Error "$SumBy is not a valid parameter for the source data" Break } # ElseIf (($InputObject | Get-Member $SumBy).Definition -NotMatch 'int|float|double|decimal') { # Write-Error "$SumBy is not a numeric field" # Break # } # First, use the standard Group-Object to do the grouping. [System.Collections.ArrayList]$Output = @() $InputObject | Group-Object $GroupBy | Sort-Object Count -Descending | ForEach-Object { $RowData = [pscustomobject][ordered] @{} Write-Verbose "Name: $($_.Name)" # If grouping by multiple fields, the 'name' field consists of each of the grouped items separated by commas. # This function splits them apart into their own column ForEach ($Item in $GroupBy) { $ItemName = ($_.Name.Split(',')[$GroupBy.IndexOf($Item)]) If (!$ItemName) { $ItemName = 'Unknown'} # Don't allow for blanks. $RowData | Add-Member -MemberType NoteProperty -Name $Item -Value $ItemName.Trim() } $RowData | Add-Member -MemberType NoteProperty -Name 'Count' -Value $_.Count If ($ShowSumByAsTimeFormat) { $RowData | Add-Member -MemberType NoteProperty -Name "SUM_$SumBy" -Value ([timespan]::FromSeconds(($_.Group | Measure-Object $SumBy -Sum).Sum)) $RowData | Add-Member -MemberType NoteProperty -Name "AVG_$SumBy" -Value ([timespan]::FromSeconds([math]::Round(($_.Group | Measure-Object $SumBy -Average).Average))) $RowData | Add-Member -MemberType NoteProperty -Name "MIN_$SumBy" -Value ([timespan]::FromSeconds(($_.Group | Measure-Object $SumBy -Minimum).Minimum)) $RowData | Add-Member -MemberType NoteProperty -Name "MAX_$SumBy" -Value ([timespan]::FromSeconds(($_.Group | Measure-Object $SumBy -Maximum).Maximum)) } Else { $RowData | Add-Member -MemberType NoteProperty -Name "SUM_$SumBy" -Value ($_.Group | Measure-Object $SumBy -Sum).Sum $RowData | Add-Member -MemberType NoteProperty -Name "AVG_$SumBy" -Value ([math]::Round(($_.Group | Measure-Object $SumBy -Average).Average,2)) $RowData | Add-Member -MemberType NoteProperty -Name "MIN_$SumBy" -Value ($_.Group | Measure-Object $SumBy -Minimum).Minimum $RowData | Add-Member -MemberType NoteProperty -Name "MAX_$SumBy" -Value ($_.Group | Measure-Object $SumBy -Maximum).Maximum } If ($ShowGroupMembers) { $GroupMemberList = $NULL $_.Group.$ShowGroupMembers | ForEach-Object { $GroupMemberList += ($(if($GroupMemberList){','}) + $_) } $RowData | Add-Member -MemberType NoteProperty -Name "$($ShowGroupMembers)_List" -Value $GroupMemberList } $Output += $RowData } Return $Output } Function Get-JSONErrorStream { <# .SYNOPSIS Returns the error text of a JSON stream .DESCRIPTION Returns the error text of a JSON stream .PARAMETER JSONResponse The error response .EXAMPLE Get-JSONErrorStream $_ Returns the error message from a JSON stream that errored out. .NOTES Version 1.0 #> Param( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] $JSONResponse ) $ResponseStream = $JSONResponse.Exception.Response.GetResponseStream() $Reader = New-Object System.IO.StreamReader($ResponseStream) $ResponseBody = $Reader.ReadToEnd() | ConvertFrom-Json Write-Host -ForegroundColor Red -BackgroundColor Black $ResponseBody.errorMessage Write-Host Write-Host } Function New-Chart { <# .SYNOPSIS Creates a chart PNG file based on the input data .DESCRIPTION Creates a chart PNG file based on the input data. ONLY WORKS IN MS WINDOWS .PARAMETER InputData The data source to use for the chart. Can be either a variable or a command enclosed in brackets .PARAMETER TimeObjectName The name of the data column to use for the time (x-axis) .PARAMETER BarNames The names of the bars to display separated by commas. Must match up with the names of the desired column in the input data .PARAMETER BarColours The colours of the bars to display separated by commas. Colours will be matched up with the BarNames by position. .PARAMETER Interval The interval between numbers to show on the axis. Defaults to auto. .PARAMETER ChartName The name to use for the chart header and the filename. Defaults to the type of chart being generated. .PARAMETER ChartType The chart type to display. Defaults to StackedColumn. .EXAMPLE $Data = Get-NectarSessionCount -TimePeriod LAST WEEK New-Chart -InputData $Data -BarNames Good,Average,Poor -BarColors Green,Yellow,Red Creates a bar chart using a variable from a previous command .EXAMPLE New-Chart -InputData (Get-NectarSessionCount -TimePeriod LAST WEEK) -BarNames Good,Average,Poor -BarColors Green,Yellow,Red Same results as previous example, but shown as full command written within the New-Chart command .NOTES Version 1.0 #> param ( [Parameter(Mandatory=$True)] [PSCustomObject]$InputData, [Parameter(Mandatory=$True)] [string]$TimeObjectName, [Parameter(Mandatory=$True)] [string[]]$BarNames, [Parameter(Mandatory=$False)] [string[]]$BarColours, [Parameter(Mandatory=$False)] [int32]$Interval, [Parameter(Mandatory=$False)] [string]$ChartName, [Parameter(Mandatory=$False)] [ValidateSet('Area', 'Bar', 'BoxPlot', 'Bubble', 'Column', 'Doughnut', 'Line', 'Pie', 'Point', 'Polar', 'Radar', 'Range', 'RangeBar', 'RangeColumn', 'Spline', 'SplineArea', 'SplineRange', 'StackedArea', 'StackedArea100', 'StackedBar', 'StackedBar100', 'StackedColumn', 'StackedColumn100', 'StepLine', IgnoreCase=$True)] [string]$ChartType = 'StackedColumn' ) [void][Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms.DataVisualization") # Creating chart object # The System.Windows.Forms.DataVisualization.Charting namespace contains methods and properties for the Chart Windows forms control. $ChartObject = New-Object System.Windows.Forms.DataVisualization.Charting.Chart $ChartObject.Width = 2000 $ChartObject.Height = 1000 $ChartObject.BackColor = [System.Drawing.Color]::white # Set Chart title If (!$ChartName) { $ChartName = $ChartType } [void]$ChartObject.Titles.Add("$ChartName for $($InputData[0].TenantName)") $ChartObject.Titles[0].Font = "Arial,13pt" $ChartObject.Titles[0].Alignment = "TopCenter" # Create a chartarea to draw on and add to chart $ChartAreaObject = New-Object System.Windows.Forms.DataVisualization.Charting.ChartArea $ChartAreaObject.Name = "ChartArea1" $ChartAreaObject.AxisY.Title = "Count" $ChartAreaObject.AxisX.Title = "Date" $ChartAreaObject.AxisY.Interval = $Interval $ChartAreaObject.AxisX.Interval = 1 $ChartAreaObject.BackColor = [System.Drawing.Color]::white $ChartObject.ChartAreas.Add($ChartAreaObject) # Creating legend for the chart $ChartLegend = New-Object system.Windows.Forms.DataVisualization.Charting.Legend $ChartLegend.name = "Legend1" $ChartObject.Legends.Add($ChartLegend) ForEach ($Bar in $BarNames) { [void]$ChartObject.Series.Add($Bar) $ChartObject.Series[$Bar].ChartType = $ChartType $ChartObject.Series[$Bar].BorderWidth = 3 $ChartObject.Series[$Bar].IsVisibleInLegend = $true $ChartObject.Series[$Bar].chartarea = "ChartArea1" $ChartObject.Series[$Bar].Legend = "Legend1" If ($BarColours[$BarNames.IndexOf($Bar)]) { $ChartObject.Series[$Bar].color = $BarColours[$BarNames.IndexOf($Bar)] } $InputData | ForEach-Object {$NULL = $ChartObject.Series[$Bar].Points.addxy([datetime]$_.$TimeObjectName, $_.$Bar) } } # Save chart with the Time frame for identifying the usage at the specific time $ChartObject.SaveImage("$($ChartName.Replace(' ','_'))_$($InputData[0].TenantName).png","png") Write-Host "Chart saved as $($ChartName.Replace(' ','_'))_$($InputData[0].TenantName).png" } # From https://github.com/allynl93/getSAMLResponse-Interactive # Unfortunately, it relies on System.Windows.Forms, which uses IE and doesn't work with most modern IDPs Function New-SAMLInteractive { [CmdletBinding()] Param( [Parameter(Mandatory=$true)] [string] $LoginIDP ) Begin{ $RegEx = '(?i)name="SAMLResponse"(?: type="hidden")? value=\"(.*?)\"(?:.*)?\/>' Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Web } Process{ # create window for embedded browser $form = New-Object Windows.Forms.Form $form.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen; $form.Width = 640 $form.Height = 700 $form.showIcon = $false $form.TopMost = $true $web = New-Object Windows.Forms.WebBrowser $web.Size = $form.ClientSize $web.Anchor = "Left,Top,Right,Bottom" $web.ScriptErrorsSuppressed = $true $form.Controls.Add($web) $web.Navigate($LoginIDP) $web.add_Navigating({ If ($web.DocumentText -match "SAMLResponse"){ $_.cancel = $true if ($web.DocumentText -match $RegEx){ $form.Close() $Script:SAMLResponse = $(($Matches[1] -replace '+', '+') -replace '=', '=') } } }) # show browser window, waits for window to close If ([system.windows.forms.application]::run($form) -ne "OK") { If ($null -ne $Script:SAMLResponse){ Write-Output $Script:SAMLResponse $form.Close() Remove-Variable -Name SAMLResponse -Scope Script -ErrorAction SilentlyContinue } Else { throw "SAMLResponse not matched" } } } End{ $form.Dispose() } } Function ParseBool { [CmdletBinding()] param( [Parameter(Position=0)] [System.String]$inputVal ) switch -regex ($inputVal.Trim()) { "^(1|true|yes|on|enabled)$" { Return $True } default { Return $False } } } Function Get-Cookies { [CmdletBinding()] Param( [Parameter(Mandatory=$true)] $CookieContainer ) try { [hashtable] $Table = $CookieContainer.GetType().InvokeMember("m_domainTable", [System.Reflection.BindingFlags]::NonPublic -bor [System.Reflection.BindingFlags]::GetField -bor [System.Reflection.BindingFlags]::Instance, $null, $CookieContainer, @() ) Write-Output $Table.Values.Values } catch { $PSCmdlet.ThrowTerminatingError($_) } } Export-ModuleMember -Alias * -Function * # SIG # Begin signature block # MIIm8QYJKoZIhvcNAQcCoIIm4jCCJt4CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUZb6/E0z/5Z5MArx6mo79X75G # EH2ggiCZMIIFjTCCBHWgAwIBAgIQDpsYjvnQLefv21DiCEAYWjANBgkqhkiG9w0B # AQwFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD # VQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVk # IElEIFJvb3QgQ0EwHhcNMjIwODAxMDAwMDAwWhcNMzExMTA5MjM1OTU5WjBiMQsw # CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu # ZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQw # ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz # 7MKnJS7JIT3yithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS # 5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7 # bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfI # SKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jH # trHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14 # Ztk6MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2 # h4mXaXpI8OCiEhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt # 6zPZxd9LBADMfRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPR # iQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ER # ElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4K # Jpn15GkvmB0t9dmpsh3lGwIDAQABo4IBOjCCATYwDwYDVR0TAQH/BAUwAwEB/zAd # BgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wHwYDVR0jBBgwFoAUReuir/SS # y4IxLVGLp6chnfNtyA8wDgYDVR0PAQH/BAQDAgGGMHkGCCsGAQUFBwEBBG0wazAk # BggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsGAQUFBzAC # hjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURS # b290Q0EuY3J0MEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0 # LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwEQYDVR0gBAowCDAGBgRV # HSAAMA0GCSqGSIb3DQEBDAUAA4IBAQBwoL9DXFXnOF+go3QbPbYW1/e/Vwe9mqyh # hyzshV6pGrsi+IcaaVQi7aSId229GhT0E0p6Ly23OO/0/4C5+KH38nLeJLxSA8hO # 0Cre+i1Wz/n096wwepqLsl7Uz9FDRJtDIeuWcqFItJnLnU+nBgMTdydE1Od/6Fmo # 8L8vC6bp8jQ87PcDx4eo0kxAGTVGamlUsLihVo7spNU96LHc/RzY9HdaXFSMb++h # UD38dglohJ9vytsgjTVgHAIDyyCwrFigDkBjxZgiwbJZ9VVrzyerbHbObyMt9H5x # aiNrIv8SuFQtJ37YOtnwtoeW/VvRXKwYw02fc7cBqZ9Xql4o4rmUMIIGrjCCBJag # AwIBAgIQBzY3tyRUfNhHrP0oZipeWzANBgkqhkiG9w0BAQsFADBiMQswCQYDVQQG # EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl # cnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjIw # MzIzMDAwMDAwWhcNMzcwMzIyMjM1OTU5WjBjMQswCQYDVQQGEwJVUzEXMBUGA1UE # ChMORGlnaUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQg # UlNBNDA5NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMIICIjANBgkqhkiG9w0BAQEF # AAOCAg8AMIICCgKCAgEAxoY1BkmzwT1ySVFVxyUDxPKRN6mXUaHW0oPRnkyibaCw # zIP5WvYRoUQVQl+kiPNo+n3znIkLf50fng8zH1ATCyZzlm34V6gCff1DtITaEfFz # sbPuK4CEiiIY3+vaPcQXf6sZKz5C3GeO6lE98NZW1OcoLevTsbV15x8GZY2UKdPZ # 7Gnf2ZCHRgB720RBidx8ald68Dd5n12sy+iEZLRS8nZH92GDGd1ftFQLIWhuNyG7 # QKxfst5Kfc71ORJn7w6lY2zkpsUdzTYNXNXmG6jBZHRAp8ByxbpOH7G1WE15/teP # c5OsLDnipUjW8LAxE6lXKZYnLvWHpo9OdhVVJnCYJn+gGkcgQ+NDY4B7dW4nJZCY # OjgRs/b2nuY7W+yB3iIU2YIqx5K/oN7jPqJz+ucfWmyU8lKVEStYdEAoq3NDzt9K # oRxrOMUp88qqlnNCaJ+2RrOdOqPVA+C/8KI8ykLcGEh/FDTP0kyr75s9/g64ZCr6 # dSgkQe1CvwWcZklSUPRR8zZJTYsg0ixXNXkrqPNFYLwjjVj33GHek/45wPmyMKVM # 1+mYSlg+0wOI/rOP015LdhJRk8mMDDtbiiKowSYI+RQQEgN9XyO7ZONj4KbhPvbC # dLI/Hgl27KtdRnXiYKNYCQEoAA6EVO7O6V3IXjASvUaetdN2udIOa5kM0jO0zbEC # AwEAAaOCAV0wggFZMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLoW2W1N # hS9zKXaaL3WMaiCPnshvMB8GA1UdIwQYMBaAFOzX44LScV1kTN8uZz/nupiuHA9P # MA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDCDB3BggrBgEFBQcB # AQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggr # BgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1 # c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybDMuZGln # aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmwwIAYDVR0gBBkwFzAI # BgZngQwBBAIwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEBCwUAA4ICAQB9WY7Ak7Zv # mKlEIgF+ZtbYIULhsBguEE0TzzBTzr8Y+8dQXeJLKftwig2qKWn8acHPHQfpPmDI # 2AvlXFvXbYf6hCAlNDFnzbYSlm/EUExiHQwIgqgWvalWzxVzjQEiJc6VaT9Hd/ty # dBTX/6tPiix6q4XNQ1/tYLaqT5Fmniye4Iqs5f2MvGQmh2ySvZ180HAKfO+ovHVP # ulr3qRCyXen/KFSJ8NWKcXZl2szwcqMj+sAngkSumScbqyQeJsG33irr9p6xeZmB # o1aGqwpFyd/EjaDnmPv7pp1yr8THwcFqcdnGE4AJxLafzYeHJLtPo0m5d2aR8XKc # 6UsCUqc3fpNTrDsdCEkPlM05et3/JWOZJyw9P2un8WbDQc1PtkCbISFA0LcTJM3c # HXg65J6t5TRxktcma+Q4c6umAU+9Pzt4rUyt+8SVe+0KXzM5h0F4ejjpnOHdI/0d # KNPH+ejxmF/7K9h+8kaddSweJywm228Vex4Ziza4k9Tm8heZWcpw8De/mADfIBZP # J/tgZxahZrrdVcA6KYawmKAr7ZVBtzrVFZgxtGIJDwq9gdkT/r+k0fNX2bwE+oLe # Mt8EifAAzV3C+dAjfwAL5HYCJtnwZXZCpimHCUcr5n8apIUP/JiW9lVUKx+A+sDy # Divl1vupL0QVSucTDh3bNzgaoSv27dZ8/DCCBrAwggSYoAMCAQICEAitQLJg0pxM # n17Nqb2TrtkwDQYJKoZIhvcNAQEMBQAwYjELMAkGA1UEBhMCVVMxFTATBgNVBAoT # DERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UE # AxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0MB4XDTIxMDQyOTAwMDAwMFoXDTM2 # MDQyODIzNTk1OVowaTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ # bmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBS # U0E0MDk2IFNIQTM4NCAyMDIxIENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC # AgoCggIBANW0L0LQKK14t13VOVkbsYhC9TOM6z2Bl3DFu8SFJjCfpI5o2Fz16zQk # B+FLT9N4Q/QX1x7a+dLVZxpSTw6hV/yImcGRzIEDPk1wJGSzjeIIfTR9TIBXEmtD # mpnyxTsf8u/LR1oTpkyzASAl8xDTi7L7CPCK4J0JwGWn+piASTWHPVEZ6JAheEUu # oZ8s4RjCGszF7pNJcEIyj/vG6hzzZWiRok1MghFIUmjeEL0UV13oGBNlxX+yT4Us # SKRWhDXW+S6cqgAV0Tf+GgaUwnzI6hsy5srC9KejAw50pa85tqtgEuPo1rn3MeHc # reQYoNjBI0dHs6EPbqOrbZgGgxu3amct0r1EGpIQgY+wOwnXx5syWsL/amBUi0nB # k+3htFzgb+sm+YzVsvk4EObqzpH1vtP7b5NhNFy8k0UogzYqZihfsHPOiyYlBrKD # 1Fz2FRlM7WLgXjPy6OjsCqewAyuRsjZ5vvetCB51pmXMu+NIUPN3kRr+21CiRshh # WJj1fAIWPIMorTmG7NS3DVPQ+EfmdTCN7DCTdhSmW0tddGFNPxKRdt6/WMtyEClB # 8NXFbSZ2aBFBE1ia3CYrAfSJTVnbeM+BSj5AR1/JgVBzhRAjIVlgimRUwcwhGug4 # GXxmHM14OEUwmU//Y09Mu6oNCFNBfFg9R7P6tuyMMgkCzGw8DFYRAgMBAAGjggFZ # MIIBVTASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBRoN+Drtjv4XxGG+/5h # ewiIZfROQjAfBgNVHSMEGDAWgBTs1+OC0nFdZEzfLmc/57qYrhwPTzAOBgNVHQ8B # Af8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwdwYIKwYBBQUHAQEEazBpMCQG # CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQQYIKwYBBQUHMAKG # NWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290 # RzQuY3J0MEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydFRydXN0ZWRSb290RzQuY3JsMBwGA1UdIAQVMBMwBwYFZ4EMAQMw # CAYGZ4EMAQQBMA0GCSqGSIb3DQEBDAUAA4ICAQA6I0Q9jQh27o+8OpnTVuACGqX4 # SDTzLLbmdGb3lHKxAMqvbDAnExKekESfS/2eo3wm1Te8Ol1IbZXVP0n0J7sWgUVQ # /Zy9toXgdn43ccsi91qqkM/1k2rj6yDR1VB5iJqKisG2vaFIGH7c2IAaERkYzWGZ # gVb2yeN258TkG19D+D6U/3Y5PZ7Umc9K3SjrXyahlVhI1Rr+1yc//ZDRdobdHLBg # XPMNqO7giaG9OeE4Ttpuuzad++UhU1rDyulq8aI+20O4M8hPOBSSmfXdzlRt2V0C # FB9AM3wD4pWywiF1c1LLRtjENByipUuNzW92NyyFPxrOJukYvpAHsEN/lYgggnDw # zMrv/Sk1XB+JOFX3N4qLCaHLC+kxGv8uGVw5ceG+nKcKBtYmZ7eS5k5f3nqsSc8u # pHSSrds8pJyGH+PBVhsrI/+PteqIe3Br5qC6/To/RabE6BaRUotBwEiES5ZNq0RA # 443wFSjO7fEYVgcqLxDEDAhkPDOPriiMPMuPiAsNvzv0zh57ju+168u38HcT5uco # P6wSrqUvImxB+YJcFWbMbA7KxYbD9iYzDAdLoNMHAmpqQDBISzSoUSC7rRuFCOJZ # DW3KBVAr6kocnqX9oKcfBnTn8tZSkP2vhUgh+Vc7tJwD7YZF9LRhbr9o4iZghurI # r6n+lB3nYxs6hlZ4TjCCBsIwggSqoAMCAQICEAVEr/OUnQg5pr/bP1/lYRYwDQYJ # KoZIhvcNAQELBQAwYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ # bmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2 # IFRpbWVTdGFtcGluZyBDQTAeFw0yMzA3MTQwMDAwMDBaFw0zNDEwMTMyMzU5NTla # MEgxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEgMB4GA1UE # AxMXRGlnaUNlcnQgVGltZXN0YW1wIDIwMjMwggIiMA0GCSqGSIb3DQEBAQUAA4IC # DwAwggIKAoICAQCjU0WHHYOOW6w+VLMj4M+f1+XS512hDgncL0ijl3o7Kpxn3GIV # WMGpkxGnzaqyat0QKYoeYmNp01icNXG/OpfrlFCPHCDqx5o7L5Zm42nnaf5bw9Yr # IBzBl5S0pVCB8s/LB6YwaMqDQtr8fwkklKSCGtpqutg7yl3eGRiF+0XqDWFsnf5x # XsQGmjzwxS55DxtmUuPI1j5f2kPThPXQx/ZILV5FdZZ1/t0QoRuDwbjmUpW1R9d4 # KTlr4HhZl+NEK0rVlc7vCBfqgmRN/yPjyobutKQhZHDr1eWg2mOzLukF7qr2JPUd # vJscsrdf3/Dudn0xmWVHVZ1KJC+sK5e+n+T9e3M+Mu5SNPvUu+vUoCw0m+PebmQZ # BzcBkQ8ctVHNqkxmg4hoYru8QRt4GW3k2Q/gWEH72LEs4VGvtK0VBhTqYggT02ke # fGRNnQ/fztFejKqrUBXJs8q818Q7aESjpTtC/XN97t0K/3k0EH6mXApYTAA+hWl1 # x4Nk1nXNjxJ2VqUk+tfEayG66B80mC866msBsPf7Kobse1I4qZgJoXGybHGvPrhv # ltXhEBP+YUcKjP7wtsfVx95sJPC/QoLKoHE9nJKTBLRpcCcNT7e1NtHJXwikcKPs # CvERLmTgyyIryvEoEyFJUX4GZtM7vvrrkTjYUQfKlLfiUKHzOtOKg8tAewIDAQAB # o4IBizCCAYcwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/ # BAwwCgYIKwYBBQUHAwgwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcB # MB8GA1UdIwQYMBaAFLoW2W1NhS9zKXaaL3WMaiCPnshvMB0GA1UdDgQWBBSltu8T # 5+/N0GSh1VapZTGj3tXjSTBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsMy5k # aWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRSU0E0MDk2U0hBMjU2VGltZVN0 # YW1waW5nQ0EuY3JsMIGQBggrBgEFBQcBAQSBgzCBgDAkBggrBgEFBQcwAYYYaHR0 # cDovL29jc3AuZGlnaWNlcnQuY29tMFgGCCsGAQUFBzAChkxodHRwOi8vY2FjZXJ0 # cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRSU0E0MDk2U0hBMjU2VGlt # ZVN0YW1waW5nQ0EuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCBGtbeoKm1mBe8cI1P # ijxonNgl/8ss5M3qXSKS7IwiAqm4z4Co2efjxe0mgopxLxjdTrbebNfhYJwr7e09 # SI64a7p8Xb3CYTdoSXej65CqEtcnhfOOHpLawkA4n13IoC4leCWdKgV6hCmYtld5 # j9smViuw86e9NwzYmHZPVrlSwradOKmB521BXIxp0bkrxMZ7z5z6eOKTGnaiaXXT # UOREEr4gDZ6pRND45Ul3CFohxbTPmJUaVLq5vMFpGbrPFvKDNzRusEEm3d5al08z # jdSNd311RaGlWCZqA0Xe2VC1UIyvVr1MxeFGxSjTredDAHDezJieGYkD6tSRN+9N # UvPJYCHEVkft2hFLjDLDiOZY4rbbPvlfsELWj+MXkdGqwFXjhr+sJyxB0JozSqg2 # 1Llyln6XeThIX8rC3D0y33XWNmdaifj2p8flTzU8AL2+nCpseQHc2kTmOt44Owde # OVj0fHMxVaCAEcsUDH6uvP6k63llqmjWIso765qCNVcoFstp8jKastLYOrixRoZr # uhf9xHdsFWyuq69zOuhJRrfVf8y2OMDY7Bz1tqG4QyzfTkx9HmhwwHcK1ALgXGC7 # KP845VJa1qwXIiNO9OzTF/tQa/8Hdx9xl0RBybhG02wyfFgvZ0dl5Rtztpn5aywG # Ru9BHvDwX+Db2a2QgESvgBBBijCCBtgwggTAoAMCAQICEAes7BBZfdyz3a9JuWha # irgwDQYJKoZIhvcNAQELBQAwaTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lD # ZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2ln # bmluZyBSU0E0MDk2IFNIQTM4NCAyMDIxIENBMTAeFw0yMzAyMTYwMDAwMDBaFw0y # NjAzMDIyMzU5NTlaMGAxCzAJBgNVBAYTAkNBMRAwDgYDVQQIEwdPbnRhcmlvMQ8w # DQYDVQQHEwZHdWVscGgxFjAUBgNVBAoTDUtlbm5ldGggTGFza28xFjAUBgNVBAMT # DUtlbm5ldGggTGFza28wggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDA # ndWGRHf+/VHW6a7GGRg4rTRiWatX/HWu3/z5RTObazn6akqA8riACugOKM17j9FY # plTRJ9tCX5LeYBL4KtuNvOqa44Fo4LJ042ULHXPGv88EBnp46oElNMAkrCYQ5M7f # senJ/W0xQ0c2gLhQ6UH7Rc6ns4AC9OKADQsk9ZGzHiEnEZ1XkfIXBZbPUSeFiJzf # StW6KC+XI0uqySxKoVtIAIi5mljDmW8rcMPko0okRa0Si5nLvl36FBct7CKqkcge # i1wgmE56rqxCzbtQOcjCVoG0IOUFoXqLLCA96qDAVZZPX+5Z/BgsZERAvA8dA6es # XqZpdEf6rTFVi/5WDgWfshcNa7riTouIfPqJvU5uvvNpB3SzzIH+XmlttuKV8/Ce # 3W7j6IBL1lr8Myt2dZQRpgsWp+WwDUluW0hx3Fi9d4a+1zQyjEf0CTd1oilwcN3W # cTrwLInHylcamPs8gHwNmo23iL7qh/TRhoUATHJNajDppIxH4RDl/P/efqhbRxMC # AwEAAaOCAgMwggH/MB8GA1UdIwQYMBaAFGg34Ou2O/hfEYb7/mF7CIhl9E5CMB0G # A1UdDgQWBBRt1/RRvnadH2iU+273ZlMWw+Wu2DAOBgNVHQ8BAf8EBAMCB4AwEwYD # VR0lBAwwCgYIKwYBBQUHAwMwgbUGA1UdHwSBrTCBqjBToFGgT4ZNaHR0cDovL2Ny # bDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0 # MDk2U0hBMzg0MjAyMUNBMS5jcmwwU6BRoE+GTWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0 # LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNIQTM4NDIw # MjFDQTEuY3JsMD4GA1UdIAQ3MDUwMwYGZ4EMAQQBMCkwJwYIKwYBBQUHAgEWG2h0 # dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzCBlAYIKwYBBQUHAQEEgYcwgYQwJAYI # KwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBcBggrBgEFBQcwAoZQ # aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29k # ZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5jcnQwCQYDVR0TBAIwADANBgkq # hkiG9w0BAQsFAAOCAgEABY8RK2Cj/yRb02RAMTvkehP0WkeWFeBq8n6OEHq82wdm # WAKDglsqlduvfAhtYR6mcZrdEPBXq4Y//4dlzanuOLFgYdpRA3fwCIXdycz23vh6 # 1yXqlhmUeT82lobw33xCQUQiG30tREXwZlEansbQ2ohKXLL3gIM2zCfCnXjdk1z3 # oYwL/09e+S+WPRZnRuWNZhsAZ9oEfmnUgE4rvykr761RQrclOJpHCsRwyzXxpLHY # oSaAqXOvCoT42dWAayWiYIJO+4VhqvfMjI4g6fb6/QqPMrNs8powRsDBXN1DP6RV # W44rkBFZfaW3bwKuWJmL88lHRScl+2JrXNJdY9vN31hcIsmM57ZT23rpVub0LzuQ # 2dXd1tKNLy5S5Osfl0HPRKRoHeevJz6SZuiXtd6e0ZFoGGYko/dRkcvcaPWMl1SI # b7c+GCkhWghSmgQw870LWCWOBvUxADIodTeihsheGxweRKCwYspxZ5uZ7Dq/2ks9 # mkOP0a7r08jFdAn2VB6A9P7PpYTLlMFRXFK7eKO77FzSCmA/377J/Znuh4Xmnmii # GFfIHGtkWTspyGdjYRCdpJozYibrzmZGSHp6feuYq0LzQ6Cr4K5fwIjGP9H4jv5x # nTKkyuTfDA+NIWK+5EqZ+z5l9i1jKcd2mhZa/uIfckM8NzcmeT8fsfUReOrsEWox # ggXCMIIFvgIBATB9MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwg # SW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcg # UlNBNDA5NiBTSEEzODQgMjAyMSBDQTECEAes7BBZfdyz3a9JuWhairgwCQYFKw4D # AhoFAKB4MBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZIhvcNAQkDMQwG # CisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwIwYJKoZI # hvcNAQkEMRYEFDNVZf+SOvd5OmJ4e0Vm5ARZnaItMA0GCSqGSIb3DQEBAQUABIIB # gGFwJvX/AiVnxKhWI7hB1v/rkMLuqKPSWeIX/rVxPQjG+3DlKvXUpI4PV91HyEgi # b/XrpJI3c1iG/9R4vo1v0GAHFCLw/G5LeNGdlIXCHdhWtwQERRpkW5tMDf1e0ZPH # 3ED8t+76OrPElxuwqZsyX9xNHfJs1xQo+6AXKPNBtM47Hn5pWzqZQWtXQJ4lWjIr # vtjazdrm/dLZJWSe5L0ioAdwn9Y+VHKfrkGacMI2s5enMgoaEZQ3LZoDGb0gIiwC # W4WUzdCsn8pbjwHYMk9dGnm4gztPX4IO0iiwnvU8CeqcKyzMsObeVN7KTRkAEMBz # wfmfTcjt7yga7B5w92PVpdZne+ypSqkd4P9QsOpkF1Kw1HEXmw/06xEnN6GDvgid # 71Am1IKq8m/LMhB4+TXg4x5KMMWtIA9Vs8NxoqMSYcyN0vhodNI4zU3NinIXD6NO # vxSL9+NRK8+m3DhroMh46tVBsTGEZNCUUn1UluX1rwPt6DvvDvsHtmvkF5iFp9Nl # 3aGCAyAwggMcBgkqhkiG9w0BCQYxggMNMIIDCQIBATB3MGMxCzAJBgNVBAYTAlVT # MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1 # c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0ECEAVEr/OUnQg5 # pr/bP1/lYRYwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcN # AQcBMBwGCSqGSIb3DQEJBTEPFw0yNDA3MzExODEyMjlaMC8GCSqGSIb3DQEJBDEi # BCDO40rC/6lRKAE6p7PcZgPaF4axcBwsSYw+SRz6oehRkTANBgkqhkiG9w0BAQEF # AASCAgBzKP8XZXne3MwD/HklzYmKTVoxUnD+fJ+M+U1GYWxwQ9ydZws4BNpqcDFp # Yt/Z1tAJHD3/yU++/21dGCjtA42wjgbPeg1nU+Gqi2EroMDAYpQHnIYr9Rsov3f+ # nCosrWNqTGA7htz/LusLqiaXT8HWy611WrKSwHfCy8bD85JpvtRvtZGb+on6KN1b # bUe9gi///aNNrHoGCk3tYykTTHQMC0vMqpqujnfl0dgvPbv1/nQrYY8yfDnmoqYB # e9TR9Su/wgn5OHy9ZKjG7FQ9ul2BnNxX/z7IgmcMuY22piVy3iieGoYh27TT0+Pa # xrkNh6jswB3UBHla4ccMYbstQ1bW3BCf+RrzbzmSBg7VWgv/XjNl8Te4FzKZ2dcC # lmiG2cGC/dEOeEjWkXlMA38kXeZFKq513SgjZl8i1A1+AITgYfAfG1CbFZMQCgx5 # uZ4vYn+9nkSjcXHDohMH+mvKOMf3aVaogqGlY5Pp+SxRxYDijVzJ4TNBn2FsbYeI # as+WXPBMXt2mUts+aqRZ0EwK5Zk68jP0Tm8fZMLTjzfJQdT3ZHK4/wwXRP/LKwht # 6+wzOM8PISrr7ayKpjFarsSx65yfXOryGFCMzW6hGdKCPZheP0P+sIi3hd6UouDZ # ZDEQKDbgPRSiLCC/+7X1AqDPKI3ZJ0pHh4899PGn2QXowQ1mkg== # SIG # End signature block |