UcsSip.psm1

<## Phone Cmdlets ##>

Function Get-UcsSipPhoneInfo
{
  <#
      .SYNOPSIS
      Collects as much information as possible about the requested Polycom phone via the SIP protocol.
 
      .DESCRIPTION
      Performs an OPTIONS request against the requested IP address, then returns an object with only the properties which were returned by the phone. Currently supports Label (Display Name), SIP address, LineUri, Firmware Version, and Model.
 
      .PARAMETER IPv4Address
      The IP address of the phone, such as 192.168.1.25.
 
      .EXAMPLE
      Get-UcsSipPhoneInfo -IPv4Address 192.168.1.25
      Returns information for the requested phone.
 
      .NOTES
      The model returned by this function may be different than the model returned by other APIs.
  #>



  Param([Parameter(Mandatory,HelpMessage = '127.0.0.1',ValueFromPipelineByPropertyName,ValueFromPipeline)][ValidatePattern('^([0-2]?[0-9]{1,2}\.){3}([0-2]?[0-9]{1,2})$')][String[]]$IPv4Address,
  [Switch]$IncludeUnreachablePhones)

  BEGIN {
    $AllResult = New-Object -TypeName System.Collections.ArrayList
  } PROCESS {
    Foreach($ThisIPv4Address in $IPv4Address)
    {
      Try
      {
        $ThisResponse = Invoke-UcsSipRequest -IPv4Address $ThisIPv4Address -CSeq 1 -Method OPTIONS -ErrorAction Stop
      }
      Catch
      {
        Write-Error -Message "Unable to get results from $ThisIPv4Address." -Category ConnectionError
      }


      <# Method to the madness description:
          We enumerate all the properties (Powershell categorizes them as NoteProperties), then iterate through each.
          Using a switch, we use special handling for certain properties, specifically User-Agent and P-Preferred-Identity.
          We then add all the data we collect into an object for reporting to the user.
          Most of the properties are from the SIP response itself, though we compute RegistrationStatus from the
      #>

      Try
      {
        $AllProperties = ($ThisResponse |
          Get-Member -ErrorAction Stop |
          Where-Object -Property MemberType -EQ -Value 'NoteProperty' |
        Select-Object -ExpandProperty Name)
      }
      Catch
      {
        Write-Error -Message "No properties available for $ThisIPv4Address"
        Continue
      }
      $TheseProperties = New-Object -TypeName PSObject
      $Registered = $false
      Foreach($Property in $AllProperties)
      {
        $Value = $ThisResponse.$Property
        Switch($Property) {
          'User-Agent'
          {
            #This line looks like this from a Polycom VVX 310: Polycom/5.5.2.8571 PolycomVVX-VVX_310-UA/5.5.2.8571
            #Or like this from a RealPresence Trio: Polycom/5.4.3.2007 PolycomRealPresenceTrio-Trio_8800-UA/5.4.3.2007
            #Older firmware (5.3) looks a little different: PolycomVVX-VVX_310-UA/5.3.0.12768
            #Sample from 4.1.4: PolycomVVX-VVX_310-UA/4.1.4.7430_0004f2abcdef
            #This gives us version and model info.
            if($Value -like 'Polycom*')
            {
              $Matches = $null
              $Version = $null
              $null = $Value -match '(?<=/)\d+\.\d+\.\d+\.\d+(?=[_\s])' #Version is preceded with a forward slash and followed by a space or an underscore.
              #If sec.tagSerialNo is set to 1, this will return the MAC address as well.

              Try
              {
                $Version = $Matches[0]
              }
              Catch
              {
                Write-Warning "Version string was in unexpected format: $Value"
              }

              $Matches = $null
              $Model = $null
              $null = $Value -match '(?<=\s?Polycom\w+-)[^_]+_[^-]+' #Polycom's format seems to be PolycomMODELNAME-MODELNAME_MODELNUMBER-UA.
              $Model = $Matches[0]
              $Model = $Model.Replace('_',' ')
              if($Model -like '*Trio*')
              {
                #Ugly workaround to get the Trio to match names properly.
                $Model = ('RealPresence {0}' -f $Model)
              }

              Try
              {
                $Matches = $null
                $MacAddress = $null
                $null = $Value -match '(?<=_)[a-f0-9]{12}'
                $MacAddress = $Matches[0]
              }
              Catch
              {
                Write-Debug -Message "Couldn't get MAC address for $ThisIPv4Address."
              }

              $TheseProperties | Add-Member -MemberType NoteProperty -Name Model -Value $Model
              $TheseProperties | Add-Member -MemberType NoteProperty -Name FirmwareRelease -Value $Version


              if($null -ne $MacAddress)
              {
                $TheseProperties | Add-Member -MemberType NoteProperty -Name MacAddress -Value $MacAddress
              }
            }
          }
          'P-Preferred-Identity'
          {
            Write-Debug -Message 'Getting identity info'
            $Registered = $true
            if($Value -match "^`".+`" <sip:.+>(,<tel:.+>)?")
            {
              Write-Debug -Message 'Identity info in parseable format.'
              #Actual value looks like: "John Smith" <sip:john@smith.com>,<tel:+15555555555;ext=55555> in Lync base profile when signed in.
              #If a user doesn't have a LineURI, it'll look like: "John Smith" <sip:john@smith.com>
              $Matches = $null
              $null = $Value -match 'sip:.+@.+\.[^>]+(?=>)'
              $SipAddress = $Matches[0]
              $Matches = $null
              $null = $Value -match "^`"[^`"]+`"" #Find the display name.
              $DisplayName = $Matches[0].Trim('"')

              $LineUri = ''
              if($Value -match "^`".+`" <sip:.+>,<tel:.+>")
              {
                $Matches = $null
                $null = $Value -match '(?<=<)tel:[^>]+(?=>)'
                $LineUri = $Matches[0]
              }

              $TheseProperties | Add-Member -MemberType NoteProperty -Name SipAddress -Value $SipAddress
              $TheseProperties | Add-Member -MemberType NoteProperty -Name Label -Value $DisplayName
              $TheseProperties | Add-Member -MemberType NoteProperty -Name LineUri -Value $LineUri
            }
          }
          'Authorization'
          {
            if($Value -like '*targetname=*')
            {
              $Matches = $null
              $null = $Value -match "(?<=targetname=`")[^`"]+(?=`")"
              $Server = $Matches[0]

              $TheseProperties | Add-Member -MemberType NoteProperty -Name Server -Value $Server
            }
          } default
          {
            Write-Debug -Message "$Property`: This SIP property is not currently supported. Value was $Value"
          }
        }
      }

      if(($TheseProperties | Get-Member -MemberType NoteProperty | Measure-Object | Select-Object -ExpandProperty Count) -eq 0 -and $IncludeUnreachablePhones -eq $true)
      {
        $TheseProperties | Add-Member -MemberType NoteProperty -Name Registered -Value $Registered
      }

      if($null -ne $TheseProperties)
      {
        $TheseProperties | Add-Member -MemberType NoteProperty -Name IPv4Address -Value $ThisIPv4Address
        $null = $AllResult.Add($TheseProperties)
      }
    }
  } END {
    Return $AllResult
  }
}

Function Restart-UcsSipPhone
{
  <#
      .SYNOPSIS
      Requests a restart of the specified phone or phones.
 
      .DESCRIPTION
      Sends a NOTIFY message with the event "check-sync." This invokes a restart on properly configured Polycom phones.
 
      .PARAMETER IPv4Address
      The IP address of the target phone.
 
      .PARAMETER PassThru
      Returns the SIP response message.
 
      .NOTES
      voIpProt.SIP.specialEvent.checkSync.alwaysReboot must be set to 1 for this to function.
 
      .LINK
      http://community.polycom.com/t5/VoIP/FAQ-Reboot-the-Phone-remotely-or-via-the-Web-Interface/td-p/4239
  #>



  [CmdletBinding(SupportsShouldProcess,ConfirmImpact = 'High')]
  Param([Parameter(Mandatory,HelpMessage = '127.0.0.1',ValueFromPipelineByPropertyName,ValueFromPipeline)][ValidatePattern('^([0-2]?[0-9]{1,2}\.){3}([0-2]?[0-9]{1,2})$')][String[]]$IPv4Address,
  [Switch]$PassThru)

  BEGIN {
    $ResultObject = New-Object -TypeName System.Collections.ArrayList
  } PROCESS {
    Foreach ($ThisIPv4Address in $IPv4Address)
    {
      if($PSCmdlet.ShouldProcess(('{0}' -f $ThisIPv4Address)))
      {
        $null = $ResultObject.Add((Invoke-UcsSipRequest -IPv4Address $ThisIPv4Address -Method NOTIFY -Event 'check-sync' -ErrorAction Stop))
      }
    }
  } END {
    if($PassThru -eq $true)
    {
      Return $ResultObject
    }
  }
}

Function Test-UcsSipModule {
  [CmdletBinding(SupportsShouldProcess,ConfirmImpact = 'High')]
  Param([Parameter(Mandatory,HelpMessage = '127.0.0.1',ValueFromPipelineByPropertyName,ValueFromPipeline)][ValidatePattern('^([0-2]?[0-9]{1,2}\.){3}([0-2]?[0-9]{1,2})$')][String[]]$IPv4Address)
  <#
      .SYNOPSIS
      Tests the SIP functions of a VVX phone. Functions which cause impact (specifically, phone restart) are executed last.
  #>


  BEGIN {
  } PROCESS {
    Foreach ($ThisIPv4Address in $IPv4Address)
    {

      Try
      {
        $PhoneInfoValues = Get-UcsSipPhoneInfo -IPv4Address $ThisIPv4Address -ErrorAction Stop
        #SIP should return server, SIP address, label, lineUri, Model, FirmwareRelease, MacAddress, Registered, and IPv4Address.
        #Of the return values, we can't depend on any value for server, SIPAddress, Label, LineUri, MacAddress, or Registered.
        #But, we can depend on Model, FirmwareRelease, and IPv4Address.
        #We'll also check if any returned results for the others are valid.

        if($PhoneInfoValues.Model.Length -lt 3)
        {
          Write-Error "Get-UcsSipPhoneInfo: $ThisIPv4Address provided no valid information for model." -ErrorAction Continue
        }
        if($PhoneInfoValues.FirmwareRelease -notmatch '(\d+[A-Z]?\.){3}\d{4,}[A-Z]*(\s.+)?')
        {
          Write-Error "Get-UcsSipPhoneInfo: $ThisIPv4Address provided an invalid firmware release." -ErrorAction Continue
        }
        if($PhoneInfoValues.IPv4Address -notmatch '^([0-2]?[0-9]{1,2}\.){3}([0-2]?[0-9]{1,2})$')
        {
          Write-Error "Get-UcsSipPhoneInfo: $ThisIPv4Address provided an invalid IPv4 Address." -ErrorAction Continue
        }

        if($PhoneInfoValues.MacAddress.Length -gt 0)
        {
          if($PhoneInfoValues.MacAddress -notmatch '[A-Fa-f0-9]{12}')
          {
            Write-Error "Get-UcsSipPhoneInfo: $ThisIPv4Address provided an invalid MacAddress." -ErrorAction Continue
          }
        }
      }
      Catch
      {
        Write-Warning "Get-UcsSipPhoneInfo: $ThisIPv4Address failed to get all data."
      }

      if($PSCmdlet.ShouldProcess(('{0}' -f $ThisIPv4Address)))
      {
        #Testing Restart-UcsSipPhone. It's difficult to be sure using only SIP that a phone has restarted - so we just look to see that no errors were returned.
        #In addition, the phone only restarts if it's set to restart on a check-sync so reporting a failure here would be unwise.
        Try
        {
          $null = Restart-UcsSipPhone -IPv4Address $ThisIPv4Address -ErrorAction Stop -Confirm:$false
        }
        Catch
        {
          Write-Warning "Restart-UcsSipPhone: $ThisIPv4Address threw an error when restart was requested: $_"
        }
      }
    }
  } END {
  }
}