src/cmdlets/Format-GraphLog.ps1

# Copyright 2019, Adam Edwards
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

. (import-script ../REST/RequestLog)

$Views = @{
    Status = @(
        'RequestTimestamp'
        'StatusCode'
        'Method'
        'Version'
        'ResourceUri'
        $::.RequestLogEntry.ERROR_MESSAGE_EXTENDED_FIELD
    )
    Timing = @(
        'RequestTimestamp'
        'ClientElapsedTime'
        'StatusCode'
        'Method'
        'ResourceUri'
        'ResponseTimestamp'
    )
    Authentication = @(
        'RequestTimestamp'
        'AppId'
        'UserUpn'
        'UserObjectId'
        'StatusCode'
        'Method'
        'ResourceUri'
        'Permissions'
    )
    Debug = @(
        'RequestTimestamp'
        'ClientRequestId'
        'StatusCode'
        'Method'
        'Version'
        'ResourceUri'
        'Query'
        'HasRequestBody'
        $::.RequestLogEntry.ERROR_MESSAGE_EXTENDED_FIELD
    )
}

<#
.SYNOPSIS
Formats the output of the Get-GraphLog command for readability and focus.

.DESCRIPTION
When a command such as Get-GraphResource or Invoke-GraphRequest issues a request to the Graph, the details of that request, including the
URI, http method, headers, along with details of the response are recorded as entries in a log. The Format-GraphLog command displays
output returned by Get-GraphLog with additional columns optimized for relevance and readability by default. It also performs
some formatting on fields that are difficult to read in a tabular format in their native representation.

.PARAMETER InputObject
The object to be displayed by the command

.PARAMETER Property
A list of properties of the InputObject to display as columns of the table

.PARMETER DisplayError
This parameter follows the behavior of the same parameter of Format-Table.

.PARAMETER Expand
This parameter follows the behavior of the same parameter of Format-Table.

.PARAMETER Force
This parameter follows the behavior of the same parameter of Format-Table.

.PARAMETER GroupBy
This parameter follows the behavior of the same parameter of Format-Table.

.PARAMETER ShowError
This parameter follows the behavior of the same parameter of Format-Table.

.PARAMETER View
The View parameter determines a predefined set of columns optimized for particular scenarios:
* Status: This is a more compact view that focuses on essential status. If neither the View parameter nor the Property parameter
  are specified, the resulting view is the same as explicitly specifying this 'Status' value for View
* Debug: This set of columns is optimized for debugging and includes the client request id
* Timing: This set of columns is optimized for analyzing timing characteristics of the requests and responses
* Authentication: Provides details of the application, user identity, and permissions used to make the request

.PARAMETER Wrap
This parameter follows the behavior of the same parameter of Format-Table.

.PARAMETER HideTableHeaders
This parameter follows the behavior of the same parameter of Format-Table.

.PARAMETER AutoSize
This parameter follows the behavior of the same parameter of Format-Table.

.OUTPUTS
This command emits output using the Format-Table command -- see the Format-Table command's 'OUTPUTS' section which also describes
the output of Format-GraphLog.

.EXAMPLE
Get-GraphLog | Format-GraphLog

RequestTimestamp StatusCode Method Version ResourceUri ErrorMessage
---------------- ---------- ------ ------- ----------- ------------
10/22/2019 8:07:06 PM 200 GET ping
10/22/2019 8:07:50 PM 200 GET v1.0 me
10/22/2019 8:07:53 PM 200 GET ping
10/22/2019 8:08:42 PM 200 GET beta organization
10/22/2019 8:08:47 PM 400 GET v1.0 me/drive/root Tenant does not have a SPO license.
10/22/2019 8:12:28 PM 200 GET ping

In this example, the default view, Status, is used to view the log.

.EXAMPLE
Get-GraphLog | Format-GraphLog ResponseTimestamp, StatusCode, AppId, ResourceUri

ResponseTimestamp StatusCode AppId ResourceUri
----------------- ---------- ----- -----------
10/22/2019 8:07:06 PM 200 ping
10/22/2019 8:07:50 PM 200 9825d80c-5aa0-42ef-bf13-61e12116704c me
10/22/2019 8:07:53 PM 200 ping
10/22/2019 8:08:42 PM 200 9825d80c-5aa0-42ef-bf13-61e12116704c organization
10/22/2019 8:08:47 PM 400 9825d80c-5aa0-42ef-bf13-61e12116704c me/drive/root
10/22/2019 8:12:29 PM 200 ping

In this example, the log entries are shown, but this time the specific fields to display are specified to the
Format-GraphLog command via the Property argument (which is unnamed since it is the first positional parameter).

.EXAMPLE
Get-GraphLog | Format-GraphLog Vview Debug

RequestTimestamp ClientRequestId StatusCode Method Version ResourceUri Query HasRequestBody ErrorMessage
---------------- --------------- ---------- ------ ------- ----------- ----- -------------- ------------
10/22/2019 8:07:06 PM 2fdf8f02-0b28-450c-b906-2f9d68d93e38 200 GET ping False
10/22/2019 8:07:50 PM 441d4546-db3a-43ae-b11f-7a81cf4b8a67 200 GET v1.0 me True
10/22/2019 8:07:53 PM d3654d3c-324b-49c7-b3ba-c3df6e6f900d 200 GET ping False
10/22/2019 8:08:42 PM 1495925b-932b-44b9-97e6-7d85e56f1ffd 200 GET v1.0 organization True
10/22/2019 8:08:47 PM 6af416ec-1b64-4a5e-9e84-38a0080d4f0a 400 GET v1.0 me/drive/root True Tenant does not have a SPO license.
10/22/2019 8:12:28 PM f37f5954-c2ac-44cd-ae13-7a7fc5652e78 200 GET ping False

In this example, the View option is specified with the value Debug. The resulting view displays the ClientRequestId submitted
with the request which can be used when obtaining support from the Graph Service team.

.LINK
Format-GraphLog
Set-GraphLogOption
Clear-GraphLog
Get-GraphResource
Invoke-GraphRequest
Format-Table
#>

function Format-GraphLog {
    [cmdletbinding(positionalbinding=$false, defaultparametersetname='specificcolumns')]
    param(
        [parameter(valuefrompipeline=$true, mandatory=$true)]
        [object] $InputObject,

        [parameter(position=0, parametersetname='specificcolumns')]
        [ArgumentCompleter({
                               param ( $commandName,
                                       $parameterName,
                                       $wordToComplete,
                                       $commandAst,
                                       $fakeBoundParameters )
                               $possiblePropertyValues = ($::.RequestLogEntry |=> GetExtendedPropertySet)
                               $lowerWordToComplete = $wordToComplete.tolower()
                               $possiblePropertyValues | where {
                                   $_.tolower().StartsWith($lowerWordToComplete)
                               }
                           })]
        [string[]] $Property,

        [parameter(parametersetname='fixedcolumns', mandatory=$true)]
        [ValidateSet('Status', 'Timing', 'Authentication', 'Debug')]
        $View,

        [switch] $DisplayError,

        [switch] $Expand,

        [switch] $Force,

        [object] $GroupBy,

        [switch] $ShowError,

        [switch] $Wrap,

        [switch] $HideTableHeaders,

        [switch] $AutoSize
    )
    begin {
        $targetProperties = if ( $View ) {
            $Views[$View]
        } elseif ( $Property ) {
            $property
        } else {
            $Views['Status']
        }

        $errorSimpleIncluded = $targetProperties -contains $::.RequestLogEntry.ERROR_MESSAGE_EXTENDED_FIELD

        $formatParameters = @{}

        $PSBoundParameters.keys | where { @('View') -notcontains $_ } | foreach {
            $formatParameters[$_] = $PSBoundParameters[$_]
        }

        $formatParameters['Property'] = $targetProperties

        $augmentedInput = @()
    }

    process {
        $propertyMap = @{}

        foreach ( $targetProperty in $targetProperties ) {
            if ( $targetProperty -ne $::.RequestLogEntry.ERROR_MESSAGE_EXTENDED_FIELD ) {
                $propertyValue = if ( $InputObject | gm  $targetProperty ) {
                    $InputObject | select -expandproperty $targetProperty
                }

                $augmentedValue = if ( $propertyValue -is [DateTimeOffset] ) {
                    # DateTimeOffset has a very long format that includes the time
                    # zone offset -- use something shorter to save display space
                    $propertyValue.ToString('G') # i.e. 10/22/2019 9:22:48 PM
                } else {
                    $propertyValue
                }
                $propertyMap[$targetProperty] = $augmentedValue
            }
        }

        $errorMessage = if ( $InputObject | gm $::.RequestLogEntry.ERROR_RESPONSE_FIELD -erroraction ignore ) {
            $InputObject.$($::.RequestLogEntry.ERROR_RESPONSE_FIELD)
        }

        if ( $errorSimpleIncluded -and $errorMessage ) {
            $errorAsObject = $errorMessage | convertfrom-json -erroraction ignore

            $simpleErrorMessage = if ( $errorAsObject ) {
                $errorMember = if ( $errorAsObject | gm error -erroraction ignore ) {
                    $errorAsObject.error
                }

                if ( $errorMember -and ( $errorMember | gm message -erroraction ignore ) ) {
                    $errorMember.message
                }
            }

            $propertyMap[$::.RequestLogEntry.ERROR_MESSAGE_EXTENDED_FIELD] = if ( $simpleErrorMessage ) {
                $simpleErrorMessage
            } else {
                $errorMessage
            }
        }

        $augmentedInput += [PSCustomObject] $propertyMap
    }

    end {
        $augmentedInput | format-table @formatParameters
    }
}