UcsProvUtil.psm1
Function Import-UcsProvFile { Param( [Parameter(Mandatory)][Alias('Path','Filename')][String]$FilePath, [ValidateSet('None','Misc','Log','Override','Contact','License','Profile','Call','Corefile')][String]$Type = 'None', [Nullable[Int]]$ProvServerIndex = $null ) <#Example file to download: 192.168.1.50/example/file.txt COMPUTERNAME/FILEPATH We disallow slashes in a computer name to prevent ftp:// etc from being included. #> Begin { $ProvisioningConfig = Get-UcsProvConfig if($ProvServerIndex -ne $null) { #If manually specified, we want to force which provisioning server to use. Mostly used for recursion. $ProvisioningConfig = $ProvisioningConfig | Where-Object -Property ProvServerIndex -EQ -Value $ProvServerIndex } $OutputContent = '' $TypeMapping = @{ None = '' Misc = 'MISC_FILES' Log = 'LOG_FILE_DIRECTORY' Override = 'OVERRIDES_DIRECTORY' Contact = 'CONTACTS_DIRECTORY' License = 'LICENSE_DIRECTORY' Profile = 'USER_PROFILES_DIRECTORY' Call = 'CALL_LISTS_DIRECTORY' Corefile = 'COREFILE_DIRECTORY' } $ThisType = $TypeMapping[$Type] } Process { $SaveLocation = '' $ThisSuccess = $false Foreach ($ThisServer in $ProvisioningConfig) { Write-Debug -Message ('{2}: Trying {0} for {1}.' -f $ThisServer.DisplayName, $ThisServer.ProvServerAddress, $PSCmdlet.MyInvocation.MyCommand.Name) if($Type -ne 'None') { #We must start by getting the location of the file in question if mode hasn't been set to "none." #We mostly need 'none' available for recursion. Try { $MasterConfigContent = Import-UcsProvFile -FilePath '000000000000.cfg' -Type None -ProvServerIndex $ThisServer.ProvServerIndex -ErrorAction Stop $MasterConfig = Convert-UcsProvMasterConfig -Content $MasterConfigContent.Content -ErrorAction Stop $ThisDirectory = ($MasterConfig.$ThisType).Trim('/\ ') if($ThisDirectory.length -gt 0) { $Working = $FilePath $Parent = $Working | Split-Path -Parent $Leaf = $Working | Split-Path -Leaf $Working = Join-Path -Path $ThisDirectory -ChildPath $Leaf if($Parent.Length -gt 0) { $Working = Join-Path -Path $Parent -ChildPath $Working } $FilePath = $Working } } Catch { Write-Error -Message ('Couldn''t get master config file for {0}.' -f $ThisServer.DisplayName) -ErrorAction Stop } } Try { Switch($ThisServer.ProvServerType) { 'FileSystem' { $FullPath = Join-Path -Path $ThisServer.ProvServerAddress -ChildPath $FilePath if($ThisServer.Credential -ne $null) { $Item = Copy-Item -Path $FullPath -Destination $env:TEMP -Credential $ThisServer.Credential -PassThru -ErrorAction Stop } else { $Item = Copy-Item -Path $FullPath -Destination $env:TEMP -PassThru -ErrorAction Stop } $SaveLocation = $Item.FullName $ThisSuccess = $true } 'FTP' { $SaveLocation = Get-UcsProvFTPFile -Address $ThisServer.ProvServerAddress -Filename $FilePath -Credential $ThisServer.Credential -ErrorAction Stop $ThisSuccess = $true } Default { Write-Debug -Message ('{2}: {0} is not supported for this operation.' -f $ThisServer.DisplayName, $ThisServer.ProvServerAddress, $PSCmdlet.MyInvocation.MyCommand.Name) } } } Catch { Write-Debug -Message ('{2}: Encountered an error using {0} provisioning protocol with {1}.' -f $ThisServer.DisplayName, $ThisServer.ProvServerAddress, $PSCmdlet.MyInvocation.MyCommand.Name) Write-Debug -Message ('{0}: {1}' -f $ThisServer.DisplayName, $_) } if($ThisSuccess -eq $true) { #We succeeded, so we don't have to retry. $OutputObject = Get-Item -Path $SaveLocation $OutputObject = $OutputObject | Select-Object -Property BaseName, Name, Directory, FullName, Extension, @{ Name = 'Content' Expression = { Get-Content -Path $SaveLocation } }, @{ Name = 'ProvServerIndex' Expression = { $ThisServer.ProvServerIndex } } $OutputContent = $OutputObject Break } } if($ThisSuccess -ne $true) { Write-Error -Message "Couldn't get file $FilePath." -ErrorAction Stop } } End { Return $OutputContent } } Function Import-UcsProvCallLogXml { <# .SYNOPSIS Takes a filename, reads the file, and parses its call information. #> Param( [Parameter(Mandatory,HelpMessage = 'Add help message for user')][String[]]$Path ) BEGIN { $AllCalls = New-Object -TypeName System.Collections.ArrayList $Count = 0 } PROCESS { Foreach($ThisFilename in $Path) { $Count++ Try { $File = Get-Item $Path $Content = $File | Get-Content } Catch { Write-Error -Message "Couldn't get content for $Path." Continue } $Error.Clear() $Content = $Content.Replace('&','&') #Polycom's log files don't conform to XML specifications and can include & within a set of XML tags. This mitigates that. $Content = [Xml] $Content if($Error.Count -ne 0) { Write-Warning -Message ('Error detected in file.') Continue } $LastUpdated = Get-Date -Date $Content.Saved.Trim('@ --') Write-Debug -Message ('{0} was last updated {1}.' -f $ThisFilename, $LastUpdated.ToShortDateString()) Foreach ($Call in $Content.callList.call) { $Duration = (Convert-UcsProvDuration -Duration $Call.duration) $ThisSource = Format-UcsProvCallData -CallData $Call.Source -Type Address $ThisDestination = Format-UcsProvCallData -CallData $Call.Destination -Type Address $ThisConnection = Format-UcsProvCallData -CallData $Call.Connection -Type Address $ThisSourceName = Format-UcsProvCallData -CallData $Call.Source -Type Name $ThisDestinationName = Format-UcsProvCallData -CallData $Call.Destination -Type Name $ThisConnectionName = Format-UcsProvCallData -CallData $Call.Connection -Type Name if($Call.direction -eq 'In') { $RemotePartyName = $ThisSourceName $RemotePartyNumber = $ThisSource $LocalPartyName = $ThisDestinationName $LocalPartyNumber = $ThisDestination } else { $RemotePartyName = $ThisDestinationName $RemotePartyNumber = $ThisDestination $LocalPartyName = $ThisSource $LocalPartyNumber = $ThisSourceName } $ThisCall = New-UcsCallObject -Type $Call.direction ` -RemotePartyName $RemotePartyName ` -RemotePartyNumber $RemotePartyNumber ` -LocalPartyName $LocalPartyName ` -LocalPartyNumber $LocalPartyNumber ` -ConnectionName $ThisConnectionName ` -ConnectionNumber $ThisConnection ` -CallState Log ` -Protocol $Call.Protocol ` -LineID $Call.Line ` -StartTime (Get-Date -Date $Call.StartTime) ` -Duration $Duration ` -MacAddress $File.BaseName.Substring(0, 12) ` -Disposition $Call.Disposition ` -IsLog ` -ExcludeNullProperties #Missing: Connection $null = $AllCalls.Add($ThisCall) } } } END { Return $AllCalls } } Function Convert-UcsProvMasterConfig { <# .SYNOPSIS Reads the Polycom 000000000000.cfg file from disk and returns an easily-parsed listing of key information. .DESCRIPTION Add a more complete description of what the function does. .PARAMETER Filename Describe parameter -Filename. .EXAMPLE Get-UcsProvMasterConfig -Filename Value Describe what this call does .NOTES Place additional notes here. .LINK URLs to related sites The first link is opened by Get-Help -Online Get-UcsProvMasterConfig .INPUTS List of input types that are accepted by this function. .OUTPUTS List of output types produced by this function. #> Param( [Parameter(Mandatory,HelpMessage = 'The content of the master config file')][String[]]$Content ) Try { [XML]$XMLContent = $Content } Catch { Write-Error ('Unable to read content from master config file.') Break } $Output = $XMLContent.Application | Select-Object -Property APP_FILE_PATH, CONFIG_FILES, MISC_FILES, LOG_FILE_DIRECTORY, OVERRIDES_DIRECTORY, CONTACTS_DIRECTORY, LICENSE_DIRECTORY, USER_PROFILES_DIRECTORY, CALL_LISTS_DIRECTORY, COREFILE_DIRECTORY Return $Output } Function Format-UcsProvCallData { <# .SYNOPSIS Takes source, destination, or connection, then returns a collection of the result, address only. .DESCRIPTION Supporting function for Import-UcsCallLogXml. .PARAMETER CallData CallData as passed from Import-UcsCallLogXml. .PARAMETER Type "Address" or "Name" #> Param($CallData, [Parameter(Mandatory,HelpMessage = 'Add help message for user')][ValidateSet('Address','Name')][String]$Type) $Output = New-Object -TypeName System.Collections.ArrayList Foreach($Data in $CallData) { if($Type -eq 'Address') { $ThisAddress = $Data.Address } elseif($Type -eq 'Name') { $ThisAddress = $Data.Name } $ThisAddress = $ThisAddress.Replace('sip:','') #Remove SIP to improve data consistency. $ThisAddress = $ThisAddress.Replace('tel:','') #Remove TEL to improve data consistency. $ThisAddress = $ThisAddress.Replace(''',"'") #Convert escaped apostrophe to an apostrophe. $ThisAddress = $ThisAddress.Replace('"',"`"") #Convert escaped quote to an quote. $null = $Output.Add($ThisAddress) } if($Output.Count -eq 0) { $Output = $null } elseif($Output.Count -eq 1) { $Output = $Output[0] } Return $Output } Function Convert-UcsProvDuration { <# .SYNOPSIS Gets a call duration from output, then converts to a timespan. .DESCRIPTION Takes a pattern such as P1DT5H6M3 and converts to a standard timespan. .PARAMETER Duration Duration string from UCS. .EXAMPLE Convert-UcsDuration -Duration Value Returns a timespan object. .OUTPUTS Timespan #> Param ([Parameter(Mandatory,HelpMessage = 'P1DT5H6M3')][ValidatePattern('^P(\d+D)?(T)?(\d+H)?(\d+M)?(\d+S)?$')][String]$Duration) $Seconds = 0 $Minutes = 0 $Hours = 0 $Days = 0 $Done = $false #The format seems to be roughly: #P1DT1H1M1S for a time including days #PT1H1M1S for a time not including days. #The parser doesn't care as long as the number is suffixed with the right letter, so we can drop P and T. $Trimmed = $Duration.Replace('P','') $Trimmed = $Trimmed.Replace('T','') While ($Done -eq $false) { $NextIndex = $Trimmed.IndexOfAny('DHMS') #Find the first time indicator. $NextValue = $Trimmed.Substring(0,$NextIndex) #This is the next numerical value. if($Trimmed[$NextIndex] -eq 'D') { $Days = $NextValue } elseif($Trimmed[$NextIndex] -eq 'H') { $Hours = $NextValue } elseif($Trimmed[$NextIndex] -eq 'M') { $Minutes = $NextValue } elseif($Trimmed[$NextIndex] -eq 'S') { $Seconds = $NextValue } else { $Done = $true } if( (($Trimmed.Length) - 1) -gt $NextIndex) { #Only use this codepath if there's anything left. $Trimmed = $Trimmed.Substring($NextIndex + 1) #Cut off the beginning and start again. } else { $Done = $true } } return New-TimeSpan -Days $Days -Hours $Hours -Minutes $Minutes -Seconds $Seconds } |