UcLobbyTools.psm1

function Build-UcPowerShellModule {
    <#
        .SYNOPSIS
        Test connection to a Service
 
        .DESCRIPTION
        This function will validate if the there is an active connection to a service and also if the required module is installed.
 
        Requirements: MsGraph, TeamsDeviceTAC - EntraAuth PowerShell module (Install-Module EntraAuth)
                        TeamsModule - MicrosoftTeams PowerShell module (Install-Module MicrosoftTeams)
 
        .PARAMETER Path
        Specify the PowerShell Module Source Path.
         
        .PARAMETER Version
        By default this cmdlet uses the current version in PowerShell Galery, we can specify one with this parameter.
    #>

    param(
        [Parameter(Mandatory = $true)]
        [string]$Path,
        [string]$Version
    )

    #Getting module name from the current script path
    $ModuleName = Split-Path $Path -Leaf

    $ModuleSourcePath = [System.IO.Path]::Combine($Path, "Source")
    $ModuleOutputPath = [System.IO.Path]::Combine($Path, "Output", $ModuleName)

    $ModulePublicFunctions = Get-ChildItem -Path ([System.IO.Path]::Combine($ModuleSourcePath, "Public")) -Filter *.ps1

    if (!$Version) {
        #Get the lastest version of the module available in the PSGallery.
        $PSGalleryModuleInfo = Find-Module -Name $ModuleName -Repository PSGallery -ErrorAction SilentlyContinue
        $PSGalleryVersion = $PSGalleryModuleInfo.Version

        #In PowerShell 7, the version is returned as String, we need to convert it to System.Version.
        if ($PSGalleryVersion -is [string]) {
            $PSGalleryVersion = [System.Version]::Parse($PSGalleryVersion)
        }

        $MinorVersion = $PSGalleryVersion.Minor
        $BuildVersion = $PSGalleryVersion.Build + 1
        if ($ModulePublicFunctions.Count -ne $PSGalleryModuleInfo.Includes.Function.Count) {
            $MinorVersion++
            $BuildVersion = $PSGalleryVersion.Build
        }
        $Version = [Version]::new($PSGalleryVersion.Major, $MinorVersion, $BuildVersion)
        Write-Host "Building $ModuleName version $Version, previous version in PSGallery is $($PSGalleryModuleInfo.Version)..." -ForegroundColor Green
    }

    #Check if Output path exists, if so deletes previous files.
    if (!(Test-Path -Path $ModuleOutputPath)) {
        New-Item -Path $ModuleOutputPath -ItemType Directory | Out-Null
    }
    else {
        Remove-Item ([System.IO.Path]::Combine($ModuleOutputPath, "*.*")) -Recurse -Force
    }

    #Region Copy Readme and Format file if exists.
    if (Test-Path -Path ([System.IO.Path]::Combine($Path, "README.md"))) {
        Copy-Item -Path ([System.IO.Path]::Combine($Path, "README.md")) -Destination ([System.IO.Path]::Combine($ModuleOutputPath, "README.md"))
    }

    if (Test-Path -Path ([System.IO.Path]::Combine($Path, "LICENSE"))) {
        Copy-Item -Path ([System.IO.Path]::Combine($Path, "LICENSE")) -Destination ([System.IO.Path]::Combine($ModuleOutputPath, "LICENSE"))
    }

    if (Test-Path -Path ([System.IO.Path]::Combine($ModuleSourcePath, "$ModuleName.format.ps1xml"))) {
        Copy-Item -Path ([System.IO.Path]::Combine($ModuleSourcePath, "$ModuleName.format.ps1xml")) -Destination ([System.IO.Path]::Combine($ModuleOutputPath, "$ModuleName.format.ps1xml"))
    }
    #endregion

    #Region Create Module
    $outModuleContent = ""
    $FunctionsToExport = "@("

    Get-ChildItem -Path ([System.IO.Path]::Combine($ModuleSourcePath, "Private")) -Filter *.ps1 | ForEach-Object { $outModuleContent += [System.IO.File]::ReadAllText($_.FullName) + [Environment]::NewLine + [Environment]::NewLine }

    $ModulePublicFunctions | ForEach-Object { $outModuleContent += [System.IO.File]::ReadAllText($_.FullName) + [Environment]::NewLine + [Environment]::NewLine; $FunctionsToExport += "'" + $_.BaseName + "'," }

    [System.IO.File]::WriteAllText([System.IO.Path]::Combine($ModuleOutputPath, "$ModuleName.psm1"), $outModuleContent, [System.Text.Encoding]::UTF8)
    $FunctionsToExport = $FunctionsToExport.Substring(0, $FunctionsToExport.Length - 1) + ")"
    #endregion

    #Region Update and copy Module Manifest
    if (Test-Path -Path ([System.IO.Path]::Combine($Path, "Source", "$ModuleName.psd1"))) {
        $ModuleManifest = [System.IO.File]::ReadAllText([System.IO.Path]::Combine($Path, "Source", "$ModuleName.psd1"))

        $ModuleManifest = $ModuleManifest -replace '#%ModuleVersion%', ("""$Version""")
        $ModuleManifest = $ModuleManifest -replace '#%FunctionsToExport%', $FunctionsToExport

        [System.IO.File]::WriteAllText([System.IO.Path]::Combine($ModuleOutputPath, "$ModuleName.psd1"), $ModuleManifest, [System.Text.Encoding]::UTF8)

    }
    else {
        Write-Warning "Missing Manifest file: $ModuleName.psd1"
    }
    #endregion

    Write-Host ("Module ready to be imported with: " + [Environment]::NewLine) -ForegroundColor Cyan
    Write-Host ("Import-Module -Name `"$ModuleOutputPath`" -Force") -ForegroundColor DarkGreen   
}

function ConvertTo-UcIPv4MaskString {
    <#
        .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 Get-UcArch {
    <#
        .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
    #>

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

function Invoke-UcGraphRequest {
    <#
        .SYNOPSIS
        Invoke a Microsoft Graph Request using Entra Auth or Microsoft.Graph.Authentication
 
        .DESCRIPTION
        This function will send a Microsoft Graph request to an available connections, "Test-UcServiceConnection -Type MsGraph" will have to be executed first to determine if we have a session with EntraAuth or Microsoft.Graph.Authentication.
 
        Requirements: EntraAuth PowerShell module (Install-Module EntraAuth)
                        or
                        Microsoft Graph Authentication PowerShell Module (Install-Module Microsoft.Graph.Authentication)
 
        .PARAMETER Path
        Specifies Microsoft Graph Path that we want to send the request.
 
        .PARAMETER Header
        Specify the header for cases we need to have a custom header.
 
        .PARAMETER Requests
        If wwe want to send a batch request.
 
        .PARAMETER Beta
        When present, it will use the Microsoft Graph Beta API.
 
        .PARAMETER IncludeBody
        Some Ms Graph APIs can require specific AuthType, Application or Delegated (User).
 
        .PARAMETER Activity
        For Batch requests we have use this for Activity Progress.
    #>

    param(
        [string]$Path = "/`$batch",
        [object]$Header,
        [object]$Requests,
        [switch]$Beta,
        [switch]$IncludeBody,
        [string]$Activity,
        [switch]$Raw
    )
    #This is an easy way to switch between v1.0 and beta.
    $BatchPath = "`$batch"
    if ($Beta) {
        $Path = "../beta" + $Path
        $BatchPath = "../beta/`$batch"
    }

    #If requests then we need to do a batch request to Graph.
    if (!$Requests) {
        if ($script:GraphEntraAuth) {
            if ($Header) {
                if ($Raw) {
                    #We need do specify both Raw and NoPaging since we want the calling function to handle it.
                    return Invoke-EntraRequest -Path $Path -Header $Header -Raw -NoPaging
                }
                else {
                    return Invoke-EntraRequest -Path $Path -Header $Header
                }
            } 
            if ($Raw) {
                #We need do specify both Raw and NoPaging since we want the calling function to handle it.
                return Invoke-EntraRequest -Path $Path -Raw -NoPaging
            }
            else {
                return Invoke-EntraRequest -Path $Path
            }
        }
        else {
            #DMAP 2025-10-17 - Adding multipage support for Invoke-MgGraphRequest and moving the old beahviour to Raw switch.
            if ($Path -notlike "*/v1.0/*") {
                $Path = "/v1.0" + $Path
            } 
            
            if ($Raw) {
                if ($Header) {
                    $GraphResponse = Invoke-MgGraphRequest -Uri $Path -Headers $Header
                }
                else {
                    $GraphResponse = Invoke-MgGraphRequest -Uri $Path
                }
                return $GraphResponse
            }            
            
            $outGraphResponse = @()
            do {
                if ($Header) {
                    $GraphResponse = Invoke-MgGraphRequest -Uri $Path -Headers $Header
                }
                else {
                    $GraphResponse = Invoke-MgGraphRequest -Uri $Path
                }
                if ($GraphResponse.value) {
                    $outGraphResponse += $GraphResponse.value
                }
                else {
                    $outGraphResponse += $GraphResponse
                }
                $Path = $GraphResponse.'@odata.nextLink'
            } while ($Path)
            return $outGraphResponse
        }
    }
    else {
        $outBatchResponses = [System.Collections.ArrayList]::new()
        $tmpGraphRequests = [System.Collections.ArrayList]::new()
        $g = 1
        $requestHeader = New-Object 'System.Collections.Generic.Dictionary[string, string]'
        $requestHeader.Add("Content-Type", "application/json")
        #If activity is null then we can use this to get the function that call this function.
        if (!($Activity)) {
            $Activity = [string]$(Get-PSCallStack)[1].FunctionName
        }
        $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) + ' }' 
                if ($script:GraphEntraAuth) {
                    $GraphResponses += (Invoke-EntraRequest -Path $BatchPath -Body $grapRequestBody -Method Post -Header $requestHeader).responses
                }
                else {
                    $GraphResponses += (Invoke-MgGraphRequest -Method Post -Uri ("/v1.0/" + $BatchPath) -Body $grapRequestBody -Headers $requestHeader).responses
                }
                $tmpGraphRequests = [System.Collections.ArrayList]::new()
            }
        }
        
        if ($tmpGraphRequests.Count -gt 0) {
            Write-Progress -Activity $Activity -Status "Running batch $g of $batchCount"
            #TODO: Look for alternatives instead of doing this.
            if ($tmpGraphRequests.Count -gt 1) {
                $grapRequestBody = ' { "requests": ' + ($tmpGraphRequests | ConvertTo-Json) + ' }' 
            }
            else {
                $grapRequestBody = ' { "requests": [' + ($tmpGraphRequests | ConvertTo-Json) + '] }' 
            }
            try {
                if ($script:GraphEntraAuth) {
                    $GraphResponses += (Invoke-EntraRequest -Path $BatchPath -Body $grapRequestBody -Method Post -Header $requestHeader).responses
                }
                else {
                    $GraphResponses += (Invoke-MgGraphRequest -Method Post -Uri  ("/v1.0/" + $BatchPath) -Body $grapRequestBody -Headers $requestHeader).responses
                }
            }
            catch {
                Write-Warning "Error while getting the Graph Request."
            }
        }
        
        #In some cases we will need the complete graph response, in that case the calling function will have to process pending pages.
        $attempts = 1
        for ($j = 0; $j -lt $GraphResponses.length; $j++) {
            $ResponseCount = 0
            if ($IncludeBody) {
                $outBatchResponses += $GraphResponses[$j]
            }
            else {
                $outBatchResponses += $GraphResponses[$j].body
                if ($GraphResponses[$j].status -eq "200") {
                    #Checking if there are more pages available
                    $GraphURI_NextPage = $GraphResponses[$j].body.'@odata.nextLink'
                    $GraphTotalCount = $GraphResponses[$j].body.'@odata.count'
                    $ResponseCount += $GraphResponses[$j].body.value.count
                    while (![string]::IsNullOrEmpty($GraphURI_NextPage)) {
                        try {
                            if ($script:GraphEntraAuth) {
                                $graphNextPageResponse = Invoke-EntraRequest -Path $GraphURI_NextPage -NoPaging
                            }
                            else {
                                $graphNextPageResponse = Invoke-MgGraphRequest -Method Get -Uri $GraphURI_NextPage
                            }
                            $outBatchResponses += $graphNextPageResponse
                            $GraphURI_NextPage = $graphNextPageResponse.'@odata.nextLink'
                            $ResponseCount += $graphNextPageResponse.value.count
                            Write-Progress -Activity $Activity -Status "$ResponseCount of $GraphTotalCount"
                        }
                        catch {
                            Write-Warning "Failed to get the next batch page, retrying..."
                            $attempts--
                        }
                        if ($attempts -eq 0) {
                            Write-Warning "Could not get next batch page, skiping it."
                            break
                        }
                    }
                }
                else {
                    Write-Warning ("Failed to get Graph Response" + [Environment]::NewLine + `
                            "Error Code: " + $GraphResponses[$j].status + " " + $GraphResponses[$j].body.error.code + [Environment]::NewLine + `
                            "Error Message: " + $GraphResponses[$j].body.error.message + [Environment]::NewLine + `
                            "Request Date: " + $GraphResponses[$j].body.error.innerError.date + [Environment]::NewLine + `
                            "Request ID: " + $GraphResponses[$j].body.error.innerError.'request-id' + [Environment]::NewLine + `
                            "Client Request Id: " + $GraphResponses[$j].body.error.innerError.'client-request-id')
                } 
            }
        }
        return $outBatchResponses
    }
}

function Test-UcElevatedPrivileges {
    return ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
}

function Test-UcIPaddressInSubnet {
    <#
        .SYNOPSIS
        Check if an IP address is part of an Subnet.
 
        .DESCRIPTION
        Returns true if the given IP address is part of the subnet, false for not or invalid ip address.
     
        Contributors: David Paulino
 
        .PARAMETER IPAddress
        IP Address that we want to confirm that belongs to a range.
 
        .PARAMETER Subnet
        Subnet in the IPaddress/SubnetMaskBits.
 
        .EXAMPLE
        PS> Test-UcIPaddressInSubnet -IPAddress 192.168.0.1 -Subnet 192.168.0.0/24
 
    #>

    param(
        [Parameter(mandatory = $true)]    
        [string]$IPAddress,
        [Parameter(mandatory = $true)]
        [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 {
        [void]($Subnet -match $regExIPAddressSubnet)
        $IPSubnet = [ipaddress]$Matches[1]
        $tmpIPAddress = [ipaddress]$IPAddress
        $subnetMask = ConvertTo-UcIPv4MaskString $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
    }
}

function Test-UcPowerShellModule {
    <#
        .SYNOPSIS
        Test if PowerShell module is installed and updated
 
        .DESCRIPTION
        This function returns FALSE if PowerShell module is not installed.
 
        .PARAMETER ModuleName
        Specifies PowerShell module name
 
        .EXAMPLE
        PS> Test-UcPowerShellModule -ModuleName UCLobbyTeams
    #>

    param(
        [string]$ModuleName = $MyInvocation.MyCommand.Module.Name
    )
    
    try {
        $ModuleNameCheck = Get-Variable -Scope Global -Name ($ModuleName + "ModuleCheck") -ErrorAction SilentlyContinue
        if ($ModuleNameCheck.Value) {
            return $true
        }
        New-Variable -Scope Global -Name ($ModuleName + "ModuleCheck") -Value $true
         
        #Get all installed versions
        $installedVersions = (Get-Module $ModuleName -ListAvailable | Sort-Object Version -Descending).Version

        #Get the lastest version available
        $availableVersion = (Find-Module -Name $ModuleName -Repository PSGallery -ErrorAction SilentlyContinue).Version

        if (!($installedVersions)) {
            if ($availableVersion ) {
                #Module not installed and there is an available version to install.
                Write-Warning ("The PowerShell Module $ModuleName is not installed, please install the latest available version ($availableVersion) with:" + [Environment]::NewLine + "Install-Module $ModuleName")
            }
            else {
                #Wrong name or not found in the registered PS Repository.
                Write-Warning ("The PowerShell Module $ModuleName not found in the registered PS Repository, please check the module name and try again.")
            }
            return $false
        }

        #Get the current loaded version
        $tmpCurrentVersion = (Get-Module $ModuleName | Sort-Object Version -Descending)
        if ($tmpCurrentVersion) {
            $currentVersion = $tmpCurrentVersion[0].Version.ToString()
        }

        if (!($currentVersion)) {
            #Module is installed but not imported, in this case we check if there is a newer version available.
            if ($availableVersion -in $installedVersions) {
                Write-Warning ("The lastest available version of $ModuleName module is installed, however the module is not imported." + [Environment]::NewLine + "Please make sure you import it with:" + [Environment]::NewLine + "Import-Module $ModuleName -RequiredVersion $availableVersion")
            }
            else {
                Write-Warning ("There is a new version available $availableVersion, the lastest installed version is " + $installedVersions[0] + "." + [Environment]::NewLine + "Please update the module with:" + [Environment]::NewLine + "Update-Module $ModuleName")
            }
            return $true
        }

        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:" + [Environment]::NewLine + "Import-Module $ModuleName -RequiredVersion $availableVersion")
            }
            else {
                Write-Warning ("There is a new version available $availableVersion, current version $currentVersion." + [Environment]::NewLine + "Please update the module with:" + [Environment]::NewLine + "Update-Module $ModuleName")
            }
            return $true
        }
    }
    catch {
    }
    return $false
}

function Test-UcServiceConnection {
    <#
        .SYNOPSIS
        Test connection to a Service

        .DESCRIPTION
        This function will validate if the there is an active connection to a service and also if the required module is installed.

        Requirements: MsGraph, TeamsDeviceTAC - EntraAuth PowerShell module (Install-Module EntraAuth)
                        TeamsModule - MicrosoftTeams PowerShell module (Install-Module MicrosoftTeams)

        .PARAMETER Type
        Specifies a Type of Service, valid options:
            MSGraph - Microsoft Graph
            TeamsModule - Microsoft Teams PowerShell module
            TeamsDeviceTAC - Teams Admin Center (TAC) API for Teams Devices

        .PARAMETER Scopes
        When present it will check if the require permissions are in the current Scope, only applicable to Microsoft Graph API.
        
        .PARAMETER AltScopes
        Allows checking for alternative permissions to the ones specified in AltScopes, only applicable to Microsoft Graph API.

        .PARAMETER AuthType
        Some Ms Graph APIs can require specific AuthType, Application or Delegated (User)
    #>

    param(
        [Parameter(mandatory = $true)]
        [ValidateSet("MSGraph", "TeamsPowerShell", "TeamsDeviceTAC")]
        [string]$Type,
        [string[]]$Scopes,
        [string[]]$AltScopes,
        [ValidateSet("Application", "Delegated")]
        [string]$AuthType,
        [switch]$NoWarning
    )
    switch ($Type) {
        "MSGraph" {
            #UCLobbyTeams is moving to use EntraAuth instead of Microsoft.Graph.Authentication, both will be supported for now.
            $script:GraphEntraAuth = $false
            $EntraAuthModuleAvailable = Get-Module EntraAuth -ListAvailable
            $MSGraphAuthAvailable = Get-Module Microsoft.Graph.Authentication -ListAvailable
      
            if ($EntraAuthModuleAvailable) {
                $AuthToken = Get-EntraToken -Service Graph
                if ($AuthToken) {
                    $script:GraphEntraAuth = $true
                    $currentScopes = $AuthToken.Scopes
                    $AuthTokenType = $AuthToken.tokendata.idtyp.replace('app', 'Application').replace('user', 'Delegated')
                }
            }

            #EntraAuth has priority if already connected.
            if ($MSGraphAuthAvailable -and !$script:GraphEntraAuth) {
                $MgGraphContext = Get-MgContext
                $currentScopes = $MgGraphContext.Scopes
                $AuthTokenType = (""+$MgGraphContext.AuthType).replace('AppOnly', 'Application')
            }

            if(!$EntraAuthModuleAvailable -and !$MSGraphAuthAvailable) {
                if (!$NoWarning) {
                    Write-Warning ("Missing EntraAuth PowerShell module. Please install it with:" + [Environment]::NewLine + "Install-Module EntraAuth") 
                }
                return $false
            }

            if (!($currentScopes)) {
                if (!$NoWarning) {
                    Write-Warning  ("Not Connected to Microsoft Graph" + `
                            [Environment]::NewLine + "Please connect to Microsoft Graph before running this cmdlet." + `
                            [Environment]::NewLine + "Commercial Tenant: Connect-EntraService -ClientID Graph -Scopes " + ($Scopes -join ",") + `
                            [Environment]::NewLine + "US Gov (GCC-H) Tenant: Connect-EntraService -ClientID Graph " + ($Scopes -join ",") + " -Environment USGov")
                }
                return $false
            }

            if ($AuthType -and $AuthTokenType -ne $AuthType) {
                if (!$NoWarning) {
                    Write-Warning "Wrong Permission Type: $AuthTokenType, this PowerShell cmdlet requires: $AuthType"
                }
                return $false
            }
            $strScope = ""
            $strAltScope = ""
            $missingScopes = ""
            $missingAltScopes = ""
            $missingScope = $false
            $missingAltScope = $false
            foreach ($scope in $Scopes) {
                $strScope += "`"" + $scope + "`","
                if ($scope -notin $currentScopes) {
                    $missingScope = $true
                    $missingScopes += $scope + ","
                }
            }
            if ($missingScope -and $AltScopes) {
                foreach ($altScope in $AltScopes) {
                    $strAltScope += "`"" + $altScope + "`","
                    if ($altScope -notin $currentScopes) {
                        $missingAltScope = $true
                        $missingAltScopes += $altScope + ","
                    }
                }
            }
            else {
                $missingAltScope = $true
            }
            #If scopes are missing we need to connect using the required scopes
            if ($missingScope -and $missingAltScope) {
                if ($Scopes -and $AltScopes) {
                    Write-Warning  ("Missing scope(s): " + $missingScopes.Substring(0, $missingScopes.Length - 1) + " and missing alternative Scope(s): " + $missingAltScopes.Substring(0, $missingAltScopes.Length - 1) + `
                            [Environment]::NewLine + "Please reconnect to Microsoft Graph before running this cmdlet." + `
                            [Environment]::NewLine + "Commercial Tenant: Connect-EntraService -ClientID Graph -Scopes " + $strScope.Substring(0, $strScope.Length - 1) + " or Connect-EntraService -ClientID Graph -Scopes " + $strAltScope.Substring(0, $strAltScope.Length - 1) + `
                            [Environment]::NewLine + "US Gov (GCC-H) Tenant: Connect-EntraService -ClientID Graph -Environment USGov -Scopes " + $strScope.Substring(0, $strScope.Length - 1) + " or Connect-EntraService -ClientID Graph -Environment USGov -Scopes " + $strAltScope.Substring(0, $strAltScope.Length - 1) )
                }
                else {
                    Write-Warning  ("Missing scope(s): " + $missingScopes.Substring(0, $missingScopes.Length - 1) + `
                            [Environment]::NewLine + "Please reconnect to Microsoft Graph before running this cmdlet." + `
                            [Environment]::NewLine + "Commercial Tenant: Connect-EntraService -ClientID Graph -Scopes " + $strScope.Substring(0, $strScope.Length - 1) + `
                            [Environment]::NewLine + "US Gov (GCC-H) Tenant: Connect-EntraService -ClientID Graph -Scopes " + $strScope.Substring(0, $strScope.Length - 1))
                }
                return $false
            }
            return $true
        }
        "TeamsPowerShell" { 
            #Checking if MicrosoftTeams module is installed
            if (!(Get-Module MicrosoftTeams -ListAvailable)) {
                Write-Warning ("Missing MicrosoftTeams PowerShell module. Please install it with:" + [Environment]::NewLine + "Install-Module MicrosoftTeams") 
                return $false
            }
            #We need to use a cmdlet to know if we are connected to MicrosoftTeams PowerShell
            try {
                Get-CsTenant -ErrorAction SilentlyContinue | Out-Null
                return $true
            }
            catch [System.UnauthorizedAccessException] {
                Write-Warning ("Please connect to Microsoft Teams PowerShell with Connect-MicrosoftTeams before running this cmdlet")
                return $false
            }
        }
        "TeamsDeviceTAC" {
            #Checking if EntraAuth module is installed
            if (!(Get-Module EntraAuth -ListAvailable)) {
                if(!$NoWarning) {
                    Write-Warning ("Missing EntraAuth PowerShell module. Please install it with:" + [Environment]::NewLine + "Install-Module EntraAuth") 
                }
                return $false
            }
            if (Get-EntraToken TeamsDeviceTAC) {
                return $true
            }
            else {
                if(!$NoWarning) {
                    Write-Warning ("Not Connected to Teams TAC API" + [Environment]::NewLine + "Please connect to Teams TAC API before running this cmdlet." + [Environment]::NewLine + "Connect-UcTeamsDeviceTAC")
                }
                return $false
            }
        }
        Default {
            return $false
        }
    }
}