Public/ResourceAccount/New-TeamsResourceAccount.ps1

# Module: Orbit
# Function: ResourceAccount
# Author: David Eberhardt
# Updated: 01-DEC-2020
# Status: Live

#VALIDATE refactor of Removing PhoneNumber and OVP in one go.


function New-TeamsResourceAccount {
  <#
  .SYNOPSIS
    Creates a new Resource Account
  .DESCRIPTION
    Teams Call Queues and Auto Attendants require a resource account.
    It can carry a license and optionally also a phone number.
    This Function was designed to create the ApplicationInstance in AD,
    apply a UsageLocation to the corresponding Graph User,
    license the User and subsequently apply a phone number, all with one Command.
  .PARAMETER UserPrincipalName
    Required. The UPN for the new ResourceAccount. Invalid characters are stripped from the provided string
  .PARAMETER DisplayName
    Optional. The Name it will show up as in Teams. Invalid characters are stripped from the provided string
  .PARAMETER ApplicationType
    Required. CallQueue or AutoAttendant. Determines the association the account can have:
    A resource Account of the type "CallQueue" can only be associated with to a Call Queue
    A resource Account of the type "AutoAttendant" can only be associated with an Auto Attendant
    The type can be switched later (this is supported and worked flawlessly when testing, but not recommended by Microsoft).
  .PARAMETER UsageLocation
    Optional. Required if applying a License directly. Two Digit Country Code of the Location of the entity.
  .PARAMETER License
    Optional. Specifies the License to be assigned. License combination must contain MCOEV or MCOEV_VirtualUser
    Preferred license is the Teams Phone Resource Account license
    Unlicensed Objects can exist, but cannot be assigned a phone number
  .PARAMETER LicenseGroup
    Optional. Specifies the Graph Group Name that will license this Account.
    This group must be configured in Graph to perform the License Assignment
  .PARAMETER OnlineVoiceRoutingPolicy
    Optional. Required for DirectRouting under specific conditions. Assigns an Online Voice Routing Policy to the Account
    If the connected Call Queue or Auto Attendant forwards to PSTN, an Online Voice Routing Policy is required (TDR only).
  .PARAMETER Sync
    Calls Sync-CsOnlineApplicationInstance cmdlet after applying settings synchronizing the application instances
    from Azure Active Directory into Agent Provisioning Service.
  .EXAMPLE
    New-TeamsResourceAccount -UserPrincipalName "Resource Account@TenantName.onmicrosoft.com" -ApplicationType CallQueue -UsageLocation US
 
    Will create a ResourceAccount of the type CallQueue with a Usage Location for 'US'
    User Principal Name will be normalised to: ResourceAccount@TenantName.onmicrosoft.com
    DisplayName will be taken from the User PrincipalName and normalised to "ResourceAccount"
  .EXAMPLE
    New-TeamsResourceAccount -UserPrincipalName "Resource Account@TenantName.onmicrosoft.com" -Displayname "My {ResourceAccount}" -ApplicationType CallQueue -UsageLocation US
 
    Will create a ResourceAccount of the type CallQueue with a Usage Location for 'US'
    User Principal Name will be normalised to: ResourceAccount@TenantName.onmicrosoft.com
    DisplayName will be normalised to "My ResourceAccount"
  .EXAMPLE
    New-TeamsResourceAccount -UserPrincipalName AA-Mainline@TenantName.onmicrosoft.com -Displayname "Mainline" -ApplicationType AutoAttendant -UsageLocation US -License PhoneSystem -PhoneNumber +1555123456
 
    Creates a Resource Account for Auto Attendants with a Usage Location for 'US'
    Applies the specified PhoneSystem License (if available in the Tenant)
    Assigns the Telephone Number if object could be licensed correctly.
  .INPUTS
    System.String
  .OUTPUTS
    System.Object
  .NOTES
    Execution requires User Admin Role in Azure AD
    Assigning the PhoneSystem license has been deactivated as it is an add-on license and cannot be assigned on its own.
  .COMPONENT
    TeamsAutoAttendant
    TeamsCallQueue
  .FUNCTIONALITY
    Creates a resource Account in Graph for use in Teams
  .LINK
    https://github.com/DEberhardt/Orbit/tree/main/docs/Orbit.Teams/New-TeamsResourceAccount.md
  .LINK
    https://github.com/DEberhardt/Orbit/tree/main/docs/about/about_TeamsResourceAccount.md
  .LINK
    https://github.com/DEberhardt/Orbit/tree/main/docs/
  #>


  [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')]
  [Alias('New-TeamsRA')]
  [OutputType([System.Object])]
  param (
    [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName, HelpMessage = 'UPN of the Object to create.')]
    [ValidateScript( {
        If ($script:OrbitRegexUPN.isMatch($_)) { $True } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid UPN'
        } })]
    [string]$UserPrincipalName,

    [Parameter(HelpMessage = 'Display Name for this Object')]
    [string]$DisplayName,

    [Parameter(Mandatory, HelpMessage = 'CallQueue or AutoAttendant')]
    [ValidateSet('CallQueue', 'AutoAttendant', 'CQ', 'AA')]
    [Alias('Type')]
    [string]$ApplicationType,

    [Parameter(HelpMessage = 'Usage Location to assign')]
    [ValidateScript( {
        if ($_ -in $( Get-OrbitAcSbTwoLetterCountryCode @args )) { $True } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid ISO3166-alpha2 Two-Letter CountryCode. Use Intellisense for options'
        } })]
    [string]$UsageLocation,

    [Parameter(HelpMessage = 'License to assign')]
    [ValidateScript( {
        if ($_ -in $( Get-OrbitAcSbGraphLicense @args )) { return $true } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid License. Use Intellisense for options or Get-Microsoft365License (ParameterName)'
        } })]
    [string[]]$License,

    [Parameter(HelpMessage = 'GraphGroup to add this user to')]
    [Alias('Group')]
    [string]$LicenseGroup,

    <# Removed due to convolution - Functionality delegated to SET only
    [Parameter(ValueFromPipelineByPropertyName, HelpMessage = 'Telephone Number to assign')]
    [ValidateScript( {
      if ( $script:OrbitRegexPhoneNumber.isMatch($_) ) { $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,
 
    #>

    [Parameter(ValueFromPipelineByPropertyName, HelpMessage = 'Name of the Online Voice Routing Policy')]
    [Alias('OVP')]
    [ValidateScript( {
        if ($_ -in $( Get-OrbitAcSbVoiceRoutingPolicy @args )) { return $true } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid Policy in the Tenant. Use Intellisense for options'
        } })]
    [string]$OnlineVoiceRoutingPolicy,

    [Parameter(ValueFromPipelineByPropertyName, HelpMessage = 'Synchronizes Resource Account with the Agent Provisioning Service')]
    [switch]$Sync
  ) #param

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

    # Asserting Graph Connection
    if ( -not (Test-GraphConnection) ) { throw 'Connection to Microsoft Graph not established. Please validate connection' }

    # Asserting MicrosoftTeams Connection
    if ( -not (Assert-MicrosoftTeamsConnection) ) { throw 'Connection to Microsoft Teams not established. Please validate connection' }

    # Setting Preference Variables according to Upstream settings
    if (-not $PSBoundParameters['Verbose']) { $VerbosePreference = $PSCmdlet.SessionState.PSVariable.GetValue('VerbosePreference') }
    if (-not $PSBoundParameters['Confirm']) { $ConfirmPreference = $PSCmdlet.SessionState.PSVariable.GetValue('ConfirmPreference') }
    if (-not $PSBoundParameters['WhatIf']) { $WhatIfPreference = $PSCmdlet.SessionState.PSVariable.GetValue('WhatIfPreference') }
    $DebugPreference = if (-not $PSBoundParameters['Debug']) { $PSCmdlet.SessionState.PSVariable.GetValue('DebugPreference') } else { 'Continue' }
    $InformationPreference = if ( $PSBoundParameters['InformationAction']) { $PSCmdlet.SessionState.PSVariable.GetValue('InformationAction') } else { '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

    #region Validating Parameter exclusivity
    # ParameterSetName cannot be used, this is used to determine exclusivity for License & LicenseGroup
    # LicenseGroup & License are mutually exclusive
    if ( $PSBoundParameters['LicenseGroup'] -and $PSBoundParameters['License'] ) {
      Write-Error -Message 'Parameter LicenseGroup and License are mutually exclusive. Please provide either License or LicenseGroup'
      break
    }

    # Usage Location is optional (guessed to be the same as tenant if not provided), but not applied if Group is used
    if ( $PSBoundParameters['LicenseGroup'] ) {
      if ( $PSBoundParameters['UsageLocation'] ) {
        Write-Verbose -Message 'Parameter LicenseGroup - Parameter UsageLocation is not supported and will be omitted!' -Verbose
        $PSBoundParameters.Remove('UsageLocation')
      }
    }
    #endregion

    #region Validating Licenses to be applied result in correct Licensing (contain PhoneSystem)
    $PlansToTest = 'MCOEV_VIRTUALUSER' #, 'MCOEV' #Refactor to validate only on MCOEV_VIRTUALUSER - can be re-instated if need be
    if ( $PSBoundParameters['License'] ) {
      $StatusID0 = 'Verifying input'
      $CurrentOperationID0 = 'Validating Licenses to be applied result in correct Licensing'
      Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
      $IncludesPlan = 0
      foreach ($L in $License) {
        foreach ($PlanToTest in $PlansToTest) {
          $Included = Test-Microsoft365LicenseContainsServicePlan -License "$L" -ServicePlan "$PlanToTest"
          if ($Included) {
            $IncludesPlan++
            Write-Verbose -Message "License '$L' ServicePlan '$PlanToTest' - Included: OK"
          }
          else {
            Write-Verbose -Message "License '$L' ServicePlan '$PlanToTest' - NOT included"
          }
        }
      }
      if ( $IncludesPlan -lt 1 ) {
        Write-Warning -Message "ServicePlan validation - None of the Licenses include any of the required ServicePlans '$PlansToTest' - Account may not be operational!"
      }
    }
    #endregion
  } #begin

  process {
    Write-Verbose -Message "[PROCESS] $($MyInvocation.MyCommand)"
    #region PREPARATION
    $StatusID0 = 'Validating input'
    #region Normalising $UserPrincipalname
    $CurrentOperationID0 = 'Processing UserPrincipalName'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    $UPN = Format-StringForUse -InputString $UserPrincipalName -As UserPrincipalName
    Write-Verbose -Message "UserPrincipalName normalised to: '$UPN'"
    #endregion

    #region Normalising $DisplayName
    $CurrentOperationID0 = 'Processing DisplayName'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    if ($PSBoundParameters['DisplayName']) {
      $Name = Format-StringForUse -InputString $DisplayName -As DisplayName
    }
    else {
      $Name = Format-StringForUse -InputString $($UserPrincipalName.Split('@')[0]) -As DisplayName
    }
    Write-Verbose -Message "DisplayName normalised to: '$Name'"
    #endregion

    #region ApplicationType
    $CurrentOperationID0 = 'Parsing Application Type'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    # Translating $ApplicationType (Name) to ID used by Commands.
    $AppId = GetAppIdFromApplicationType $ApplicationType
    Write-Verbose -Message "'$Name' ApplicationType parsed"
    #endregion

    #region UsageLocation
    $CurrentOperationID0 = 'Parsing Usage Location'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    if ($PSBoundParameters['UsageLocation']) {
      Write-Verbose -Message "'$Name' UsageLocation parsed: Using '$UsageLocation'"
    }
    elseif ( $PSBoundParameters['License'] ) {
      # Querying Tenant Country as basis for Usage Location
      # This is never triggered as UsageLocation is mandatory! Remaining here regardless
      $Tenant = Get-CsTenant -WarningAction SilentlyContinue
      if ($null -ne $Tenant.CountryAbbreviation) {
        $UsageLocation = $Tenant.CountryAbbreviation
        Write-Warning -Message "'$Name' UsageLocation not provided. Defaulting to: $UsageLocation. - Please verify and change if needed!"
      }
      else {
        Write-Error -Message "'$Name' Usage Location not provided and Country not found in the Tenant!" -Category ObjectNotFound -RecommendedAction 'Please run command again and specify -UsageLocation' -ErrorAction Stop
      }
    }
    #endregion
    #endregion


    #region ACTION
    $StatusID0 = $CurrentOperationID0 = ''
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    #region Creating Account
    $ActivityID1 = 'Creating Resource Account'
    try {
      #Trying to create the Resource Account
      Write-Verbose -Message "'$Name' Creating Resource Account with New-CsOnlineApplicationInstance..."
      if ($PSCmdlet.ShouldProcess("$UPN", 'New-CsOnlineApplicationInstance')) {
        $null = (New-CsOnlineApplicationInstance -UserPrincipalName "$UPN" -ApplicationId $AppId -DisplayName $Name -ErrorAction STOP)
        $i = 0
        $iMax = 60
        Write-Information "INFO: Resource Account '$Name' ($ApplicationType) created; Waiting for Graph to write object ($iMax s)"
        $StatusID1 = 'Azure Active Directory is propagating Object. Please wait'
        $CurrentOperationID1 = 'Waiting for Get-GraphUser to return a Result'
        Write-Verbose -Message "$StatusID1 - $CurrentOperationID1"
        do {
          if ($i -gt $iMax) {
            Write-Error -Message "Could not find Object in Graph in the last $iMax Seconds" -Category ObjectNotFound -RecommendedAction 'Please verify Object has been created (UserPrincipalName); Continue with Set-TeamsResourceAccount'
            return
          }
          Write-Progress -Id 1 -ParentId 0 -Activity $ActivityID1 -Status $StatusID1 -CurrentOperation $CurrentOperationID1 -SecondsRemaining $($iMax - $i) -PercentComplete (($i * 100) / $iMax)
          Start-Sleep -Milliseconds 1000
          $i++

          $UserCreated = Test-GraphUser "$UPN"
        }
        while ( -not $UserCreated )
        Write-Progress -Id 1 -Activity $ActivityID1 -Completed

        $ResourceAccountCreated = Get-MgUser -UserId "$UPN" -WarningAction SilentlyContinue
        if ($PSBoundParameters['Debug'] -or $DebugPreference -eq 'Continue') {
          "Function: $($MyInvocation.MyCommand.Name) - Created ResourceAccount:", ($ResourceAccountCreated | Format-Table -AutoSize | Out-String).Trim() | Write-Debug
        }
      }
      else {
        Write-Progress -Id 1 -Activity $ActivityID1 -Completed
        return
      }
    }
    catch {
      # Catching anything
      Write-Progress -Id 1 -Activity $ActivityID1 -Completed
      Write-Error -Message "Resource Account '$Name' - Creation failed: $($_.Exception.Message)" -Exception $_.Exception
      return
    }
    #endregion

    $StatusID0 = 'Applying Settings'
    #region UsageLocation
    if ( $PSBoundParameters['UsageLocation'] ) {
      $CurrentOperationID0 = 'Setting Usage Location'
      Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
      try {
        if ($PSCmdlet.ShouldProcess("$UPN", "Update-MgUser -UsageLocation $UsageLocation")) {
          Update-MgUser -UserId $UPN -UsageLocation $UsageLocation -ErrorAction STOP
          Write-Verbose -Message "'$Name' SUCCESS - Usage Location set to: $UsageLocation"
        }
      }
      catch {
        if ($PSBoundParameters['License']) {
          Write-Error -Message "'$Name' Usage Location could not be set. Please apply manually before applying license" -Category NotSpecified -RecommendedAction 'Apply manually, then run Set-TeamsResourceAccount to apply license and phone number'
        }
        else {
          Write-Warning -Message "'$Name' Usage Location cannot be set. If a license is needed, please assign UsageLocation manually beforehand"
        }
      }
    }
    #endregion

    #region Licensing
    switch ($PSCmdlet.ParameterSetName) {
      'LicenseDirect' {
        $CurrentOperationID0 = 'Processing License assignment - Direct'
        Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
        try {
          if ($PSCmdlet.ShouldProcess("$UPN", "Set-GraphUserLicense -Add $License")) {
            $null = (Set-GraphUserLicense -Identity "$UPN" -Add $License -ErrorAction STOP)
            Write-Information "INFO: Resource Account '$Name' License assignment - '$License' SUCCESS"
            $IsLicensed = $true
          }
        }
        catch {
          Write-Error -Message "'$Name' License assignment failed for '$License' with Exception: '$($_.Exception.Message)'"
        }
      }
      'LicenseGroup' {
        $CurrentOperationID0 = 'Processing License assignment - GraphGroup'
        Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
        try {
          # Querying Group
          $AdGroup = Get-MgGroup -Search "$LicenseGroup"
          $AdGroup = $AdGroup | Where-Object DisplayName -EQ "$LicenseGroup"
          if ( $AdGroup.Count -NE 1 ) {
            throw "Group '$LicenseGroup' - No Unique object determined. Please validate Parameter LicenseGroup"
          }
          New-MgGroupMember -GroupId $AdGroup.ObjectId -DirectoryObjectId $ResourceAccountCreated.ObjectId -ErrorAction Stop
          Write-Information "Group '$GroupName' - Account '$UserPrincipalName' added as a member"
        }
        catch {
          Write-Error "Group '$GroupName' - Account '$UserPrincipalName' - Adding Account failed: $($_.Exception.Message)"
        }
      }
    }
    #endregion


    <# Removed to avoid scope creep/convoluted design. - If this works for SET though, we might be able to re-instate this
    #region Waiting for License Application
    if ($PSBoundParameters['License'] -and $PSBoundParameters['PhoneNumber']) {
      $CurrentOperationID0 = $StatusID0 = ''
      Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
      $i = 0
      $iMax = 600
      Write-Warning -Message "Applying a License may take longer than provisioned for ($($iMax/60) mins) in this Script - If so, please apply PhoneNumber manually with Set-TeamsResourceAccount"
      Write-Verbose -Message "License '$License'- Expecting one of the corresponding ServicePlans '$PlansToTest'"
      $ActivityID1 = 'Checking License propagation as a requirement before applying Phone Number'
      $StatusID1 = 'Azure Active Directory is propagating Object. Please wait'
      $CurrentOperationID1 = 'Waiting for Test-GraphUserLicense to return a positive Result'
      do {
        if ($i -gt $iMax) {
          Write-Error -Message "Could not find Successful Provisioning Status of ServicePlan '$PlansToTest' in Graph in the last $iMax Seconds" -Category LimitsExceeded -RecommendedAction 'Please verify License has been applied correctly (Get-TeamsResourceAccount); Continue with Set-TeamsResourceAccount' -ErrorAction Stop
        }
        Write-Progress -Id 1 -ParentId 0 -Activity $ActivityID1 -Status $StatusID1 -CurrentOperation $CurrentOperationID1 -SecondsRemaining $($iMax - $i) -PercentComplete (($i * 100) / $iMax)
        Start-Sleep -Milliseconds 1000
        $i++
 
        $AllTests = $false
        $AllTests = foreach ($PlanToTest in $PlansToTest) { Test-GraphUserLicense -Identity "$UPN" -ServicePlan "$PlanToTest" }
        $TeamsUserLicenseAssigned = if ( ($AllTests) -contains $true ) { $true } else { $false }
      }
      while (-not $TeamsUserLicenseAssigned)
 
      do {
        if ($i -gt $iMax) {
          Write-Error -Message "Could not find Successful Provisioning Status of ServicePlan '$PlansToTest' in Graph 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-GraphUserLicense -Identity "$($Object.UserPrincipalName)" -ServicePlan MCOEV) )
      Write-Progress -Id 1 -Activity $ActivityID1 -Completed
    }
    #endregion
 
    #region PhoneNumber
    if ($PSBoundParameters['PhoneNumber']) {
      $CurrentOperationID0 = 'Applying Phone Number'
      Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
      # Assigning Telephone Number
      Write-Verbose -Message "'$Name' Processing Phone Number"
      Write-Information 'INFO: Assigning a phone number might fail if the Object is not yet replicated'
      if (-not $IsLicensed) {
        Write-Error -Message 'A Phone Number can only be assigned to licensed objects. Please apply a license before assigning the number. Set-TeamsResourceAccount can be used to do both'
      }
      else {
        # Applying Phone Number with Set-TeamsPhoneNumber
        try {
          $SetTeamsPhoneNumber = @{
            'UserPrincipalName' = "$UPN"
            'PhoneNumber' = "$PhoneNumber"
            'WarningAction' = 'SilentlyContinue'
            'ErrorAction' = 'Stop'
            'Force' = $false
          }
          if ($PSBoundParameters['Debug'] -or $DebugPreference -eq 'Continue') {
            "Function: $($MyInvocation.MyCommand.Name) - Parameters (SetTeamsPhoneNumber)", ($SetTeamsPhoneNumber | Format-Table -AutoSize | Out-String).Trim() | Write-Debug
          }
          Set-TeamsPhoneNumber @SetTeamsPhoneNumber
          $StatusMessage = "Number assigned to $ObjectType`: '$PhoneNumber'"
          Write-Information "SUCCESS: '$UPN' - $CurrentOperationID0`: OK - $StatusMessage"
        }
        catch {
          $ErrorLogMessage = "'$UPN' - $CurrentOperationID0`: Failed: '$($_.Exception.Message)'"
          Write-Error -Message $ErrorLogMessage
        }
      }
    }
    #>


    # Waiting for AAD to write the PhoneNumber so that it may be queried correctly
    $CurrentOperationID0 = 'Waiting for Graph to write Object (2s)'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    Start-Sleep -Seconds 2
    #endregion

    #region OnlineVoiceRoutingPolicy
    if ( $OnlineVoiceRoutingPolicy ) {
      try {
        Grant-CsOnlineVoiceRoutingPolicy -Identity $UPN -PolicyName "$OnlineVoiceRoutingPolicy" -ErrorAction Stop
        Write-Information "SUCCESS: '$Name ($UPN)' Assigning OnlineVoiceRoutingPolicy: OK: '$OnlineVoiceRoutingPolicy'"
      }
      catch {
        $ErrorLogMessage = "User '$Name ($UPN)' Assigning OnlineVoiceRoutingPolicy`: Failed: '$($_.Exception.Message)'"
        Write-Error -Message $ErrorLogMessage
      }
    }
    #endregion
    #endregion


    #region OUTPUT
    $StatusID0 = 'Validation'
    #Creating new PS Object
    try {
      # Data
      $CurrentOperationID0 = 'Querying Object'
      Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
      $ResourceAccount = Get-CsOnlineApplicationInstance -Identity "$UPN" -WarningAction SilentlyContinue -ErrorAction STOP

      $CurrentOperationID0 = 'Querying Object License'
      Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
      $ResourceAccountLicense = Get-GraphUserLicense -Identity "$UPN"

      # readable Application type
      $ResourceAccountApplicationType = GetApplicationTypeFromAppId $ResourceAccount.ApplicationId

      # Resource Account License
      #TODO Refactor (incl. object Output) if no PhoneNumbers are applied - also feed back OVP
      if ($IsLicensed) {
        if ($null -ne $ResourceAccount.PhoneNumber) {
          # Phone Number Type
          $ResourceAccountPhoneNumberType = (Get-TeamsPhoneNumber -PhoneNumber $ResourceAccount.PhoneNumber -WarningAction SilentlyContinue).NumberType
        }
        else {
          $ResourceAccountPhoneNumberType = $null
        }

        # Phone Number is taken from Original Object and should be populated correctly

      }
      else {
        $ResourceAccountPhoneNumberType = $null
        # Phone Number is taken from Original Object and should be empty at this point
      }

      # creating new PS Object (synchronous with Get and Set)
      $ResourceAccountObject = [PSCustomObject][ordered]@{
        PSTypeName               = 'PowerShell.TeamsFunctsions.ResourceAccount'
        UserPrincipalName        = $ResourceAccount.UserPrincipalName
        DisplayName              = $ResourceAccount.DisplayName
        ApplicationType          = $ResourceAccountApplicationType
        UsageLocation            = $UsageLocation
        License                  = $ResourceAccountLicense.Licenses
        PhoneNumberType          = $ResourceAccountPhoneNumberType
        PhoneNumber              = $ResourceAccount.PhoneNumber
        OnlineVoiceRoutingPolicy = $OnlineVoiceRoutingPolicy
      }

      Write-Information "SUCCESS: Resource Account '$($ResourceAccountObject.UserPrincipalName)' created"
      if ($PSBoundParameters['PhoneNumber'] -and $IsLicensed -and $ResourceAccount.PhoneNumber -eq '') {
        Write-Warning -Message 'Object replication pending, Phone Number does not show yet. Run Get-TeamsResourceAccount to verify'
      }

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

      # Synchronisation
      if ( $PSBoundParameters['Sync'] ) {
        Write-Verbose -Message "Switch 'Sync' - Resource Account is synchronised with Agent Provisioning Service"
        $null = Sync-CsOnlineApplicationInstance -ObjectId $ResourceAccount.ObjectId -Force
        Write-Information 'SUCCESS: Synchronising Resource Account with Agent Provisioning Service'
      }
    }
    catch {
      Write-Warning -Message 'Object Output could not be verified. Please verify manually with Get-CsOnlineApplicationInstance'
      if ( $PSBoundParameters['Sync'] ) {
        Write-Verbose -Message 'Synchronisation could not be started. Please trigger again with Set-TeamsResourceAccount or Sync-CsOnlineApplicationInstance directly' -Verbose
      }
    }
    #endregion
  } #process

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

  } #end
} #New-TeamsResourceAccount