src/cmdlets/Invoke-GraphMethod.ps1
# Copyright 2021, 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 ../typesystem/TypeManager) . (import-script common/TypeUriHelper) . (import-script common/QueryTranslationHelper) . (import-script common/GraphParameterCompleter) . (import-script common/TypeParameterCompleter) . (import-script common/TypePropertyParameterCompleter) . (import-script common/TypeUriParameterCompleter) . (import-script common/MethodNameParameterCompleter) . (import-script common/MethodUriParameterCompleter) . (import-script common/FunctionParameterHelper) function Invoke-GraphMethod { [cmdletbinding(positionalbinding=$false, defaultparametersetname='byuri', supportspaging=$true)] param( [parameter(parametersetname='byuripipeline', valuefrompipelinebypropertyname=$true, mandatory=$true)] [parameter(position=0, parametersetname='byuri', mandatory=$true)] [Alias('GraphUri')] [Uri] $Uri = $null, [parameter(position=1, parametersetname='bytypeandid')] [parameter(position=1, parametersetname='byobject')] [parameter(position=1, parametersetname='byuripipeline')] [parameter(position=1, parametersetname='byuri')] [string[]] $Parameter, [parameter(position=2, parametersetname='bytypeandid')] [parameter(position=2, parametersetname='byobject')] [parameter(position=2, parametersetname='byuripipeline')] [parameter(position=2, parametersetname='byuri')] [object[]] $Value, [parameter(parametersetname='bytypeandid', mandatory=$true)] [parameter(parametersetname='byobject', mandatory=$true)] [parameter(parametersetname='byuripipeline', mandatory=$true)] [parameter(parametersetname='byuri')] [string] $MethodName, [parameter(parametersetname='bytypeandid', mandatory=$true)] [Alias('FullTypeName')] [String] $TypeName, [parameter(parametersetname='bytypeandid', mandatory=$true)] [String] $Id, [string[]] $Property, [HashTable] $ParameterTable, [Alias('Body')] [PSCustomObject] $ParameterObject, [parameter(parametersetname='byobject', valuefrompipeline=$true, mandatory=$true)] [PSTypeName('GraphResponseObject')] $GraphItem, [parameter(parametersetname='byuripipeline', valuefrompipelinebypropertyname=$true)] [parameter(parametersetname='byuri')] [parameter(parametersetname='bytypeandid')] [parameter(parametersetname='byobject')] $GraphName = $null, $PropertyFilter, $Filter, [Alias('SearchString')] $SimpleMatch, [String] $Search, [string[]] $Expand, [Alias('Sort')] [object[]] $OrderBy = $null, [Switch] $Descending, [switch] $RawContent, [switch] $NoMetadata, [switch] $ChildrenOnly, [switch] $FullyQualifiedTypeName, [switch] $SkipPropertyCheck, [switch] $SkipParameterCheck ) begin { Enable-ScriptClassVerbosePreference $coreParameters = $null if ( $Filter -and $SimpleMatch ) { throw 'Only one of Filter and SimpleMatch arguments may be specified' } $parameterSpecs = 'ParameterObject', 'ParameterTable', 'Parameter' | where { $PSBoundParameters[$_] } if ( ( $parameterSpecs | measure-object ).count -gt 1 ) { throw [ArgumentException]::new("Only one of the following specified parameters may be specified: {0}" -f ($parameterSpecs -join ', ')) } $filterParameter = @{} $filterValue = $::.QueryTranslationHelper |=> ToFilterParameter $PropertyFilter $Filter if ( $filterValue ) { $filterParameter['Filter'] = $filterValue } $valueLength = 0 if ( $PSBoundParameters.ContainsKey('Value') ) { $valueLength = ($Value | measure-object).count } if ( $Parameter ) { $parameterCount = ($Parameter | measure-object).Count if ( $parameterCount -ne $valueLength ) { throw [ArgumentException]::new("$($parameterCount) parameters were specified by the Parameter argument, but an unequal number of values, $valueLength, was specified through the Value argument.") } } } process { $targetUri = if ( $Uri ) { $Uri } elseif ( ! $TypeName ) { '.' } $requestInfo = $::.TypeUriHelper |=> GetTypeAwareRequestInfo $GraphName $TypeName $FullyQualifiedTypeName.IsPresent $targetUri $Id $GraphItem $true if ( ! $SkipPropertyCheck.IsPresent ) { $::.QueryTranslationHelper |=> ValidatePropertyProjection $requestInfo.Context $requestInfo.TypeInfo $Property } $expandArgument = @{} if ( $Expand ) { $expandArgument['Expand'] = $Expand } if ( $SimpleMatch ) { $filterParameter['Filter'] = $::.QueryTranslationHelper |=> GetSimpleMatchFilter $requestInfo.Context $requestInfo.TypeName $SimpleMatch } $pagingParameters = @{} if ( $pscmdlet.pagingparameters.First -ne $null ) { $pagingParameters['First'] = $pscmdlet.pagingparameters.First } if ( $pscmdlet.pagingparameters.Skip -ne $null ) { $pagingParameters['Skip'] = $pscmdlet.pagingparameters.Skip } if ( $pscmdlet.pagingparameters.IncludeTotalCount -ne $null ) { $pagingParameters['IncludeTotalCount'] = $pscmdlet.pagingparameters.IncludeTotalCount } $coreParameters = @{ Select = $Property Expand = $Expand RawContent = $RawContent OrderBy = $OrderBy Descending = $Descending Search = $Search } $targetMethodName = $MethodName $targetTypeName = if ( $requestInfo | gm FullTypeName -erroraction ignore ) { $requestInfo.FullTypeName } else { $requestInfo.TypeInfo.FullTypeName } if ( $targetUri ) { $typeUriInfo = Get-GraphUriInfo $targetUri -GraphName $requestInfo.Context.Name -erroraction stop if ( ! $MethodName -and $typeUriInfo.Class -notin 'Action', 'Function' ) { throw [ArgumentException]::new("The URI '$targetUri' is not a method URI but the MethodName parameter was not specified -- please specify a method URI or include the MethodName parameter and retry the command") } } $methodUri = if ( $MethodName ) { $requestInfo.Uri.tostring().trimend('/'), $MethodName -join '/' } elseif ( $requestInfo.TypeInfo.UriInfo.Class -in 'Action', 'Function' ) { $targetMethodName = $typeUriInfo.Name $typeUriInfo = Get-GraphUriInfo $typeUriInfo.ParentPath -GraphName $requestInfo.Context.Name -erroraction stop $targetTypeName = $typeUriInfo.FullTypeName $targetUri.tostring() } else { $requestInfo.Uri.tostring() } if ( ! $methodUri ) { throw 'Unable to determine the method Uri from the parameters specified.' } $transitiveMethods = Get-GraphMethod -TypeName $targetTypeName -GraphName $requestInfo.Context.Name -erroraction stop $method = $transitiveMethods | where Name -eq $targetMethodName if ( ! $method ) { if ( $targetUri -or $GraphItem ) { throw [ArgumentException]::new("The specified method URI '$methodUri' could not be found in the graph '$($requestInfo.Context.Name)'") } else { throw [ArgumentException]::new("The specified method '$MethodName' could not be found for the '$TypeName' in the graph '$($requestInfo.Context.Name)'") } } $methodUriInfo = Get-GraphUriInfo $methodUri -GraphName $requestInfo.Context.Name -erroraction stop $methodClass = $methodUriInfo.Class if ( $methodClass -notin 'Action', 'Function' ) { throw "Invalid method class '$methodClass' -- only 'Action' and 'Function' methods are supported" } $parameterNames = @() $methodBody = if ( $methodClass ) { if ( $ParameterObject ) { $parameterNames = $ParameterObject | gm -membertype noteproperty | select -expandproperty name $ParameterObject } elseif ( $ParameterTable ) { $parameterNames = $parameterTable.keys $ParameterTable } elseif ( $Parameter ) { $parameters = @{} $parameterIndex = 0 foreach ( $parameterName in $Parameter ) { $parameters.Add($parameterName, $Value[$parameterIndex]) $parameterIndex++ } $parameterNames = $parameters.keys | foreach { $_ } $parameters } } if ( ! $SkipParameterCheck.IsPresent -and $method.parameters) { $difference = compare-object $parameterNames $method.parameters.name if ( $difference ) { $invalidParameters = ( $difference | where sideindicator -eq '<=' | select -expandproperty inputobject ) -join ', ' $missingParameters = ( $difference | where sideindicator -eq '=>' | select -expandproperty inputobject ) -join ', ' $invalidParameterMessage = if ( $invalidParameters ) { "`n - Invalid parameter names: '$invalidParameters'" } $missingParameterMessage = if ( $missingParameters ) { "`n - Missing parameters: '$missingParameters'" } $errorMessage = @" Unable to invoke method '$targetMethodName' on type '$($targetTypeName)' with {0} parameters. This was due to: {1}{2} The complete set of valid parameters is '{3}'. "@ -f ($method.parameters.name | measure-object).count, $missingParameterMessage, $invalidParameterMessage, ( $method.parameters.name -join ', ' ) throw [ArgumentException]::new($errorMessage) } } $bodyParam = if ( $methodBody ) { @{Body=$methodBody} } else { @{} } $methodUriWithParameters = if ( $methodClass -eq 'Function' ) { $parameterString = $::.FunctionParameterHelper |=> ToUriParameterString $methodBody $methodUri.tostring().trimend('/') + $parameterString } else { $methodUri } Invoke-GraphApiRequest -Uri $methodUriWithParameters -Method POST @bodyParam -Connection $requestInfo.Context.Connection @coreParameters @filterParameter @pagingParameters -erroraction stop | foreach { # Note that there is not relation between the structure of the URI used to make an action or function # request and the types returned in the response -- they could be literally anything. The method below # addresses this by creating a public segment that reflects the type returned in the response metadata, # which is the only way to know the returned type. This can also be true in conventional GET requests # for entity types or their structural properties, but only due to polymorphism, and of course in those # cases rather than not having a type at all, the worst case is that we are forced to assume the least-derived # type, which is a situation of information loss but not of incorrectness. if ( ! $NoMetadata.IsPresent -and $_ -and ( $_.gettype().fullname -eq 'System.Management.Automation.PSCustomObject' ) ) { # Handle the strange case where we get an empty result, but the response is not empty if ( ! ( $_ | gm '@odata.context' -erroraction ignore ) -or ! ( $_ | gm 'value' -erroraction ignore ) -or ! ( $_.value -eq $null ) ) { $::.SegmentHelper |=> ToPublicSegmentFromGraphResponseObject $requestInfo.Context $_ } } else { $_ } } } end { } } $::.ParameterCompleter |=> RegisterParameterCompleter Invoke-GraphMethod TypeName (new-so TypeParameterCompleter TypeName) $::.ParameterCompleter |=> RegisterParameterCompleter Invoke-GraphMethod MethodName (new-so MethodUriParameterCompleter MethodName) $::.ParameterCompleter |=> RegisterParameterCompleter Invoke-GraphMethod Parameter (new-so MethodUriParameterCompleter ParameterName) $::.ParameterCompleter |=> RegisterParameterCompleter Invoke-GraphMethod OrderBy (new-so TypeUriParameterCompleter Property) $::.ParameterCompleter |=> RegisterParameterCompleter Invoke-GraphMethod Expand (new-so TypeUriParameterCompleter Property $false NavigationProperty) $::.ParameterCompleter |=> RegisterParameterCompleter Invoke-GraphMethod GraphName (new-so GraphParameterCompleter) $::.ParameterCompleter |=> RegisterParameterCompleter Invoke-GraphMethod Uri (new-so GraphUriParameterCompleter ([GraphUriCompletionType]::LocationOrMethodUri )) |