src/cmdlets/Test-Graph.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/RESTRequest)
. (import-script ../graphservice/GraphEndpoint)
. (import-script ../client/GraphConnection)
. (import-script ../client/GraphContext)

$AlternatePropertyMapping = @{
    'Time-Local'=@('TimeLocal', {param($val) [DateTime] $val})
    'Time-UTC'=@('TimeUtc', {param($val) [DateTime]::new(([DateTime] $val).ticks, [DateTimeKind]::Utc)})
}

<#
.SYNOPSIS
Determines without authentication whether a Graph endpoint is accessible over the network.

.DESCRIPTION
The Test-Graph cmdlet makes a simple GET request to a specified Graph endpoint's 'ping' URL. If a successful (HTTP status code 200) response is received, parameters of the response are returned by the cmdlet.

.PARAMETER Cloud
Specifies that the target Graph endpoint to test is the Graph endpoint associated with cloud environment indicated by the parameter. By default, Test-Graph makes a request against the current connection, which itself defaults to https://graph.microsoft.com, so this parameter allows the default to be overridden.

.PARAMETER Connection
Specifies a Connection object returned by the New-GraphConnection command whose Graph endpoint will be accessed when making Graph requests with this Connection object.

.PARAMETER EndpointUri
Specifies an arbitrary URI as the Graph endpoint -- the URI must be an absolute URI, e.g. https://graph.microsoft.com.

.PARAMETER RawContent
By default, the output of the cmdlet is deserialized PowerShell objects. If this switch is specified, the output is the response JSON formatted value returned by the Graph endpoint without deserialization.

.OUTPUTS
If successful, this cmdlet returns deserialized PowerShell objects that provide diagnostic information about the Graph endpoint such as the name of the datacenter that served the request, the time of the request as seen by the system that served the response, and the host name of the system that served the response. The output may be piped to other commands including Select-Object to project specific fields or perform other additional processing.
If the command is not successful, an HTTP status code will be surfaced in an exception. Failure indicates that the Graph endpoint may not be reachable from the system on which this cmdlet was executed, or that the Graph service is being disrupted.

.EXAMPLE
Test-Graph

ADSiteName : wus
Build : 1.0.9954.5
DataCenter : west us
Host : agsfe_in_38
PingUri : https://graph.microsoft.com/ping
Ring : 5
ScaleUnit : 002
Slice : slicec
TimeLocal : 1/24/2019 6:54:13 AM
TimeUtc : 1/24/2019 6:54:13 AM

When no parameters are specified, the command targets the Graph endpoint of the current connection, in this case https://graph.microsoft.com, and outputs diagnostic information.

.EXAMPLE
Test-Graph -Cloud GermanyCloud

ADSiteName : dne
Build : 1.0.9954.4
DataCenter : germany northeast
Host : agsfe_in_3
PingUri : https://graph.microsoft.de/ping
Ring : 4
ScaleUnit : 000
Slice : slicec
TimeLocal : 1/24/2019 7:04:06 AM
TimeUtc : 1/24/2019 7:04:06 AM

This command targets the Graph endpoint for the Germany cloud, https://graph.microsoft.de, ando outputs its diagnostic information.

.EXAMPLE
Test-Graph -RawContent

{"Time-Local":"1/24/2019 7:06:20 AM","Time-UTC":"1/24/2019 7:06:20 AM","Build":"1.0.9954.5","DataCenter":"west us","Slice":"slicec","Ring":"5","ScaleUnit":"001","Host":"agsfe_in_0","ADSiteName":"wus"}

This command returns the same information as in the first example, but by specifying the RawContent parameter the command is directed not to output the response as deserialized structured objects, but in the exact format in which it was returned by Graph, in this case JSON.

.LINK
Get-GraphConnectionInfo
New-GraphConnection
Connect-GraphApi
#>

function Test-Graph {
    [cmdletbinding(defaultparametersetname='currentconnection')]
    param(
        [parameter(parametersetname='KnownClouds')]
        [validateset("Public", "ChinaCloud", "GermanyCloud", "USGovernmentCloud")]
        [string] $Cloud,

        [parameter(parametersetname='Connection', mandatory=$true)]
        [PSCustomObject] $Connection,

        [parameter(parametersetname='CustomEndpoint', mandatory=$true)]
        [Uri] $EndpointUri,

        [switch] $RawContent
    )
    Enable-ScriptClassVerbosePreference

    $logger = $::.RequestLog |=> GetDefault

    $graphEndpointUri = if ( $Connection ) {
        $Connection.GraphEndpoint.Graph
    } elseif ( $Cloud ) {
        (new-so GraphEndpoint $Cloud).Graph
    } elseif ( $endpointUri ) {
        $endpointUri
    } else {
        ($::.GraphContext |=> GetConnection).GraphEndpoint.Graph
    }

    $pingUri = [Uri]::new($graphEndpointUri, 'ping')
    $request = new-so RESTRequest $pingUri
    $logEntry = if ( $logger ) { $logger |=> NewLogEntry $null $request }

    $response = try {
        $request |=> Invoke -logEntry $logEntry
    } catch {
        throw
    } finally {
        if ( $logEntry ) { $logger |=> CommitLogEntry $logEntry }
    }

    if ( ! $RawContent.ispresent ) {
        # The [ordered] type adapter will ensure that enumeration of items in a hashtable
        # is sorted by insertion order
        $result = [ordered] @{}

        $content = $response.content | convertfrom-json
        $content | add-member -notepropertyname PingUri -notepropertyvalue $pinguri

        # Sort by name to get consistent sort formatting
        $content | gm -membertype noteproperty | sort-object name | foreach {
            $value = ($content | select -expandproperty $_.name)
            $mapping = $alternatePropertyMapping[$_.name]

            $destination = if ($mapping -eq $null) {
                $_.name
            } else {
                $value = invoke-command -scriptblock $mapping[1] -argumentlist $value
                $mapping[0]
            }

            $result[$destination] = $value
        }

        [PSCustomObject] $result
    } else {
        $response.content
    }
}