Public/ResourceAccount/Get-TeamsResourceAccount.ps1

# Module: TeamsFunctions
# Function: ResourceAccount
# Author: David Eberhardt
# Updated: 01-MAR-2022
# Status: Live




function Get-TeamsResourceAccount {
  <#
  .SYNOPSIS
    Returns Resource Accounts from AzureAD
  .DESCRIPTION
    Returns one or more Resource Accounts based on input.
    This runs Get-CsOnlineApplicationInstance but reformats the Output with friendly names
  .PARAMETER UserPrincipalName
    Default and positional. One or more UserPrincipalNames to be queried.
  .PARAMETER DisplayName
    Optional. Search parameter. Alternative to Find-TeamsResourceAccount
    Use Find-TeamsUserVoiceConfig for more search options
  .PARAMETER ApplicationType
    Optional. Returns all Call Queues or AutoAttendants
  .PARAMETER PhoneNumber
    Optional. Returns all ResourceAccount with a specific string in the PhoneNumber
  .EXAMPLE
    Get-TeamsResourceAccount
 
    Returns all Resource Accounts.
    Depending on size of the Tenant, this might take a while.
  .EXAMPLE
    Get-TeamsResourceAccount -Identity ResourceAccount@TenantName.onmicrosoft.com
 
    Returns the Resource Account with the Identity specified, if found.
  .EXAMPLE
    Get-TeamsResourceAccount -DisplayName "Queue"
 
    Returns all Resource Accounts with "Queue" as part of their Display Name.
    Use Find-TeamsResourceAccount / Find-CsOnlineApplicationInstance for finer search
  .EXAMPLE
    Get-TeamsResourceAccount -ApplicationType AutoAttendant
 
    Returns all Resource Accounts of the specified ApplicationType.
  .EXAMPLE
    Get-TeamsResourceAccount -PhoneNumber +1555123456
 
    Returns the Resource Account with the Phone Number specified, if found.
  .INPUTS
    None
    System.String
  .OUTPUTS
    System.Object
  .NOTES
    Pipeline input possible
    Running the CmdLet without any input might take a while, depending on size of the Tenant.
  .COMPONENT
    TeamsResourceAccount
    TeamsAutoAttendant
    TeamsCallQueue
  .FUNCTIONALITY
    Returns one or more Resource Accounts from the Tenant
  .LINK
    https://github.com/DEberhardt/TeamsFunctions/tree/master/docs/Get-TeamsResourceAccount.md
  .LINK
    https://github.com/DEberhardt/TeamsFunctions/tree/master/docs/about_TeamsResourceAccount.md
  .LINK
    https://github.com/DEberhardt/TeamsFunctions/tree/master/docs/
  #>


  [CmdletBinding(SupportsPaging, DefaultParameterSetName = 'Identity')]
  [Alias('Get-TeamsRA')]
  [OutputType([System.Object])]
  param (
    [Parameter(Position = 0, ParameterSetName = 'Identity', ValueFromPipeline, ValueFromPipelineByPropertyName, HelpMessage = 'User Principal Name of the Object.')]
    [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(ParameterSetName = 'DisplayName', ValueFromPipelineByPropertyName, HelpMessage = 'Searches for AzureAD Object with this Name')]
    [ValidateLength(3, 255)]
    [string]$DisplayName,

    [Parameter(ParameterSetName = 'AppType', HelpMessage = 'Limits search to specific Types: CallQueue or AutoAttendant')]
    [ValidateSet('CallQueue', 'AutoAttendant', 'CQ', 'AA')]
    [Alias('Type')]
    [string]$ApplicationType,

    [Parameter(ParameterSetName = 'Number', ValueFromPipelineByPropertyName, HelpMessage = 'Telephone Number of the Object')]
    [ValidateScript( {
        If ( $_ -match $script:TFMatchNumber ) { $True } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid phone number. E.164 format expected, min 4 digits, but multiple formats accepted. Extensions will be stripped'
        } })]
    [Alias('Tel', 'Number', 'TelephoneNumber')]
    [string]$PhoneNumber
  ) #param

  begin {
    Show-FunctionStatus -Level Live
    Write-Verbose -Message "[BEGIN ] $($MyInvocation.MyCommand)"
    Write-Verbose -Message "Need help? Online: $global:TeamsFunctionsHelpURLBase$($MyInvocation.MyCommand)`.md"

    # Asserting AzureAD Connection
    if ( -not $script:TFPSSA) { $script:TFPSSA = Assert-AzureADConnection; if ( -not $script:TFPSSA ) { break } }

    # Asserting MicrosoftTeams Connection
    if ( -not (Assert-MicrosoftTeamsConnection) ) { break }

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

    #Initialising Counters
    $private:StepsID0, $private:StepsID1 = Get-WriteBetterProgressSteps -Code $($MyInvocation.MyCommand.Definition) -MaxId 1
    $private:ActivityID0 = $($MyInvocation.MyCommand.Name)
    [int] $private:CountID0 = [int] $private:CountID1 = 1

    #Initialising splatting Object
    $GetCsApplicationInstanceParameters = @{
      WarningAction = 'SilentlyContinue'
      ErrorAction   = 'SilentlyContinue'
    }
    if ( $PSBoundParameters.ContainsKey('First') ) { $GetCsApplicationInstanceParameters.First = $PSCmdlet.PagingParameters.First }
    if ( $PSBoundParameters.ContainsKey('Skip') ) { $GetCsApplicationInstanceParameters.Skip = $PSCmdlet.PagingParameters.Skip }
    if ( $PSBoundParameters.ContainsKey('IncludeTotalCount') ) { $GetCsApplicationInstanceParameters.IncludeTotalCount = $PSCmdlet.PagingParameters.IncludeTotalCount }

    #Worker Function
    function GetResourceAccounts {
      [CmdletBinding()]
      [OutputType([System.Object])]
      param(
        [Parameter(Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName, HelpMessage = 'Resource Account object')]
        [AllowNull()]
        [object]$ResourceAccount
      ) #param

      begin {
        $Stack = Get-PSCallStack
      }
      process {
        [int] $private:CountID0 = 1
        $ActivityID0 = "$($Stack[1].FunctionName)"
        $StatusID0 = "'$($ResourceAccount.UserPrincipalName)'"
        $CurrentOperationID0 = 'Parsing ApplicationType'
        Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
        # readable Application type
        if ($PSBoundParameters.ContainsKey('ApplicationType')) {
          $ResourceAccountApplicationType = $ApplicationType
        }
        else {
          $ResourceAccountApplicationType = GetApplicationTypeFromAppId $ResourceAccount.ApplicationId
        }

        # Parsing CsOnlineUser
        $CurrentOperationID0 = 'Parsing Online Voice Routing Policy'
        Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
        try {
          $CsOnlineUser = $null
          $CsOnlineUser = Get-CsOnlineUser -Identity "$($ResourceAccount.UserPrincipalName)" -WarningAction SilentlyContinue -ErrorAction Stop
        }
        catch {
          Write-Verbose -Message "$StatusID0 - Parsing: Online Voice Routing Policy FAILED. CsOnlineUser not found" -Verbose
        }


        # Parsing TeamsUserLicense
        $CurrentOperationID0 = 'Parsing License Assignments'
        Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
        try {
          $ResourceAccountLicenseDetail = Get-AzureADUserLicenseDetail -ObjectId "$($ResourceAccount.UserPrincipalName)" -WarningAction SilentlyContinue -ErrorAction STOP
          if ( $ResourceAccountLicenseDetail ) {
            $ResourceAccountLicense = Get-UserLicensesFromLicenseDetailObject -UserLicenseDetail $ResourceAccountLicenseDetail @args
          }
        }
        catch {
          Write-Verbose -Message "$StatusID0 - License Query: No licenses assigned to this account"
        }

        # Phone Number Type
        $CurrentOperationID0 = 'Parsing NumberType'
        Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
        if ($null -ne $ResourceAccount.PhoneNumber) {
          try {
            $TeamsPhoneNumberObject = Get-CsPhoneNumberAssignment -TelephoneNumber $($ResourceAccount.PhoneNumber | Format-StringForUse -As Number) -ErrorAction Stop
            $ResourceAccountPhoneNumberType = switch ($TeamsPhoneNumberObject.PstnPartnerName) {
              'Microsoft' { 'CallingPlan' }
              '' { 'DirectRouting' }
              Default { 'OperatorConnect' }
            }
          }
          catch {
            Write-Verbose -Message "$StatusID0 - Phone Number Query: No phone number found with Get-CsPhoneNumberAssignment"
          }
        }

        # Calling Line Identity
        $CurrentOperationID0 = 'Parsing Calling Line Identity'
        Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
        try {
          $CallingLineIdentity = Get-TeamsCLI -Identity $ResourceAccount.UserPrincipalName -ErrorAction Stop
        }
        catch {
          Write-Verbose -Message "$StatusID0 - Calling Line Identity: No CLI found with Get-TeamsCLI"
        }

        # Associations
        $CurrentOperationID0 = 'Parsing Association'
        Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
        $Association = Get-CsOnlineApplicationInstanceAssociation -Identity $ResourceAccount.ObjectId -WarningAction SilentlyContinue -ErrorAction SilentlyContinue
        if ( $Association ) {
          $AssociationObject = switch ($Association.ConfigurationType) {
            'CallQueue' { Get-CsCallQueue -Identity $Association.ConfigurationId -WarningAction SilentlyContinue -ErrorAction SilentlyContinue }
            'AutoAttendant' { Get-CsAutoAttendant -Identity $Association.ConfigurationId -WarningAction SilentlyContinue -ErrorAction SilentlyContinue }
          }
          $AssociationStatus = Get-CsOnlineApplicationInstanceAssociationStatus -Identity $ResourceAccount.ObjectId -WarningAction SilentlyContinue -ErrorAction SilentlyContinue
        }
        else {
          Write-Verbose -Message "$StatusID0 - Association: No Association found with Get-CsOnlineApplicationInstanceAssociation"
          $AssociationObject = $null
          $AssociationStatus = $null
        }

        # creating new PS Object (synchronous with Get and Set)
        $ResourceAccountObject = [PSCustomObject][ordered]@{
          PSTypeName               = 'PowerShell.TeamsFunctsions.ResourceAccount'
          ObjectId                 = $ResourceAccount.ObjectId
          UserPrincipalName        = $ResourceAccount.UserPrincipalName
          DisplayName              = $ResourceAccount.DisplayName
          ApplicationType          = $ResourceAccountApplicationType
          InterpretedUserType      = $CsOnlineUser.InterpretedUserType
          UsageLocation            = $CsOnlineUser.UsageLocation
          License                  = $($ResourceAccountLicense.ProductName -join ',')
          PhoneNumberType          = $ResourceAccountPhoneNumberType
          PhoneNumber              = $ResourceAccount.PhoneNumber
          OnlineVoiceRoutingPolicy = $CsOnlineUser.OnlineVoiceRoutingPolicy
          CallingLineIdentity      = $CallingLineIdentity.Description
          AssociatedTo             = $AssociationObject.Name
          AssociatedAs             = $Association.ConfigurationType
          AssociationStatus        = $AssociationStatus.Status
        }

        # Output
        Write-Progress -Id 0 -Activity $ActivityID0 -Completed
        Write-Output $ResourceAccountObject
      }
      end {

      }
    }
  } #begin

  process {
    Write-Verbose -Message "[PROCESS] $($MyInvocation.MyCommand)"
    [int] $private:CountID0 = 1
    $ResourceAccounts = $null

    $StatusID0 = 'Information Gathering'
    #region Data gathering
    $CurrentOperationID0 = 'Querying Resource Accounts'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    try {
      if ($PSBoundParameters.ContainsKey('UserPrincipalName')) {
        # Default Parameterset
        [System.Collections.Generic.List[object]]$ResourceAccounts = @()
        foreach ($I in $UserPrincipalName) {
          $RA = $null
          Write-Verbose -Message "Querying Resource Account with UserPrincipalName '$I'"
          $RA = Get-CsOnlineApplicationInstance -Identity "$I" @GetCsApplicationInstanceParameters
          if ($RA) {
            [void]$ResourceAccounts.Add($RA)
          }
          else {
            Write-Verbose -Message "No Resource Account found with UserPrincipalName '$I'"
          }
        }
      }
      elseif ($PSBoundParameters.ContainsKey('DisplayName')) {
        # Minimum Character length is 3
        Write-Verbose -Message "DisplayName - Searching for Accounts with DisplayName '$DisplayName'"
        $ResourceAccounts = Get-CsOnlineApplicationInstance @GetCsApplicationInstanceParameters | Where-Object -Property DisplayName -Like -Value "*$DisplayName*"
      }
      elseif ($PSBoundParameters.ContainsKey('ApplicationType')) {
        Write-Verbose -Message "ApplicationType - Searching for Accounts with ApplicationType '$ApplicationType'"
        $AppId = GetAppIdFromApplicationType $ApplicationType
        $ResourceAccounts = Get-CsOnlineApplicationInstance @GetCsApplicationInstanceParameters | Where-Object -Property ApplicationId -EQ -Value $AppId
      }
      elseif ($PSBoundParameters.ContainsKey('PhoneNumber')) {
        $SearchString = Format-StringForUse "$($PhoneNumber.split(';')[0].split('x')[0])" -SpecialChars 'telx:+() -'
        Write-Verbose -Message "PhoneNumber - Searching for normalised PhoneNumber '$SearchString'"
        $ResourceAccounts = Get-CsOnlineApplicationInstance @GetCsApplicationInstanceParameters | Where-Object -Property PhoneNumber -Like -Value "*$SearchString*"
      }
      else {
        Write-Information 'INFO: Resource Account: Listing UserPrincipalName only. To query individual items, please provide Identity'
        Get-CsOnlineApplicationInstance @GetCsApplicationInstanceParameters | Select-Object UserPrincipalName
        return
      }
    }
    catch {
      if ($_.Exception.Message.Contains('RBAC')) {
        Write-Warning -Message 'AzureAd Admin Roles are not assigned, activated or have timed out. Please check your Administrative Roles'
      }
      Write-Error "$($_.Exception.Message)"
      Write-Information "INFO: Resource Account '$I' - Not found!"
    }

    # Stop script if no data has been determined
    if ($ResourceAccounts.Count -eq 0) {
      Write-Verbose -Message 'No Data found.'
      return
    }
    #endregion

    # Output
    Write-Verbose -Message "[PROCESS] Processing found Resource Accounts: $($ResourceAccounts.Count)"
    foreach ($ResourceAccount in $ResourceAccounts) {
      Write-Output (GetResourceAccounts -ResourceAccount $ResourceAccount @Args)
    }
  } #process

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

  } #end
} #Get-TeamsResourceAccount