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 '.\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 { $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 ) $regex = "^(.*@)(\w{8}-\w{4}-\w{4}-\w{4}-\w{12})$" try { $AllowedAudiences = Invoke-WebRequest -Uri ("https://accounts.accesscontrol.windows.net/" + $Domain + "/metadata/json/1") | ConvertFrom-Json | Select-Object -ExpandProperty allowedAudiences } 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" } foreach ($AllowedAudience in $AllowedAudiences) { $temp = [regex]::Match($AllowedAudience , $regex).captures.groups if ($temp.count -ge 2) { return New-Object -TypeName PSObject -Property @{ TenantID = $temp[2].value } } } } #EndRegion '.\Public\Get-UcM365TenantId.ps1' 42 #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 and Bryan Kendrick 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 DeviceId Specifies the Teams Device ID .PARAMETER Detailed When present it will get detailed information from Teams Devices .EXAMPLE PS> Get-UcTeamsDevice .EXAMPLE PS> Get-UcTeamsDevice -Filter MTR .EXAMPLE PS> Get-UcTeamsDevice -DeviceId 00000000-0000-0000-0000-000000000000 .EXAMPLE PS> Get-UcTeamsDevice -Detailed #> $GraphURI_BetaAPIBatch = "https://graph.microsoft.com/beta/`$batch" $GraphURI_Users = "https://graph.microsoft.com/v1.0/users/" Function Get-UcTeamsDevice { Param( [Parameter(Mandatory = $false)] [ValidateSet("Phone","MTR","MTRA","MTRW","SurfaceHub","Display","Panel","SIPPhone")] [string]$Filter, [Parameter(Mandatory = $false)] [string]$DeviceId, [Parameter(Mandatory = $false)] [switch]$Detailed ) $outTeamsDevices = [System.Collections.ArrayList]::new() #Checking if we have the required scopes. $scopes = (Get-MgContext).Scopes if (!($scopes) -or !(( "TeamworkDevice.Read.All" -in $scopes ) -and ("User.Read.All" -in $scopes))) { Connect-MgGraph -Scopes "TeamworkDevice.Read.All", "User.Read.All" } if($DeviceId){ $graphRequests = [System.Collections.ArrayList]::new() $gRequestTmp = New-Object -TypeName PSObject -Property @{ id = "device" method = "GET" url = "/teamwork/devices/"+$DeviceId } $graphRequests.Add($gRequestTmp) | Out-Null $gRequestTmp = New-Object -TypeName PSObject -Property @{ id = "activity" method = "GET" url = "/teamwork/devices/"+$DeviceId+"/activity" } $graphRequests.Add($gRequestTmp) | Out-Null $gRequestTmp = New-Object -TypeName PSObject -Property @{ id = "configuration" method = "GET" url = "/teamwork/devices/"+$DeviceId+"/configuration" } $graphRequests.Add($gRequestTmp) | Out-Null $gRequestTmp = New-Object -TypeName PSObject -Property @{ id = "health" method = "GET" url = "/teamwork/devices/"+$DeviceId+"/health" } $graphRequests.Add($gRequestTmp) | Out-Null $gRequestTmp = New-Object -TypeName PSObject -Property @{ id = "operations" method = "GET" url = "/teamwork/devices/"+$DeviceId+"/operations" } $graphRequests.Add($gRequestTmp) | Out-Null $graphBody = ' { "requests": '+ ($graphRequests | ConvertTo-Json) + ' }' $graphResponses = (Invoke-MgGraphRequest -Method Post -Uri $GraphURI_BetaAPIBatch -Body $graphBody).responses $TeamsDevice = ($graphResponses | Where-Object{$_.id -eq "device"}).body $TeamsDeviceActivity = ($graphResponses | Where-Object{$_.id -eq "activity"}).body $TeamsDeviceConfiguration = ($graphResponses | Where-Object{$_.id -eq "configuration"}).body $TeamsDeviceHealth = ($graphResponses | Where-Object{$_.id -eq "health"}).body $TeamsDeviceOperations = ($graphResponses | Where-Object{$_.id -eq "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 = "" } if ($TeamsDevice.currentuser.id) { $userUPN = (Invoke-MgGraphRequest -Uri ($GraphURI_Users + $TeamsDevice.currentuser.id ) -Method GET).userPrincipalName } else { $userUPN = "" } $outTDObj = New-Object -TypeName PSObject -Property @{ UserDisplayName = $TeamsDevice.currentuser.displayName UserUPN = $userUPN DeviceType = Convert-UcTeamsDeviceType $TeamsDevice.deviceType DeviceID = $DeviceId Notes = $TeamsDevice.notes CompanyAssetTag = $TeamsDevice.companyAssetTag Manufacturer = $TeamsDevice.hardwaredetail.manufacturer Model = $TeamsDevice.hardwaredetail.model SerialNumber = $TeamsDevice.hardwaredetail.serialNumber MacAddresses = $TeamsDevice.hardwaredetail.macAddresses 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 SpeakerConfiguration = $TeamsDeviceConfiguration.speakerConfiguration MicrophoneConfiguration = $TeamsDeviceConfiguration.microphoneConfiguration TeamsClientConfiguration = $TeamsDeviceConfiguration.teamsClientConfiguration HardwareConfiguration = $TeamsDeviceConfiguration.hardwareConfiguration SystemConfiguration = $TeamsDeviceConfiguration.systemConfiguration #Health 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 } $outTDObj.PSObject.TypeNAmes.Insert(0, 'TeamsDevice') return $outTDObj } else{ $graphRequests = [System.Collections.ArrayList]::new() 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 } } #TO DO: Look for alternatives instead of doing this. if($graphRequests.Count -gt 1){ $graphBody = ' { "requests": '+ ($graphRequests | ConvertTo-Json) + ' }' } else { $graphBody = ' { "requests": ['+ ($graphRequests | ConvertTo-Json) + '] }' } $graphResponses = (Invoke-MgGraphRequest -Method Post -Uri $GraphURI_BetaAPIBatch -Body $graphBody).responses #For performance is better to get all users in one graph request $graphResponseExtra = [System.Collections.ArrayList]::new() for($j=0;$j -lt $graphResponses.length; $j++){ $graphRequestsExtra = [System.Collections.ArrayList]::new() $TeamsDeviceList = $graphResponses[$j].body.value $i = 1 foreach($TeamsDevice in $TeamsDeviceList){ $batchCount = [int](($TeamsDeviceList.length * 5)/20)+1 Write-Progress -Activity "Teams Device List" -Status "Running batch $i of $batchCount" -PercentComplete (($i / $batchCount) * 100) if(($graphRequestsExtra.id -notcontains $TeamsDevice.currentuser.id) -and ($null -ne $TeamsDevice.currentuser.id) -and ($graphResponseExtra.id -notcontains $TeamsDevice.currentuser.id)) { $gRequestTmp = New-Object -TypeName PSObject -Property @{ id = $TeamsDevice.currentuser.id method = "GET" url = "/users/"+ $TeamsDevice.currentuser.id } $graphRequestsExtra.Add($gRequestTmp) | Out-Null } if($Detailed){ $gRequestTmp = New-Object -TypeName PSObject -Property @{ id = $TeamsDevice.id+"-activity" method = "GET" url = "/teamwork/devices/"+$TeamsDevice.id+"/activity" } $graphRequestsExtra.Add($gRequestTmp) | Out-Null $gRequestTmp = New-Object -TypeName PSObject -Property @{ id = $TeamsDevice.id+"-configuration" method = "GET" url = "/teamwork/devices/"+$TeamsDevice.id+"/configuration" } $graphRequestsExtra.Add($gRequestTmp) | Out-Null $gRequestTmp = New-Object -TypeName PSObject -Property @{ id =$TeamsDevice.id+"-health" method = "GET" url = "/teamwork/devices/"+$TeamsDevice.id+"/health" } $graphRequestsExtra.Add($gRequestTmp) | Out-Null $gRequestTmp = New-Object -TypeName PSObject -Property @{ id = $TeamsDevice.id+"-operations" method = "GET" url = "/teamwork/devices/"+$TeamsDevice.id+"/operations" } $graphRequestsExtra.Add($gRequestTmp) | Out-Null } #MS Graph is limited to 20 requests per batch, each device has 5 requests unless we already know the User UPN. if($graphRequestsExtra.Count -gt 15) { $i++ $graphBodyExtra = ' { "requests": '+ ($graphRequestsExtra | ConvertTo-Json) + ' }' $graphResponseExtra += (Invoke-MgGraphRequest -Method Post -Uri $GraphURI_BetaAPIBatch -Body $graphBodyExtra).responses $graphRequestsExtra = [System.Collections.ArrayList]::new() } } #Checking if we have requests pending if ($graphRequestsExtra.Count -gt 0){ Write-Progress -Activity "Teams Device List" -Status "Running batch $i of $batchCount" -PercentComplete (($i / $batchCount) * 100) if($graphRequestsExtra.Count -gt 1){ $graphBodyExtra = ' { "requests": '+ ($graphRequestsExtra | ConvertTo-Json) + ' }' } else { $graphBodyExtra = ' { "requests": ['+ ($graphRequestsExtra | ConvertTo-Json) + '] }' } $graphResponseExtra += (Invoke-MgGraphRequest -Method Post -Uri $GraphURI_BetaAPIBatch -Body $graphBodyExtra).responses } } for($j=0;$j -lt $graphResponses.length; $j++){ if($graphResponses[$j].status -eq 200){ $TeamsDeviceList = $graphResponses[$j].body.value 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 = "" } $TDObj = New-Object -TypeName PSObject -Property @{ UserDisplayName = $TeamsDevice.currentuser.displayName UserUPN = $userUPN DeviceType = Convert-UcTeamsDeviceType $TeamsDevice.deviceType DeviceID = $TeamsDevice.id Notes = $TeamsDevice.notes CompanyAssetTag = $TeamsDevice.companyAssetTag Manufacturer = $TeamsDevice.hardwaredetail.manufacturer Model = $TeamsDevice.hardwaredetail.model SerialNumber = $TeamsDevice.hardwaredetail.serialNumber MacAddresses = $TeamsDevice.hardwaredetail.macAddresses 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 SpeakerConfiguration = $TeamsDeviceConfiguration.speakerConfiguration MicrophoneConfiguration = $TeamsDeviceConfiguration.microphoneConfiguration TeamsClientConfiguration = $TeamsDeviceConfiguration.teamsClientConfiguration HardwareConfiguration = $TeamsDeviceConfiguration.hardwareConfiguration SystemConfiguration = $TeamsDeviceConfiguration.systemConfiguration #Health 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 DeviceType = Convert-UcTeamsDeviceType $TeamsDevice.deviceType DeviceID = $TeamsDevice.id 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 } } } $outTeamsDevices | Sort-Object DeviceType,Manufacturer,Model } } #EndRegion '.\Public\Get-UcTeamsDevice.ps1' 460 #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 { $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' 38 #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 .EXAMPLE PS> Get-UcTeamsVersion .EXAMPLE PS> Get-UcTeamsVersion -Path C:\Temp\ .EXAMPLE PS> Get-UcTeamsVersion -Computer workstation124 #> Function Get-UcTeamsVersion { Param( [string]$Path, [string]$Computer ) $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($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\" if(Test-Path -Path $RemotePath) { $Profiles = Get-ChildItem -Path $RemotePath -ErrorAction SilentlyContinue } else { Write-Error -Message ("Cannot get users on : " + $computer + " check if name is correct and permissions.") } } else { $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 @{ 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 } } } return $outTeamsVersion } #EndRegion '.\Public\Get-UcTeamsVersion.ps1' 177 #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( [Parameter(Mandatory = $false)] [string]$TeamName, [Parameter(Mandatory = $false)] [ValidateSet("Owner", "User", "Guest")] [string]$Role ) $output = [System.Collections.ArrayList]::new() 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' 80 #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 all settings in each Intune Compliance Policy .EXAMPLE PS> Test-UcTeamsDevicesCompliancePolicy .EXAMPLE PS> Test-UcTeamsDevicesCompliancePolicy -Detailed #> Function Test-UcTeamsDevicesCompliancePolicy { Param( [Parameter(Mandatory = $false)] [switch]$Detailed, [Parameter(Mandatory = $false)] [string]$PolicyID, [Parameter(Mandatory = $false)] [string]$PolicyName ) $connectedMSGraph = $false $CompliancePolicies = $null $scopes = (Get-MgContext).Scopes if (!($scopes) -or !( "DeviceManagementConfiguration.Read.All" -in $scopes )) { Connect-MgGraph -Scopes "DeviceManagementConfiguration.Read.All" } try { $CompliancePolicies = (Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/deviceManagement/deviceCompliancePolicies" -Method GET).value $connectedMSGraph = $true } catch { Write-Error 'Please connect to MS Graph with Connect-MgGraph -Scopes "DeviceManagementConfiguration.Read.All" before running this script' } if ($connectedMSGraph) { $output = [System.Collections.ArrayList]::new() $outputSum = [System.Collections.ArrayList]::new() foreach ($CompliancePolicy in $CompliancePolicies) { if ((($PolicyID -eq $CompliancePolicy.id) -or ($PolicyName -eq $CompliancePolicy.displayName) -or (!$PolicyID -and !$PolicyName)) -and ($CompliancePolicy."@odata.type" -eq "#microsoft.graph.androidCompliancePolicy")) { $PolicyErrors = 0 $PolicyWarnings = 0 $ID = 1 $Setting = "deviceThreatProtectionEnabled" $Comment = "" if ($CompliancePolicy.deviceThreatProtectionEnabled) { $Status = "Unsupported" $PolicyErrors++ } else { $Status = "Supported" } $SettingPSObj = New-Object -TypeName PSObject -Property @{ ID = $ID PolicyID = $CompliancePolicy.id PolicyName = $CompliancePolicy.displayName Setting = $Setting Value = $CompliancePolicy.deviceThreatProtectionEnabled TeamsDevicesStatus = $Status Comment = $Comment } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceCompliancePolicy') $output.Add($SettingPSObj) | Out-Null $ID = 3 $Setting = "securityBlockJailbrokenDevices" $Comment = "" if ($CompliancePolicy.securityBlockJailbrokenDevices) { $Status = "Warning" $Comment = "This setting can cause sign in issues." $PolicyWarnings++ } else { $Status = "Supported" } $SettingPSObj = New-Object -TypeName PSObject -Property @{ ID = $ID PolicyID = $CompliancePolicy.id PolicyName = $CompliancePolicy.displayName Setting = $Setting Value = $CompliancePolicy.securityBlockJailbrokenDevices TeamsDevicesStatus = $Status Comment = $Comment } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceCompliancePolicy') $output.Add($SettingPSObj) | Out-Null $ID = 4 $Setting = "deviceThreatProtectionRequiredSecurityLevel" $Comment = "" if ($CompliancePolicy.deviceThreatProtectionRequiredSecurityLevel -ne "unavailable") { $Status = "Unsupported" $PolicyErrors++ } else { $Status = "Supported" } $SettingPSObj = New-Object -TypeName PSObject -Property @{ ID = $ID PolicyID = $CompliancePolicy.id PolicyName = $CompliancePolicy.displayName Setting = $Setting Value = $CompliancePolicy.deviceThreatProtectionRequiredSecurityLevel TeamsDevicesStatus = $Status Comment = $Comment } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceCompliancePolicy') $output.Add($SettingPSObj) | Out-Null $ID = 5 $Setting = "securityRequireGooglePlayServices" $Comment = "" if ($CompliancePolicy.securityRequireGooglePlayServices) { $Status = "Unsupported" $PolicyErrors++ } else { $Status = "Supported" } $SettingPSObj = New-Object -TypeName PSObject -Property @{ ID = $ID PolicyID = $CompliancePolicy.id PolicyName = $CompliancePolicy.displayName Setting = $SettingPSObj Value = $CompliancePolicy.securityRequireGooglePlayServices TeamsDevicesStatus = $Status Comment = $Comment } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceCompliancePolicy') $output.Add($SettingPSO) | Out-Null $ID = 6 $Setting = "securityRequireUpToDateSecurityProviders" $Comment = "" if ($CompliancePolicy.securityRequireUpToDateSecurityProviders) { $Status = "Unsupported" $Comment = "Google play isn't installed on Teams Android devices." $PolicyErrors++ } else { $Status = "Supported" } $SettingPSObj = New-Object -TypeName PSObject -Property @{ ID = $ID PolicyID = $CompliancePolicy.id PolicyName = $CompliancePolicy.displayName Setting = $Setting Value = $CompliancePolicy.securityRequireUpToDateSecurityProviders TeamsDevicesStatus = $Status Comment = $Comment } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceCompliancePolicy') $output.Add($SettingPSObj) | Out-Null $ID = 7 $Setting = "securityRequireVerifyApps" $Comment = "" if ($CompliancePolicy.securityRequireVerifyApps) { $Status = "Unsupported" $PolicyErrors++ } else { $Status = "Supported" } $SettingPSObj = New-Object -TypeName PSObject -Property @{ ID = $ID PolicyID = $CompliancePolicy.id PolicyName = $CompliancePolicy.displayName Setting = $Setting Value = $CompliancePolicy.securityRequireVerifyApps TeamsDevicesStatus = $Status Comment = $Comment } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceCompliancePolicy') $output.Add($SettingPSObj) | Out-Null $ID = 8.1 $Setting = "securityRequireSafetyNetAttestationBasicIntegrity" $Comment = "" if ($CompliancePolicy.securityRequireSafetyNetAttestationBasicIntegrity) { $Status = "Unsupported" $PolicyErrors++ } else { $Status = "Supported" } $SettingPSObj = New-Object -TypeName PSObject -Property @{ ID = $ID PolicyID = $CompliancePolicy.id PolicyName = $CompliancePolicy.displayName Setting = $Setting Value = $CompliancePolicy.securityRequireSafetyNetAttestationBasicIntegrity TeamsDevicesStatus = $Status Comment = $Comment } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceCompliancePolicy') $output.Add($SettingPSObj) | Out-Null $ID = 8.2 $Setting = "securityRequireSafetyNetAttestationCertifiedDevice" $Comment = "" if ($CompliancePolicy.securityRequireSafetyNetAttestationCertifiedDevice) { $Status = "Unsupported" $PolicyErrors++ } else { $Status = "Supported" } $SettingPSObj = New-Object -TypeName PSObject -Property @{ ID = $ID PolicyID = $CompliancePolicy.id PolicyName = $CompliancePolicy.displayName Setting = $Setting Value = $CompliancePolicy.securityRequireSafetyNetAttestationCertifiedDevice TeamsDevicesStatus = $Status Comment = $Comment } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceCompliancePolicy') $output.Add($SettingPSObj) | Out-Null $ID = 9.1 $Setting = "osMinimumVersion" $Comment = "" if ($CompliancePolicy.osMinimumVersion) { $Status = "Warning" $Comment = "This setting can cause sign in issues." $PolicyWarnings++ } else { $Status = "Supported" } $SettingPSObj = New-Object -TypeName PSObject -Property @{ ID = $ID PolicyID = $CompliancePolicy.id PolicyName = $CompliancePolicy.displayName Setting = $Setting Value = $CompliancePolicy.osMinimumVersion TeamsDevicesStatus = $Status Comment = $Comment } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceCompliancePolicy') $output.Add($SettingPSObj) | Out-Null $ID = 9.2 $Setting = "osMaximumVersion" $Comment = "" if ($CompliancePolicy.osMaximumVersion) { $Status = "Warning" $Comment = "This setting can cause sign in issues." $PolicyWarnings++ } else { $Status = "Supported" } $SettingPSObj = New-Object -TypeName PSObject -Property @{ ID = $ID PolicyID = $CompliancePolicy.id PolicyName = $CompliancePolicy.displayName Setting = $Setting Value = $CompliancePolicy.osMaximumVersion TeamsDevicesStatus = $Status Comment = $Comment } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceCompliancePolicy') $output.Add($SettingPSObj) | Out-Null $ID = 10 $Setting = "storageRequireEncryption" $Comment = "" if ($CompliancePolicy.storageRequireEncryption) { $Status = "Warning" $Comment = "https://docs.microsoft.com/en-us/microsoftteams/rooms/supported-ca-and-compliance-policies?tabs=phones#supported-device-compliance-policies" $PolicyWarnings++ } else { $Status = "Supported" } $SettingPSObj = New-Object -TypeName PSObject -Property @{ ID = $ID PolicyID = $CompliancePolicy.id PolicyName = $CompliancePolicy.displayName Setting = $Setting Value = $CompliancePolicy.storageRequireEncryption TeamsDevicesStatus = $Status Comment = $Comment } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceCompliancePolicy') $output.Add($SettingPSObj) | Out-Null $ID = 11 $Setting = "securityPreventInstallAppsFromUnknownSources" $Comment = "" if ($CompliancePolicy.securityPreventInstallAppsFromUnknownSources) { $Status = "Unsupported" $PolicyErrors++ } else { $Status = "Supported" } $SettingPSObj = New-Object -TypeName PSObject -Property @{ ID = $ID PolicyID = $CompliancePolicy.id PolicyName = $CompliancePolicy.displayName Setting = $Setting Value = $CompliancePolicy.securityPreventInstallAppsFromUnknownSources TeamsDevicesStatus = $Status Comment = $Comment } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceCompliancePolicy') $output.Add($SettingPSObj) | Out-Null $ID = 15 $Setting = "passwordMinutesOfInactivityBeforeLock" $Comment = "" if ($null -ne $CompliancePolicy.passwordMinutesOfInactivityBeforeLock) { $Status = "Unsupported" $PolicyErrors++ } else { $Status = "Supported" } $SettingPSObj = New-Object -TypeName PSObject -Property @{ ID = $ID PolicyID = $CompliancePolicy.id PolicyName = $CompliancePolicy.displayName Setting = $Setting Value = $CompliancePolicy.passwordMinutesOfInactivityBeforeLock TeamsDevicesStatus = $Status Comment = $Comment } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceCompliancePolicy') $output.Add($SettingPSObj) | Out-Null $ID = 16 $Setting = "passwordRequired" $Comment = "" if ($CompliancePolicy.passwordRequired) { $Status = "Unsupported" $PolicyErrors++ } else { $Status = "Supported" } $SettingPSObj = New-Object -TypeName PSObject -Property @{ ID = $ID PolicyID = $CompliancePolicy.id PolicyName = $CompliancePolicy.displayName Setting = $Setting Value = $CompliancePolicy.passwordRequired TeamsDevicesStatus = $Status Comment = $Comment } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceCompliancePolicy') $output.Add($SettingPSObj) | Out-Null $ID = 17.1 $Setting = "passwordRequiredType" $Comment = "" if ($CompliancePolicy.passwordRequiredType -ne 'deviceDefault') { $Status = "Unsupported" $PolicyErrors++ } else { $Status = "Supported" } $SettingPSObj = New-Object -TypeName PSObject -Property @{ ID = $ID PolicyID = $CompliancePolicy.id PolicyName = $CompliancePolicy.displayName Setting = $Setting Value = $CompliancePolicy.passwordRequiredType TeamsDevicesStatus = $Status Comment = $Comment } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceCompliancePolicy') $output.Add($SettingPSObj) | Out-Null $ID = 17.2 $Setting = "passwordMinimumLength" $Comment = "" if ($null -ne $CompliancePolicy.passwordMinimumLength) { $Status = "Unsupported" $PolicyErrors++ } else { $Status = "Supported" } $SettingPSObj = New-Object -TypeName PSObject -Property @{ ID = $ID PolicyID = $CompliancePolicy.id PolicyName = $CompliancePolicy.displayName Setting = $Setting Value = $CompliancePolicy.passwordMinimumLength TeamsDevicesStatus = $Status Comment = $Comment } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceCompliancePolicy') $output.Add($SettingPSObj) | Out-Null $ID = 17.3 $Setting = "passwordExpirationDays" $Comment = "" if ($null -ne $CompliancePolicy.passwordExpirationDays) { $Status = "Unsupported" $PolicyErrors++ } else { $Status = "Supported" } $SettingPSObj = New-Object -TypeName PSObject -Property @{ ID = $ID PolicyID = $CompliancePolicy.id PolicyName = $CompliancePolicy.displayName Setting = $Setting Value = $CompliancePolicy.passwordExpirationDays TeamsDevicesStatus = $Status Comment = $Comment } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceCompliancePolicy') $output.Add($SettingPSObj) | Out-Null $ID = 17.4 $Setting = "passwordPreviousPasswordBlockCount" $Comment = "" if ($null -ne $CompliancePolicy.passwordPreviousPasswordBlockCount) { $Status = "Unsupported" $PolicyErrors++ } else { $Status = "Supported" } $SettingPSObj = New-Object -TypeName PSObject -Property @{ ID = $ID PolicyID = $CompliancePolicy.id PolicyName = $CompliancePolicy.displayName Setting = $Setting Value = $CompliancePolicy.passwordPreviousPasswordBlockCount TeamsDevicesStatus = $Status Comment = $Comment } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceCompliancePolicy') $output.Add($SettingPSObj) | Out-Null $ID = 18 $Setting = "minAndroidSecurityPatchLevel" $Comment = "" if ($CompliancePolicy.minAndroidSecurityPatchLevel -ne "") { $Status = "Warning" $Comment = "This setting can cause sign in issues." $PolicyWarnings++ } else { $Status = "Supported" } $SettingPSObj = New-Object -TypeName PSObject -Property @{ ID = $ID PolicyID = $CompliancePolicy.id PolicyName = $CompliancePolicy.displayName Setting = $Setting Value = $CompliancePolicy.minAndroidSecurityPatchLevel TeamsDevicesStatus = $Status Comment = $Comment } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceCompliancePolicy') $output.Add($SettingPSObj) | Out-Null if ($PolicyErrors -gt 0) { $StatusSum = "Unsupported" } elseif ($PolicyWarnings -gt 0) { $StatusSum = "Warning" } else { $StatusSum = "Supported" } $PolicySum = New-Object -TypeName PSObject -Property @{ PolicyID = $CompliancePolicy.id PolicyName = $CompliancePolicy.displayName TeamsDevicesStatus = $StatusSum } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceCompliancePolicySumary') $outputSum.Add($PolicySum) | Out-Null } } if ($Detailed) { $output | Sort-Object PolicyName, ID } else { $outputSum } } } #EndRegion '.\Public\Test-UcTeamsDevicesCompliancePolicy.ps1' 540 #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 .EXAMPLE PS> Test-UcTeamsDevicesConditionalAccessPolicy .EXAMPLE PS> Test-UcTeamsDevicesConditionalAccessPolicy -Detailed #> Function Test-UcTeamsDevicesConditionalAccessPolicy { Param( [Parameter(Mandatory = $false)] [switch]$Detailed ) $connectedMSGraph = $false $ConditionalAccessPolicies = $null $URLTeamsDevicesCA = "aka.ms/TeamsDevicesAndroidPolicies#supported-conditional-access-policies" $URLTeamsDevicesKnownIssues = "https://docs.microsoft.com/microsoftteams/troubleshoot/teams-rooms-and-devices/rooms-known-issues#teams-phone-devices" $scopes = (Get-MgContext).Scopes if (!($scopes) -or !( "Policy.Read.All" -in $scopes )) { Connect-MgGraph -Scopes "Policy.Read.All" } try { $ConditionalAccessPolicies = (Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies" -Method GET).Value $connectedMSGraph = $true } catch { Write-Error 'Please connect to MS Graph with Connect-MgGraph -Scopes "Policy.Read.All" before running this script' } if ($connectedMSGraph) { $output = [System.Collections.ArrayList]::new() $outputSum = [System.Collections.ArrayList]::new() foreach ($ConditionalAccessPolicy in $ConditionalAccessPolicies) { $CAPolicyState = $ConditionalAccessPolicy.State if ($CAPolicyState -eq "enabledForReportingButNotEnforced") { $CAPolicyState = "ReportOnly" } $PolicyErrors = 0 $PolicyWarnings = 0 #Cloud Apps #Exchange 00000002-0000-0ff1-ce00-000000000000 #SharePoint 00000003-0000-0ff1-ce00-000000000000 #Teams cc15fd57-2c6c-4117-a88c-83b1d56b4bbe $hasExchange = $false $hasSharePoint = $false $hasTeams = $false $hasOffice365 = $false $CloudAppValue = "" foreach ($Application in $ConditionalAccessPolicy.Conditions.Applications.IncludeApplications) { switch ($Application) { "All" { $hasOffice365 = $true; $CloudAppValue = "All" } "Office365" { $hasOffice365 = $true; $CloudAppValue = "Office 365" } "00000002-0000-0ff1-ce00-000000000000" { $hasExchange = $true; $CloudAppValue += "Exchange, " } "00000003-0000-0ff1-ce00-000000000000" { $hasSharePoint = $true; $CloudAppValue += "SharePoint, " } "cc15fd57-2c6c-4117-a88c-83b1d56b4bbe" { $hasTeams = $true; $CloudAppValue += "Teams, " } "None" { $CloudAppValue = "None" } } } if ($CloudAppValue.EndsWith(", ")) { $CloudAppValue = $CloudAppValue.Substring(0, $CloudAppValue.Length - 2) } if (($hasExchange -and $hasSharePoint -and $hasTeams) -or ($hasOffice365)) { $Status = "Supported" $Comment = "" } else { $Status = "Unsupported" $Comment = "Teams Devices needs to access: Office 365 or Exchange Online, SharePoint Online, and Microsoft Teams" $PolicyErrors++ } $SettingPSObj = New-Object -TypeName PSObject -Property @{ CAPolicyID = $ConditionalAccessPolicy.id CAPolicyName = $ConditionalAccessPolicy.displayName CAPolicyState = $CAPolicyState Setting = "Cloud Apps or actions" Value = $CloudAppValue TeamsDevicesStatus = $Status Comment = $Comment } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceConditionalAccessPolicy') $output.Add($SettingPSObj) | Out-Null #Conditions - ClientAppTypes foreach ($ClientAppType in $ConditionalAccessPolicy.Conditions.ClientAppTypes) { if ($ClientAppType -eq "All") { $Status = "Supported" $Comment = "" } else { $PolicyErrors++ $Status = "Unsupported" $Comment = $URLTeamsDevicesCA } $SettingPSObj = New-Object -TypeName PSObject -Property @{ CAPolicyID = $ConditionalAccessPolicy.id CAPolicyName = $ConditionalAccessPolicy.displayName CAPolicyState = $CAPolicyState Setting = "Conditions - ClientAppTypes" Value = $ClientAppType TeamsDevicesStatus = $Status Comment = $Comment } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceConditionalAccessPolicy') $output.Add($SettingPSObj) | Out-Null } #Device if ($ConditionalAccessPolicy.conditions.devices) { $SettingPSObj = New-Object -TypeName PSObject -Property @{ CAPolicyID = $ConditionalAccessPolicy.id CAPolicyName = $ConditionalAccessPolicy.displayName CAPolicyState = $CAPolicyState Setting = "Device Filter" Value = "Configured" TeamsDevicesStatus = "Supported" Comment = $ConditionalAccessPolicy.conditions.devices.deviceFilter.mode + ": " + $ConditionalAccessPolicy.conditions.devices.deviceFilter.rule } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceConditionalAccessPolicy') $output.Add($SettingPSObj) | Out-Null } else { $SettingPSObj = New-Object -TypeName PSObject -Property @{ CAPolicyID = $ConditionalAccessPolicy.id CAPolicyName = $ConditionalAccessPolicy.displayName CAPolicyState = $CAPolicyState Setting = "Device Filter" Value = "Missing" TeamsDevicesStatus = "Warning" Comment = "Device Filter is required when multiple Conditional Access policies exist." } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceConditionalAccessPolicy') $output.Add($SettingPSObj) | Out-Null } #Grant Controls $Setting = "GrantControls" foreach ($BuiltInControl in $ConditionalAccessPolicy.GrantControls.BuiltInControls) { $Comment = "" if ($BuiltInControl -in 'DomainJoinedDevice', 'ApprovedApplication', 'CompliantApplication', 'PasswordChange') { $PolicyErrors++ $Status = "Unsupported" $Comment = $URLTeamsDevicesCA } else { $Status = "Supported" } $SettingPSObj = New-Object -TypeName PSObject -Property @{ CAPolicyID = $ConditionalAccessPolicy.id CAPolicyName = $ConditionalAccessPolicy.displayName CAPolicyState = $CAPolicyState Setting = $Setting Value = $BuiltInControl TeamsDevicesStatus = $Status Comment = $Comment } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceConditionalAccessPolicy') $output.Add($SettingPSObj) | Out-Null } if ($ConditionalAccessPolicy.GrantControls.CustomAuthenticationFactors) { $PolicyWarnings++ $SettingPSObj = New-Object -TypeName PSObject -Property @{ CAPolicyID = $ConditionalAccessPolicy.id CAPolicyName = $ConditionalAccessPolicy.displayName CAPolicyState = $CAPolicyState Setting = $Setting Value = "CustomAuthenticationFactors" TeamsDevicesStatus = "Unsupported" Comment = $URLTeamsDevicesCA } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceConditionalAccessPolicy') $output.Add($SettingPSObj) | Out-Null } if ($ConditionalAccessPolicy.GrantControls.TermsOfUse) { $PolicyWarnings++ $SettingPSObj = New-Object -TypeName PSObject -Property @{ CAPolicyID = $ConditionalAccessPolicy.id CAPolicyName = $ConditionalAccessPolicy.displayName CAPolicyState = $CAPolicyState Setting = $Setting Value = "Terms of Use" TeamsDevicesStatus = "Warning" Comment = $URLTeamsDevicesKnownIssues } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceConditionalAccessPolicy') $output.Add($SettingPSObj) | Out-Null } $Setting = "SessionControls" $Comment = "" if ($ConditionalAccessPolicy.SessionControls.ApplicationEnforcedRestrictions) { $PolicyErrors++ $SettingPSObj = New-Object -TypeName PSObject -Property @{ CAPolicyID = $ConditionalAccessPolicy.id CAPolicyName = $ConditionalAccessPolicy.displayName CAPolicyState = $CAPolicyState Setting = $Setting Value = "ApplicationEnforcedRestrictions" TeamsDevicesStatus = "Unsupported" Comment = $Comment } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceConditionalAccessPolicy') $output.Add($SettingPSObj) | Out-Null } if ($ConditionalAccessPolicy.SessionControls.CloudAppSecurity) { $PolicyErrors++ $SettingPSObj = New-Object -TypeName PSObject -Property @{ CAPolicyID = $ConditionalAccessPolicy.id CAPolicyName = $ConditionalAccessPolicy.displayName CAPolicyState = $CAPolicyState Setting = $Setting Value = "CloudAppSecurity" TeamsDevicesStatus = "Unsupported" Comment = $URLTeamsDevicesCA } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceConditionalAccessPolicy') $output.Add($SettingPSObj) | Out-Null } if ($ConditionalAccessPolicy.SessionControls.SignInFrequency) { $PolicyWarnings++ $SettingPSObj = New-Object -TypeName PSObject -Property @{ CAPolicyID = $ConditionalAccessPolicy.id CAPolicyName = $ConditionalAccessPolicy.displayName CAPolicyState = $CAPolicyState Setting = $Setting Value = "SignInFrequency" TeamsDevicesStatus = "Warning" Comment = "Users will be signout from Teams Device every " + $ConditionalAccessPolicy.SessionControls.SignInFrequency.Value + " " + $ConditionalAccessPolicy.SessionControls.SignInFrequency.Type } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceConditionalAccessPolicy') $output.Add($SettingPSObj) | Out-Null } if ($ConditionalAccessPolicy.SessionControls.PersistentBrowser) { $PolicyErrors++ $SettingPSObj = New-Object -TypeName PSObject -Property @{ CAPolicyID = $ConditionalAccessPolicy.id CAPolicyName = $ConditionalAccessPolicy.displayName CAPolicyState = $CAPolicyState Setting = $Setting Value = "PersistentBrowser" TeamsDevicesStatus = "Unsupported" Comment = $URLTeamsDevicesCA } $SettingPSObj.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceConditionalAccessPolicy') $output.Add($SettingPSObj) | Out-Null } if ($PolicyErrors -gt 0) { $StatusSum = "Unsupported" } elseif ($PolicyWarnings -gt 0) { $StatusSum = "Warning" } else { $StatusSum = "Supported" } $PolicySum = New-Object -TypeName PSObject -Property @{ CAPolicyID = $ConditionalAccessPolicy.id CAPolicyName = $ConditionalAccessPolicy.DisplayName CAPolicyState = $CAPolicyState TeamsDevicesStatus = $StatusSum } $PolicySum.PSObject.TypeNAmes.Insert(0, 'TeamsDeviceConditionalAccessPolicySummary') $outputSum.Add($PolicySum) | Out-Null } if ($Detailed) { $output | Sort-Object CAPolicyName, Setting } else { $outputSum } } } #EndRegion '.\Public\Test-UcTeamsDevicesConditionalAccessPolicy.ps1' 312 #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 { $365Domains = Get-CsOnlineSipDomain $connectedMSTeams = $true } catch { Write-Error "Please Connect to before running this cmdlet with Connect-MicrosoftTeams" } } $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' 117 |