Public/UserManagement/VoiceConfig/Set-TeamsUserEmergencyConfiguration.ps1

# Module: TeamsFunctions
# Function: VoiceConfig
# Author: David Eberhardt
# Updated: 24-MAY-2021
# Status: RC

#TODO Validate application of $Null - emptying the setting (currently not possible!)


function Set-TeamsUserEmergencyConfiguration {
  <#
  .SYNOPSIS
    Changes settings for a Common Area Phone
  .DESCRIPTION
    Applies settings relevant to a Common Area Phone.
    This includes DisplayName, UsageLocation, License, IP Phone Policy, Calling Policy and Call Park Policy can be applied.
  .PARAMETER UserPrincipalName
    Required for Parameterset UserPrincipalName. UserPrincipalName of a CsOnlineUser Object to be provisioned.
  .PARAMETER Object
    Required for Parameterset Object. CsOnlineUser Object passed to the function to reduce query time.
  .PARAMETER TeamsEmergencyCallingPolicy
    Optional. Adds a Teams Emergency Calling Policy to the User
  .PARAMETER TeamsEmergencyCallRoutingPolicy
    Optional. Adds a Teams Emergency Call Routing Policy to the User
  .PARAMETER TeamsEmergencyAddress
    Optional. Adds an Emergency Address to the User
  .PARAMETER PassThru
    Optional. Displays the Object after execution.
  .PARAMETER WriteErrorLog
    If Errors are encountered, writes log to C:\Temp
  .EXAMPLE
    Set-TeamsUserEmergencyConfiguration -UserPrincipalName User@Domain.com -TeamsEmergencyCallingPolicy US
 
    Changes the Object User@Domain.com. Applies the Emergency Calling Policy US to the User.
  .EXAMPLE
    Set-TeamsUserEmergencyConfiguration -UserPrincipalName User@Domain.com -TeamsEmergencyCallRoutingPolicy USMAIN
 
    Changes the Object User@Domain.com. Applies the Emergency Call Routing Policy USMAIN to the User.
  .EXAMPLE
    Set-TeamsUserEmergencyConfiguration -UserPrincipalName User@Domain.com -TeamsEmergencyAddress "US Main office"
 
    Changes the Object User@Domain.com. Applies the Location with the Description "US Main office" to the User.
  .EXAMPLE
    Set-TeamsUserEmergencyConfiguration -UserPrincipalName "User@Domain.com" -TeamsEmergencyCallingPolicy US -TeamsEmergencyCallRoutingPolicy USMAIN -TeamsEmergencyAddress "US Main office" -PassThru
 
    Applies Emergency Calling Policy, Emergency Call Routing Policy and Emergency Address (Location) to the User
    Displays the Common Area Phone Object afterwards
  .EXAMPLE
    Set-TeamsUserEmergencyConfiguration -UserPrincipalName "User@Domain.com" -TeamsEmergencyCallingPolicy US -WriteErrorLog
 
    Applies Emergency Calling Policy to the User
    If Errors are encountered, they are written to C:\Temp as well as on screen
  .INPUTS
    System.String
  .OUTPUTS
    System.Void - Default Behaviour
    System.Object - With Switch PassThru
    System.File - With Switch WriteErrorLog
  .NOTES
    Execution requires Teams Communication Admin Role (or higher) in Azure AD
    This CmdLet deliberately does not apply a Phone Number to the Object. To do so, please run Set-TeamsPhoneNumber
    or Set-TeamsUserVoiceConfig for a full Voice Configuration apply a Calling Plan or Online Voice Routing Policy
    a Phone Number and optionally a Tenant Dial Plan.
    This Script only covers relevant user-specific elements for Emergency calling configuration themselves.
  .COMPONENT
    UserManagement
  .FUNCTIONALITY
    Changes a Users Emergency Calling Configuration in Teams
  .LINK
    https://github.com/DEberhardt/TeamsFunctions/tree/main/docs/Set-TeamsUserEmergencyConfiguration.md
  .LINK
    https://github.com/DEberhardt/TeamsFunctions/tree/main/docs/about_VoiceConfiguration.md
  .LINK
    https://github.com/DEberhardt/TeamsFunctions/tree/main/docs/about_UserManagement.md
  .LINK
    https://github.com/DEberhardt/TeamsFunctions/tree/main/docs/
  #>


  [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium', DefaultParameterSetName = 'UserPrincipalName')]
  [Alias('Set-TeamsEMS')]
  [OutputType([System.Void])]
  param (
    [Parameter(Mandatory, Position = 0, ParameterSetName = 'Object', ValueFromPipeline)]
    [Object[]]$Object,

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

    [Parameter(HelpMessage = 'Teams Emergency Calling Policy')]
    [AllowNull()]
    [AllowEmptyString()]
    [ValidateScript( {
        if ($null -eq $_ -or $_ -eq '' -or $_ -in $(&$global:TfAcSbEmergencyCallingPolicy)) { return $true } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid Policy in the Tenant. Use Intellisense for options'
        } })]
    [ArgumentCompleter({ &$global:TfAcSbEmergencyCallingPolicy })]
    [string]$TeamsEmergencyCallingPolicy,

    [Parameter(HelpMessage = 'Teams Emergency CallRouting Policy')]
    [AllowNull()]
    [AllowEmptyString()]
    [ValidateScript( {
        if ($null -eq $_ -or $_ -eq '' -or $_ -in $(&$global:TfAcSbEmergencyCallRoutingPolicy)) { return $true } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid Policy in the Tenant. Use Intellisense for options'
        } })]
    [ArgumentCompleter({ &$global:TfAcSbEmergencyCallRoutingPolicy })]
    [string]$TeamsEmergencyCallRoutingPolicy,

    [Parameter(HelpMessage = 'Teams Emergency Address - Location')]
    [string]$TeamsEmergencyAddress,

    [Parameter(HelpMessage = 'No output is written by default, Switch PassThru will return changed object')]
    [switch]$PassThru,

    [Parameter(HelpMessage = 'Writes a Log File to C:\Temp')]
    [switch]$WriteErrorLog

  ) #param

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

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

    # Worker function to apply settings once accounts have been ascertained
    function SetTeamsUserEMSConfig {
      [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
      param (
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, HelpMessage = 'CsOnlineUser Object')]
        [Object[]]$UserObject,

        [Parameter(HelpMessage = 'Teams Emergency Calling Policy')]
        [string]$WFTeamsEmergencyCallingPolicy,

        [Parameter(HelpMessage = 'Teams Emergency CallRouting Policy')]
        [string]$WFTeamsEmergencyCallRoutingPolicy,

        [Parameter(HelpMessage = 'Teams Emergency Address - Location')]
        [string]$WFTeamsEmergencyAddress,

        [Parameter(HelpMessage = 'No output is written by default, Switch PassThru will return changed object')]
        [switch]$PassThru
      )

      begin {
        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' }

        #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

      } #begin

      process {
        Write-Verbose -Message "[PROCESS] $($MyInvocation.MyCommand)"
        $StatusID0 = 'Applying Users Emergency Calling and Call Routing Confugration'
        #region ACTION
        #region Teams Emergency Calling Policy
        $ActivityID0 = 'Teams Emergency Calling Policy'
        $CurrentOperationID0 = "Processing $ActivityID0"
        Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
        if ($PSBoundParameters.ContainsKey('WFTeamsEmergencyCallingPolicy')) {
          try {
            Grant-CsTeamsEmergencyCallingPolicy -Identity $UserObject.Identity -PolicyName $WFTeamsEmergencyCallingPolicy -ErrorAction Stop
          }
          catch {
            $ErrorLog = $_.Exception.Message
            Write-Error -Message $ErrorLog
            if ( $WriteErrorLog.IsPresent ) {
              Write-TFErrorLog -ErrorLog $ErrorLog -Artifact $UserObject.UserPrincipalName
            }
          }
        }
        elseif ( $UserObject.TeamsEmergencyCallingPolicy ) {
          Write-Verbose -Message "Object '$($UserObject.UserPrincipalName)' - $ActivityID0 '$($UserObject.TeamsEmergencyCallingPolicy)' present"
        }
        else {
          Write-Verbose -Message "Object '$($UserObject.UserPrincipalName)' - $ActivityID0 not assigned - Dynamic configuration may be present (undetermined)"
        }
        #endregion

        #region Teams Emergency Call Routing Policy
        $ActivityID0 = 'Teams Emergency Call Routing Policy'
        $CurrentOperationID0 = "Processing $ActivityID0"
        Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
        if ($PSBoundParameters.ContainsKey('WFTeamsEmergencyCallRoutingPolicy')) {
          try {
            Grant-CsTeamsEmergencyCallRoutingPolicy -Identity $UserObject.Identity -PolicyName $WFTeamsEmergencyCallRoutingPolicy -ErrorAction Stop
          }
          catch {
            $ErrorLog = $_.Exception.Message
            Write-Error -Message $ErrorLog
            if ( $WriteErrorLog.IsPresent ) {
              Write-TFErrorLog -ErrorLog $ErrorLog -Artifact $UserObject.UserPrincipalName
            }
          }
        }
        elseif ( $UserObject.TeamsEmergencyCallRoutingPolicy ) {
          Write-Verbose -Message "Object '$($UserObject.UserPrincipalName)' - $ActivityID0 '$($UserObject.TeamsEmergencyCallRoutingPolicy)' present"
        }
        else {
          Write-Verbose -Message "Object '$($UserObject.UserPrincipalName)' - $ActivityID0 not assigned - Dynamic configuration may be present (undetermined)"
        }
        #endregion

        #region Teams Emergency Address
        $ActivityID0 = 'Teams Emergency Address'
        $CurrentOperationID0 = "Processing $ActivityID0"
        Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
        if ($PSBoundParameters.ContainsKey('WFTeamsEmergencyAddress')) {
          # This calls the TeamsFunctions Cmdlet
          try {
            Grant-TeamsEmergencyAddress -Identity $UserObject.Identity -PolicyName $WFTeamsEmergencyAddress -ErrorAction Stop
          }
          catch {
            $ErrorLog = $_.Exception.Message
            Write-Error -Message $ErrorLog
            if ( $WriteErrorLog.IsPresent ) {
              Write-TFErrorLog -ErrorLog $ErrorLog -Artifact $UserObject.UserPrincipalName
            }
          }
        }
        else {
          Write-Verbose -Message "Object '$($UserObject.UserPrincipalName)' - $ActivityID0 not queried. To gain feedback, please run Get-TeamsUserVoiceConfig with DiagnosticLevel 1 or higher"
        }
        #endregion
        #endregion

        #region OUTPUT
        $CurrentOperationID0 = 'Validation & Output'
        Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
        $TeamsUVCObject = $null
        if ( $PassThru ) {
          $TeamsUVCObject = Get-TeamsUserVoiceConfig -UserPrincipalName "$($UserObject.UserPrincipalName)" -DiagnosticLevel 1 -WarningAction SilentlyContinue
        }
        Write-Progress -Id 0 -Activity $ActivityID0 -Completed
        Write-Output $TeamsUVCObject | Select-Object UserPrincipalName, Identity, InterpretedUserType, TeamsEmergencyCallingPolicy, TeamsEmergencyCallRoutingPolicy, TeamsEmergencyAddress
        #endregion

      } #process

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

    #Preparing Splatting Parameter
    $parameters = $null
    $Parameters += @{ 'ErrorAction' = 'STOP' }

    #TODO Check whether this can be passed with $Args (removing the UPN or Object respectively!)
    #BODGE Check for ALlowNULL and configuration with a Value of NULL or $NULL
    if ( $PSBoundParameters.ContainsKey('TeamsEmergencyCallingPolicy') ) { $Parameters += @{ 'WFTeamsEmergencyCallingPolicy' = $TeamsEmergencyCallingPolicy } }
    if ( $PSBoundParameters.ContainsKey('TeamsEmergencyCallRoutingPolicy') ) { $Parameters += @{ 'WFTeamsEmergencyCallRoutingPolicy' = $TeamsEmergencyCallRoutingPolicy } }
    if ( $PSBoundParameters.ContainsKey('TeamsEmergencyAddress') ) { $Parameters += @{ 'WFTeamsEmergencyAddress' = $TeamsEmergencyAddress } }
    if ( $PassThru.IsPresent ) { $Parameters += @{ 'PassThru' = $PassThru } }
  } #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 {
              $CsUser = Get-CsOnlineUser -Identity "$User" -WarningAction SilentlyContinue -ErrorAction Stop
            }
            catch {
              $ErrorLog = "'$User' not found"
              Write-Error -Message $ErrorLog -Category ObjectNotFound
              if ( $WriteErrorLog.IsPresent ) {
                Write-TFErrorLog -ErrorLog $ErrorLog -Artifact $User
              }
              continue
            }
            if ($Force -or $PSCmdlet.ShouldProcess("$($CsUser.UserPrincipalName)", 'Set Emergency Configuration')) {
              SetTeamsUserEMSConfig -UserObject $CsUser @Parameters
            }
          }
        }
        'Object' {
          foreach ($O in $Object) {
            Write-Verbose -Message "[PROCESS] Processing provided CsOnlineUser Object for '$($O.UserPrincipalName)'"
            if ($Force -or $PSCmdlet.ShouldProcess("$($CsUser.UserPrincipalName)", 'Set Emergency Configuration')) {
              SetTeamsUserEMSConfig -UserObject $O @Parameters
            }
          }
        }
      }
    }
    catch {
      Write-Error -Message $($_.Exception.Message) -ErrorAction $ErrorActionPreference
    }
  } #process

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