Public/UserManagement/TeamsCallableEntity/Assert-TeamsCallableEntity.ps1

# Module: TeamsFunctions
# Function: VoiceConfig
# Author: David Eberhardt
# Updated: 15-DEC-2020
# Status: Live




function Assert-TeamsCallableEntity {
  <#
  .SYNOPSIS
    Verifies User is ready for Voice Config
  .DESCRIPTION
    Tests whether a the Object can be used as a Callable Entity in Call Queues or Auto Attendant
  .PARAMETER UserObject
    Required for Parameterset Object. CsOnlineUser Object
  .PARAMETER LicenseObject
    Optional for Parameterset Object. AzureAdUserLicense Object
  .PARAMETER UserPrincipalName
    Required for Parameterset UserPrincipalName. UserPrincipalName of a User or ResourceAccount
  .PARAMETER RequireEV
    Optional. By default, the Command will not check for EnterpriseVoice Enablement to be True
    This is a requirement for adding Users to CallQueues (and needs to be ascertained there)
  .EXAMPLE
    Assert-TeamsCallableEntity -UserObject $CsOnlineUser
 
    Verifies the UserObject has a valid PhoneSystem License (Provisioning Status: Success) and is ready to receive Voice Configuration
  .EXAMPLE
    Assert-TeamsCallableEntity -UserObject $CsOnlineUser -LicenseObject $AzureAdUserLicense -RequireEV
 
    Verifies the UserObject has a valid PhoneSystem License (Provisioning Status: Success) and is ready to receive Voice Configuration
    Enables the User for Enterprise Voice if not yet done.
  .EXAMPLE
    Assert-TeamsCallableEntity -UserPrincipalName Jane@domain.com
 
    Verifies Jane has a valid PhoneSystem License (Provisioning Status: Success) and is ready to receive Voice Configuration
  .EXAMPLE
    Assert-TeamsCallableEntity -UserPrincipalName John@domain.com -RequireEV
 
    Verifies John has a valid PhoneSystem License (Provisioning Status: Success) and is ready to receive Voice Configuration
    Enables John for Enterprise Voice if not yet done.
  .INPUTS
    System.String
  .OUTPUTS
    Boolean
  .NOTES
    Returns Boolean Result
    This CmdLet does verify User Objects only - Channels are not validated
  .COMPONENT
    UserManagement
    TeamsAutoAttendant
    TeamsCallQueue
  .FUNCTIONALITY
    Verifies whether a User Object is correctly configured to be used for Auto Attendants or Call Queues
  .LINK
    https://github.com/DEberhardt/TeamsFunctions/tree/main/docs/Assert-TeamsCallableEntity.md
  .LINK
    https://github.com/DEberhardt/TeamsFunctions/tree/main/docs/about_TeamsAutoAttendant.md
  .LINK
    https://github.com/DEberhardt/TeamsFunctions/tree/main/docs/about_TeamsCallQueue.md
  .LINK
    https://github.com/DEberhardt/TeamsFunctions/tree/main/docs/about_UserManagement.md
  .LINK
    https://github.com/DEberhardt/TeamsFunctions/tree/main/docs/
  #>


  [CmdletBinding()]
  [OutputType([Boolean])]
  param(
    [Parameter(Mandatory, Position = 0, ParameterSetName = 'Object', HelpMessage = 'CsOnlineUser Object')]
    [Object]$UserObject,

    [Parameter(ParameterSetName = 'Object', HelpMessage = 'AzureAdUserLicense Object')]
    [Object]$LicenseObject,

    [Parameter(Mandatory, Position = 0, ParameterSetName = 'UserPrincipalName', ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [Alias('ObjectId', 'Identity')]
    [ValidateScript( {
        If ($_ -match '@' -or $_ -match $script:TFMatchGuid) { $True } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid UPN or ObjectId'
        } })]
    [string[]]$UserPrincipalName,

    [Parameter(HelpMessage = 'Switch to instruct require EnterpriseVoice to be Enabled')]
    [switch]$RequireEV
  )

  begin {
    Show-FunctionStatus -Level Live
    Write-Verbose -Message "[BEGIN ] $($MyInvocation.MyCommand)"

    # Setting Preference Variables according to Upstream settings
    if (-not $PSBoundParameters.ContainsKey('Verbose')) { $VerbosePreference = $PSCmdlet.SessionState.PSVariable.GetValue('VerbosePreference') }
    if (-not $PSBoundParameters.ContainsKey('Confirm')) { $ConfirmPreference = $PSCmdlet.SessionState.PSVariable.GetValue('ConfirmPreference') }
    if (-not $PSBoundParameters.ContainsKey('WhatIf')) { $WhatIfPreference = $PSCmdlet.SessionState.PSVariable.GetValue('WhatIfPreference') }
    if (-not $PSBoundParameters.ContainsKey('Debug')) { $DebugPreference = $PSCmdlet.SessionState.PSVariable.GetValue('DebugPreference') } else { $DebugPreference = 'Continue' }
    if ( $PSBoundParameters.ContainsKey('InformationAction')) { $InformationPreference = $PSCmdlet.SessionState.PSVariable.GetValue('InformationAction') } else { $InformationPreference = 'Continue' }

    #Worker Function
    function AssertTeamsCallableEntity {
      [CmdletBinding()]
      [OutputType([System.Object])]
      param(
        [Parameter(Mandatory, Position = 0, ParameterSetName = 'Object', HelpMessage = 'CsOnlineUser Object')]
        [Object]$UserObject,

        [Parameter(ParameterSetName = 'Object', HelpMessage = 'AzureAdUserLicense Object')]
        [Object]$LicenseObject,

        [Parameter(HelpMessage = 'Switch to instruct require EnterpriseVoice to be Enabled')]
        [switch]$RequireEV
      ) #param

      begin {
        #$Stack = Get-PSCallStack

        # Determining ObjectType
        try {
          $ObjectType = if ($UserObject.AccountType) { $UserObject.AccountType }
          else {
            if ( $UserObject.InterpretedUserType -match 'User' ) { 'User' }
            elseif ( $UserObject.InterpretedUserType -match 'ApplicationInstance' ) { 'ResourceAccount' }
            else {
              Write-Verbose -Message 'ObjectType cannot be parsed through AccountType or InterpretedUserType - Querying Get-TeamsObjectType'
              Get-TeamsObjectType -Identity "$($UserObject.UserPrincipalName)" -ErrorAction STOP
            }
          }
        }
        catch {
          $ObjectType = 'Unknown'
        }

        # Determine Scope
        $CheckLicense = $CheckAssignment = $false
        switch ($ObjectType) {
          'ResourceAccount' {
            #Check RA is assigned to CQ/AA
            $CheckLicense = $false
            $CheckAssignment = $true
            #Return Object if true, otherwise error
          }
          'User' {
            #Check License and EV-enable if needed
            $CheckLicense = $true
          }
          default {
            $ErrorMessage = "'$($UserObject.UserPrincipalName)' not a User or Resource Account. No verification done"
            Write-Error -Message $ErrorMessage
            return $false
          }
        }

        # Catching Disabled Objects
        $DisabledString = 'Disabled' + "$($ObjectType)"
        if ($UserObject.InterpretedUserType -match $DisabledString) {
          $ErrorMessage = "'$($UserObject.UserPrincipalName)' found Disabled - InterpretedUserType: $($UserObject.InterpretedUserType)"
          Write-Error -Message $ErrorMessage
          return $false
        }

      }
      process {
        # Verification
        if ( $CheckLicense ) {
          if ( $LicenseObject.PhoneSystemStatus.Contains('Disabled')) {
            Write-Verbose -Message "'$($LicenseObject.UserPrincipalName)' found and licensed (Disabled) - Trying to enable"
            try {
              Write-Information "TRYING: Object '$($UserObject.UserPrincipalName)' - PhoneSystem License is assigned - ServicePlan PhoneSystem is Disabled - Trying to activate"
              Set-AzureAdUserLicenseServicePlan -Identity "$($LicenseObject.UserPrincipalName)" -Enable MCOEV -ErrorAction Stop
              $i = 0
              $iMax = 60
              Write-Verbose -Message "Azure Active Directory is propagating Object. Testing periodically for up to $iMax seconds"
              do {
                if ($i -gt $iMax) {
                  Write-Error -Message "Could not find Successful Provisioning Status of ServicePlan '$PlansToTest' in AzureAD in the last $iMax Seconds" -Category LimitsExceeded -RecommendedAction 'Please verify License has been applied correctly (Get-TeamsResourceAccount); Continue with Set-TeamsResourceAccount' -ErrorAction Stop
                }
                Start-Sleep -Milliseconds 1000
                $i++
              }
              while (-not $(Test-TeamsUserLicense -Identity "$($UserObject.UserPrincipalName)" -ServicePlan MCOEV) )
              $LicenseStatus = $true
            }
            catch {
              $ErrorMessage = "'$($UserObject.UserPrincipalName)' found but not licensed correctly (PhoneSystem) - Object could not be enabled"
              Write-Error -Message $ErrorMessage
              $LicenseStatus = $false
            }
          }
          elseif ( $LicenseObject.PhoneSystemStatus.Contains('Success')) {
            Write-Verbose -Message "'$($UserObject.UserPrincipalName)' found and licensed"
            $LicenseStatus = $true
          }
          elseif ( $LicenseObject.PhoneSystemStatus.Contains('PendingInput')) {
            Write-Verbose -Message "'$($UserObject.UserPrincipalName)' found and licensed (Pending Input)"
            $LicenseStatus = $true
          }
          else {
            $ErrorMessage = "'$($UserObject.UserPrincipalName)' found but not licensed correctly (PhoneSystem)"
            Write-Error -Message $ErrorMessage
            $LicenseStatus = $false
          }
          Write-Verbose -Message "'$($UserObject.UserPrincipalName)' LicenseStatus is $LicenseStatus"
        }
        else {
          Write-Verbose -Message "'$($UserObject.UserPrincipalName)' LicenseStatus is UNCHECKED"
        }

        # Enterprise Voice enablement check (including trying to enable if not)
        if ( $RequireEV.IsPresent ) {
          if ( $UserObject.EnterpriseVoiceEnabled ) {
            Write-Verbose -Message "'$($UserObject.UserPrincipalName)' found and licensed and enabled for EnterpriseVoice" -Verbose
            $EnterpriseVoiceEnabledStatus = $true
          }
          else {
            if ( $(Enable-TeamsUserForEnterpriseVoice -UserObject $UserObject -LicenseObject $LicenseObject -Force) ) {
              Write-Verbose -Message "'$($UserObject.UserPrincipalName)' found and licensed and successfully enabled for EnterpriseVoice" -Verbose
              $EnterpriseVoiceEnabledStatus = $true
            }
            else {
              $ErrorMessage = "'$($UserObject.UserPrincipalName)' found and licensed, but not enabled for EnterpriseVoice (unable to enable user - please verify)!"
              Write-Error -Message $ErrorMessage
              $EnterpriseVoiceEnabledStatus = $false
            }
          }
          Write-Verbose -Message "'$($UserObject.UserPrincipalName)' EnterpriseVoiceEnabled is $EnterpriseVoiceEnabledStatus"
        }
        else {
          Write-Verbose -Message "'$($UserObject.UserPrincipalName)' EnterpriseVoiceEnabled is UNCHECKED"
        }

        # Resource Account Assignment status (required for use of RA as a Target in CQ/AA)
        if ( $CheckAssignment ) {
          $RA = Get-TeamsResourceAccount "$($UserObject.UserPrincipalName)"
          if ( $RA.AssociationStatus -eq 'Success' ) {
            Write-Verbose -Message "'$($UserObject.UserPrincipalName)' found and correctly assigned"
            $AssignmentStatus = $true
          }
          else {
            $ErrorMessage = "'$($UserObject.UserPrincipalName)' found but not assigned to any Call Queue or Auto Attendant"
            Write-Error -Message $ErrorMessage
            $AssignmentStatus = $false
          }
          Write-Verbose -Message "'$($UserObject.UserPrincipalName)' AssignmentStatus is $AssignmentStatus"
        }
        else {
          Write-Verbose -Message "'$($UserObject.UserPrincipalName)' AssignmentStatus is UNCHECKED"
        }

        # Aggregating AssertionStatus
        $AssertionStatus = $(
          $(if ( $CheckLicense ) { $LicenseStatus } else { $true }) -and `
          $(if ( $RequireEV.IsPresent ) { $EnterpriseVoiceEnabledStatus } else { $true }) -and `
          $(if ( $CheckAssignment ) { $AssignmentStatus } else { $true })
        )

        Write-Output $AssertionStatus
      }
      end {

      }
    }

    # Preparing Splatting Object
    $parameters = $null
    $Parameters = @{
      'ErrorAction' = 'Stop'
      'RequireEV'   = $($RequireEV.IsPresent)
    }
  } #begin

  process {
    Write-Verbose -Message "[PROCESS] $($MyInvocation.MyCommand)"
    try {
      switch ($PSCmdlet.ParameterSetName) {
        'UserprincipalName' {
          foreach ($User in $UserPrincipalName) {
            Write-Verbose -Message "[PROCESS] Processing provided UserPrincipalName '$User'"
            try {
              $UserObject = Get-CsOnlineUser -Identity "$User" -WarningAction SilentlyContinue -ErrorAction Stop
              $LicenseObject = Get-AzureAdUserLicense "$User"
            }
            catch {
              Write-Error "'$User' not found" -Category ObjectNotFound
              continue
            }
            AssertTeamsCallableEntity -UserObject $UserObject -LicenseObject $LicenseObject @Parameters
          }
        }
        'Object' {
          Write-Verbose -Message "[PROCESS] Processing provided Objects for '$($UserObject.UserPrincipalName)'"
          if ( -not $LicenseObject.IsPresent ) { $LicenseObject = Get-AzureAdUserLicense "$($UserObject.UserPrincipalName)" }
          AssertTeamsCallableEntity -UserObject $UserObject -LicenseObject $LicenseObject @Parameters
        }
      }
    }
    catch {
      Write-Error -Message $($_.Exception.Message) -ErrorAction $ErrorActionPreference
    }
  } #process

  end {
    Write-Verbose -Message "[END ] $($MyInvocation.MyCommand)"
  } #end
} # Assert-TeamsCalLableEntity