UcLobbyTeams.psm1

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

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() }
    }
}

<#
.SYNOPSIS
Get Microsoft Teams Desktop Version
.DESCRIPTION
This function returns the installed Microsoft Teams desktop version for each user profile.
#>

Function Get-UcTeamsVersion {
    $regexVersion = '("version":")(\d{1,4}\.\d{1,4}.\d{1,4}\.\d{1,4})'
    $currentDateFormat = [cultureinfo]::CurrentCulture.DateTimeFormat.ShortDatePattern
    $Profiles = Get-childItem 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' | ForEach-Object {Get-ItemProperty $_.pspath } | Where-Object {$_.fullprofile -eq 1}
    foreach($Profile in $Profiles){
        $TeamsSettingPath = $Profile.ProfileImagePath + "\AppData\Roaming\Microsoft\Teams\settings.json"
        if(Test-Path $TeamsSettingPath -ErrorAction SilentlyContinue) {
            try {
                $TeamsSettings = Get-Content -Path $TeamsSettingPath
                $Version = [regex]::Match($TeamsSettings,$regexVersion).captures.groups[2].value
            } catch {
                $Version = ""
            }
            $TeamsApp = $Profile.ProfileImagePath + "\AppData\Local\Microsoft\Teams\current\Teams.exe"
            $InstallDateStr = Get-Content ($Profile.ProfileImagePath + "\AppData\Roaming\Microsoft\Teams\installTime.txt")
            $TeamsVersion = New-Object –TypeName PSObject -Property @{
                Profile = (New-Object System.Security.Principal.SecurityIdentifier($Profile.PSChildName)).Translate( [System.Security.Principal.NTAccount]).Value
                ProfilePath = $Profile.ProfileImagePath
                Version = $Version
                Arch = Get-UcArch $TeamsApp
                InstallDate = [Datetime]::ParseExact($InstallDateStr, 'M/d/yyyy', $null) | Get-Date -Format $currentDateFormat
            }
            Write-Output $TeamsVersion
        }
    }
}

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

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  $TeamsVersion = New-Object –TypeName PSObject -Property @{ TenantID = $temp[2].value}
        }
    }
}


<#
.SYNOPSIS
Get Microsoft 365 Domains from a Tenant
.DESCRIPTION
This function returns a list of domains that are associated with a Microsoft 365 Tenant.
#>

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
}

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

Function Test-UcTeamsOnlyModeReadiness {
Param(
 [Parameter(Mandatory=$false)]
 [string]$Domain
)
    $outTeamsOnly = [System.Collections.ArrayList]::new()
    if($Domain){
        $365Domains = Get-UcM365Domains -Domain $Domain
    } else {
        try{
            $365Domains =  Get-CsOnlineSipDomain | Where-Object -Property Status -EQ -Value Enabled | Select-Object Name
        } catch {
            Write-Error "Please Connect to before running this cmdlet with Connect-MicrosoftTeams"
        }
    }
        Write-Warning ("Found "  + ($365Domains.Count) + " entries")
    
        foreach($365Domain in $365Domains) {
            $status = "Not Ready"
            $DNSRecord = ""
            $hasARecord = $false

            $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
            }
            $Validation.PSObject.TypeNAmes.Insert(0,'TeamsOnlyModeReadiness')
            $outTeamsOnly.Add($Validation) | Out-Null
        }
        
        return $outTeamsOnly
    
}