UCLobbyTeams.psm1

#Region '.\Private\Convert-UcTeamsDeviceType.ps1' 0
function Convert-UcTeamsDeviceType {
    param (
        [string]$DeviceType
    )
    switch ($DeviceType) {
        "ipPhone" { return "Phone" }
        "lowCostPhone" { return "Phone" }
        "teamsRoom" { return "MTR Windows" }
        "collaborationBar" { return "MTR Android" }
        "surfaceHub" { return "Surface Hub" }
        "teamsDisplay" { return "Display" }
        "touchConsole" { return "Touch Console (MTRA)" }
        "teamsPanel" { return "Panel" }
        "sip" { return "SIP Phone" }
        Default { return $DeviceType}
    }
}
#EndRegion '.\Private\Convert-UcTeamsDeviceType.ps1' 18
#Region '.\Private\Invoke-UcMgGraphBatch.ps1' 0
Function Invoke-UcMgGraphBatch {
    Param(
        [object]$Requests,
        [ValidateSet("beta", "v1.0")]
        [string]$MgProfile,
        [string]$Activity,
        [switch]$IncludeBody
    )
    $GraphURI_BetaAPIBatch = "https://graph.microsoft.com/beta/`$batch"
    $GraphURI_ProdAPIBatch = "https://graph.microsoft.com/v1.0/`$batch"
    $outBatchResponses = [System.Collections.ArrayList]::new()
    $tmpGraphRequests = [System.Collections.ArrayList]::new()

    if ($MgProfile.Equals("beta")) {
        $GraphURI_Batch = $GraphURI_BetaAPIBatch
    }
    else {
        $GraphURI_Batch = $GraphURI_ProdAPIBatch
    }

    #If activity is null then we can use this to get the function that call this function.
    if (!($Activity)) {
        $Activity = (c)[1].FunctionName
    }

    $g = 1
    $batchCount = [int][Math]::Ceiling(($Requests.count / 20))
    foreach ($GraphRequest in $Requests) {
        Write-Progress -Activity $Activity -Status "Running batch $g of $batchCount"
        [void]$tmpGraphRequests.Add($GraphRequest) 
        if ($tmpGraphRequests.Count -ge 20) {
            $g++
            $grapRequestBody = ' { "requests": ' + ($tmpGraphRequests  | ConvertTo-Json) + ' }' 
            $GraphResponses += (Invoke-MgGraphRequest -Method Post -Uri $GraphURI_Batch -Body $grapRequestBody).responses
            $tmpGraphRequests = [System.Collections.ArrayList]::new()
        }
    }

    if ($tmpGraphRequests.Count -gt 0) {
        Write-Progress -Activity $Activity -Status "Running batch $g of $batchCount"
        #TO DO: Look for alternatives instead of doing this.
        if ($tmpGraphRequests.Count -gt 1) {
            $grapRequestBody = ' { "requests": ' + ($tmpGraphRequests | ConvertTo-Json) + ' }' 
        }
        else {
            $grapRequestBody = ' { "requests": [' + ($tmpGraphRequests | ConvertTo-Json) + '] }' 
        }
        $GraphResponses += (Invoke-MgGraphRequest -Method Post -Uri $GraphURI_Batch -Body $grapRequestBody).responses
    }
    for ($j = 0; $j -lt $GraphResponses.length; $j++) {
        #In some cases we will need the complete graph response, in that case the calling function will have to process pending pages.
        if($IncludeBody){
            $outBatchResponses += $GraphResponses[$j]
        } else {
            $outBatchResponses += $GraphResponses[$j].body
             #Checking if there are more pages available
            $GraphURI_NextPage = $GraphResponses[$j].body.'@odata.nextLink'
            while (![string]::IsNullOrEmpty($GraphURI_NextPage)) {
                $graphNextPageResponse = Invoke-MgGraphRequest -Method Get -Uri $GraphURI_NextPage
                $outBatchResponses += $graphNextPageResponse
                $GraphURI_NextPage = $graphNextPageResponse.'@odata.nextLink'
            } 
        }
    }
    return $outBatchResponses
}
#EndRegion '.\Private\Invoke-UcMgGraphBatch.ps1' 67
#Region '.\Private\Test-UcIPAddressInSubnet.ps1' 0
function ConvertTo-IPv4MaskString {
    <#
    .SYNOPSIS
    Converts a number of bits (0-32) to an IPv4 network mask string (e.g., "255.255.255.0").
   
    .DESCRIPTION
    Converts a number of bits (0-32) to an IPv4 network mask string (e.g., "255.255.255.0").
   
    .PARAMETER MaskBits
    Specifies the number of bits in the mask.
 
    Credits to: Bill Stewart - https://www.itprotoday.com/powershell/working-ipv4-addresses-powershell
 
    #>

    param(
        [parameter(Mandatory = $true)]
        [ValidateRange(0, 32)]
        [Int] $MaskBits
    )
    $mask = ([Math]::Pow(2, $MaskBits) - 1) * [Math]::Pow(2, (32 - $MaskBits))
    $bytes = [BitConverter]::GetBytes([UInt32] $mask)
    (($bytes.Count - 1)..0 | ForEach-Object { [String] $bytes[$_] }) -join "."
}

Function Test-UcIPaddressInSubnet {

    param(
        [string]$IPAddress,
        [string]$Subnet
    )

    $regExIPAddressSubnet = "^((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9]))\/(3[0-2]|[1-2]{1}[0-9]{1}|[1-9])$"

    try {
        $Subnet -match $regExIPAddressSubnet | Out-Null

        $IPSubnet = [ipaddress]$Matches[1]
        $tmpIPAddress = [ipaddress]$IPAddress
        $subnetMask = ConvertTo-IPv4MaskString $Matches[6]
        $tmpSubnet = [ipaddress] ($subnetMask)
        $netidSubnet = [ipaddress]($IPSubnet.address -band $tmpSubnet.address)
        $netidIPAddress = [ipaddress]($tmpIPAddress.address -band $tmpSubnet.address)
        return ($netidSubnet.ipaddresstostring -eq $netidIPAddress.ipaddresstostring)
    }
    catch {
        return $false
    }
}
#EndRegion '.\Private\Test-UcIPAddressInSubnet.ps1' 49
#Region '.\Private\Test-UcMgGraphConnection.ps1' 0
<#
.SYNOPSIS
Test connection to Microsoft Graph
 
.DESCRIPTION
This function will validate if the current connection to Microsoft Graph has the required Scopes and will attempt to connect if we are missing one.
  
Contributors: Daniel Jelinek, David Paulino
 
Requirements: Microsoft Graph PowerShell Module (Install-Module Microsoft.Graph)
 
.PARAMETER Scopes
When present it will get detailed information from Teams Devices
 
.EXAMPLE
PS> Connect-UcMgGraph "TeamworkDevice.Read.All","Directory.Read.All"
 
#>


function Test-UcMgGraphConnection {
    Param(
    [string[]]$Scopes
    )
    #Checking if Microsoft.Graph is installed
    if(!(Get-Module Microsoft.Graph.Authentication -ListAvailable)){
        Write-Warning ("Missing Microsoft.Graph PowerShell module. Please install it with:" + [Environment]::NewLine + "Install-Module Microsoft.Graph") 
        return $false
    }
    #Checking if we have the required scopes.
    $currentScopes = (Get-MgContext).Scopes
    $strScope =""
    $missingScope = $false
    foreach($scope in $Scopes){
        $strScope += "`"" + $scope + "`","
        if(($scope -notin $currentScopes) -and $currentScopes){
            $missingScope = $true
            Write-Warning -Message ("Missing Scope in Microsoft Graph: " + $scope) 
        }
    }

    if(!($currentScopes)){
        Write-Warning  ("Not Connected to Microsoft Graph" + [Environment]::NewLine  + "Please connect to Microsoft Graph before running this cmdlet." + [Environment]::NewLine +"Commercial Tenant: Connect-MgGraph -Scopes " + $strScope.Substring(0,$strScope.Length -1) + [Environment]::NewLine  + "US Gov (GCC-H) Tenant: Connect-MgGraph -Scopes " + $strScope.Substring(0,$strScope.Length -1)  + " -Environment USGov")
        return $false
    }

    #If scopes are missing we need to connect using the required scopes
    if($missingScope){
        Write-Warning  ("Some scopes are missing please reconnect to Microsoft Graph before running this cmdlet." + [Environment]::NewLine +"Commercial Tenant: Connect-MgGraph -Scopes " + $strScope.Substring(0,$strScope.Length -1) + [Environment]::NewLine  + "US Gov (GCC-H) Tenant: Connect-MgGraph -Scopes " + $strScope.Substring(0,$strScope.Length -1)  + " -Environment USGov")
        return $false
    } else {
        return $true
    }
}
#EndRegion '.\Private\Test-UcMgGraphConnection.ps1' 54
#Region '.\Public\Get-UcArch.ps1' 0
<#
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.
#>


<#
.SYNOPSIS
Funcion to get the Architecture from .exe file
 
.DESCRIPTION
Based on PowerShell script Get-ExecutableType.ps1 by David Wyatt, please check the complete script in:
 
Identify 16-bit, 32-bit and 64-bit executables with PowerShell
https://gallery.technet.microsoft.com/scriptcenter/Identify-16-bit-32-bit-and-522eae75
 
.PARAMETER FilePath
Specifies the executable full file path.
 
.EXAMPLE
PS> Get-UcArch -FilePath C:\temp\example.exe
#>

Function Get-UcArch([string]$FilePath) {
    try {
        $stream = New-Object System.IO.FileStream(
            $FilePath,
            [System.IO.FileMode]::Open,
            [System.IO.FileAccess]::Read,
            [System.IO.FileShare]::Read )
        $exeType = 'Unknown'
        $bytes = New-Object byte[](4)
        if ($stream.Seek(0x3C, [System.IO.SeekOrigin]::Begin) -eq 0x3C -and $stream.Read($bytes, 0, 4) -eq 4) {
            if (-not [System.BitConverter]::IsLittleEndian) { [Array]::Reverse($bytes, 0, 4) }
            $peHeaderOffset = [System.BitConverter]::ToUInt32($bytes, 0)

            if ($stream.Length -ge $peHeaderOffset + 6 -and
                $stream.Seek($peHeaderOffset, [System.IO.SeekOrigin]::Begin) -eq $peHeaderOffset -and
                $stream.Read($bytes, 0, 4) -eq 4 -and
                $bytes[0] -eq 0x50 -and $bytes[1] -eq 0x45 -and $bytes[2] -eq 0 -and $bytes[3] -eq 0) {
                $exeType = 'Unknown'
                if ($stream.Read($bytes, 0, 2) -eq 2) {
                    if (-not [System.BitConverter]::IsLittleEndian) { [Array]::Reverse($bytes, 0, 2) }
                    $machineType = [System.BitConverter]::ToUInt16($bytes, 0)
                    switch ($machineType) {
                        0x014C { $exeType = 'x86' }
                        0x8664 { $exeType = 'x64' }
                    }
                }
            }
        }
        return $exeType
    }
    catch {
        return "Unknown"
    }
    finally {
        if ($null -ne $stream) { $stream.Dispose() }
    }
}
#EndRegion '.\Public\Get-UcArch.ps1' 64
#Region '.\Public\Get-UcM365Domains.ps1' 0
<#
.SYNOPSIS
Get Microsoft 365 Domains from a Tenant
 
.DESCRIPTION
This function returns a list of domains that are associated with a Microsoft 365 Tenant.
 
.PARAMETER Domain
Specifies a domain registered with Microsoft 365
 
.EXAMPLE
PS> Get-UcM365Domains -Domain uclobby.com
#>

Function Get-UcM365Domains {
    Param(
        [Parameter(Mandatory = $true)]
        [string]$Domain
    )
    $regex = "^(.*@)(.*[.].*)$"
    $outDomains = [System.Collections.ArrayList]::new()
    try {
        Test-UcModuleUpdateAvailable -ModuleName UcLobbyTeams
        $AllowedAudiences = Invoke-WebRequest -Uri ("https://accounts.accesscontrol.windows.net/" + $Domain + "/metadata/json/1") | ConvertFrom-Json | Select-Object -ExpandProperty allowedAudiences
        foreach ($AllowedAudience in $AllowedAudiences) {
            $temp = [regex]::Match($AllowedAudience , $regex).captures.groups
            if ($temp.count -ge 2) {
                $tempObj = New-Object -TypeName PSObject -Property @{
                    Name = $temp[2].value
                }
                $outDomains.Add($tempObj) | Out-Null
            }
        }
    }
    catch [System.Net.WebException] {
        if ($PSItem.Exception.Message -eq "The remote server returned an error: (400) Bad Request.") {
            Write-Warning "The domain $Domain is not part of a Microsoft 365 Tenant."
        }
        else {
            Write-Warning $PSItem.Exception.Message
        }
    }
    catch {
        Write-Warning "Unknown error while checking domain: $Domain"
    }
    return $outDomains
}
#EndRegion '.\Public\Get-UcM365Domains.ps1' 47
#Region '.\Public\Get-UcM365TenantId.ps1' 0
<#
.SYNOPSIS
Get Microsoft 365 Tenant Id
 
.DESCRIPTION
This function returns the Tenant ID associated with a domain that is part of a Microsoft 365 Tenant.
 
.PARAMETER Domain
Specifies a domain registered with Microsoft 365
 
.EXAMPLE
PS> Get-UcM365TenantId -Domain uclobby.com
#>

Function Get-UcM365TenantId {
    Param(
        [Parameter(Mandatory = $true)]
        [string]$Domain
    )
    $regexTenantID = "^(.*@)(\w{8}-\w{4}-\w{4}-\w{4}-\w{12})$"
    $regexOnMicrosoftDomain = "^(.*@)(?!.*mail)(.*.onmicrosoft.com)$"

    try {
        Test-UcModuleUpdateAvailable -ModuleName UcLobbyTeams
        $AllowedAudiences = Invoke-WebRequest -Uri ("https://accounts.accesscontrol.windows.net/" + $Domain + "/metadata/json/1") | ConvertFrom-Json | Select-Object -ExpandProperty allowedAudiences
    }
    catch [System.Net.Http.HttpRequestException]{
        if ($PSItem.Exception.Response.StatusCode -eq "BadRequest"){
            Write-Error "The domain $Domain is not part of a Microsoft 365 Tenant."
        }
        else {
            Write-Error $PSItem.Exception.Message
        }
    }
    catch {
        Write-Error "Unknown error while checking domain: $Domain"
    }
    $output = [System.Collections.ArrayList]::new()
    $OnMicrosoftDomains = [System.Collections.ArrayList]::new()
    $TenantID = ""
    foreach ($AllowedAudience in $AllowedAudiences) {
        $tempTID = [regex]::Match($AllowedAudience , $regexTenantID).captures.groups
        $tempID = [regex]::Match($AllowedAudience , $regexOnMicrosoftDomain).captures.groups
        if ($tempTID.count -ge 2) {
            $TenantID = $tempTID[2].value 
        }
        if ($tempID.count -ge 2) {
            [void]$OnMicrosoftDomains.Add($tempID[2].value)
        }
    }
    #Multi Geo will have multiple OnMicrosoft Domains
    foreach($OnMicrosoftDomain in $OnMicrosoftDomains){
        if($TenantID -and $OnMicrosoftDomain){
            $M365TidPSObj = [PSCustomObject]@{ TenantID = $TenantID
                OnMicrosoftDomain = $OnMicrosoftDomain}
            $M365TidPSObj.PSObject.TypeNames.Insert(0, 'M365TenantId')
            [void]$output.Add($M365TidPSObj)
        }
    }
    return $output 
}
#EndRegion '.\Public\Get-UcM365TenantId.ps1' 61
#Region '.\Public\Get-UcTeamsDevice.ps1' 0
<#
.SYNOPSIS
Get Microsoft Teams Devices information
 
.DESCRIPTION
This function fetch Teams Devices provisioned in a M365 Tenant using MS Graph.
  
 
Contributors: David Paulino, Silvio Schanz, Gonçalo Sepulveda, Bryan Kendrick and Daniel Jelinek
 
Requirements: Microsoft Graph PowerShell Module (Install-Module Microsoft.Graph)
                Microsoft Graph Scopes:
                        "TeamworkDevice.Read.All"
                        "User.Read.All"
 
.PARAMETER Filter
Specifies a filter, valid options:
    Phone - Teams Native Phones
    MTR - Microsoft Teams Rooms running Windows or Android
    MTRW - Microsoft Teams Room Running Windows
    MTRA - Microsoft Teams Room Running Android
    SurfaceHub - Surface Hub
    Display - Microsoft Teams Displays
    Panel - Microsoft Teams Panels
 
.PARAMETER Detailed
When present it will get detailed information from Teams Devices
 
.PARAMETER ExportCSV
When present will export the detailed results to a CSV file. By defautl will save the file under the current user downloads, unless we specify the OutputPath.
 
.PARAMETER OutputPath
Allows to specify the path where we want to save the results.
 
.EXAMPLE
PS> Get-UcTeamsDevice
 
.EXAMPLE
PS> Get-UcTeamsDevice -Filter MTR
 
.EXAMPLE
PS> Get-UcTeamsDevice -Detailed
 
#>


Function Get-UcTeamsDevice {
    Param(
        [ValidateSet("Phone","MTR","MTRA","MTRW","SurfaceHub","Display","Panel","SIPPhone")]
        [string]$Filter,
        [switch]$Detailed,
        [switch]$ExportCSV,
        [string]$OutputPath
    )    
    $outTeamsDevices = [System.Collections.ArrayList]::new()

    if($ExportCSV){
        $Detailed = $true
    }

    #Verify if the Output Path exists
    if($OutputPath){
        if (!(Test-Path $OutputPath -PathType Container)){
            Write-Host ("Error: Invalid folder " + $OutputPath) -ForegroundColor Red
            return
        } 
    } else {                
        $OutputPath = [System.IO.Path]::Combine($env:USERPROFILE,"Downloads")
    }

    if(Test-UcMgGraphConnection -Scopes "TeamworkDevice.Read.All", "User.Read.All"){
        Test-UcModuleUpdateAvailable -ModuleName UcLobbyTeams
        $graphRequests =  [System.Collections.ArrayList]::new()
        $tmpFileName = "MSTeamsDevices_" + $Filter + "_" + ( get-date ).ToString('yyyyMMdd-HHmmss') + ".csv"
        switch ($filter) {
            "Phone" { 
                $gRequestTmp = New-Object -TypeName PSObject -Property @{
                    id = "ipPhone"
                    method = "GET"
                    url = "/teamwork/devices/?`$filter=deviceType eq 'ipPhone'"
                }
                $graphRequests.Add($gRequestTmp) | Out-Null
                $gRequestTmp = New-Object -TypeName PSObject -Property @{
                    id = "lowCostPhone"
                    method = "GET"
                    url = "/teamwork/devices/?`$filter=deviceType eq 'lowCostPhone'"
                }
                $graphRequests.Add($gRequestTmp) | Out-Null
            }
            "MTR" {
                $gRequestTmp = New-Object -TypeName PSObject -Property @{
                    id = "teamsRoom"
                    method = "GET"
                    url = "/teamwork/devices/?`$filter=deviceType eq 'teamsRoom'"
                }
                $graphRequests.Add($gRequestTmp) | Out-Null
                $gRequestTmp = New-Object -TypeName PSObject -Property @{
                    id = "collaborationBar"
                    method = "GET"
                    url = "/teamwork/devices/?`$filter=deviceType eq 'collaborationBar'"
                }
                $graphRequests.Add($gRequestTmp) | Out-Null
                $gRequestTmp = New-Object -TypeName PSObject -Property @{
                    id = "touchConsole"
                    method = "GET"
                    url = "/teamwork/devices/?`$filter=deviceType eq 'touchConsole'"
                }
                $graphRequests.Add($gRequestTmp) | Out-Null
            }
            "MTRW"{
                $gRequestTmp = New-Object -TypeName PSObject -Property @{
                    id = "teamsRoom"
                    method = "GET"
                    url = "/teamwork/devices/?`$filter=deviceType eq 'teamsRoom'"
                }
                $graphRequests.Add($gRequestTmp) | Out-Null
            }
            "MTRA"{            
                $gRequestTmp = New-Object -TypeName PSObject -Property @{
                    id = "collaborationBar"
                    method = "GET"
                    url = "/teamwork/devices/?`$filter=deviceType eq 'collaborationBar'"
                }
                $graphRequests.Add($gRequestTmp) | Out-Null
                $gRequestTmp = New-Object -TypeName PSObject -Property @{
                    id = "touchConsole"
                    method = "GET"
                    url = "/teamwork/devices/?`$filter=deviceType eq 'touchConsole'"
                }
                $graphRequests.Add($gRequestTmp) | Out-Null
            }
            "SurfaceHub" {
                $gRequestTmp = New-Object -TypeName PSObject -Property @{
                    id = "surfaceHub"
                    method = "GET"
                    url = "/teamwork/devices/?`$filter=deviceType eq 'surfaceHub'"
                }
                $graphRequests.Add($gRequestTmp) | Out-Null
            }
            "Display"{
                $gRequestTmp = New-Object -TypeName PSObject -Property @{
                    id = "teamsDisplay"
                    method = "GET"
                    url = "/teamwork/devices/?`$filter=deviceType eq 'teamsDisplay'"
                }
                $graphRequests.Add($gRequestTmp) | Out-Null
            }
            "Panel" {
                $gRequestTmp = New-Object -TypeName PSObject -Property @{
                    id = "teamsPanel"
                    method = "GET"
                    url = "/teamwork/devices/?`$filter=deviceType eq 'teamsPanel'"
                }
                $graphRequests.Add($gRequestTmp) | Out-Null
            }
            "SIPPhone" {
                $gRequestTmp = New-Object -TypeName PSObject -Property @{
                    id = "sip"
                    method = "GET"
                    url = "/teamwork/devices/?`$filter=deviceType eq 'sip'"
                }
                $graphRequests.Add($gRequestTmp) | Out-Null
            }
            Default {
                $gRequestTmp = New-Object -TypeName PSObject -Property @{
                    id = 1
                    method = "GET"
                    url = "/teamwork/devices"
                }
                $graphRequests.Add($gRequestTmp) | Out-Null
                $tmpFileName = "MSTeamsDevices_All_" + ( get-date ).ToString('yyyyMMdd-HHmmss') + ".csv"
            }
        }
        
        $TeamsDeviceList = (Invoke-UcMgGraphBatch -Requests $graphRequests -MgProfile beta -Activity "Get-UcTeamsDevice, getting Teams device info").value

        #To improve performance we will use batch requests
        $graphRequests =  [System.Collections.ArrayList]::new()
        foreach($TeamsDevice in $TeamsDeviceList){
            if(($graphRequests.id -notcontains $TeamsDevice.currentuser.id) -and !([string]::IsNullOrEmpty($TeamsDevice.currentuser.id))) {
                $gRequestTmp = New-Object -TypeName PSObject -Property @{
                    id =  $TeamsDevice.currentuser.id
                    method = "GET"
                    url = "/users/"+ $TeamsDevice.currentuser.id
                }
                $graphRequests.Add($gRequestTmp) | Out-Null
            }
            if($Detailed){
                $gRequestTmp = New-Object -TypeName PSObject -Property @{
                    id = $TeamsDevice.id+"-activity"
                    method = "GET"
                    url = "/teamwork/devices/"+$TeamsDevice.id+"/activity"
                }
                $graphRequests.Add($gRequestTmp) | Out-Null
                $gRequestTmp = New-Object -TypeName PSObject -Property @{
                    id = $TeamsDevice.id+"-configuration"
                    method = "GET"
                    url = "/teamwork/devices/"+$TeamsDevice.id+"/configuration"
                }
                $graphRequests.Add($gRequestTmp) | Out-Null
                $gRequestTmp = New-Object -TypeName PSObject -Property @{
                    id =$TeamsDevice.id+"-health"
                    method = "GET"
                    url = "/teamwork/devices/"+$TeamsDevice.id+"/health"
                }
                $graphRequests.Add($gRequestTmp) | Out-Null
                $gRequestTmp = New-Object -TypeName PSObject -Property @{
                    id = $TeamsDevice.id+"-operations"
                    method = "GET"
                    url = "/teamwork/devices/"+$TeamsDevice.id+"/operations"
                }
                $graphRequests.Add($gRequestTmp) | Out-Null
            } 
        }
        if ($graphRequests.Count -gt 0){
            
            if($Detailed){
                $ActivityInfo = "Get-UcTeamsDevice, getting Teams device addtional information (User UPN/Health/Operations/Configurarion)."
            } else {
                $ActivityInfo = "Get-UcTeamsDevice, getting Teams device user information."
            }
            $graphResponseExtra = (Invoke-UcMgGraphBatch -Requests $graphRequests -MgProfile beta -Activity $ActivityInfo -IncludeBody)
        }
        
        foreach($TeamsDevice in $TeamsDeviceList){
            $userUPN = ($graphResponseExtra | Where-Object{$_.id -eq $TeamsDevice.currentuser.id}).body.userPrincipalName

            if($Detailed){
                $TeamsDeviceActivity = ($graphResponseExtra | Where-Object{$_.id -eq ($TeamsDevice.id+"-activity")}).body
                $TeamsDeviceConfiguration = ($graphResponseExtra | Where-Object{$_.id -eq ($TeamsDevice.id+"-configuration")}).body
                $TeamsDeviceHealth = ($graphResponseExtra | Where-Object{$_.id -eq ($TeamsDevice.id+"-health")}).body
                $TeamsDeviceOperations = ($graphResponseExtra | Where-Object{$_.id -eq ($TeamsDevice.id+"-operations")}).body.value

                if($TeamsDeviceOperations.count -gt 0){
                    $LastHistoryAction = $TeamsDeviceOperations[0].operationType
                    $LastHistoryStatus = $TeamsDeviceOperations[0].status
                    $LastHistoryInitiatedBy = $TeamsDeviceOperations[0].createdBy.user.displayName
                    $LastHistoryModifiedDate = $TeamsDeviceOperations[0].lastActionDateTime
                    $LastHistoryErrorCode = $TeamsDeviceOperations[0].error.code
                    $LastHistoryErrorMessage = $TeamsDeviceOperations[0].error.message
                } else {
                    $LastHistoryAction = ""
                    $LastHistoryStatus = ""
                    $LastHistoryInitiatedBy = ""
                    $LastHistoryModifiedDate = ""
                    $LastHistoryErrorCode = ""
                    $LastHistoryErrorMessage = ""
                }

                $outMacAddress = ""
                foreach ($macAddress in $TeamsDevice.hardwaredetail.macAddresses){
                    $outMacAddress += $macAddress + ";"
                }
        
                $TDObj = New-Object -TypeName PSObject -Property @{
                    UserDisplayName = $TeamsDevice.currentuser.displayName
                    UserUPN         = $userUPN 
            
                    TACDeviceID     = $TeamsDevice.id
                    DeviceType      = Convert-UcTeamsDeviceType $TeamsDevice.deviceType
                    Notes           = $TeamsDevice.notes
                    CompanyAssetTag = $TeamsDevice.companyAssetTag
        
                    Manufacturer    = $TeamsDevice.hardwaredetail.manufacturer
                    Model           = $TeamsDevice.hardwaredetail.model
                    SerialNumber    = $TeamsDevice.hardwaredetail.serialNumber 
                    MacAddresses    = $outMacAddress.subString(0,$outMacAddress.length-1)
                                
                    DeviceHealth    = $TeamsDevice.healthStatus
                    WhenCreated = $TeamsDevice.createdDateTime
                    WhenChanged = $TeamsDevice.lastModifiedDateTime
                    ChangedByUser = $TeamsDevice.lastModifiedBy.user.displayName
        
                    #Activity
                    ActivePeripherals = $TeamsDeviceActivity.activePeripherals
        
                    #Configuration
                    LastUpdate = $TeamsDeviceConfiguration.createdDateTime
        
                    DisplayConfiguration = $TeamsDeviceConfiguration.displayConfiguration
                    CameraConfiguration = $TeamsDeviceConfiguration.cameraConfiguration.contentCameraConfiguration
                    SpeakerConfiguration = $TeamsDeviceConfiguration.speakerConfiguration
                    MicrophoneConfiguration = $TeamsDeviceConfiguration.microphoneConfiguration
                    TeamsClientConfiguration = $TeamsDeviceConfiguration.teamsClientConfiguration
                    SupportedMeetingMode = $TeamsDeviceConfiguration.teamsClientConfiguration.accountConfiguration.supportedClient
                    HardwareProcessor = $TeamsDeviceConfiguration.hardwareConfiguration.processorModel
                    SystemConfiguration = $TeamsDeviceConfiguration.systemConfiguration
        
                    #Health
                    ComputeStatus = $TeamsDeviceHealth.hardwareHealth.computeHealth.connection.connectionStatus
                    HdmiIngestStatus = $TeamsDeviceHealth.hardwareHealth.hdmiIngestHealth.connection.connectionStatus
                    RoomCameraStatus = $TeamsDeviceHealth.peripheralsHealth.roomCameraHealth.connection.connectionStatus
                    ContentCameraStatus = $TeamsDeviceHealth.peripheralsHealth.contentCameraHealth.connection.connectionStatus
                    SpeakerStatus = $TeamsDeviceHealth.peripheralsHealth.speakerHealth.connection.connectionStatus
                    CommunicationSpeakerStatus = $TeamsDeviceHealth.peripheralsHealth.communicationSpeakerHealth.connection.connectionStatus
                    #DisplayCollection = $TeamsDeviceHealth.peripheralsHealth.displayHealthCollection.connectionStatus
                    MicrophoneStatus = $TeamsDeviceHealth.peripheralsHealth.microphoneHealth.connection.connectionStatus

                    TeamsAdminAgentVersion = $TeamsDeviceHealth.softwareUpdateHealth.adminAgentSoftwareUpdateStatus.currentVersion
                    FirmwareVersion = $TeamsDeviceHealth.softwareUpdateHealth.firmwareSoftwareUpdateStatus.currentVersion
                    CompanyPortalVersion = $TeamsDeviceHealth.softwareUpdateHealth.companyPortalSoftwareUpdateStatus.currentVersion
                    OEMAgentAppVersion = $TeamsDeviceHealth.softwareUpdateHealth.partnerAgentSoftwareUpdateStatus.currentVersion
                    TeamsAppVersion = $TeamsDeviceHealth.softwareUpdateHealth.teamsClientSoftwareUpdateStatus.currentVersion
                    
                    #LastOperation
                    LastHistoryAction = $LastHistoryAction
                    LastHistoryStatus = $LastHistoryStatus
                    LastHistoryInitiatedBy = $LastHistoryInitiatedBy
                    LastHistoryModifiedDate = $LastHistoryModifiedDate
                    LastHistoryErrorCode = $LastHistoryErrorCode
                    LastHistoryErrorMessage = $LastHistoryErrorMessage 
                }
                $TDObj.PSObject.TypeNames.Insert(0, 'TeamsDevice')
        
            } else {
                $TDObj = New-Object -TypeName PSObject -Property @{
                    UserDisplayName = $TeamsDevice.currentuser.displayName
                    UserUPN         = $userUPN 
                    TACDeviceID     = $TeamsDevice.id
                    DeviceType      = Convert-UcTeamsDeviceType $TeamsDevice.deviceType
                    Manufacturer    = $TeamsDevice.hardwaredetail.manufacturer
                    Model           = $TeamsDevice.hardwaredetail.model
                    SerialNumber    = $TeamsDevice.hardwaredetail.serialNumber 
                    MacAddresses    = $TeamsDevice.hardwaredetail.macAddresses
                                
                    DeviceHealth    = $TeamsDevice.healthStatus
                }
                $TDObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceList')
            }
            $outTeamsDevices.Add($TDObj) | Out-Null
        }
        #region: Modified by Daniel Jelinek
        if($ExportCSV){
            $OutputFullPath = [System.IO.Path]::Combine($OutputPath, $tmpFileName)
            $outTeamsDevices | Sort-Object DeviceType,Manufacturer,Model| Select-Object TACDeviceID, DeviceType, Manufacturer, Model, UserDisplayName, UserUPN, Notes, CompanyAssetTag, SerialNumber, MacAddresses, WhenCreated, WhenChanged, ChangedByUser, HdmiIngestStatus, ComputeStatus, RoomCameraStatus, SpeakerStatus, CommunicationSpeakerStatus, MicrophoneStatus, SupportedMeetingMode, HardwareProcessor, SystemConfiguratio, TeamsAdminAgentVersion, FirmwareVersion, CompanyPortalVersion, OEMAgentAppVersion, TeamsAppVersion, LastUpdate, LastHistoryAction, LastHistoryStatus, LastHistoryInitiatedBy, LastHistoryModifiedDate, LastHistoryErrorCode, LastHistoryErrorMessage| Export-Csv -path $OutputFullPath -NoTypeInformation
            Write-Host ("Results available in: " + $OutputFullPath) -ForegroundColor Cyan
        } else {
            $outTeamsDevices | Sort-Object DeviceType,Manufacturer,Model
        }
        #endregion
    }
}
#EndRegion '.\Public\Get-UcTeamsDevice.ps1' 342
#Region '.\Public\Get-UcTeamsForest.ps1' 0
<#
.SYNOPSIS
Get Teams Forest
 
.DESCRIPTION
This function returns the forest for a SIP enabled domain.
 
.PARAMETER Domain
Specifies a domain registered with Microsoft 365
 
.EXAMPLE
PS> Get-UcTeamsForest -Domain uclobby.com
#>

Function Get-UcTeamsForest {
    Param(
        [Parameter(Mandatory = $true)]
        [string]$Domain
    )
    $regex = "^.*[redirect].*(webdir)(\w*)(.online.lync.com).*$"
    try {
        Test-UcModuleUpdateAvailable -ModuleName UcLobbyTeams
        $WebRequest = Invoke-WebRequest -Uri ("https://webdir.online.lync.com/AutoDiscover/AutoDiscoverservice.svc/root?originalDomain=" + $Domain)
        $temp = [regex]::Match($WebRequest, $regex).captures.groups
        $result = New-Object -TypeName PSObject -Property @{
                
            Domain       = $Domain
            Forest       = $temp[2].Value
            MigrationURL = "https://admin" + $temp[2].Value + ".online.lync.com/HostedMigration/hostedmigrationService.svc"
        }
        return $result
    }
    catch {
 
        if ($Error[0].Exception.Message -like "*404*") {
            Write-Warning ($Domain + " is not enabled for SIP." )
        }
    }
}
#EndRegion '.\Public\Get-UcTeamsForest.ps1' 39
#Region '.\Public\Get-UcTeamsVersion.ps1' 0

<#
.SYNOPSIS
Get Microsoft Teams Desktop Version
 
.DESCRIPTION
This function returns the installed Microsoft Teams desktop version for each user profile.
 
.PARAMETER Path
Specify the path with Teams Log Files
 
.PARAMETER Computer
Specify the remote computer
 
.PARAMETER Credential
Specify the credential to be used to connect to the remote computer
 
.EXAMPLE
PS> Get-UcTeamsVersion
 
.EXAMPLE
PS> Get-UcTeamsVersion -Path C:\Temp\
 
.EXAMPLE
PS> Get-UcTeamsVersion -Computer workstation124
 
.EXAMPLE
PS> $cred = Get-Credential
PS> Get-UcTeamsVersion -Computer workstation124 -Credential $cred
 
#>

Function Get-UcTeamsVersion {
    Param(
        [string]$Path,
        [string]$Computer,
        [System.Management.Automation.PSCredential]$Credential,
        [switch]$SkipModuleCheck
    )
    $regexVersion = '("version":")([0-9.]*)'
    $regexRing = '("ring":")(\w*)'
    $regexEnv = '("environment":")(\w*)'
    $regexCloudEnv = '("cloudEnvironment":")(\w*)'
    $regexRegion = '("region":")([a-zA-Z0-9._-]*)'

    $regexWindowsUser = '("upnWindowUserUpn":")([a-zA-Z0-9@._-]*)'
    $regexTeamsUserName = '("userName":")([a-zA-Z0-9@._-]*)'

    $outTeamsVersion = [System.Collections.ArrayList]::new()
    if(!$SkipModuleCheck){
        Test-UcModuleUpdateAvailable -ModuleName UcLobbyTeams
    }

    if ($Path) {
        if (Test-Path $Path -ErrorAction SilentlyContinue) {
            $TeamsSettingsFiles = Get-ChildItem -Path $Path -Include "settings.json" -Recurse
            foreach ($TeamsSettingsFile in $TeamsSettingsFiles) {
                $TeamsSettings = Get-Content -Path $TeamsSettingsFile.FullName
                $Version = ""
                $Ring = ""
                $Env = ""
                $CloudEnv = ""
                $Region = ""
                try {
                    $VersionTemp = [regex]::Match($TeamsSettings, $regexVersion).captures.groups
                    if ($VersionTemp.Count -ge 2) {
                        $Version = $VersionTemp[2].value
                    }
                    $RingTemp = [regex]::Match($TeamsSettings, $regexRing).captures.groups
                    if ($RingTemp.Count -ge 2) {
                        $Ring = $RingTemp[2].value
                    }
                    $EnvTemp = [regex]::Match($TeamsSettings, $regexEnv).captures.groups
                    if ($EnvTemp.Count -ge 2) {
                        $Env = $EnvTemp[2].value
                    }
                    $CloudEnvTemp = [regex]::Match($TeamsSettings, $regexCloudEnv).captures.groups
                    if ($CloudEnvTemp.Count -ge 2) {
                        $CloudEnv = $CloudEnvTemp[2].value
                    }
                    $RegionTemp = [regex]::Match($TeamsSettings, $regexRegion).captures.groups
                    if ($RegionTemp.Count -ge 2) {
                        $Region = $RegionTemp[2].value
                    }
                }
                catch { }
                $TeamsDesktopSettingsFile = $TeamsSettingsFile.Directory.FullName + "\desktop-config.json"
                if (Test-Path $TeamsDesktopSettingsFile -ErrorAction SilentlyContinue) {
                    $TeamsDesktopSettings = Get-Content -Path $TeamsDesktopSettingsFile
                    $WindowsUser = ""
                    $TeamsUserName = ""
                    $RegexTemp = [regex]::Match($TeamsDesktopSettings, $regexWindowsUser).captures.groups
                    if ($RegexTemp.Count -ge 2) {
                        $WindowsUser = $RegexTemp[2].value
                    }
                    $RegexTemp = [regex]::Match($TeamsDesktopSettings, $regexTeamsUserName).captures.groups
                    if ($RegexTemp.Count -ge 2) {
                        $TeamsUserName = $RegexTemp[2].value
                    }
                }
                $TeamsVersion = New-Object -TypeName PSObject -Property @{
                    WindowsUser      = $WindowsUser
                    TeamsUser        = $TeamsUserName
                    Version          = $Version
                    Ring             = $Ring
                    Environment      = $Env
                    CloudEnvironment = $CloudEnv
                    Region           = $Region
                    Path             = $TeamsSettingsFile.Directory.FullName
                }
                $TeamsVersion.PSObject.TypeNames.Insert(0, 'TeamsVersionFromPath')
                $outTeamsVersion.Add($TeamsVersion) | Out-Null
            }
        }
        else {
            Write-Error -Message ("Invalid Path, please check if path: " + $path + " is correct and exists.")
        }
    }
    else {
        $currentDateFormat = [cultureinfo]::CurrentCulture.DateTimeFormat.ShortDatePattern
        if ($Computer) {
            $RemotePath = "\\" + $Computer + "\C$\Users"
            $ComputerName = $Computer
            if ($Credential) {
                if ($Computer.IndexOf('.') -gt 0){
                    $PSDriveName = $Computer.Substring(0,$Computer.IndexOf('.')) + "_TmpTeamsVersion"
                } else {
                    $PSDriveName = $Computer + "_TmpTeamsVersion"
                }
                New-PSDrive -Root $RemotePath -Name $PSDriveName -PSProvider FileSystem -Credential $Credential | Out-Null
            }

            if (Test-Path -Path $RemotePath) {
                $Profiles = Get-ChildItem -Path $RemotePath -ErrorAction SilentlyContinue
            }
            else {
                Write-Error -Message ("Error: Cannot get users on " + $computer + ", please check if name is correct and if the current user has permissions.")
            }
        }
        else {
            $ComputerName = $Env:COMPUTERNAME
            $Profiles = Get-childItem 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' | ForEach-Object { Get-ItemProperty $_.pspath } | Where-Object { $_.fullprofile -eq 1 }
        }
        
        foreach ($UserProfile in $Profiles) {
            if ($Computer) {
                $ProfilePath = $UserProfile.FullName
                $ProfileName = $UserProfile.Name
            }
            else {
                $ProfilePath = $UserProfile.ProfileImagePath
                $ProfileName = (New-Object System.Security.Principal.SecurityIdentifier($UserProfile.PSChildName)).Translate( [System.Security.Principal.NTAccount]).Value
            }
            $TeamsSettingPath = $ProfilePath + "\AppData\Roaming\Microsoft\Teams\settings.json"
            if (Test-Path $TeamsSettingPath -ErrorAction SilentlyContinue) {
                $TeamsSettings = Get-Content -Path $TeamsSettingPath
                $Version = ""
                $Ring = ""
                $Env = ""
                $CloudEnv = ""
                $Region = ""
                try {
                    $VersionTemp = [regex]::Match($TeamsSettings, $regexVersion).captures.groups
                    if ($VersionTemp.Count -ge 2) {
                        $Version = $VersionTemp[2].value
                    }
                    $RingTemp = [regex]::Match($TeamsSettings, $regexRing).captures.groups
                    if ($RingTemp.Count -ge 2) {
                        $Ring = $RingTemp[2].value
                    }
                    $EnvTemp = [regex]::Match($TeamsSettings, $regexEnv).captures.groups
                    if ($EnvTemp.Count -ge 2) {
                        $Env = $EnvTemp[2].value
                    }
                    $CloudEnvTemp = [regex]::Match($TeamsSettings, $regexCloudEnv).captures.groups
                    if ($CloudEnvTemp.Count -ge 2) {
                        $CloudEnv = $CloudEnvTemp[2].value
                    }
                    $RegionTemp = [regex]::Match($TeamsSettings, $regexRegion).captures.groups
                    if ($RegionTemp.Count -ge 2) {
                        $Region = $RegionTemp[2].value
                    }
                }
                catch { }
                $TeamsApp = $ProfilePath + "\AppData\Local\Microsoft\Teams\current\Teams.exe"
                $InstallDateStr = Get-Content ($ProfilePath + "\AppData\Roaming\Microsoft\Teams\installTime.txt")
                
                $TeamsVersion = New-Object -TypeName PSObject -Property @{
                    Computer         = $ComputerName
                    Profile          = $ProfileName
                    ProfilePath      = $ProfilePath
                    Version          = $Version
                    Ring             = $Ring
                    Environment      = $Env
                    CloudEnvironment = $CloudEnv
                    Region           = $Region
                    Arch             = Get-UcArch $TeamsApp
                    InstallDate      = [Datetime]::ParseExact($InstallDateStr, 'M/d/yyyy', $null) | Get-Date -Format $currentDateFormat
                }
                $TeamsVersion.PSObject.TypeNames.Insert(0, 'TeamsVersion')
                $outTeamsVersion.Add($TeamsVersion) | Out-Null
            }
        }
        if ($Credential -and $PSDriveName) {
            try {
                Remove-PSDrive -Name $PSDriveName -ErrorAction SilentlyContinue
            }
            catch {}
        }
    }
    return $outTeamsVersion
}
#EndRegion '.\Public\Get-UcTeamsVersion.ps1' 212
#Region '.\Public\Get-UcTeamsVersionBatch.ps1' 0
<#
.SYNOPSIS
Get Microsoft Teams Desktop Version from all computers in a csv file.
 
.DESCRIPTION
This function returns the installed Microsoft Teams desktop version for each user profile.
 
.PARAMETER InputCSV
CSV with the list of computers that we want to get the Teams Version
 
.PARAMETER OutputPath
Specify the output path
 
.PARAMETER ExportCSV
Export the output to a CSV file
 
.PARAMETER Credential
Specify the credential to be used to connect to the remote computers
 
.EXAMPLE
PS> Get-UcTeamsVersionBatch
 
.EXAMPLE
PS> Get-UcTeamsVersionBatch -InputCSV C:\Temp\ComputerList.csv -Credential $cred
 
.EXAMPLE
PS> Get-UcTeamsVersionBatch -InputCSV C:\Temp\ComputerList.csv -Credential $cred -ExportCSV
#>


Function Get-UcTeamsVersionBatch {
    Param(
        [Parameter(Mandatory = $true)]
        [string]$InputCSV,
        [string]$OutputPath,
        [switch]$ExportCSV,
        [System.Management.Automation.PSCredential]$Credential
    )
    Test-UcModuleUpdateAvailable -ModuleName UcLobbyTeams
    if (Test-Path $InputCSV) {
        try{
            $Computers = Import-Csv -Path $InputCSV
        } catch {
            Write-Host ("Invalid CSV input file: " + $InputCSV) -ForegroundColor Red
            return
        }
        $outTeamsVersion = [System.Collections.ArrayList]::new()
        #Verify if the Output Path exists
        if ($OutputPath) {
            if (!(Test-Path $OutputPath -PathType Container)) {
                Write-Host ("Error: Invalid folder: " + $OutputPath) -ForegroundColor Red
                return
            } 
        }
        else {                
            $OutputPath = [System.IO.Path]::Combine($env:USERPROFILE, "Downloads")
        }

        $c = 0
        $compCount = $Computers.count
        
        foreach ($computer in $Computers) {
            $c++
            Write-Progress -Activity ("Getting Teams Version from: " + $computer.Computer)  -Status "Computer $c of $compCount "
            $tmpTV = Get-UcTeamsVersion -Computer $computer.Computer -Credential $cred -SkipModuleCheck
            $outTeamsVersion.Add($tmpTV) | Out-Null
        }
        if ($ExportCSV) {
            $tmpFileName = "MSTeamsVersion_" + ( get-date ).ToString('yyyyMMdd-HHmmss') + ".csv"
            $OutputFullPath = [System.IO.Path]::Combine($OutputPath, $tmpFileName)
            $outTeamsVersion | Sort-Object Computer, Profile | Select-Object Computer, Profile, ProfilePath, Arch, Version, Environment, Ring, InstallDate | Export-Csv -path $OutputFullPath -NoTypeInformation
            Write-Host ("Results available in: " + $OutputFullPath) -ForegroundColor Cyan
        }
        else {
            return $outTeamsVersion 
        }
    } else {
        Write-Host ("Error: File not found " + $InputCSV) -ForegroundColor Red
    }
}
#EndRegion '.\Public\Get-UcTeamsVersionBatch.ps1' 80
#Region '.\Public\Get-UcTeamsWithSingleOwner.ps1' 0
<#
.SYNOPSIS
Get Teams that have a single owner
 
.DESCRIPTION
This function returns a list of Teams that only have a single owner.
 
.EXAMPLE
PS> Get-UcTeamsWithSingleOwner
#>

Function Get-UcTeamsWithSingleOwner {
    Get-UcTeamUsersEmail -Role Owner -Confirm:$false | Group-Object -Property TeamDisplayName | Where-Object { $_.Count -lt 2 } | Select-Object -ExpandProperty Group
}
#EndRegion '.\Public\Get-UcTeamsWithSingleOwner.ps1' 14
#Region '.\Public\Get-UcTeamUsersEmail.ps1' 0

<#
.SYNOPSIS
Get Users Email Address that are in a Team
 
.DESCRIPTION
This function returns a list of users email address that are part of a Team.
 
.PARAMETER TeamName
Specifies Team Name
 
.PARAMETER Role
Specifies which roles to filter (Owner, User, Guest)
 
.EXAMPLE
PS> Get-UcTeamUsersEmail
 
.EXAMPLE
PS> Get-UcTeamUsersEmail -TeamName "Marketing"
 
.EXAMPLE
PS> Get-UcTeamUsersEmail -Role "Guest"
 
.EXAMPLE
PS> Get-UcTeamUsersEmail -TeamName "Marketing" -Role "Guest"
#>

Function Get-UcTeamUsersEmail {
    [cmdletbinding(SupportsShouldProcess)]
    Param(
        [string]$TeamName,
        [ValidateSet("Owner", "User", "Guest")] 
        [string]$Role
    )
    $output = [System.Collections.ArrayList]::new()
    Test-UcModuleUpdateAvailable -ModuleName UcLobbyTeams
    if ($TeamName) {
        $Teams = Get-Team -DisplayName $TeamName
    }
    else {
        if ($ConfirmPreference) {
            $title = 'Confirm'
            $question = 'Are you sure that you want to list all Teams?'
            $choices = '&Yes', '&No'
            $decision = $Host.UI.PromptForChoice($title, $question, $choices, 1)
        }
        else {
            $decision = 0
        }
        if ($decision -eq 0) {
            $Teams = Get-Team
        }
        else {
            return
        }
    }
    foreach ($Team in $Teams) { 
        if ($Role) {
            $TeamMembers = Get-TeamUser -GroupId $Team.GroupID -Role $Role
        }
        else {
            $TeamMembers = Get-TeamUser -GroupId $Team.GroupID 
        }
        foreach ($TeamMember in $TeamMembers) {
            $Email = ( Get-csOnlineUser $TeamMember.User | Select-Object @{Name = 'PrimarySMTPAddress'; Expression = { $_.ProxyAddresses -cmatch '^SMTP:' -creplace 'SMTP:' } }).PrimarySMTPAddress
            $Member = New-Object -TypeName PSObject -Property @{
                TeamGroupID     = $Team.GroupID
                TeamDisplayName = $Team.DisplayName
                TeamVisibility  = $Team.Visibility
                UPN             = $TeamMember.User
                Role            = $TeamMember.Role
                Email           = $Email
            }
            $Member.PSObject.TypeNames.Insert(0, 'TeamUsersEmail')
            $output.Add($Member) | Out-Null
        }
    }
    return $output
}
#EndRegion '.\Public\Get-UcTeamUsersEmail.ps1' 79
#Region '.\Public\Test-UcModuleUpdateAvailable.ps1' 0
Function Test-UcModuleUpdateAvailable {
    Param(
        [Parameter(Mandatory = $true)]    
        [string]$ModuleName
    )
    try { 
        #Get the current loaded version
        $tmpCurrentVersion = (get-module $ModuleName | Sort-Object Version -Descending)
        if ($tmpCurrentVersion){
            $currentVersion = $tmpCurrentVersion[0].Version.ToString()
        }
        
        #Get the lastest version available
        $availableVersion = (Find-Module -Name $ModuleName -Repository PSGallery -ErrorAction SilentlyContinue).Version
        #Get all installed versions
        $installedVersions = (get-module $ModuleName -ListAvailable).Version

        if ($currentVersion -ne $availableVersion ) {
            if ($availableVersion -in $installedVersions) {
                Write-Warning ("The lastest available version of $ModuleName module is installed, however version $currentVersion is imported." + [Environment]::NewLine + "Please make sure you import it with: Import-Module $ModuleName -RequiredVersion $availableVersion")
            }
            else {
                Write-Warning ("There is a new version available ($availableVersion), please update the module with: Update-Module $ModuleName")
            }
        }
    }
    catch {
    }
}
#EndRegion '.\Public\Test-UcModuleUpdateAvailable.ps1' 30
#Region '.\Public\Test-UcTeamsDevicesCompliancePolicy.ps1' 0
<#
.SYNOPSIS
Validate which Intune Compliance policies are supported by Microsoft Teams Android Devices
 
.DESCRIPTION
This function will validate each setting in the Intune Compliance Policy to make sure they are in line with the supported settings:
 
    https://docs.microsoft.com/en-us/microsoftteams/rooms/supported-ca-and-compliance-policies?tabs=phones#supported-device-compliance-policies
 
Contributors: Traci Herr, David Paulino
 
Requirements: Microsoft Graph PowerShell Module (Install-Module Microsoft.Graph)
 
.PARAMETER Detailed
Displays test results for unsupported settings in each Intune Compliance Policy
 
.PARAMETER All
Will check all Intune Compliance policies independently if they are assigned to a Group(s)
 
.PARAMETER IncludeSupported
Displays results for all settings in each Intune Compliance Policy
 
.PARAMETER PolicyID
Specifies a Policy ID that will be checked if is supported by Microsoft Teams Android Devices
 
.PARAMETER PolicyName
Specifies a Policy Name that will be checked if is supported by Microsoft Teams Android Devices
 
.PARAMETER UserUPN
Specifies a UserUPN that we want to check for applied compliance policies
 
.PARAMETER DeviceID
Specifies DeviceID that we want to check for applied compliance policies
 
.PARAMETER ExportCSV
When present will export the detailed results to a CSV file. By defautl will save the file under the current user downloads, unless we specify the OutputPath.
 
.PARAMETER OutputPath
Allows to specify the path where we want to save the results.
 
.EXAMPLE
PS> Test-UcTeamsDevicesCompliancePolicy
 
.EXAMPLE
PS> Test-UcTeamsDevicesCompliancePolicy -Detailed
 
#>

Function Test-UcTeamsDevicesCompliancePolicy {
    Param(
        [switch]$Detailed,
        [switch]$All,
        [switch]$IncludeSupported,
        [string]$PolicyID,
        [string]$PolicyName,
        [string]$UserUPN,
        [string]$DeviceID,
        [switch]$ExportCSV,
        [string]$OutputPath
    )

    $connectedMSGraph = $false
    $CompliancePolicies = $null
    $totalCompliancePolicies = 0
    $skippedCompliancePolicies = 0

    $GraphURI_CompliancePolicies = "https://graph.microsoft.com/beta/deviceManagement/deviceCompliancePolicies/"
    $GraphURI_Users = "https://graph.microsoft.com/v1.0/users"
    $GraphURI_Groups = "https://graph.microsoft.com/v1.0/groups"
    $GraphURI_Devices = "https://graph.microsoft.com/v1.0/devices"

    $SupportedAndroidCompliancePolicies = "#microsoft.graph.androidCompliancePolicy", "#microsoft.graph.androidDeviceOwnerCompliancePolicy", "#microsoft.graph.aospDeviceOwnerCompliancePolicy"
    $SupportedWindowsCompliancePolicies = "#microsoft.graph.windows10CompliancePolicy"

    $URLSupportedCompliancePoliciesAndroid = "https://aka.ms/TeamsDevicePolicies?tabs=phones#supported-device-compliance-policies"
    $URLSupportedCompliancePoliciesWindows = "https://aka.ms/TeamsDevicePolicies?tabs=mtr-w#supported-device-compliance-policies"

    if (Test-UcMgGraphConnection -Scopes "DeviceManagementConfiguration.Read.All", "Directory.Read.All") {
        Test-UcModuleUpdateAvailable -ModuleName UcLobbyTeams
        $outFileName = "TeamsDevices_CompliancePolicy_Report_" + ( get-date ).ToString('yyyyMMdd-HHmmss') + ".csv"
        if ($OutputPath) {
            if (!(Test-Path $OutputPath -PathType Container)) {
                Write-Host ("Error: Invalid folder " + $OutputPath) -ForegroundColor Red
                return
            } 
            $OutputFullPath = [System.IO.Path]::Combine($OutputPath, $outFileName)
        }
        else {                
            $OutputFullPath = [System.IO.Path]::Combine($env:USERPROFILE, "Downloads", $outFileName)
        }

        try {
            Write-Progress -Activity "Test-UcTeamsDeviceCompliancePolicy" -Status "Getting Compliance Policies"
            $CompliancePolicies = (Invoke-MgGraphRequest -Uri $GraphURI_CompliancePolicies -Method GET).value
            $connectedMSGraph = $true
        }
        catch [System.Net.Http.HttpRequestException] {
            if ($PSItem.Exception.Response.StatusCode -eq "Unauthorized") {
                Write-Error "Access Denied, please make sure the user connecing to MS Graph is part of one of the following Global Reader/Intune Service Administrator/Global Administrator roles"
            }
            else {
                Write-Error $PSItem.Exception.Message
            }
        }
        catch {
            Write-Error 'Please connect to MS Graph with Connect-MgGraph -Scopes "DeviceManagementConfiguration.Read.All","Directory.Read.All" before running this script'
        }

        if ($connectedMSGraph) {
            $output = [System.Collections.ArrayList]::new()
            $outputSum = [System.Collections.ArrayList]::new()
            if ($UserUPN) {
                try {
                    $UserGroups = (Invoke-MgGraphRequest -Uri ($GraphURI_Users + "/" + $userUPN + "/transitiveMemberOf?`$select=id") -Method GET).value.id
                }
                catch [System.Net.Http.HttpRequestException] {
                    if ($PSItem.Exception.Response.StatusCode -eq "NotFound") {
                        Write-warning -Message ("User Not Found: " + $UserUPN)
                    }
                    return
                }
                #We also need to take in consideration devices that are registered to this user
                $DeviceGroups = [System.Collections.ArrayList]::new()
                $userDevices = (Invoke-MgGraphRequest -Uri ($GraphURI_Users + "/" + $userUPN + "/registeredDevices?`$select=deviceId,displayName") -Method GET).value
                foreach ($userDevice in $userDevices) {
                    $tmpGroups = (Invoke-MgGraphRequest -Uri ($GraphURI_Devices + "(deviceId='{" + $userDevice.deviceID + "}')/transitiveMemberOf?`$select=id") -Method GET).value.id
                    foreach ($tmpGroup in $tmpGroups) {
                        $tmpDG = New-Object -TypeName PSObject -Property @{
                            GroupId           = $tmpGroup
                            DeviceId          = $userDevice.deviceID
                            DeviceDisplayName = $userDevice.displayName
                        }
                        $DeviceGroups.Add($tmpDG) | Out-Null
                    }
                }
            }
        
            if ($DeviceID) {
                try {
                    $DeviceGroups = (Invoke-MgGraphRequest -Uri ($GraphURI_Devices + "(deviceId='{" + $DeviceID + "}')/transitiveMemberOf?`$select=id") -Method GET).value.id
                }
                catch [System.Net.Http.HttpRequestException] {
                    if ($PSItem.Exception.Response.StatusCode -eq "BadRequest") {
                        Write-warning -Message ("Device ID Not Found: " + $DeviceID)
                    }
                    return
                }
            }

            $Groups = New-Object 'System.Collections.Generic.Dictionary[string, string]'
            $p=0
            $policyCount = $CompliancePolicies.Count
            foreach ($CompliancePolicy in $CompliancePolicies) {
                $p++
                Write-Progress -Activity "Test-UcTeamsDeviceCompliancePolicy" -Status ("Checking policy " + $CompliancePolicy.displayName + " - $p of $policyCount")
                if ((($PolicyID -eq $CompliancePolicy.id) -or ($PolicyName -eq $CompliancePolicy.displayName) -or (!$PolicyID -and !$PolicyName)) -and (($CompliancePolicy."@odata.type" -in $SupportedAndroidCompliancePolicies) -or ($CompliancePolicy."@odata.type" -in $SupportedWindowsCompliancePolicies))) {
                    
                    #We need to check if the policy has assignments (Groups/All Users/All Devices)
                    $CompliancePolicyAssignments = (Invoke-MgGraphRequest -Uri ($GraphURI_CompliancePolicies + $CompliancePolicy.id + "/assignments" ) -Method GET).value
                    $AssignedToGroup = [System.Collections.ArrayList]::new()
                    $ExcludedFromGroup = [System.Collections.ArrayList]::new()
                    $outAssignedToGroup = ""
                    $outExcludedFromGroup = ""
                    #We wont need to check the settings if the policy is not assigned to a user
                    if ($UserUPN -or $DeviceID) {
                        $userOrDeviceIncluded = $false
                    }
                    else {
                        $userOrDeviceIncluded = $true
                    }

                    #Define the Compliance Policy type
                    switch ($CompliancePolicy."@odata.type") {
                        "#microsoft.graph.androidCompliancePolicy" { $CPType = "Android Device" }
                        "#microsoft.graph.androidDeviceOwnerCompliancePolicy" { $CPType = "Android Enterprise" }
                        "#microsoft.graph.aospDeviceOwnerCompliancePolicy" { $CPType = "Android (AOSP)" }
                        "#microsoft.graph.windows10CompliancePolicy" { $CPType = "Windows 10 or later" }
                        Default { $CPType = $CompliancePolicy."@odata.type".split('.')[2] }
                    }

                    #Checking Compliance Policy assigments since we can skip non assigned policies.
                    foreach ($CompliancePolicyAssignment in $CompliancePolicyAssignments) {
                        $GroupDisplayName = $CompliancePolicyAssignment.target.Groupid
                        if ($Groups.ContainsKey($CompliancePolicyAssignment.target.Groupid)) {
                            $GroupDisplayName = $Groups.Item($CompliancePolicyAssignment.target.Groupid)
                        }
                        else {
                            try {
                                $GroupInfo = Invoke-MgGraphRequest -Uri ($GraphURI_Groups + "/" + $CompliancePolicyAssignment.target.Groupid + "/?`$select=id,displayname") -Method GET
                                $Groups.Add($GroupInfo.id, $GroupInfo.displayname)
                                $GroupDisplayName = $GroupInfo.displayname
                            }
                            catch {
                            }
                        }
                        $GroupEntry = New-Object -TypeName PSObject -Property @{
                            GroupID          = $CompliancePolicyAssignment.target.Groupid
                            GroupDisplayName = $GroupDisplayName
                        }
                        switch ($CompliancePolicyAssignment.target."@odata.type") {
                            #Policy assigned to all users
                            "#microsoft.graph.allLicensedUsersAssignmentTarget" {
                                $GroupEntry = New-Object -TypeName PSObject -Property @{
                                    GroupID          = "allLicensedUsersAssignment"
                                    GroupDisplayName = "All Users"
                                }
                                $AssignedToGroup.Add($GroupEntry) | Out-Null
                                $userOrDeviceIncluded = $true
                            }
                            #Policy assigned to all devices
                            "#microsoft.graph.allDevicesAssignmentTarget" {
                                $GroupEntry = New-Object -TypeName PSObject -Property @{
                                    GroupID          = "allDevicesAssignmentTarget"
                                    GroupDisplayName = "All Devices"
                                }
                                $AssignedToGroup.Add($GroupEntry) | Out-Null
                                $userOrDeviceIncluded = $true
                            }
                            #Group that this policy is assigned
                            "#microsoft.graph.groupAssignmentTarget" {
                                $AssignedToGroup.Add($GroupEntry) | Out-Null
                                if (($UserUPN -or $DeviceID) -and (($CompliancePolicyAssignment.target.Groupid -in $UserGroups) -or ($CompliancePolicyAssignment.target.Groupid -in $DeviceGroups))) {
                                    $userOrDeviceIncluded = $true
                                }
                            }
                            #Group that this policy is excluded
                            "#microsoft.graph.exclusionGroupAssignmentTarget" {
                                $ExcludedFromGroup.Add($GroupEntry) | Out-Null
                                #If user is excluded then we dont need to check the policy
                                if ($UserUPN -and ($CompliancePolicyAssignment.target.Groupid -in $UserGroups)) {
                                    Write-Warning ("Skiping compliance policy " + $CompliancePolicy.displayName + ", since user " + $UserUPN + " is part of an Excluded Group: " + $GroupEntry.GroupDisplayName)
                                    $userOrDeviceExcluded = $true
                                }
                                elseif ($DeviceID -and ($CompliancePolicyAssignment.target.Groupid -in $DeviceGroups)) {
                                    Write-Warning ("Skiping compliance policy " + $CompliancePolicy.displayName + ", since device " + $DeviceID + " is part of an Excluded Group: " + $GroupEntry.GroupDisplayName)
                                    $userOrDeviceExcluded = $true
                                }
                                elseif ($UserUPN -and (($CompliancePolicyAssignment.target.Groupid -in $DeviceGroups.GroupId))) {
                                    #In case a device is excluded we will check the policy but output a message
                                    $tmpDev = ($DeviceGroups | Where-Object -Property GroupId -eq -Value $CompliancePolicyAssignment.target.Groupid)
                                    Write-Warning ("Compliance policy " + $CompliancePolicy.displayName + " will not be applied to device " + $tmpDev.DeviceDisplayName + " (" + $tmpDev.DeviceID + "), since this device is part of an Excluded Group: " + $GroupEntry.GroupDisplayName)
                                }
                            }
                        }
                    }
                
                    if ((($AssignedToGroup.count -gt 0) -and !$userOrDeviceExcluded -and $userOrDeviceIncluded) -or $all) {
                        $totalCompliancePolicies++ 
                        $PolicyErrors = 0
                        $PolicyWarnings = 0

                        #If only assigned/excluded from a group we will show the group display name, otherwise the number of groups assigned/excluded.
                        if ($AssignedToGroup.count -eq 1) {
                            $outAssignedToGroup = $AssignedToGroup.GroupDisplayName
                        }
                        elseif ($AssignedToGroup.count -eq 0) {
                            $outAssignedToGroup = "None"
                        }
                        else {
                            $outAssignedToGroup = "" + $AssignedToGroup.count + " groups"
                        }

                        if ($ExcludedFromGroup.count -eq 1) {
                            $outExcludedFromGroup = $ExcludedFromGroup.GroupDisplayName
                        }
                        elseif ($ExcludedFromGroup.count -eq 0) {
                            $outExcludedFromGroup = "None"
                        }
                        else {
                            $outExcludedFromGroup = "" + $ExcludedFromGroup.count + " groups"
                        }

                        if ($CompliancePolicy."@odata.type" -in $SupportedAndroidCompliancePolicies) {
                            $URLSupportedCompliancePolicies = $URLSupportedCompliancePoliciesAndroid
                        }
                        elseif ($CompliancePolicy."@odata.type" -in $SupportedWindowsCompliancePolicies) {
                            $URLSupportedCompliancePolicies = $URLSupportedCompliancePoliciesWindows
                        }

                        #region Common settings between Android and Windows
                        #region 9: Device Properties > Operation System Version
                        $ID = 9.1
                        $Setting = "osMinimumVersion"
                        $SettingDescription = "Device Properties > Operation System Version > Minimum OS version"
                        $SettingValue = "Not Configured"
                        $Comment = ""
                        if (!([string]::IsNullOrEmpty($CompliancePolicy.osMinimumVersion))) {
                            if ($CompliancePolicy."@odata.type" -in $SupportedWindowsCompliancePolicies) {
                                $Status = "Unsupported"
                                $Comment = "Teams Rooms automatically updates to newer versions of Windows and setting values here could prevent successful sign-in after an OS update."
                                $PolicyErrors++
                            }
                            else {
                                $Status = "Warning"
                                $Comment = "This setting can cause sign in issues."
                                $PolicyWarnings++
                            }
                            $SettingValue = $CompliancePolicy.osMinimumVersion
                        }
                        else {
                            $Status = "Supported"
                        }
                        $SettingPSObj = [PSCustomObject]@{
                            PolicyName            = $CompliancePolicy.displayName
                            PolicyType            = $CPType
                            Setting               = $Setting
                            Value                 = $SettingValue
                            TeamsDevicesStatus    = $Status 
                            Comment               = $Comment
                            SettingDescription    = $SettingDescription
                            AssignedToGroup       = $outAssignedToGroup
                            ExcludedFromGroup     = $outExcludedFromGroup 
                            AssignedToGroupList   = $AssignedToGroup
                            ExcludedFromGroupList = $ExcludedFromGroup
                            PolicyID              = $CompliancePolicy.id
                            ID                    = $ID
                        }
                        $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceCompliancePolicyDetailed')
                        [void]$output.Add($SettingPSObj)
            
                        $ID = 9.2
                        $Setting = "osMaximumVersion"
                        $SettingDescription = "Device Properties > Operation System Version > Maximum OS version"
                        $SettingValue = "Not Configured"
                        $Comment = ""
                        if (!([string]::IsNullOrEmpty($CompliancePolicy.osMaximumVersion))) {
                            if ($CompliancePolicy."@odata.type" -in $SupportedWindowsCompliancePolicies) {
                                $Status = "Unsupported"
                                $Comment = "Teams Rooms automatically updates to newer versions of Windows and setting values here could prevent successful sign-in after an OS update."
                                $PolicyErrors++
                            }
                            else {
                                $Status = "Warning"
                                $Comment = "This setting can cause sign in issues."
                                $PolicyWarnings++
                            }
                            $SettingValue = $CompliancePolicy.osMaximumVersion
                        }
                        else {
                            $Status = "Supported"
                        }
                        $SettingPSObj = [PSCustomObject]@{
                            PolicyName            = $CompliancePolicy.displayName
                            PolicyType            = $CPType
                            Setting               = $Setting
                            Value                 = $SettingValue
                            TeamsDevicesStatus    = $Status 
                            Comment               = $Comment
                            SettingDescription    = $SettingDescription
                            AssignedToGroup       = $outAssignedToGroup
                            ExcludedFromGroup     = $outExcludedFromGroup 
                            AssignedToGroupList   = $AssignedToGroup
                            ExcludedFromGroupList = $ExcludedFromGroup
                            PolicyID              = $CompliancePolicy.id
                            ID                    = $ID
                        }
                        $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceCompliancePolicyDetailed')
                        [void]$output.Add($SettingPSObj)
                        #endregion

                        #region 17: System Security > All Android devices > Require a password to unlock mobile devices
                        $ID = 17
                        $Setting = "passwordRequired"
                        $SettingDescription = "System Security > All Android devices > Require a password to unlock mobile devices"
                        $SettingValue = "Not Configured"
                        $Comment = ""
                        if ($CompliancePolicy.passwordRequired) {
                            $Status = "Unsupported"
                            $SettingValue = "Require"
                            $Comment = $URLSupportedCompliancePolicies
                            $PolicyErrors++
                        }
                        else {
                            $Status = "Supported"
                        }
                        $SettingPSObj = [PSCustomObject]@{
                            PolicyName            = $CompliancePolicy.displayName
                            PolicyType            = $CPType
                            Setting               = $Setting
                            Value                 = $SettingValue
                            TeamsDevicesStatus    = $Status 
                            Comment               = $Comment
                            SettingDescription    = $SettingDescription
                            AssignedToGroup       = $outAssignedToGroup
                            ExcludedFromGroup     = $outExcludedFromGroup 
                            AssignedToGroupList   = $AssignedToGroup
                            ExcludedFromGroupList = $ExcludedFromGroup
                            PolicyID              = $CompliancePolicy.id
                            ID                    = $ID
                        }
                        $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceCompliancePolicyDetailed')
                        [void]$output.Add($SettingPSObj)
                        #endregion
                        #endregion

                        if ($CompliancePolicy."@odata.type" -in $SupportedAndroidCompliancePolicies) {

                            #region 1: Microsoft Defender for Endpoint > Require the device to be at or under the machine risk score
                            $ID = 1
                            $Setting = "deviceThreatProtectionEnabled"
                            $SettingDescription = "Microsoft Defender for Endpoint > Require the device to be at or under the machine risk score"
                            $SettingValue = "Not Configured"
                            $Comment = ""
                            if ($CompliancePolicy.deviceThreatProtectionEnabled) {
                                $Status = "Unsupported"
                                $PolicyErrors++
                                $SettingValue = $CompliancePolicy.advancedThreatProtectionRequiredSecurityLevel
                                $Comment = $URLSupportedCompliancePolicies
                            }
                            else {
                                $Status = "Supported"
                            }
                            $SettingPSObj = [PSCustomObject]@{
                                PolicyName            = $CompliancePolicy.displayName
                                PolicyType            = $CPType
                                Setting               = $Setting
                                Value                 = $SettingValue
                                TeamsDevicesStatus    = $Status 
                                Comment               = $Comment
                                SettingDescription    = $SettingDescription
                                AssignedToGroup       = $outAssignedToGroup
                                ExcludedFromGroup     = $outExcludedFromGroup 
                                AssignedToGroupList   = $AssignedToGroup
                                ExcludedFromGroupList = $ExcludedFromGroup
                                PolicyID              = $CompliancePolicy.id
                                ID                    = $ID
                            }
                            $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceCompliancePolicyDetailed')
                            [void]$output.Add($SettingPSObj)
                            #endregion

                            #region 2: Device Health > Device managed with device administrator
                            $ID = 2
                            $Setting = "securityBlockDeviceAdministratorManagedDevices"
                            $SettingDescription = "Device Health > Device managed with device administrator"
                            $SettingValue = "Not Configured"
                            $Comment = ""
                            if ($CompliancePolicy.securityBlockDeviceAdministratorManagedDevices) {
                                $Status = "Unsupported"
                                $SettingValue = "Block"
                                $Comment = "Teams Android devices management requires device administrator to be enabled."
                                $PolicyErrors++
                            }
                            else {
                                $Status = "Supported"
                            }
                            $SettingPSObj = [PSCustomObject]@{
                                PolicyName            = $CompliancePolicy.displayName
                                PolicyType            = $CPType
                                Setting               = $Setting
                                Value                 = $SettingValue
                                TeamsDevicesStatus    = $Status 
                                Comment               = $Comment
                                SettingDescription    = $SettingDescription
                                AssignedToGroup       = $outAssignedToGroup
                                ExcludedFromGroup     = $outExcludedFromGroup 
                                AssignedToGroupList   = $AssignedToGroup
                                ExcludedFromGroupList = $ExcludedFromGroup
                                PolicyID              = $CompliancePolicy.id
                                ID                    = $ID
                            }
                            $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceCompliancePolicyDetailed')
                            [void]$output.Add($SettingPSObj)
                            #endregion

                            #region 3: Device Health > Rooted devices
                            $ID = 3
                            $Setting = "securityBlockJailbrokenDevices"
                            $SettingDescription = "Device Health > Rooted devices"
                            $SettingValue = "Not Configured"
                            $Comment = ""
                            if ($CompliancePolicy.securityBlockJailbrokenDevices) {
                                $Status = "Warning"
                                $SettingValue = "Block"
                                $Comment = "This setting can cause sign in issues."
                                $PolicyWarnings++
                            }
                            else {
                                $Status = "Supported"
                            }
                            $SettingPSObj = [PSCustomObject]@{
                                PolicyName            = $CompliancePolicy.displayName
                                PolicyType            = $CPType
                                Setting               = $Setting
                                Value                 = $SettingValue
                                TeamsDevicesStatus    = $Status 
                                Comment               = $Comment
                                SettingDescription    = $SettingDescription
                                AssignedToGroup       = $outAssignedToGroup
                                ExcludedFromGroup     = $outExcludedFromGroup 
                                AssignedToGroupList   = $AssignedToGroup
                                ExcludedFromGroupList = $ExcludedFromGroup
                                PolicyID              = $CompliancePolicy.id
                                ID                    = $ID
                            }
                            $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceCompliancePolicyDetailed')
                            [void]$output.Add($SettingPSObj)
                            #endregion

                            #region 4: Device Health > Require the device to be at or under the Device Threat Level
                            $ID = 4
                            $Setting = "deviceThreatProtectionRequiredSecurityLevel"
                            $SettingDescription = "Device Health > Require the device to be at or under the Device Threat Level"
                            $SettingValue = "Not Configured"
                            $Comment = ""
                            if ($CompliancePolicy.deviceThreatProtectionRequiredSecurityLevel -ne "unavailable") {
                                $Status = "Unsupported"
                                $SettingValue = $CompliancePolicy.deviceThreatProtectionRequiredSecurityLevel
                                $Comment = $URLSupportedCompliancePolicies
                                $PolicyErrors++
                            }
                            else {
                                $Status = "Supported"
                            }
                            $SettingPSObj = [PSCustomObject]@{
                                PolicyName            = $CompliancePolicy.displayName
                                PolicyType            = $CPType
                                Setting               = $Setting
                                Value                 = $SettingValue
                                TeamsDevicesStatus    = $Status 
                                Comment               = $Comment
                                SettingDescription    = $SettingDescription
                                AssignedToGroup       = $outAssignedToGroup
                                ExcludedFromGroup     = $outExcludedFromGroup 
                                AssignedToGroupList   = $AssignedToGroup
                                ExcludedFromGroupList = $ExcludedFromGroup
                                PolicyID              = $CompliancePolicy.id
                                ID                    = $ID
                            }
                            $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceCompliancePolicyDetailed')
                            [void]$output.Add($SettingPSObj)
                            #endregion

                            #region 5: Device Health > Google Protect > Google Play Services is Configured
                            $ID = 5
                            $Setting = "securityRequireGooglePlayServices"
                            $SettingDescription = "Device Health > Google Protect > Google Play Services is Configured"
                            $SettingValue = "Not Configured"
                            $Comment = ""
                            if ($CompliancePolicy.securityRequireGooglePlayServices) {
                                $Status = "Unsupported"
                                $SettingValue = "Require"
                                $Comment = "Google play isn't installed on Teams Android devices."
                                $PolicyErrors++
                            }
                            else {
                                $Status = "Supported"
                            }
                            $SettingPSObj = [PSCustomObject]@{
                                PolicyName            = $CompliancePolicy.displayName
                                PolicyType            = $CPType
                                Setting               = $Setting
                                Value                 = $SettingValue
                                TeamsDevicesStatus    = $Status 
                                Comment               = $Comment
                                SettingDescription    = $SettingDescription
                                AssignedToGroup       = $outAssignedToGroup
                                ExcludedFromGroup     = $outExcludedFromGroup 
                                AssignedToGroupList   = $AssignedToGroup
                                ExcludedFromGroupList = $ExcludedFromGroup
                                PolicyID              = $CompliancePolicy.id
                                ID                    = $ID
                            }
                            $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceCompliancePolicyDetailed')
                            [void]$output.Add($SettingPSObj)
                            #endregion

                            #region 6: Device Health > Google Protect > Up-to-date security provider
                            $ID = 6
                            $Setting = "securityRequireUpToDateSecurityProviders"
                            $SettingDescription = "Device Health > Google Protect > Up-to-date security provider"
                            $SettingValue = "Not Configured"
                            $Comment = ""
                            if ($CompliancePolicy.securityRequireUpToDateSecurityProviders) {
                                $Status = "Unsupported"
                                $SettingValue = "Require"
                                $Comment = "Google play isn't installed on Teams Android devices."
                                $PolicyErrors++
                            }
                            else {
                                $Status = "Supported"
                            }
                            $SettingPSObj = [PSCustomObject]@{
                                PolicyName            = $CompliancePolicy.displayName
                                PolicyType            = $CPType
                                Setting               = $Setting
                                Value                 = $SettingValue
                                TeamsDevicesStatus    = $Status 
                                Comment               = $Comment
                                SettingDescription    = $SettingDescription
                                AssignedToGroup       = $outAssignedToGroup
                                ExcludedFromGroup     = $outExcludedFromGroup 
                                AssignedToGroupList   = $AssignedToGroup
                                ExcludedFromGroupList = $ExcludedFromGroup
                                PolicyID              = $CompliancePolicy.id
                                ID                    = $ID
                            }
                            $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceCompliancePolicyDetailed')
                            [void]$output.Add($SettingPSObj)
                            #endregion
                
                            #region 7: Device Health > Google Protect > Threat scan on apps
                            $ID = 7
                            $Setting = "securityRequireVerifyApps"
                            $SettingDescription = "Device Health > Google Protect > Threat scan on apps"
                            $SettingValue = "Not Configured"
                            $Comment = ""
                            if ($CompliancePolicy.securityRequireVerifyApps) {
                                $Status = "Unsupported"
                                $SettingValue = "Require"
                                $Comment = "Google play isn't installed on Teams Android devices."
                                $PolicyErrors++
                            }
                            else {
                                $Status = "Supported"
                            }
                            $SettingPSObj = [PSCustomObject]@{
                                PolicyName            = $CompliancePolicy.displayName
                                PolicyType            = $CPType
                                Setting               = $Setting
                                Value                 = $SettingValue
                                TeamsDevicesStatus    = $Status 
                                Comment               = $Comment
                                SettingDescription    = $SettingDescription
                                AssignedToGroup       = $outAssignedToGroup
                                ExcludedFromGroup     = $outExcludedFromGroup 
                                AssignedToGroupList   = $AssignedToGroup
                                ExcludedFromGroupList = $ExcludedFromGroup
                                PolicyID              = $CompliancePolicy.id
                                ID                    = $ID
                            }
                            $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceCompliancePolicyDetailed')
                            [void]$output.Add($SettingPSObj)
                            #endregion

                            #region 8: Device Health > Google Protect > SafetyNet device attestation
                            $ID = 8
                            $Setting = "securityRequireSafetyNetAttestation"
                            $SettingDescription = "Device Health > Google Protect > SafetyNet device attestation"
                            $SettingValue = "Not Configured"
                            $Comment = ""
                            if (($CompliancePolicy.securityRequireSafetyNetAttestationBasicIntegrity) -or ($CompliancePolicy.securityRequireSafetyNetAttestationCertifiedDevice)) {
                                $Status = "Unsupported"
                                $Comment = "Google play isn't installed on Teams Android devices."
                                $PolicyErrors++
                                if ($CompliancePolicy.securityRequireSafetyNetAttestationCertifiedDevice) {
                                    $SettingValue = "Check basic integrity and certified devices"
                                }
                                elseif ($CompliancePolicy.securityRequireSafetyNetAttestationBasicIntegrity) {
                                    $SettingValue = "Check basic integrity"
                                }
                            }
                            else {
                                $Status = "Supported"
                            }
                            $SettingPSObj = [PSCustomObject]@{
                                PolicyName            = $CompliancePolicy.displayName
                                PolicyType            = $CPType
                                Setting               = $Setting
                                Value                 = $SettingValue
                                TeamsDevicesStatus    = $Status 
                                Comment               = $Comment
                                SettingDescription    = $SettingDescription
                                AssignedToGroup       = $outAssignedToGroup
                                ExcludedFromGroup     = $outExcludedFromGroup 
                                AssignedToGroupList   = $AssignedToGroup
                                ExcludedFromGroupList = $ExcludedFromGroup
                                PolicyID              = $CompliancePolicy.id
                                ID                    = $ID
                            }
                            $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceCompliancePolicyDetailed')
                            [void]$output.Add($SettingPSObj)
                            #endregion

                        

                            #region 10: System Security > Encryption > Require encryption of data storage on device.
                            $ID = 10
                            $Setting = "storageRequireEncryption"
                            $SettingDescription = "System Security > Encryption > Require encryption of data storage on device"
                            $SettingValue = "Not Configured"
                            $Comment = ""
                            if ($CompliancePolicy.storageRequireEncryption) {
                                $Status = "Warning"
                                $SettingValue = "Require"
                                $Comment = "Manufacturers might configure encryption attributes on their devices in a way that Intune doesn't recognize. If this happens, Intune marks the device as noncompliant."
                                $PolicyWarnings++
                            }
                            else {
                                $Status = "Supported"
                            }
                            $SettingPSObj = [PSCustomObject]@{
                                PolicyName            = $CompliancePolicy.displayName
                                PolicyType            = $CPType
                                Setting               = $Setting
                                Value                 = $SettingValue
                                TeamsDevicesStatus    = $Status 
                                Comment               = $Comment
                                SettingDescription    = $SettingDescription
                                AssignedToGroup       = $outAssignedToGroup
                                ExcludedFromGroup     = $outExcludedFromGroup 
                                AssignedToGroupList   = $AssignedToGroup
                                ExcludedFromGroupList = $ExcludedFromGroup
                                PolicyID              = $CompliancePolicy.id
                                ID                    = $ID
                            }
                            $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceCompliancePolicyDetailed')
                            [void]$output.Add($SettingPSObj)
                            #endregion

                            #region 11: System Security > Device Security > Block apps from unknown sources
                            $ID = 11
                            $Setting = "securityPreventInstallAppsFromUnknownSources"
                            $SettingDescription = "System Security > Device Security > Block apps from unknown sources"
                            $SettingValue = "Not Configured"
                            $Comment = ""
                            if ($CompliancePolicy.securityPreventInstallAppsFromUnknownSources) {
                                $Status = "Unsupported"
                                $SettingValue = "Block"
                                $Comment = "Only Teams admins install apps or OEM tools"
                                $PolicyErrors++
                            }
                            else {
                                $Status = "Supported"
                            }
                            $SettingPSObj = [PSCustomObject]@{
                                PolicyName            = $CompliancePolicy.displayName
                                PolicyType            = $CPType
                                Setting               = $Setting
                                Value                 = $SettingValue
                                TeamsDevicesStatus    = $Status 
                                Comment               = $Comment
                                SettingDescription    = $SettingDescription
                                AssignedToGroup       = $outAssignedToGroup
                                ExcludedFromGroup     = $outExcludedFromGroup 
                                AssignedToGroupList   = $AssignedToGroup
                                ExcludedFromGroupList = $ExcludedFromGroup
                                PolicyID              = $CompliancePolicy.id
                                ID                    = $ID
                            }
                            $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceCompliancePolicyDetailed')
                            [void]$output.Add($SettingPSObj)
                            #endregion

                            #region 14: System Security > Device Security > Minimum security patch level
                            $ID = 14
                            $Setting = "minAndroidSecurityPatchLevel"
                            $SettingDescription = "System Security > Device Security > Minimum security patch level"
                            $SettingValue = "Not Configured"
                            $Comment = ""
                            if (!([string]::IsNullOrEmpty($CompliancePolicy.minAndroidSecurityPatchLevel))) {
                                $Status = "Warning"
                                $SettingValue = $CompliancePolicy.minAndroidSecurityPatchLevel
                                $Comment = "This setting can cause sign in issues."
                                $PolicyWarnings++
                            }
                            else {
                                $Status = "Supported"
                            }
                            $SettingPSObj = [PSCustomObject]@{
                                PolicyName            = $CompliancePolicy.displayName
                                PolicyType            = $CPType
                                Setting               = $Setting
                                Value                 = $SettingValue
                                TeamsDevicesStatus    = $Status 
                                Comment               = $Comment
                                SettingDescription    = $SettingDescription
                                AssignedToGroup       = $outAssignedToGroup
                                ExcludedFromGroup     = $outExcludedFromGroup 
                                AssignedToGroupList   = $AssignedToGroup
                                ExcludedFromGroupList = $ExcludedFromGroup
                                PolicyID              = $CompliancePolicy.id
                                ID                    = $ID
                            }
                            $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceCompliancePolicyDetailed')
                            [void]$output.Add($SettingPSObj)
                            #endregion

                            #region 15: System Security > Device Security > Restricted apps
                            $ID = 15
                            $Setting = "securityPreventInstallAppsFromUnknownSources"
                            $SettingDescription = "System Security > Device Security > Restricted apps"
                            $SettingValue = "Not Configured"
                            $Comment = ""
                            if (($CompliancePolicy.restrictedApps).count -gt 0 ) {
                                $Status = "Unsupported"
                                $SettingValue = "Found " + ($CompliancePolicy.restrictedApps).count + " restricted app(s)"
                                $Comment = $URLSupportedCompliancePolicies
                                $PolicyErrors++
                            }
                            else {
                                $Status = "Supported"
                            }
                            $SettingPSObj = [PSCustomObject]@{
                                PolicyName            = $CompliancePolicy.displayName
                                PolicyType            = $CPType
                                Setting               = $Setting
                                Value                 = $SettingValue
                                TeamsDevicesStatus    = $Status 
                                Comment               = $Comment
                                SettingDescription    = $SettingDescription
                                AssignedToGroup       = $outAssignedToGroup
                                ExcludedFromGroup     = $outExcludedFromGroup 
                                AssignedToGroupList   = $AssignedToGroup
                                ExcludedFromGroupList = $ExcludedFromGroup
                                PolicyID              = $CompliancePolicy.id
                                ID                    = $ID
                            }
                            $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceCompliancePolicyDetailed')
                            [void]$output.Add($SettingPSObj)
                            #endregion

                            #region 16: System Security > All Android devices > Maximum minutes of inactivity before password is required
                            $ID = 16
                            $Setting = "passwordMinutesOfInactivityBeforeLock"
                            $SettingDescription = "System Security > All Android devices > Maximum minutes of inactivity before password is required"
                            $SettingValue = "Not Configured"
                            $Comment = ""
                            if (!([string]::IsNullOrEmpty($CompliancePolicy.passwordMinutesOfInactivityBeforeLock))) {
                                $Status = "Unsupported"
                                $SettingValue = "" + $CompliancePolicy.passwordMinutesOfInactivityBeforeLock + " minutes"
                                $Comment = $URLSupportedCompliancePolicies
                                $PolicyErrors++
                            }
                            else {
                                $Status = "Supported"
                            }
                            $SettingPSObj = [PSCustomObject]@{
                                PolicyName            = $CompliancePolicy.displayName
                                PolicyType            = $CPType
                                Setting               = $Setting
                                Value                 = $SettingValue
                                TeamsDevicesStatus    = $Status 
                                Comment               = $Comment
                                SettingDescription    = $SettingDescription
                                AssignedToGroup       = $outAssignedToGroup
                                ExcludedFromGroup     = $outExcludedFromGroup 
                                AssignedToGroupList   = $AssignedToGroup
                                ExcludedFromGroupList = $ExcludedFromGroup
                                PolicyID              = $CompliancePolicy.id
                                ID                    = $ID
                            }
                            $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceCompliancePolicyDetailed')
                            [void]$output.Add($SettingPSObj)
                            #endregion
                        } 
                        elseif ($CompliancePolicy."@odata.type" -in $SupportedWindowsCompliancePolicies) {

                            #region 18: Device Properties > Operation System Version
                            $ID = 18.1
                            $Setting = "mobileOsMinimumVersion"
                            $SettingDescription = "Device Properties > Operation System Version > Minimum OS version for mobile devices"
                            $SettingValue = "Not Configured"
                            $Comment = ""
                            if (!([string]::IsNullOrEmpty($CompliancePolicy.mobileOsMinimumVersion))) {
                                $Status = "Unsupported"
                                $SettingValue = $CompliancePolicy.mobileOsMinimumVersion
                                $Comment = $URLSupportedCompliancePolicies
                                $PolicyErrors++
                            }
                            else {
                                $Status = "Supported"
                            }
                            $SettingPSObj = [PSCustomObject]@{
                                PolicyName            = $CompliancePolicy.displayName
                                PolicyType            = $CPType
                                Setting               = $Setting
                                Value                 = $SettingValue
                                TeamsDevicesStatus    = $Status 
                                Comment               = $Comment
                                SettingDescription    = $SettingDescription
                                AssignedToGroup       = $outAssignedToGroup
                                ExcludedFromGroup     = $outExcludedFromGroup 
                                AssignedToGroupList   = $AssignedToGroup
                                ExcludedFromGroupList = $ExcludedFromGroup
                                PolicyID              = $CompliancePolicy.id
                                ID                    = $ID
                            }
                            $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceCompliancePolicyDetailed')
                            [void]$output.Add($SettingPSObj)
                
                            $ID = 18.2
                            $Setting = "mobileOsMaximumVersion"
                            $SettingDescription = "Device Properties > Operation System Version > Maximum OS version for mobile devices"
                            $SettingValue = "Not Configured"
                            $Comment = ""
                            if (!([string]::IsNullOrEmpty($CompliancePolicy.mobileOsMaximumVersion))) {
                                $Status = "Unsupported"
                                $SettingValue = $CompliancePolicy.mobileOsMaximumVersion
                                $Comment = $URLSupportedCompliancePolicies
                                $PolicyErrors++
                            }
                            else {
                                $Status = "Supported"
                            }
                            $SettingPSObj = [PSCustomObject]@{
                                PolicyName            = $CompliancePolicy.displayName
                                PolicyType            = $CPType
                                Setting               = $Setting
                                Value                 = $SettingValue
                                TeamsDevicesStatus    = $Status 
                                Comment               = $Comment
                                SettingDescription    = $SettingDescription
                                AssignedToGroup       = $outAssignedToGroup
                                ExcludedFromGroup     = $outExcludedFromGroup 
                                AssignedToGroupList   = $AssignedToGroup
                                ExcludedFromGroupList = $ExcludedFromGroup
                                PolicyID              = $CompliancePolicy.id
                                ID                    = $ID
                            }
                            $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceCompliancePolicyDetailed')
                            [void]$output.Add($SettingPSObj)
                            #endregion

                            #region 19: Device Properties > Operation System Version > Valid operating system builds
                            $ID = 19
                            $Setting = "validOperatingSystemBuildRanges"
                            $SettingDescription = "Device Properties > Operation System Version > Valid operating system builds"
                            $SettingValue = "Not Configured"
                            $Comment = ""
                            if (!([string]::IsNullOrEmpty($CompliancePolicy.validOperatingSystemBuildRanges))) {
                                $Status = "Unsupported"
                                $SettingValue = "Found " + ($CompliancePolicy.validOperatingSystemBuildRanges).count + " valid OS configured build(s)"
                                $Comment = $URLSupportedCompliancePolicies
                                $PolicyErrors++
                            }
                            else {
                                $Status = "Supported"
                            }
                            $SettingPSObj = [PSCustomObject]@{
                                PolicyName            = $CompliancePolicy.displayName
                                PolicyType            = $CPType
                                Setting               = $Setting
                                Value                 = $SettingValue
                                TeamsDevicesStatus    = $Status 
                                Comment               = $Comment
                                SettingDescription    = $SettingDescription
                                AssignedToGroup       = $outAssignedToGroup
                                ExcludedFromGroup     = $outExcludedFromGroup 
                                AssignedToGroupList   = $AssignedToGroup
                                ExcludedFromGroupList = $ExcludedFromGroup
                                PolicyID              = $CompliancePolicy.id
                                ID                    = $ID
                            }
                            $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceCompliancePolicyDetailed')
                            [void]$output.Add($SettingPSObj)
                            #endregion

                            #region 20: System Security > Defender > Microsoft Defender Antimalware minimum version
                            $ID = 20
                            $Setting = "defenderVersion"
                            $SettingDescription = "System Security > Defender > Microsoft Defender Antimalware minimum version"
                            $SettingValue = "Not Configured"
                            $Comment = ""
                            if (!([string]::IsNullOrEmpty($CompliancePolicy.defenderVersion))) {
                                $Status = "Unsupported"
                                $SettingValue = $CompliancePolicy.defenderVersion
                                $Comment = "Teams Rooms automatically updates this component so there's no need to set compliance policies."
                                $PolicyErrors++
                            }
                            else {
                                $Status = "Supported"
                            }
                            $SettingPSObj = [PSCustomObject]@{
                                PolicyName            = $CompliancePolicy.displayName
                                PolicyType            = $CPType
                                Setting               = $Setting
                                Value                 = $SettingValue
                                TeamsDevicesStatus    = $Status 
                                Comment               = $Comment
                                SettingDescription    = $SettingDescription
                                AssignedToGroup       = $outAssignedToGroup
                                ExcludedFromGroup     = $outExcludedFromGroup 
                                AssignedToGroupList   = $AssignedToGroup
                                ExcludedFromGroupList = $ExcludedFromGroup
                                PolicyID              = $CompliancePolicy.id
                                ID                    = $ID
                            }
                            $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceCompliancePolicyDetailed')
                            [void]$output.Add($SettingPSObj)
                            #endregion
                        }

                        if ($PolicyErrors -gt 0) {
                            $StatusSum = "Found " + $PolicyErrors + " unsupported settings."
                            $displayWarning = $true
                        }
                        elseif ($PolicyWarnings -gt 0) {
                            $StatusSum = "Found " + $PolicyWarnings + " settings that may impact users."
                            $displayWarning = $true
                        }
                        else {
                            $StatusSum = "No issues found."
                        }
                        $PolicySum = [PSCustomObject]@{
                            PolicyID              = $CompliancePolicy.id
                            PolicyName            = $CompliancePolicy.displayName
                            PolicyType            = $CPType
                            AssignedToGroup       = $outAssignedToGroup
                            AssignedToGroupList   = $AssignedToGroup
                            ExcludedFromGroup     = $outExcludedFromGroup 
                            ExcludedFromGroupList = $ExcludedFromGroup
                            TeamsDevicesStatus    = $StatusSum
                        }
                        $PolicySum.PSObject.TypeNames.Insert(0, 'TeamsDeviceCompliancePolicy')
                        $outputSum.Add($PolicySum) | Out-Null
                    }
                    elseif (($AssignedToGroup.count -eq 0) -and !($UserUPN -or $DeviceID -or $Detailed)) {
                        $skippedCompliancePolicies++
                    }
                }
            }
            if ($totalCompliancePolicies -eq 0) {
                if ($UserUPN) {
                    Write-Warning ("The user " + $UserUPN + " doesn't have any Compliance Policies assigned.")
                }
                else {
                    Write-Warning "No Compliance Policies assigned to All Users, All Devices or group found. Please use Test-UcTeamsDevicesCompliancePolicy -All to check all policies."
                }
            }
            if ($IncludeSupported -and $Detailed) {
                if ($ExportCSV) {
                    $output | Sort-Object PolicyName, ID | Select-Object PolicyName, PolicyID, PolicyType, AssignedToGroup, ExcludedFromGroup, TeamsDevicesStatus, Setting, SettingDescription, Value, Comment | Export-Csv -path $OutputFullPath -NoTypeInformation
                    Write-Host ("Results available in: " + $OutputFullPath) -ForegroundColor Cyan
                    return
                }
                else {
                    $output | Sort-Object PolicyName, ID
                }
            }
            elseif ($Detailed) {
                if ((( $output | Where-Object -Property TeamsDevicesStatus -NE -Value "Supported").count -eq 0) -and !$IncludeSupported) {
                    Write-Warning "No unsupported settings found, please use Test-UcTeamsDevicesCompliancePolicy -IncludeSupported to output all settings."
                }
                else {
                    if ($ExportCSV) {
                        $output | Where-Object -Property TeamsDevicesStatus -NE -Value "Supported" | Sort-Object PolicyName, ID | Select-Object PolicyName, PolicyID, PolicyType, AssignedToGroup, ExcludedFromGroup, TeamsDevicesStatus, Setting, SettingDescription, Value, Comment | Export-Csv -path $OutputFullPath -NoTypeInformation
                        Write-Host ("Results available in: " + $OutputFullPath) -ForegroundColor Cyan
                        return
                    }
                    else {
                        $output | Where-Object -Property TeamsDevicesStatus -NE -Value "Supported" | Sort-Object PolicyName, ID
                    }
                }
            }
            else {
                if (($skippedCompliancePolicies -gt 0) -and !$All) {
                    Write-Warning ("Skipping $skippedCompliancePolicies compliance policies since will not be applied to Teams Devices.")
                    Write-Warning ("Please use the All switch to check all policies: Test-UcTeamsDevicesCompliancePolicy -All")
                }
                if ($displayWarning) {
                    Write-Warning "One or more policies contain unsupported settings, please use Test-UcTeamsDevicesCompliancePolicy -Detailed to identify the unsupported settings."
                }
                $outputSum | Sort-Object PolicyName
            }
        }
    }
}
#EndRegion '.\Public\Test-UcTeamsDevicesCompliancePolicy.ps1' 1058
#Region '.\Public\Test-UcTeamsDevicesConditionalAccessPolicy.ps1' 0
<#
.SYNOPSIS
Validate which Conditional Access policies are supported by Microsoft Teams Android Devices
 
.DESCRIPTION
This function will validate each setting in a Conditional Access Policy to make sure they are in line with the supported settings:
 
    https://docs.microsoft.com/microsoftteams/rooms/supported-ca-and-compliance-policies?tabs=phones#conditional-access-policies"
 
Contributors: Traci Herr, David Paulino
 
Requirements: Microsoft Graph PowerShell Module (Install-Module Microsoft.Graph)
 
.PARAMETER Detailed
Displays test results for all settings in each Conditional Access Policy
 
.PARAMETER All
Will check all Conditional Access policies independently if they are assigned to a Group(s) or to Teams
 
.PARAMETER IncludeSupported
Displays results for all settings in each Conditional Access Policy
 
.PARAMETER UserUPN
Specifies a UserUPN that we want to check for applied Conditional Access policies
 
.PARAMETER ExportCSV
When present will export the detailed results to a CSV file. By defautl will save the file under the current user downloads, unless we specify the OutputPath.
 
.PARAMETER OutputPath
Allows to specify the path where we want to save the results.
 
.EXAMPLE
PS> Test-UcTeamsDevicesConditionalAccessPolicy
 
.EXAMPLE
PS> Test-UcTeamsDevicesConditionalAccessPolicy -All
 
.EXAMPLE
PS> Test-UcTeamsDevicesConditionalAccessPolicy -Detailed
 
.EXAMPLE
PS> Test-UcTeamsDevicesConditionalAccessPolicy -Detailed -IncludedSupported
 
.EXAMPLE
PS> Test-UcTeamsDevicesConditionalAccessPolicy -UserUPN
 
#>


Function Test-UcTeamsDevicesConditionalAccessPolicy {
    Param(
        [switch]$Detailed,
        [switch]$All,
        [switch]$IncludeSupported,
        [string]$UserUPN,
        [switch]$ExportCSV,
        [string]$OutputPath
    )

    $GraphURI_Users = "https://graph.microsoft.com/v1.0/users"
    $GraphURI_Groups = "https://graph.microsoft.com/v1.0/groups"
    $GraphURI_ConditionalAccess = "https://graph.microsoft.com/beta/identity/conditionalAccess/policies"

    $connectedMSGraph = $false
    $ConditionalAccessPolicies = $null
    $totalCAPolicies = 0
    $skippedCAPolicies = 0

    $URLTeamsDevicesCA = "https://aka.ms/TeamsDevicePolicies#supported-conditional-access-policies"
    $URLTeamsDevicesKnownIssues = "https://docs.microsoft.com/microsoftteams/troubleshoot/teams-rooms-and-devices/rooms-known-issues#teams-phone-devices"

    if (Test-UcMgGraphConnection -Scopes "Policy.Read.All", "Directory.Read.All") {
        Test-UcModuleUpdateAvailable -ModuleName UcLobbyTeams
        $outFileName = "TeamsDevices_ConditionalAccessPolicy_Report_" + ( get-date ).ToString('yyyyMMdd-HHmmss') + ".csv"

        if ($OutputPath) {
            if (!(Test-Path $OutputPath -PathType Container)) {
                Write-Host ("Error: Invalid folder " + $OutputPath) -ForegroundColor Red
                return
            } 
            $OutputFullPath = [System.IO.Path]::Combine($OutputPath, $outFileName)
        }
        else {                
            $OutputFullPath = [System.IO.Path]::Combine($env:USERPROFILE, "Downloads", $outFileName)
        }
        try {
            Write-Progress -Activity "Test-UcTeamsDevicesConditionalAccessPolicy" -Status "Getting Conditional Access Policies"
            $ConditionalAccessPolicies = (Invoke-MgGraphRequest -Uri ($GraphURI_ConditionalAccess + $GraphFilter) -Method GET).Value
            $connectedMSGraph = $true
        }
        catch [System.Net.Http.HttpRequestException] {
            if ($PSItem.Exception.Response.StatusCode -eq "Forbidden") {
                Write-Host "Access Denied, please make sure the user connecing to MS Graph is part of one of the following Global Reader/Conditional Access Administrator/Global Administrator roles"
                return
            }
            else {
                Write-Error $PSItem.Exception.Message
            }
        }
        catch {
            Write-Error $PSItem.Exception.Message
        }

        if ($connectedMSGraph) {
            $output = [System.Collections.ArrayList]::new()
            $outputSum = [System.Collections.ArrayList]::new()
            if ($UserUPN) {
                try {
                    $UserID = (Invoke-MgGraphRequest -Uri ($GraphURI_Users + "/" + $userUPN + "?`$select=id") -Method GET).id
                    $UserGroups = (Invoke-MgGraphRequest -Uri ($GraphURI_Users + "/" + $userUPN + "/transitiveMemberOf?`$select=id") -Method GET).value.id
                }
                catch [System.Net.Http.HttpRequestException] {
                    if ($PSItem.Exception.Response.StatusCode -eq "NotFound") {
                        Write-warning -Message ("User Not Found: " + $UserUPN)
                    }
                    return
                }
            }

            $Groups = New-Object 'System.Collections.Generic.Dictionary[string, string]'
            try{
            Write-Progress -Activity "Test-UcTeamsDevicesConditionalAccessPolicy" -Status "Fetching Service Principals details."
            $ServicePrincipals = Get-MgServicePrincipal -Select AppId, DisplayName -All
            } catch {}

            $p=0
            $policyCount = $ConditionalAccessPolicies.Count
            foreach ($ConditionalAccessPolicy in $ConditionalAccessPolicies) {
                $p++
                Write-Progress -Activity "Test-UcTeamsDevicesConditionalAccessPolicy" -Status ("Checking policy " +  $ConditionalAccessPolicy.displayName + " - $p of $policyCount")
                $AssignedToGroup = [System.Collections.ArrayList]::new()
                $ExcludedFromGroup = [System.Collections.ArrayList]::new()
                $AssignedToUserCount = 0
                $ExcludedFromUserCount = 0
                $outAssignedToGroup = ""
                $outExcludedFromGroup = ""
                $userExcluded = $false
                $StatusSum = ""
            
                $totalCAPolicies++
                $PolicyErrors = 0
                $PolicyWarnings = 0

                if ($UserUPN) {
                    if ($UserID -in $ConditionalAccessPolicy.conditions.users.excludeUsers) {
                        $userExcluded = $true
                        Write-Warning ("Skiping conditional access policy " + $ConditionalAccessPolicy.displayName + ", since user " + $UserUPN + " is part of Excluded Users")
                    }
                    elseif ($UserID -in $ConditionalAccessPolicy.conditions.users.includeUsers) {
                        $userIncluded = $true
                    }
                    else {
                        $userIncluded = $false
                    }
                }

                #All Users in Conditional Access Policy will show as a 'All' in the includeUsers.
                if ("All" -in $ConditionalAccessPolicy.conditions.users.includeUsers) {
                    $GroupEntry = New-Object -TypeName PSObject -Property @{
                        GroupID          = "All"
                        GroupDisplayName = "All Users"
                    }
                    $AssignedToGroup.Add($GroupEntry) | Out-Null
                    $userIncluded = $true
                }
                elseif ((($ConditionalAccessPolicy.conditions.users.includeUsers).count -gt 0) -and "None" -notin $ConditionalAccessPolicy.conditions.users.includeUsers) {
                    $AssignedToUserCount = ($ConditionalAccessPolicy.conditions.users.includeUsers).count
                    if (!$UserUPN) {
                        $userIncluded = $true
                    }
                }
                foreach ($includedGroup in $ConditionalAccessPolicy.conditions.users.includeGroups) {
                    $GroupDisplayName = $includedGroup
                    if ($Groups.ContainsKey($includedGroup)) {
                        $GroupDisplayName = $Groups.Item($includedGroup)
                    }
                    else {
                        try {
                            $GroupInfo = Invoke-MgGraphRequest -Uri ($GraphURI_Groups + "/" + $includedGroup + "/?`$select=id,displayname") -Method GET
                            $Groups.Add($GroupInfo.id, $GroupInfo.displayname)
                            $GroupDisplayName = $GroupInfo.displayname
                        }
                        catch {
                        }
                    }
                    $GroupEntry = New-Object -TypeName PSObject -Property @{
                        GroupID          = $includedGroup
                        GroupDisplayName = $GroupDisplayName
                    }
                    #We only need to add if we didn't specify a UPN or if the user is part of the group that has the CA assigned.
                    if (!$UserUPN) {
                        $AssignedToGroup.Add($GroupEntry) | Out-Null
                    }
                    if ($includedGroup -in $UserGroups) {
                        $userIncluded = $true
                        $AssignedToGroup.Add($GroupEntry) | Out-Null
                    }
                }

            
                foreach ($excludedGroup in $ConditionalAccessPolicy.conditions.users.excludeGroups) {
                    $GroupDisplayName = $excludedGroup
                    if ($Groups.ContainsKey($excludedGroup)) {
                        $GroupDisplayName = $Groups.Item($excludedGroup)
                    }
                    else {
                        try {
                            $GroupInfo = Invoke-MgGraphRequest -Uri ($GraphURI_Groups + "/" + $excludedGroup + "/?`$select=id,displayname") -Method GET
                            $Groups.Add($GroupInfo.id, $GroupInfo.displayname)
                            $GroupDisplayName = $GroupInfo.displayname
                        }
                        catch { }
                    }
                    $GroupEntry = New-Object -TypeName PSObject -Property @{
                        GroupID          = $excludedGroup
                        GroupDisplayName = $GroupDisplayName
                    }
                    $ExcludedFromGroup.Add($GroupEntry) | Out-Null                
                    if ($excludedGroup -in $UserGroups) {
                        $userExcluded = $true
                        Write-Warning ("Skiping conditional access policy " + $ConditionalAccessPolicy.displayName + ", since user " + $UserUPN + " is part of an Excluded Group: " + $GroupEntry.GroupDisplayName)
                    }
                }
                $ExcludedFromUserCount = ($ConditionalAccessPolicy.conditions.users.excludeUsers).count

                if ("GuestsOrExternalUsers" -in $ConditionalAccessPolicy.conditions.users.excludeUsers) {
                    $ExcludedFromUserCount--
                }

                #If only assigned/excluded from a group we will show the group display name, otherwise the number of groups assigned/excluded.
                if (($AssignedToGroup.count -gt 0) -and ($AssignedToUserCount -gt 0)) {
                    $outAssignedToGroup = "$AssignedToUserCount user(s)," + $AssignedToGroup.count + " group(s)"
                }
                elseif (($AssignedToGroup.count -eq 0) -and ($AssignedToUserCount -gt 0)) {
                    $outAssignedToGroup = "$AssignedToUserCount user(s)"
                }
                elseif (($AssignedToGroup.count -gt 0) -and ($AssignedToUserCount -eq 0)) {
                    if ($AssignedToGroup.count -eq 1) {
                        $outAssignedToGroup = $AssignedToGroup[0].GroupDisplayName
                    }
                    else {
                        $outAssignedToGroup = "" + $AssignedToGroup.count + " group(s)"
                    }
                }
                else {
                    $outAssignedToGroup = "None"
                }

                if (($ExcludedFromGroup.count -gt 0) -and ($ExcludedFromUserCount -gt 0)) {
                    $outExcludedFromGroup = "$ExcludedFromUserCount user(s), " + $ExcludedFromGroup.count + " group(s)"
                }
                elseif (($ExcludedFromGroup.count -eq 0) -and ($ExcludedFromUserCount -gt 0)) {
                    $outExcludedFromGroup = "$ExcludedFromUserCount user(s)"
                }
                elseif (($ExcludedFromGroup.count -gt 0) -and ($ExcludedFromUserCount -eq 0)) {
                    if ($ExcludedFromGroup.count -eq 1) {
                        $outExcludedFromGroup = $ExcludedFromGroup[0].GroupDisplayName
                    }
                    else {
                        $outExcludedFromGroup = "" + $ExcludedFromGroup.count + " group(s)"
                    }
                }
                else {
                    $outExcludedFromGroup = "None"
                }

                $PolicyState = $ConditionalAccessPolicy.State
                if ($PolicyState -eq "enabledForReportingButNotEnforced") {
                    $PolicyState = "ReportOnly"
                }
                
                #region 2: Assignment > Cloud apps or actions > Cloud Apps
                #Exchange 00000002-0000-0ff1-ce00-000000000000
                #SharePoint 00000003-0000-0ff1-ce00-000000000000
                #Teams cc15fd57-2c6c-4117-a88c-83b1d56b4bbe
                $ID = 2
                $Setting = "CloudApps"
                $SettingDescription = "Assignment > Cloud apps or actions > Cloud Apps"
                $Comment = ""
                $hasExchange = $false
                $hasSharePoint = $false
                $hasTeams = $false
                $hasOffice365 = $false
                $SettingValue = ""
                foreach ($Application in $ConditionalAccessPolicy.Conditions.Applications.IncludeApplications) {
                    $appDisplayName = ($ServicePrincipals |  Where-Object -Property AppId -eq -Value $Application).DisplayName
                    switch ($Application) {
                        "All" { $hasOffice365 = $true; $SettingValue = "All" }
                        "Office365" { $hasOffice365 = $true; $SettingValue = "Office 365" }
                        "00000002-0000-0ff1-ce00-000000000000" { $hasExchange = $true; $SettingValue += $appDisplayName + "; " }
                        "00000003-0000-0ff1-ce00-000000000000" { $hasSharePoint = $true; $SettingValue += $appDisplayName + "; " }
                        "cc15fd57-2c6c-4117-a88c-83b1d56b4bbe" { $hasTeams = $true; $SettingValue += $appDisplayName + "; " }
                        default { $SettingValue += $appDisplayName + "; " }
                    }
                }
                if ($SettingValue.EndsWith("; ")) {
                    $SettingValue = $SettingValue.Substring(0, $SettingValue.Length - 2)
                }

                if (((($AssignedToGroup.count -gt 0) -and ($hasOffice365 -or $hasTeams) -and ($PolicyState -NE "disabled")) -and (!$userExcluded) -and $userIncluded) -or $all) {

                    if (($hasExchange -and $hasSharePoint -and $hasTeams) -or ($hasOffice365)) {
                        $Status = "Supported"
                    }
                    else {
                        $Status = "Unsupported"
                        $Comment = "Teams Devices needs to access: Office 365 or Exchange Online, SharePoint Online, and Microsoft Teams"
                        $PolicyErrors++
                    }

                    $SettingPSObj = [PSCustomObject]@{
                        PolicyName            = $ConditionalAccessPolicy.displayName
                        PolicyState           = $PolicyState
                        Setting               = $Setting 
                        Value                 = $SettingValue
                        TeamsDevicesStatus    = $Status 
                        Comment               = $Comment
                        SettingDescription    = $SettingDescription 
                        AssignedToGroup       = $outAssignedToGroup
                        ExcludedFromGroup     = $outExcludedFromGroup 
                        AssignedToGroupList   = $AssignedToGroup
                        ExcludedFromGroupList = $ExcludedFromGroup
                        PolicyID              = $ConditionalAccessPolicy.id
                        ID                    = $ID
                    }
                    $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceConditionalAccessPolicyDetailed')
                    [void]$output.Add($SettingPSObj)
                    #endregion

                    #region 6: Assignment > Conditions > Locations
                    $ID = 6.1
                    $Setting = "includeLocations"
                    $SettingDescription = "Assignment > Conditions > Locations"
                    $Comment = ""
                    $Status = "Supported"
                    if ($ConditionalAccessPolicy.conditions.locations.includeLocations) {
                        $SettingValue = $ConditionalAccessPolicy.conditions.locations.includeLocations
                    }
                    else {
                        $SettingValue = "Not Configured"
                    }
                    $SettingPSObj = [PSCustomObject]@{
                        PolicyName            = $ConditionalAccessPolicy.displayName
                        PolicyState           = $PolicyState
                        Setting               = $Setting 
                        Value                 = $SettingValue
                        TeamsDevicesStatus    = $Status 
                        Comment               = $Comment
                        SettingDescription    = $SettingDescription 
                        AssignedToGroup       = $outAssignedToGroup
                        ExcludedFromGroup     = $outExcludedFromGroup 
                        AssignedToGroupList   = $AssignedToGroup
                        ExcludedFromGroupList = $ExcludedFromGroup
                        PolicyID              = $ConditionalAccessPolicy.id
                        ID                    = $ID
                    }
                    $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceConditionalAccessPolicyDetailed')
                    [void]$output.Add($SettingPSObj)

                    $ID = 6.2
                    $Setting = "excludeLocations"
                    $SettingDescription = "Assignment > Conditions > Locations"
                    $Comment = ""
                    $Status = "Supported"
                    if ($ConditionalAccessPolicy.conditions.locations.excludeLocations) {
                        $SettingValue = $ConditionalAccessPolicy.conditions.locations.excludeLocations
                    }
                    else {
                        $SettingValue = "Not Configured"
                    }
                    $SettingPSObj = [PSCustomObject]@{
                        PolicyName            = $ConditionalAccessPolicy.displayName
                        PolicyState           = $PolicyState
                        Setting               = $Setting 
                        Value                 = $SettingValue
                        TeamsDevicesStatus    = $Status 
                        Comment               = $Comment
                        SettingDescription    = $SettingDescription 
                        AssignedToGroup       = $outAssignedToGroup
                        ExcludedFromGroup     = $outExcludedFromGroup 
                        AssignedToGroupList   = $AssignedToGroup
                        ExcludedFromGroupList = $ExcludedFromGroup
                        PolicyID              = $ConditionalAccessPolicy.id
                        ID                    = $ID
                    }
                    $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceConditionalAccessPolicyDetailed')
                    [void]$output.Add($SettingPSObj)
                    #endregion

                    #region 7: Assignment > Conditions > Client apps
                    $ID = 7
                    $Setting = "ClientAppTypes"
                    $SettingDescription = "Assignment > Conditions > Client apps"
                    $SettingValue = ""
                    $Comment = ""
                    foreach ($ClientAppType in $ConditionalAccessPolicy.Conditions.ClientAppTypes) {
                        if ($ClientAppType -eq "All") {
                            $Status = "Supported"
                            $SettingValue = $ClientAppType
                            $Comment = ""
                        }
                        else {
                            $Status = "Unsupported"
                            $SettingValue += $ClientAppType + ";"
                            $Comment = $URLTeamsDevicesCA
                            $PolicyErrors++
                        }
                    
                    }
                    $SettingPSObj = [PSCustomObject]@{
                        PolicyName            = $ConditionalAccessPolicy.displayName
                        PolicyState           = $PolicyState
                        Setting               = $Setting 
                        Value                 = $SettingValue
                        TeamsDevicesStatus    = $Status 
                        Comment               = $Comment
                        SettingDescription    = $SettingDescription 
                        AssignedToGroup       = $outAssignedToGroup
                        ExcludedFromGroup     = $outExcludedFromGroup 
                        AssignedToGroupList   = $AssignedToGroup
                        ExcludedFromGroupList = $ExcludedFromGroup
                        PolicyID              = $ConditionalAccessPolicy.id
                        ID                    = $ID
                    }
                    $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceConditionalAccessPolicyDetailed')
                    [void]$output.Add($SettingPSObj)
                    #endregion

                    #region 8: Assignment > Conditions > Filter for devices
                    $ID = 8
                    $Setting = "deviceFilter"
                    $SettingDescription = "Assignment > Conditions > Filter for devices"
                    $Comment = ""
                    if ($ConditionalAccessPolicy.conditions.devices.deviceFilter.mode -eq "exclude") {
                        $Status = "Supported"
                        $SettingValue = $ConditionalAccessPolicy.conditions.devices.deviceFilter.mode + ": " + $ConditionalAccessPolicy.conditions.devices.deviceFilter.rule
                    }
                    else {
                        $SettingValue = "Not Configured"
                        $Status = "Warning"
                        $Comment = "https://learn.microsoft.com/microsoftteams/troubleshoot/teams-rooms-and-devices/teams-android-devices-conditional-access-issues"
                    }
                    $SettingPSObj = [PSCustomObject]@{
                        PolicyName            = $ConditionalAccessPolicy.displayName
                        PolicyState           = $PolicyState
                        Setting               = $Setting 
                        Value                 = $SettingValue
                        TeamsDevicesStatus    = $Status 
                        Comment               = $Comment
                        SettingDescription    = $SettingDescription 
                        AssignedToGroup       = $outAssignedToGroup
                        ExcludedFromGroup     = $outExcludedFromGroup 
                        AssignedToGroupList   = $AssignedToGroup
                        ExcludedFromGroupList = $ExcludedFromGroup
                        PolicyID              = $ConditionalAccessPolicy.id
                        ID                    = $ID
                    }
                    $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceConditionalAccessPolicyDetailed')
                    [void]$output.Add($SettingPSObj)
                    #endregion

                    #region 10: Access controls > Grant
                    $Setting = "GrantControls"
                    foreach ($BuiltInControl in $ConditionalAccessPolicy.GrantControls.BuiltInControls) {
                        $Comment = "" 
                        $SettingValue = "Enabled"
                        switch ($BuiltInControl) {
                            "mfa" { 
                                $ID = 11
                                $Status = "Warning"
                                $SettingDescription = "Access controls > Grant > Require multi-factor authentication"
                                $PolicyWarnings++
                                $Comment = "Require multi-factor authentication only supported for Teams Phones and Displays." 
                            }
                            "compliantDevice" {
                                $ID = 12
                                $Status = "Supported"
                                $SettingDescription = "Access controls > Grant > Require device to be marked as compliant"
                            }
                            "DomainJoinedDevice" { 
                                $ID = 13
                                $Status = "Unsupported"
                                $SettingDescription = "Access controls > Grant > Require Hybrid Azure AD joined device"
                                $PolicyErrors++
                            }
                            "ApprovedApplication" { 
                                $ID = 14
                                $Status = "Unsupported"
                                $SettingDescription = "Access controls > Grant > Require approved client app"
                                $Comment = $URLTeamsDevicesCA
                                $PolicyErrors++
                            }
                            "CompliantApplication" { 
                                $ID = 15
                                $Status = "Unsupported"
                                $SettingDescription = "Access controls > Grant > Require app protection policy"
                                $Comment = $URLTeamsDevicesCA
                                $PolicyErrors++
                            }
                            "PasswordChange" { 
                                $ID = 16
                                $Status = "Unsupported"
                                $SettingDescription = "Access controls > Grant > Require password change"
                                $Comment = $URLTeamsDevicesCA 
                                $PolicyErrors++
                            }
                            default { 
                                $ID = 10
                                $SettingDescription = "Access controls > Grant > " + $BuiltInControl
                                $Status = "Supported"
                            }
                        }
                        $SettingPSObj = [PSCustomObject]@{
                            PolicyName            = $ConditionalAccessPolicy.displayName
                            PolicyState           = $PolicyState
                            Setting               = $Setting 
                            Value                 = $SettingValue
                            TeamsDevicesStatus    = $Status 
                            Comment               = $Comment
                            SettingDescription    = $SettingDescription 
                            AssignedToGroup       = $outAssignedToGroup
                            ExcludedFromGroup     = $outExcludedFromGroup 
                            AssignedToGroupList   = $AssignedToGroup
                            ExcludedFromGroupList = $ExcludedFromGroup
                            PolicyID              = $ConditionalAccessPolicy.id
                            ID                    = $ID
                        }
                        $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceConditionalAccessPolicyDetailed')
                        [void]$output.Add($SettingPSObj) 
                    }
                    #endregion
                
                    #region 17: Access controls > Grant > Custom Authentication Factors
                    $ID = 17
                    $Setting = "CustomAuthenticationFactors"
                    $SettingDescription = "Access controls > Grant > Custom Authentication Factors"
                    if ($ConditionalAccessPolicy.GrantControls.CustomAuthenticationFactors) {
                        $Status = "Unsupported"
                        $SettingValue = "Enabled"
                        $PolicyErrors++
                        $Comment = $URLTeamsDevicesCA
                    }
                    else {
                        $Status = "Supported"
                        $SettingValue = "Disabled"
                    }
                    $SettingPSObj = [PSCustomObject]@{
                        PolicyName            = $ConditionalAccessPolicy.displayName
                        PolicyState           = $PolicyState
                        Setting               = $Setting 
                        Value                 = $SettingValue
                        TeamsDevicesStatus    = $Status 
                        Comment               = $Comment
                        SettingDescription    = $SettingDescription 
                        AssignedToGroup       = $outAssignedToGroup
                        ExcludedFromGroup     = $outExcludedFromGroup 
                        AssignedToGroupList   = $AssignedToGroup
                        ExcludedFromGroupList = $ExcludedFromGroup
                        PolicyID              = $ConditionalAccessPolicy.id
                        ID                    = $ID
                    }
                    $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceConditionalAccessPolicyDetailed')
                    [void]$output.Add($SettingPSObj)
                    #endregion

                    #region 18: Access controls > Grant > Terms of Use
                    $ID = 18
                    $Setting = "TermsOfUse"
                    $SettingDescription = "Access controls > Grant > Terms of Use"
                    $Comment = "" 
                    if ($ConditionalAccessPolicy.GrantControls.TermsOfUse) {
                        $Status = "Warning"
                        $SettingValue = "Enabled"
                        $Comment = $URLTeamsDevicesKnownIssues
                        $PolicyWarnings++
                    }
                    else {
                        $Status = "Supported"
                        $SettingValue = "Disabled"
                    }
                    $SettingPSObj = [PSCustomObject]@{
                        PolicyName            = $ConditionalAccessPolicy.displayName
                        PolicyState           = $PolicyState
                        Setting               = $Setting 
                        Value                 = $SettingValue
                        TeamsDevicesStatus    = $Status 
                        Comment               = $Comment
                        SettingDescription    = $SettingDescription 
                        AssignedToGroup       = $outAssignedToGroup
                        ExcludedFromGroup     = $outExcludedFromGroup 
                        AssignedToGroupList   = $AssignedToGroup
                        ExcludedFromGroupList = $ExcludedFromGroup
                        PolicyID              = $ConditionalAccessPolicy.id
                        ID                    = $ID
                    }
                    $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceConditionalAccessPolicyDetailed')
                    [void]$output.Add($SettingPSObj)
                    #endregion

                    #region 19: Access controls > Session > Use app enforced restrictions
                    $ID = 19
                    $Setting = "ApplicationEnforcedRestrictions"
                    $SettingDescription = "Access controls > Session > Use app enforced restrictions"
                    $Comment = "" 
                    if ($ConditionalAccessPolicy.SessionControls.ApplicationEnforcedRestrictions) {
                        $Status = "Unsupported"
                        $SettingValue = "Enabled"
                        $PolicyErrors++
                    }
                    else {
                        $Status = "Supported"
                        $SettingValue = "Disabled"
                    }
                    $SettingPSObj = [PSCustomObject]@{
                        PolicyName            = $ConditionalAccessPolicy.displayName
                        PolicyState           = $PolicyState
                        Setting               = $Setting 
                        Value                 = $SettingValue
                        TeamsDevicesStatus    = $Status 
                        Comment               = $Comment
                        SettingDescription    = $SettingDescription 
                        AssignedToGroup       = $outAssignedToGroup
                        ExcludedFromGroup     = $outExcludedFromGroup 
                        AssignedToGroupList   = $AssignedToGroup
                        ExcludedFromGroupList = $ExcludedFromGroup
                        PolicyID              = $ConditionalAccessPolicy.id
                        ID                    = $ID
                    }
                    $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceConditionalAccessPolicyDetailed')
                    [void]$output.Add($SettingPSObj)
                    #endregion
                
                    #region 19: Access controls > Session > Use Conditional Access App Control
                    $ID = 19
                    $Setting = "CloudAppSecurity"
                    $SettingDescription = "Access controls > Session > Use Conditional Access App Control"
                    $Comment = "" 
                    if ($ConditionalAccessPolicy.SessionControls.CloudAppSecurity) {
                        $Status = "Unsupported"
                        $SettingValue = $ConditionalAccessPolicy.SessionControls.CloudAppSecurity.cloudAppSecurityType
                        $PolicyErrors++
                    }
                    else {
                        $Status = "Supported"
                        $SettingValue = "Not Configured"
                    }
                    $SettingPSObj = [PSCustomObject]@{
                        PolicyName            = $ConditionalAccessPolicy.displayName
                        PolicyState           = $PolicyState
                        Setting               = $Setting 
                        Value                 = $SettingValue
                        TeamsDevicesStatus    = $Status 
                        Comment               = $Comment
                        SettingDescription    = $SettingDescription 
                        AssignedToGroup       = $outAssignedToGroup
                        ExcludedFromGroup     = $outExcludedFromGroup 
                        AssignedToGroupList   = $AssignedToGroup
                        ExcludedFromGroupList = $ExcludedFromGroup
                        PolicyID              = $ConditionalAccessPolicy.id
                        ID                    = $ID
                    }
                    $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceConditionalAccessPolicyDetailed')
                    [void]$output.Add($SettingPSObj)
                    #endregion

                    #region 20: Access controls > Session > Sign-in frequency
                    $ID = 20
                    $Setting = "SignInFrequency"
                    $SettingDescription = "Access controls > Session > Sign-in frequency"
                    $Comment = "" 
                    if ($ConditionalAccessPolicy.SessionControls.SignInFrequency.isEnabled -eq "true") {
                        $Status = "Warning"
                        $SettingValue = "" + $ConditionalAccessPolicy.SessionControls.SignInFrequency.Value + " " + $ConditionalAccessPolicy.SessionControls.SignInFrequency.Type
                        $Comment = "Users will be signout from Teams Device every " + $ConditionalAccessPolicy.SessionControls.SignInFrequency.Value + " " + $ConditionalAccessPolicy.SessionControls.SignInFrequency.Type
                        $PolicyWarnings++
                    }
                    else {
                        $Status = "Supported"
                        $SettingValue = "Not Configured"
                    }
                    $SettingPSObj = [PSCustomObject]@{
                        PolicyName            = $ConditionalAccessPolicy.displayName
                        PolicyState           = $PolicyState
                        Setting               = $Setting 
                        Value                 = $SettingValue
                        TeamsDevicesStatus    = $Status 
                        Comment               = $Comment
                        SettingDescription    = $SettingDescription 
                        AssignedToGroup       = $outAssignedToGroup
                        ExcludedFromGroup     = $outExcludedFromGroup 
                        AssignedToGroupList   = $AssignedToGroup
                        ExcludedFromGroupList = $ExcludedFromGroup
                        PolicyID              = $ConditionalAccessPolicy.id
                        ID                    = $ID
                    }
                    $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceConditionalAccessPolicyDetailed')
                    [void]$output.Add($SettingPSObj)
                    #endregion

                    #region 21: Access controls > Session > Persistent browser session
                    $ID = 21
                    $Setting = "PersistentBrowser"
                    $SettingDescription = "Access controls > Session > Persistent browser session"
                    $Comment = "" 
                    if ($ConditionalAccessPolicy.SessionControls.PersistentBrowser.isEnabled -eq "true") {
                        $Status = "Unsupported"
                        $SettingValue = $ConditionalAccessPolicy.SessionControls.persistentBrowser.mode
                        $PolicyErrors++
                    }
                    else {
                        $Status = "Supported"
                        $SettingValue = "Not Configured"
                    }
                
                    $SettingPSObj = [PSCustomObject]@{
                        PolicyName            = $ConditionalAccessPolicy.displayName
                        PolicyState           = $PolicyState
                        Setting               = $Setting 
                        Value                 = $SettingValue
                        TeamsDevicesStatus    = $Status 
                        Comment               = $Comment
                        SettingDescription    = $SettingDescription 
                        AssignedToGroup       = $outAssignedToGroup
                        ExcludedFromGroup     = $outExcludedFromGroup 
                        AssignedToGroupList   = $AssignedToGroup
                        ExcludedFromGroupList = $ExcludedFromGroup
                        PolicyID              = $ConditionalAccessPolicy.id
                        ID                    = $ID
                    }
                    $SettingPSObj.PSObject.TypeNames.Insert(0, 'TeamsDeviceConditionalAccessPolicyDetailed')
                    [void]$output.Add($SettingPSObj)
                    #endregion

                    if ($PolicyErrors -gt 0) {
                        $StatusSum = "Has " + $PolicyErrors + " unsupported settings."
                        $displayWarning = $true
                    }
                    elseif ($PolicyWarnings -gt 0) {
                        $StatusSum = "Has " + $PolicyWarnings + " settings that may impact users."
                        $displayWarning = $true
                    }
                    else {
                        $StatusSum = "All settings supported."
                    }
                    $PolicySum = [PSCustomObject]@{
                        PolicyID              = $ConditionalAccessPolicy.id
                        PolicyName            = $ConditionalAccessPolicy.DisplayName
                        PolicyState           = $PolicyState
                        AssignedToGroup       = $outAssignedToGroup
                        AssignedToGroupList   = $AssignedToGroup
                        ExcludedFromGroup     = $outExcludedFromGroup 
                        ExcludedFromGroupList = $ExcludedFromGroup
                        TeamsDevicesStatus    = $StatusSum
                    }
                    $PolicySum.PSObject.TypeNames.Insert(0, 'TeamsDeviceConditionalAccessPolicy')
                    [void]$outputSum.Add($PolicySum)
                }
                else {
                    $skippedCAPolicies++
                }
            }
            if ($totalCAPolicies -eq 0) {
                if ($UserUPN) {
                    Write-Warning ("The user " + $UserUPN + " doesn't have any Compliance Policies assigned.")
                }
                else {
                    Write-Warning "No Conditional Access Policies assigned to All Users, All Devices or group found. Please use Test-UcTeamsDevicesConditionalAccessPolicy -IgnoreAssigment to check all policies."
                }
            }
        
            if ($IncludeSupported -and $Detailed) {
                if ($ExportCSV) {
                    $output | Sort-Object PolicyName, ID | Select-Object PolicyName, PolicyID, PolicyState, AssignedToGroup, ExcludedFromGroup, TeamsDevicesStatus, Setting, SettingDescription, Value, Comment | Export-Csv -path $OutputFullPath -NoTypeInformation
                    Write-Host ("Results available in: " + $OutputFullPath) -ForegroundColor Cyan
                    return
                }
                else {
                    $output | Sort-Object PolicyName, ID
                }
            }
            elseif ($Detailed) {
                if ((( $output | Where-Object -Property TeamsDevicesStatus -NE -Value "Supported").count -eq 0) -and !$IncludeSupported) {
                    Write-Warning "No unsupported settings found, please use Test-UcTeamsDevicesConditionalAccessPolicy -IncludeSupported, to output all settings."
                }
                else {
                    if ($ExportCSV) {
                        $output | Where-Object -Property TeamsDevicesStatus -NE -Value "Supported" | Sort-Object PolicyName, ID | Select-Object PolicyName, PolicyID, PolicyState, AssignedToGroup, ExcludedFromGroup, TeamsDevicesStatus, Setting, SettingDescription, Value, Comment | Export-Csv -path $OutputFullPath -NoTypeInformation
                        Write-Host ("Results available in: " + $OutputFullPath) -ForegroundColor Cyan
                    }
                    else {    
                        $output | Where-Object -Property TeamsDevicesStatus -NE -Value "Supported" | Sort-Object PolicyName, ID
                    }
                }
            }
            else {
                if (($skippedCAPolicies -gt 0) -and !$All) {
                    Write-Warning ("Skipping $skippedCAPolicies conditional access policies since they will not be applied to Teams Devices")
                    Write-Warning ("Please use the All switch to check all policies: Test-UcTeamsDevicesConditionalAccessPolicy -All")
                }
                if ($displayWarning) {
                    Write-Warning "One or more policies contain unsupported settings, please use Test-UcTeamsDevicesConditionalAccessPolicy -Detailed to identify the unsupported settings."
                }
                $outputSum | Sort-Object PolicyName
            }
        }
    }
}
#EndRegion '.\Public\Test-UcTeamsDevicesConditionalAccessPolicy.ps1' 807
#Region '.\Public\Test-UcTeamsOnlyModeReadiness.ps1' 0
<#
.SYNOPSIS
Check if a Tenant can be changed to TeamsOnly
 
.DESCRIPTION
This function will check if there is a lyncdiscover DNS Record that prevents a tenant to be switched to TeamsOnly.
 
.PARAMETER Domain
Specifies a domain registered with Microsoft 365
 
.EXAMPLE
PS> Test-UcTeamsOnlyModeReadiness
 
.EXAMPLE
PS> Test-UcTeamsOnlyModeReadiness -Domain uclobby.com
#>

Function Test-UcTeamsOnlyModeReadiness {
    Param(
        [Parameter(Mandatory = $false)]
        [string]$Domain
    )
    $outTeamsOnly = [System.Collections.ArrayList]::new()
    $connectedMSTeams = $false
    if ($Domain) {
        $365Domains = Get-UcM365Domains -Domain $Domain
    }
    else {
        try {
            Test-UcModuleUpdateAvailable -ModuleName UcLobbyTeams
            $365Domains = Get-CsOnlineSipDomain
            $connectedMSTeams = $true
        }
        catch {
            Write-Host "Error: Please Connect to before running this cmdlet with Connect-MicrosoftTeams" -ForegroundColor Red
            return
        }
    }
    $DomainCount = ($365Domains.Count)
    $i = 1
    foreach ($365Domain in $365Domains) {
        $tmpDomain = $365Domain.Name
        Write-Progress -Activity "Teams Only Mode Readiness" -Status "Checking: $tmpDomain - $i of $DomainCount"  -PercentComplete (($i / $DomainCount) * 100)
        $i++
        $status = "Not Ready"
        $DNSRecord = ""
        $hasARecord = $false
        $enabledSIP = $false
        
        # 20220522 - Skipping if the domain is not SIP Enabled.
        if (!($connectedMSTeams)) {
            try {
                Invoke-WebRequest -Uri ("https://webdir.online.lync.com/AutoDiscover/AutoDiscoverservice.svc/root?originalDomain=" + $tmpDomain) | Out-Null
                $enabledSIP = $true
            }
            catch {
 
                if ($Error[0].Exception.Message -like "*404*") {
                    $enabledSIP = $false
                }
            }
        }
        else {
            if ($365Domain.Status -eq "Enabled") {
                $enabledSIP = $true
            }
        }
        if ($enabledSIP) {
            $DiscoverFQDN = "lyncdiscover." + $365Domain.Name
            $FederationFQDN = "_sipfederationtls._tcp." + $365Domain.Name
            $DNSResultA = (Resolve-DnsName $DiscoverFQDN -ErrorAction Ignore -Type A)
            $DNSResultSRV = (Resolve-DnsName $FederationFQDN -ErrorAction Ignore -Type SRV).NameTarget
            foreach ($tmpRecord in $DNSResultA) {
                if (($tmpRecord.NameHost -eq "webdir.online.lync.com") -and ($DNSResultSRV -eq "sipfed.online.lync.com")) {
                    break
                }
        
                if ($tmpRecord.Type -eq "A") {
                    $hasARecord = $true
                    $DNSRecord = $tmpRecord.IPAddress
                }
            }
            if (!($hasARecord)) {
                $DNSResultCNAME = (Resolve-DnsName $DiscoverFQDN -ErrorAction Ignore -Type CNAME)
                if ($DNSResultCNAME.count -eq 0) {
                    $status = "Ready"
                }
                if (($DNSResultCNAME.NameHost -eq "webdir.online.lync.com") -and ($DNSResultSRV -eq "sipfed.online.lync.com")) {
                    $status = "Ready"
                    $DNSRecord = $DNSResultCNAME.NameHost
                }
                else {
                    $DNSRecord = $DNSResultCNAME.NameHost
                }
                if ($DNSResultCNAME.Type -eq "SOA") {
                    $status = "Ready"
                }
            }
            $Validation = New-Object -TypeName PSObject -Property @{
                DiscoverRecord   = $DNSRecord
                FederationRecord = $DNSResultSRV
                Status           = $status
                Domain           = $365Domain.Name
            }

        }
        else {
            $Validation = New-Object -TypeName PSObject -Property @{
                DiscoverRecord   = ""
                FederationRecord = ""
                Status           = "Not SIP Enabled"
                Domain           = $365Domain.Name
            }
        }
        $Validation.PSObject.TypeNames.Insert(0, 'TeamsOnlyModeReadiness')
        $outTeamsOnly.Add($Validation) | Out-Null
    }
    return $outTeamsOnly
}
#EndRegion '.\Public\Test-UcTeamsOnlyModeReadiness.ps1' 119
#Region '.\Public\Update-UcTeamsDevice.ps1' 0
<#
.SYNOPSIS
Update Microsoft Teams Devices
 
.DESCRIPTION
This function will send update commands to Teams Android Devices using MS Graph.
 
Contributors: Eileen Beato, David Paulino and Bryan Kendrick
 
Requirements: Microsoft Graph PowerShell Module (Install-Module Microsoft.Graph)
                Microsoft Graph Scopes:
                        "TeamworkDevice.ReadWrite.All"
.PARAMETER DeviceID
Specify the Teams Admin Center Device ID that we want to update.
 
.PARAMETER DeviceType
Specifies a filter, valid options:
    Phone - Teams Native Phones
    MTRA - Microsoft Teams Room Running Android
    Display - Microsoft Teams Displays
    Panel - Microsoft Teams Panels
 
.PARAMETER UpdateType
Allow to specify which time of update we want to do:
        Firmware
        TeamsApp
        All
 
.PARAMETER InputCSV
When present will use this file as Input, we only need a column with Device Id. It supports files exported from Teams Admin Center (TAC).
 
.PARAMETER Subnet
Only available when using InputCSV and requires a “IP Address” column, it allows to only send updates to Teams Android devices within a subnet.
Format examples:
10.0.0.0/8
192.168.0.0/24
 
 
.PARAMETER OutputPath
Allows to specify the path where we want to save the results. By default will save on current user Download.
 
.PARAMETER ReportOnly
Will read Teams Device Android versions info and generate a report
 
.EXAMPLE
PS> Update-UcTeamsDevice
 
.EXAMPLE
PS> Update-UcTeamsDevice -ReportOnly
 
.EXAMPLE
PS> Update-UcTeamsDevice -InputCSV C:\Temp\DevicesList_2023-04-20_15-19-00-UTC.csv
 
#>


Function Update-UcTeamsDevice {
    [cmdletbinding(SupportsShouldProcess)]
    Param(
        [ValidateSet("Firmware", "TeamsApp", "All")]
        [string]$UpdateType = "All",
        [ValidateSet("Phone", "MTRA", "Display", "Panel")]
        [string]$DeviceType,
        [string]$DeviceID,
        [string]$InputCSV,
        [string]$Subnet,
        [string]$OutputPath,
        [switch]$ReportOnly
    )
    $regExIPAddressSubnet = "^((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9]))\/(3[0-2]|[1-2]{1}[0-9]{1}|[1-9])$"

    if (Test-UcMgGraphConnection -Scopes "TeamworkDevice.ReadWrite.All") {
        $outTeamsDevices = [System.Collections.ArrayList]::new()
        Test-UcModuleUpdateAvailable -ModuleName UcLobbyTeams
        #Checking if the Subnet is valid
        if($Subnet){
            if(!($Subnet -match $regExIPAddressSubnet)){
                Write-Host ("Error: Subnet " + $Subnet + " is invalid, please make sure the subnet is valid and in this format 10.0.0.0/8, 192.168.0.0/24") -ForegroundColor Red
                return
            } 
        }

        if ($ReportOnly) {
            $outFileName = "UpdateTeamsDevices_ReportOnly_" + ( get-date ).ToString('yyyyMMdd-HHmmss') + ".csv"
            $StatusType = "offline", "critical", "nonUrgent", "healthy"
        }
        else {
            $outFileName = "UpdateTeamsDevices_" + ( get-date ).ToString('yyyyMMdd-HHmmss') + ".csv"
            $StatusType = "critical", "nonUrgent"
        }
        #Verify if the Output Path exists
        if ($OutputPath) {
            if (!(Test-Path $OutputPath -PathType Container)) {
                Write-Host ("Error: Invalid folder " + $OutputPath) -ForegroundColor Red
                return
            }
            else {
                $OutputFullPath = [System.IO.Path]::Combine($OutputPath, $outFileName)
            }
        }
        else {                
            $OutputFullPath = [System.IO.Path]::Combine($env:USERPROFILE, "Downloads", $outFileName)
        }

        $graphRequests = [System.Collections.ArrayList]::new()
        if ($DeviceID) {
            $gRequestTmp = New-Object -TypeName PSObject -Property @{
                id     = $DeviceID
                method = "GET"
                url    = "/teamwork/devices/" + $DeviceID
            }
            [void]$graphRequests.Add($gRequestTmp)
            $GraphResponse = Invoke-UcMgGraphBatch -Requests $graphRequests -MgProfile beta -Activity "Update-UcTeamsDevices, getting device info" -IncludeBody
            
            if ($GraphResponse.status -eq 200) {
                $TeamsDeviceList = $GraphResponse.body
            }
            elseif ($GraphResponse.status -eq 404) {
                Write-Host ("Error: Device ID $DeviceID not found.") -ForegroundColor Red
                return
            }
        }
        elseif ($InputCSV) {
            if (Test-Path $InputCSV) {
                try {
                    $TeamsDeviceInput = Import-Csv -Path $InputCSV
                }
                catch {
                    Write-Host ("Invalid CSV input file: " + $InputCSV) -ForegroundColor Red
                    return
                }
                foreach ($TeamsDevice in $TeamsDeviceInput) {
                    $includeDevice = $true
                    if ($Subnet) {
                        $includeDevice = Test-UcIPaddressInSubnet -IPAddress $TeamsDevice.'IP Address' -Subnet $Subnet
                    }

                    if ($includeDevice) {
                        $gRequestTmp = New-Object -TypeName PSObject -Property @{
                            id     = $TeamsDevice.'Device Id'
                            method = "GET"
                            url    = "/teamwork/devices/" + $TeamsDevice.'Device Id'
                        }
                        [void]$graphRequests.Add($gRequestTmp)
                    }
                }
                if ($graphRequests.Count -gt 0) {
                    $TeamsDeviceList = (Invoke-UcMgGraphBatch -Requests $graphRequests -MgProfile beta -Activity "Update-UcTeamsDevices, getting device info" )
                } 
            }
            else {
                Write-Host ("Error: File not found " + $InputCSV) -ForegroundColor Red
                return
            }
        }
        else {
            #Currently only Android based Teams devices are supported.
            switch ($DeviceType) {
                "Phone" { 
                    $gRequestTmp = New-Object -TypeName PSObject -Property @{
                        id     = "ipPhone"
                        method = "GET"
                        url    = "/teamwork/devices/?`$filter=deviceType eq 'ipPhone'"
                    }
                    [void]$graphRequests.Add($gRequestTmp)
                    $gRequestTmp = New-Object -TypeName PSObject -Property @{
                        id     = "lowCostPhone"
                        method = "GET"
                        url    = "/teamwork/devices/?`$filter=deviceType eq 'lowCostPhone'"
                    }
                    [void]$graphRequests.Add($gRequestTmp)
                }
                "MTRA" {            
                    $gRequestTmp = New-Object -TypeName PSObject -Property @{
                        id     = "collaborationBar"
                        method = "GET"
                        url    = "/teamwork/devices/?`$filter=deviceType eq 'collaborationBar'"
                    }
                    [void]$graphRequests.Add($gRequestTmp)
                    $gRequestTmp = New-Object -TypeName PSObject -Property @{
                        id     = "touchConsole"
                        method = "GET"
                        url    = "/teamwork/devices/?`$filter=deviceType eq 'touchConsole'"
                    }
                    [void]$graphRequests.Add($gRequestTmp)
                }
                "Display" {
                    $gRequestTmp = New-Object -TypeName PSObject -Property @{
                        id     = "teamsDisplay"
                        method = "GET"
                        url    = "/teamwork/devices/?`$filter=deviceType eq 'teamsDisplay'"
                    }
                    [void]$graphRequests.Add($gRequestTmp)
                }
                "Panel" {
                    $gRequestTmp = New-Object -TypeName PSObject -Property @{
                        id     = "teamsPanel"
                        method = "GET"
                        url    = "/teamwork/devices/?`$filter=deviceType eq 'teamsPanel'"
                    }
                    [void]$graphRequests.Add($gRequestTmp)
                }
                Default {
                    #This is the only way to exclude MTRW and SurfaceHub by creating a request per device type.
                    $gRequestTmp = New-Object -TypeName PSObject -Property @{
                        id     = "ipPhone"
                        method = "GET"
                        url    = "/teamwork/devices/?`$filter=deviceType eq 'ipPhone'"
                    }
                    [void]$graphRequests.Add($gRequestTmp) 
                    $gRequestTmp = New-Object -TypeName PSObject -Property @{
                        id     = "lowCostPhone"
                        method = "GET"
                        url    = "/teamwork/devices/?`$filter=deviceType eq 'lowCostPhone'"
                    }
                    [void]$graphRequests.Add($gRequestTmp)
                    $gRequestTmp = New-Object -TypeName PSObject -Property @{
                        id     = "collaborationBar"
                        method = "GET"
                        url    = "/teamwork/devices/?`$filter=deviceType eq 'collaborationBar'"
                    }
                    [void]$graphRequests.Add($gRequestTmp)
                    $gRequestTmp = New-Object -TypeName PSObject -Property @{
                        id     = "touchConsole"
                        method = "GET"
                        url    = "/teamwork/devices/?`$filter=deviceType eq 'touchConsole'"
                    }
                    [void]$graphRequests.Add($gRequestTmp) 
                    $gRequestTmp = New-Object -TypeName PSObject -Property @{
                        id     = "teamsDisplay"
                        method = "GET"
                        url    = "/teamwork/devices/?`$filter=deviceType eq 'teamsDisplay'"
                    }
                    [void]$graphRequests.Add($gRequestTmp)
                    $gRequestTmp = New-Object -TypeName PSObject -Property @{
                        id     = "teamsPanel"
                        method = "GET"
                        url    = "/teamwork/devices/?`$filter=deviceType eq 'teamsPanel'"
                    }
                    [void]$graphRequests.Add($gRequestTmp)
                }
            }
            #Using new cmdlet to get a list of devices
            $TeamsDeviceList = (Invoke-UcMgGraphBatch -Requests $graphRequests -MgProfile beta -Activity "Update-UcTeamsDevices, getting device info").value
        }

        $devicesWithUpdatePending = 0
        $graphRequests = [System.Collections.ArrayList]::new()
        foreach ($TeamsDevice in $TeamsDeviceList) {

            if(($graphRequests.id -notcontains $TeamsDevice.currentuser.id) -and !([string]::IsNullOrEmpty($TeamsDevice.currentuser.id))) {
                $gRequestTmp = New-Object -TypeName PSObject -Property @{
                    id =  $TeamsDevice.currentuser.id
                    method = "GET"
                    url = "/users/"+ $TeamsDevice.currentuser.id
                }
                [void]$graphRequests.Add($gRequestTmp)
            }

            if ($TeamsDevice.healthStatus -in $StatusType) {
                $devicesWithUpdatePending++
                $gRequestTmp = New-Object -TypeName PSObject -Property @{
                    id     = $TeamsDevice.id + "-health"
                    method = "GET"
                    url    = "/teamwork/devices/" + $TeamsDevice.id + "/health"
                }
                [void]$graphRequests.Add($gRequestTmp)
            }

            $gRequestTmp = New-Object -TypeName PSObject -Property @{
                id = $TeamsDevice.id+"-operations"
                method = "GET"
                url = "/teamwork/devices/"+$TeamsDevice.id+"/operations"
            }
            [void]$graphRequests.Add($gRequestTmp) 

        }
        if ($graphRequests.Count -gt 0) {
            $graphResponseExtra = Invoke-UcMgGraphBatch -Requests $graphRequests -MgProfile beta -Activity "Update-UcTeamsDevices, getting device health info" -IncludeBody
        }

        #In case we detect more than 5 devices with updates pending we will request confirmation that we can continue.
        if (($devicesWithUpdatePending -gt 5) -and !$ReportOnly) {
            if ($ConfirmPreference) {
                $title = 'Confirm'
                $question = "There are " + $devicesWithUpdatePending + " Teams Devices pending update. Are you sure that you want to continue?"
                $choices = '&Yes', '&No'
                $decision = $Host.UI.PromptForChoice($title, $question, $choices, 1)
            }
            else {
                $decision = 0
            }
            if ($decision -ne 0) {
                return
            }
        }
        $graphRequests = [System.Collections.ArrayList]::new()
        foreach ($TeamsDevice in $TeamsDeviceList) {
            if ($TeamsDevice.healthStatus -in $StatusType) {
                $TeamsDeviceHealth = ($graphResponseExtra | Where-Object { $_.id -eq ($TeamsDevice.id + "-health") }).body
                
                #Valid types are: adminAgent, operatingSystem, teamsClient, firmware, partnerAgent, companyPortal.
                #Currently we only consider Firmware and TeamsApp(teamsClient)
                
                #Firmware
                if ($TeamsDeviceHealth.softwareUpdateHealth.firmwareSoftwareUpdateStatus.softwareFreshness.Equals("updateAvailable") -and ($UpdateType.Equals("All") -or $UpdateType.Equals("Firmware"))) {
                    if (!($ReportOnly)) {

                        $requestHeader = New-Object 'System.Collections.Generic.Dictionary[string, string]'
                        $requestHeader.Add("Content-Type", "application/json")
                        $requestBody = New-Object 'System.Collections.Generic.Dictionary[string, string]'
                        $requestBody.Add("softwareType", "firmware")
                        $requestBody.Add("softwareVersion", $TeamsDeviceHealth.softwareUpdateHealth.firmwareSoftwareUpdateStatus.availableVersion)

                        $gRequestTmp = New-Object -TypeName PSObject -Property @{
                            id      = $TeamsDevice.id + "-updateFirmware"
                            method  = "POST"
                            url     = "/teamwork/devices/" + $TeamsDevice.id + "/updateSoftware"
                            body    = $requestBody
                            headers = $requestHeader
                        }
                        [void]$graphRequests.Add($gRequestTmp)
                    }
                }
                
                #TeamsApp
                if ($TeamsDeviceHealth.softwareUpdateHealth.teamsClientSoftwareUpdateStatus.softwareFreshness.Equals("updateAvailable") -and ($UpdateType.Equals("All") -or $UpdateType.Equals("TeamsApp"))) {
                    if (!($ReportOnly)) {
                        $requestHeader = New-Object 'System.Collections.Generic.Dictionary[string, string]'
                        $requestHeader.Add("Content-Type", "application/json")

                        $requestBody = New-Object 'System.Collections.Generic.Dictionary[string, string]'
                        $requestBody.Add("softwareType", "teamsClient")
                        $requestBody.Add("softwareVersion", $TeamsDeviceHealth.softwareUpdateHealth.teamsClientSoftwareUpdateStatus.availableVersion)

                        $gRequestTmp = New-Object -TypeName PSObject -Property @{
                            id      = $TeamsDevice.id + "-updateTeamsClient"
                            method  = "POST"
                            url     = "/teamwork/devices/" + $TeamsDevice.id + "/updateSoftware"
                            body    = $requestBody
                            headers = $requestHeader
                        }
                        [void]$graphRequests.Add($gRequestTmp)
                    }
                }
            }
        }
        if ($graphRequests.Count -gt 0) {
            $updateGraphResponse = Invoke-UcMgGraphBatch -Requests $graphRequests -MgProfile beta -Activity "Update-UcTeamsDevices, sending update commands" -IncludeBody
        }

        foreach ($TeamsDevice in $TeamsDeviceList) {
            if ($TeamsDevice.healthStatus -in $StatusType) {
                $TeamsDeviceHealth = ($graphResponseExtra | Where-Object { $_.id -eq ($TeamsDevice.id + "-health") }).body
                if ($ReportOnly) {
                    $UpdateStatus = "Report Only:"
                    $pendingUpdate = $false
                    if ($TeamsDeviceHealth.softwareUpdateHealth.firmwareSoftwareUpdateStatus.softwareFreshness.Equals("updateAvailable") -and ($UpdateType.Equals("All") -or $UpdateType.Equals("Firmware"))) {
                        $UpdateStatus += " Firmware Update Pending;"
                        $pendingUpdate = $true
                    }
                    if ($TeamsDeviceHealth.softwareUpdateHealth.teamsClientSoftwareUpdateStatus.softwareFreshness.Equals("updateAvailable") -and ($UpdateType.Equals("All") -or $UpdateType.Equals("TeamsApp"))) {
                        $UpdateStatus += " Teams App Update Pending;"
                        $pendingUpdate = $true
                    }
                    if (!$pendingUpdate) {
                        $UpdateStatus = "Report Only: No firmware or Teams App updates pending."
                    }
                }
                else {
                    $tmpUpdateStatus = ($updateGraphResponse | Where-Object { $_.id -eq ($TeamsDevice.id + "-updateFirmware") })
                    if ($tmpUpdateStatus.status -eq 202) {
                        $UpdateStatus = "Firmware update Command was sent to the device"
                    }
                    elseif ($tmpUpdateStatus.status -eq 409) {
                        $UpdateStatus = "There is a firmware update pending, please check the update status."
                    }
                    $tmpUpdateStatus = ($updateGraphResponse | Where-Object { $_.id -eq ($TeamsDevice.id + "-updateTeamsClient") })
                    if ($tmpUpdateStatus.status -eq 202) {
                        $UpdateStatus = "Teams App update Command was sent to the device"
                    }
                    elseif ($tmpUpdateStatus.status -eq 409) {
                        $UpdateStatus = "There is a Teams App update pending, please check the update status."
                    }
                }
                $userUPN = ($graphResponseExtra | Where-Object{$_.id -eq $TeamsDevice.currentuser.id}).body.userPrincipalName

                $TeamsDeviceOperations = ($graphResponseExtra | Where-Object{$_.id -eq ($TeamsDevice.id+"-operations")}).body.value

                $LastUpdateStatus = ""
                $LastUpdateInitiatedBy = ""
                $LastUpdateModifiedDate = ""

                foreach($TeamsDeviceOperation in $TeamsDeviceOperations){
                    if($TeamsDeviceOperation.operationType -eq 'softwareUpdate'){
                        $LastUpdateStatus = $TeamsDeviceOperation.status
                        $LastUpdateInitiatedBy = $TeamsDeviceOperation.createdBy.user.displayName
                        $LastUpdateModifiedDate = $TeamsDeviceOperation.lastActionDateTime
                        break;
                    }
                }

                $TDObj = New-Object -TypeName PSObject -Property @{
                    'Device Id'                     = $TeamsDevice.id
                    DisplayName                     = $TeamsDevice.currentuser.displayName
                    UserUPN                         = $userUPN
                    DeviceType                      = Convert-UcTeamsDeviceType $TeamsDevice.deviceType
                    Manufacturer                    = $TeamsDevice.hardwaredetail.manufacturer
                    Model                           = $TeamsDevice.hardwaredetail.model
                    HealthStatus                    = $TeamsDevice.healthStatus
                    TeamsAdminAgentCurrentVersion   = $TeamsDeviceHealth.softwareUpdateHealth.adminAgentSoftwareUpdateStatus.currentVersion
                    TeamsAdminAgentAvailableVersion = $TeamsDeviceHealth.softwareUpdateHealth.adminAgentSoftwareUpdateStatus.availableVersion
                    FirmwareCurrentVersion          = $TeamsDeviceHealth.softwareUpdateHealth.firmwareSoftwareUpdateStatus.currentVersion
                    FirmwareAvailableVersion        = $TeamsDeviceHealth.softwareUpdateHealth.firmwareSoftwareUpdateStatus.availableVersion
                    CompanyPortalCurrentVersion     = $TeamsDeviceHealth.softwareUpdateHealth.companyPortalSoftwareUpdateStatus.currentVersion
                    CompanyPortalAvailableVersion   = $TeamsDeviceHealth.softwareUpdateHealth.companyPortalSoftwareUpdateStatus.availableVersion
                    OEMAgentAppCurrentVersion       = $TeamsDeviceHealth.softwareUpdateHealth.partnerAgentSoftwareUpdateStatus.currentVersion
                    OEMAgentAppAvailableVersion     = $TeamsDeviceHealth.softwareUpdateHealth.partnerAgentSoftwareUpdateStatus.availableVersion
                    TeamsAppCurrentVersion          = $TeamsDeviceHealth.softwareUpdateHealth.teamsClientSoftwareUpdateStatus.currentVersion
                    TeamsAppAvailableVersion        = $TeamsDeviceHealth.softwareUpdateHealth.teamsClientSoftwareUpdateStatus.availableVersion
                    UpdateStatus                    = $UpdateStatus
                    
                    #LastUpdate
                    LastUpdateStatus = $LastUpdateStatus
                    LastUpdateInitiatedBy = $LastUpdateInitiatedBy
                    LastUpdateModifiedDate = $LastUpdateModifiedDate
                }
                [void]$outTeamsDevices.Add($TDObj)
            }
        }

        if ( $outTeamsDevices.Count -gt 0) {
            $outTeamsDevices | Sort-Object DeviceType, Manufacturer, Model | Select-Object 'Device Id', DisplayName, UserUPN, DeviceType, Manufacturer, Model, HealthStatus, TeamsAdminAgentCurrentVersion, TeamsAdminAgentAvailableVersion, FirmwareCurrentVersion, FirmwareAvailableVersion, CompanyPortalCurrentVersion, CompanyPortalAvailableVersion, OEMAgentAppCurrentVersion, OEMAgentAppAvailableVersion, TeamsAppCurrentVersion, TeamsAppAvailableVersion, UpdateStatus, LastUpdateStatus, LastUpdateInitiatedBy, LastUpdateModifiedDate | Export-Csv -path $OutputFullPath -NoTypeInformation
            Write-Host ("Results available in: " + $OutputFullPath) -ForegroundColor Cyan
        }
        else {
            Write-Host ("No Teams Device(s) found that have pending update.") -ForegroundColor Cyan
        }
    }
}
#EndRegion '.\Public\Update-UcTeamsDevice.ps1' 440