Functions/Public/DevicesRooms.ps1

# Device, room, and client management 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 {
        # Get the encoding for resolving special characters
        $TextEncoding = [System.Text.Encoding]::GetEncoding('ISO-8859-1')
        Connect-NectarCloud
    }        
    Process {
        # Use globally set tenant name, if one was set and not explicitly included in the command
        If ($Global:NectarTenantName -And !$PSBoundParameters.ContainsKey('TenantName')) { 
                $TenantName = $Global:NectarTenantName 
            } ElseIf ($TenantName) {
                If ($TenantName -NotIn $Global:NectarTenantList) {
                    $TList = $Global:NectarTenantList -join ', '
                    Throw "Could not find a tenant with the name $TenantName on https://$Global:NectarCloud. Select one of $TList. $($_.Exception.Message)"
                }
            }
        
        $URI = "https://$Global:NectarCloud/aapi/supported/devices?q=$SearchQuery&tenant=$TenantName&pageSize=$ResultSize"
        Write-Verbose $URI

        Try {
            # Has to be done this way to get the proper UTF8 encoded characters
            $WebContent = Invoke-WebRequest -Method GET -Uri $URI -UseBasicParsing -Headers $Global:NectarAuthHeader
            $JSON = ([System.Text.Encoding]::UTF8).GetString($TextEncoding.GetBytes($WebContent.Content)) | ConvertFrom-Json

            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. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { 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,
        [Alias('devicePlatform')]
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$Platform,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName
    )
    
    Begin {
        Connect-NectarCloud
    }        
    Process {
        # Use globally set tenant name, if one was set and not explicitly included in the command
        If ($Global:NectarTenantName -And !$PSBoundParameters.ContainsKey('TenantName')) { 
                $TenantName = $Global:NectarTenantName 
            } ElseIf ($TenantName) {
                If ($TenantName -NotIn $Global:NectarTenantList) {
                    $TList = $Global:NectarTenantList -join ', '
                    Throw "Could not find a tenant with the name $TenantName on https://$Global:NectarCloud. Select one of $TList. $($_.Exception.Message)"
                }
            }
        
        $UrlEncodedDeviceName = [uri]::EscapeDataString($DeviceName)

        $URI = "https://$Global:NectarCloud/aapi/supported/device?deviceName=$UrlEncodedDeviceName&platform=$Platform&tenant=$TenantName"
        Write-Verbose $URI

        $Body = @{
            deviceSupported = $Supported
        }
        
        $JSONBody = $Body | ConvertTo-Json

        Try {
            $NULL = Invoke-RestMethod -Method PUT -URI $URI -Headers $Global:NectarAuthHeader -Body $JSONBody -ContentType 'application/json'
            Write-Verbose $JSONBody
        }
        Catch {
            Write-Error "Unable to apply changes for device $DeviceName. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { 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 !$PSBoundParameters.ContainsKey('TenantName')) { 
                $TenantName = $Global:NectarTenantName 
            } ElseIf ($TenantName) {
                If ($TenantName -NotIn $Global:NectarTenantList) {
                    $TList = $Global:NectarTenantList -join ', '
                    Throw "Could not find a tenant with the name $TenantName on https://$Global:NectarCloud. Select one of $TList. $($_.Exception.Message)"
                }
            }
        
        $URI = "https://$Global:NectarCloud/aapi/supported/client/versions?q=$SearchQuery&tenant=$TenantName&pageSize=$ResultSize"
        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.ClientList'
            Return $JSON.elements
        }
        Catch {
            Write-Error "Unable to get client version details. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { 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 !$PSBoundParameters.ContainsKey('TenantName')) { 
                $TenantName = $Global:NectarTenantName 
            } ElseIf ($TenantName) {
                If ($TenantName -NotIn $Global:NectarTenantList) {
                    $TList = $Global:NectarTenantList -join ', '
                    Throw "Could not find a tenant with the name $TenantName on https://$Global:NectarCloud. Select one of $TList. $($_.Exception.Message)"
                }
            }
        
        If ($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"
        Write-Verbose $URI

        $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. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }
        }
    }
}





#################################################################################################################################################
# #
# User 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 !$PSBoundParameters.ContainsKey('TenantName')) { 
                $TenantName = $Global:NectarTenantName 
            } ElseIf ($TenantName) {
                If ($TenantName -NotIn $Global:NectarTenantList) {
                    $TList = $Global:NectarTenantList -join ', '
                    Throw "Could not find a tenant with the name $TenantName on https://$Global:NectarCloud. Select one of $TList. $($_.Exception.Message)"
                }
            }
            
            If ($TenantName) { 
                $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. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { 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=$False)]
        [int]$ID,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$Name,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [Alias('businessUnits')]
        [string[]]$BusinessUnitID,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$LocationName,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [int]$LocationID,
        [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 !$PSBoundParameters.ContainsKey('TenantName')) { 
                $TenantName = $Global:NectarTenantName 
            } ElseIf ($TenantName) {
                If ($TenantName -NotIn $Global:NectarTenantList) {
                    $TList = $Global:NectarTenantList -join ', '
                    Throw "Could not find a tenant with the name $TenantName on https://$Global:NectarCloud. Select one of $TList. $($_.Exception.Message)"
                }
            }

            If ($Name) {
                $RoomInfo = Get-NectarRoom -SearchQuery $Name -TenantName $TenantName
                If ($RoomInfo.Count -gt 1) {
                    $RList = $RoomInfo | Select-Object id, Name
                    Throw "More than one room found matching $Name. Please refine your search to a single room. $($RList | Out-String)"
                } ElseIf ($RoomInfo.Count -eq 0) {
                    Throw "Could not find a room matching $Name"
                }
                $Body = @{
                    roomName = $RoomInfo.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)
            }

            If ($LocationID) {
                $Body.Add('locationId', $LocationID)
                $Body.Add('networkSource', 'virtual')
            } ElseIf ($LocationName) {
                $LocationInfo = Get-NectarLocation -SearchQuery $LocationName -TenantName $TenantName | Where-Object { $_.networkRangeType -eq 'Virtual' }
                If ($LocationInfo.Count -gt 1) {
                    $LList = $LocationInfo | Select-Object id, networkName
                    Throw "More than one location found matching $LocationName. Please refine your search to a single location. $($LList | Out-String)"
                } ElseIf ($LocationInfo.Count -eq 0) {
                    Throw "Could not find a location matching $LocationName"
                }

                $Body.Add('locationId', $LocationInfo.id)
                $Body.Add('networkSource', 'virtual')
            }

            $URI = "https://$Global:NectarCloud/dapi/room-and-device/room/$($RoomInfo.ID)?tenant=$TenantName"
            Write-Verbose $URI
            
            $JSONBody = $Body | ConvertTo-Json
            Write-Verbose $JSONBody

            $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. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { 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 !$PSBoundParameters.ContainsKey('TenantName')) { 
                $TenantName = $Global:NectarTenantName 
            } ElseIf ($TenantName) {
                If ($TenantName -NotIn $Global:NectarTenantList) {
                    $TList = $Global:NectarTenantList -join ', '
                    Throw "Could not find a tenant with the name $TenantName on https://$Global:NectarCloud. Select one of $TList. $($_.Exception.Message)"
                }
            }

            $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. $($_.Exception.Message)$($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { 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 !$PSBoundParameters.ContainsKey('TenantName')) { 
                $TenantName = $Global:NectarTenantName 
            } ElseIf ($TenantName) {
                If ($TenantName -NotIn $Global:NectarTenantList) {
                    $TList = $Global:NectarTenantList -join ', '
                    Throw "Could not find a tenant with the name $TenantName on https://$Global:NectarCloud. Select one of $TList. $($_.Exception.Message)"
                }
            }

            $URI = "https://$Global:NectarCloud/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. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { 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 !$PSBoundParameters.ContainsKey('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 {
        $URI = "https://$Global:NectarCloud/dapi/room-and-device/device/connect?device_united_id=$DeviceUnitedID&room_id=$RoomID&tenant=$TenantName"
        Write-Verbose $URI
        
        Invoke-RestMethod_ErrorHandling -Method POST -URI $URI -Headers $Global:NectarAuthHeader
    }
}


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 !$PSBoundParameters.ContainsKey('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. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { 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 !$PSBoundParameters.ContainsKey('TenantName')) { 
                $TenantName = $Global:NectarTenantName 
            } ElseIf ($TenantName) {
                If ($TenantName -NotIn $Global:NectarTenantList) {
                    $TList = $Global:NectarTenantList -join ', '
                    Throw "Could not find a tenant with the name $TenantName on https://$Global:NectarCloud. Select one of $TList. $($_.Exception.Message)"
                }
            }
            
            If ($TenantName) { 
                $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. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { 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 !$PSBoundParameters.ContainsKey('TenantName')) { 
                $TenantName = $Global:NectarTenantName 
            } ElseIf ($TenantName) {
                If ($TenantName -NotIn $Global:NectarTenantList) {
                    $TList = $Global:NectarTenantList -join ', '
                    Throw "Could not find a tenant with the name $TenantName on https://$Global:NectarCloud. Select one of $TList. $($_.Exception.Message)"
                }
            }
            
            If ($TenantName) { 
                $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. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { 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 {
        # Use globally set tenant name, if one was set and not explicitly included in the command
        If ($Global:NectarTenantName -And !$PSBoundParameters.ContainsKey('TenantName')) { 
            $TenantName = $Global:NectarTenantName 
        }

        $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_ErrorHandling -Method POST -URI $URI -Headers $Global:NectarAuthHeader -Body $JSONBody -ContentType 'application/json; charset=utf-8'
        Return $JSON
    }
}