Private/NestedFunctions/Invoke-JCApi.ps1

Function Invoke-JCApi
{
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, Position = 0)][ValidateNotNullOrEmpty()][string]$Url,
        [Parameter(Mandatory = $true, Position = 1)][ValidateNotNullOrEmpty()][string]$Method,
        [Parameter(Mandatory = $false, Position = 2)][ValidateNotNullOrEmpty()][ValidateRange(1, [int]::MaxValue)][int]$Limit = 100,
        [Parameter(Mandatory = $false, Position = 3)][ValidateNotNullOrEmpty()][ValidateRange(0, [int]::MaxValue)][int]$Skip = 0,
        [Parameter(Mandatory = $false, Position = 4)][ValidateNotNull()][array]$Fields = @(),
        [Parameter(Mandatory = $false, Position = 5)][ValidateNotNull()][string]$Body = '',
        [Parameter(Mandatory = $false, Position = 6)][ValidateNotNullOrEmpty()][bool]$Paginate = $false,
        [Parameter(Mandatory = $false, Position = 7)][ValidateNotNullOrEmpty()][switch]$ReturnCount
    )
    Begin
    {
        # Debug message for parameter call
        $PSBoundParameters | Out-DebugParameter | Write-Debug
        # Populate $env:JCApiKey if its not set
        If ([System.String]::IsNullOrEmpty($env:JCApiKey))
        {
            Connect-JCOnline -force | Out-Null
        }
        # Populate $env:JCOrgId if its not set
        If (-not [System.String]::IsNullOrEmpty($env:JCApiKey) -and [System.String]::IsNullOrEmpty($env:JCOrgId) -and $Url -notlike '*/api/organizations*')
        {
            Set-JCOrganization -JumpCloudAPIKey:($env:JCApiKey) | Out-Null
        }
        #Set JC headers
        $Headers = @{
            'Content-Type' = 'application/json';
            'Accept'       = 'application/json';
            'x-api-key'    = "$($env:JCApiKey)";
            'x-org-id'     = "$($env:JCOrgId)";
        }
        # Organizations endpoint does not accept x-org-id in header
        If ($Url -like '*/api/organizations*')
        {
            $Headers.Remove('x-org-id') | Out-Null
        }
    }
    Process
    {
        Try
        {
            $Results = @()
            If ([System.String]::IsNullOrEmpty($JCUrlBasePath))
            {
                $JCUrlBasePath = 'https://console.jumpcloud.com'
            }
            If ($Url -notlike ('*' + $JCUrlBasePath + '*'))
            {
                $Url = $JCUrlBasePath + $Url
            }
            If ($Url -like '*`?*')
            {
                $SearchOperator = '&'
            }
            Else
            {
                $SearchOperator = '?'
            }
            # Convert passed in body to json
            If ($Body)
            {
                $ObjectBody = $Body | ConvertFrom-Json
            }
            Else
            {
                $ObjectBody = ''
            }
            # Pagination
            Do
            {
                $QueryStrings = @()
                # Add fields
                If ($Fields)
                {
                    $JoinedFields = ($Fields -join ' ')
                    If ($ObjectBody.PSObject.Properties.name -eq 'fields')
                    {
                        $JoinedFields = $ObjectBody.fields
                    }
                    Else
                    {
                        $ObjectBody = $ObjectBody | Select-Object *, @{Name = 'fields'; Expression = { $JoinedFields } }
                    }
                    If ($Url -notlike '*fields*') { $QueryStrings += 'fields=' + $JoinedFields }
                }
                # Add limit
                If ($ObjectBody.PSObject.Properties.name -eq 'limit')
                {
                    $ObjectBody.limit = $Limit
                }
                Else
                {
                    $ObjectBody = $ObjectBody | Select-Object *, @{Name = 'limit'; Expression = { $Limit } }
                }
                If ($Url -notlike '*limit*') { $QueryStrings += 'limit=' + $Limit }
                # Add skip
                If ($ObjectBody.PSObject.Properties.name -eq 'skip')
                {
                    $ObjectBody.skip = $Skip
                }
                Else
                {
                    $ObjectBody = $ObjectBody | Select-Object *, @{Name = 'skip'; Expression = { $Skip } }
                }
                If ($Url -notlike '*skip*') { $QueryStrings += 'skip=' + $Skip }
                # Build url query string and body
                $ObjectBody = $ObjectBody | Select-Object -Property * -ExcludeProperty Length
                $Body = $ObjectBody | ConvertTo-Json -Depth:(10) -Compress | Sort-Object
                If ($QueryStrings)
                {
                    $Uri = $Url + $SearchOperator + (($QueryStrings | Sort-Object) -join '&')
                }
                Else
                {
                    $Uri = $Url
                }
                # Run request
                $UserAgent = Get-JCUserAgent
                Write-Verbose ('Connecting to: ' + $Uri)
                # PowerShell 5 won't let you send a GET with a body.
                If ($Method -eq 'GET')
                {
                    Write-Debug("[CallFunction]: Invoke-WebRequest -Method:('$Method') -Headers:(@" + ($Headers | ConvertTo-Json -Compress).Replace(':', '=').Replace(',', ';') + ") -Uri:('$Uri') -UseBasicParsing -UserAgent:('$UserAgent')")
                    $RequestResult = Invoke-WebRequest -Method:($Method) -Headers:($Headers) -Uri:($Uri) -UserAgent:($UserAgent) -UseBasicParsing
                }
                Else
                {
                    Write-Debug("[CallFunction]: Invoke-WebRequest -Method:('$Method') -Headers:(@" + ($Headers | ConvertTo-Json -Compress).Replace(':', '=').Replace(',', ';') + ") -Uri:('$Uri') -UseBasicParsing -UserAgent:('$UserAgent') -Body:('$Body')")
                    $RequestResult = Invoke-WebRequest -Method:($Method) -Headers:($Headers) -Uri:($Uri) -UserAgent:($UserAgent) -Body:($Body) -UseBasicParsing
                }
                If ($RequestResult)
                {
                    $Result = $RequestResult.Content | ConvertFrom-Json
                    $httpMetaData = $RequestResult | Select-Object -Property:('*') -ExcludeProperty:('Content')
                    If ($Result)
                    {
                        $ResultPopulated = $false
                        # Specific logic for v1 and v2 api specs
                        If ($Url -like '*/api/*' -and ($Url -notlike '*/api/v2/*' -and $Result.PSObject.Properties.name -eq 'results'))
                        {
                            $ResultCount = ($Result.results | Measure-Object).Count
                            If ($ResultCount -gt 0)
                            {
                                $ResultPopulated = $true
                                If ($ReturnCount)
                                {
                                    $ResultObjects = $Result
                                    $Paginate = $false
                                }
                                Else
                                {
                                    $ResultObjects = $Result.results
                                }
                            }
                        }
                        ElseIf ($Url -like '*/api/*' -and ($Url -like '*/api/v2/*' -or $Result.PSObject.Properties.name -ne 'results'))
                        {
                            $ResultCount = ($Result | Measure-Object).Count
                            If ($ResultCount -gt 0)
                            {
                                $ResultPopulated = $true
                                If ($ReturnCount)
                                {
                                    $ResultObjects = [PSCustomObject]@{'totalCount' = [int](($httpMetaData.Headers.'X-Total-Count') -join ','); 'results' = $Result; }
                                    $Paginate = $false
                                }
                                Else
                                {
                                    $ResultObjects = $Result
                                }
                            }
                        }
                        Else
                        {
                            Write-Error ('Url is not a valid JumpCloud V1 or V2 endpoint')
                        }
                        If ($ResultPopulated -eq $true)
                        {
                            $Skip += $ResultCount
                            $Results += $ResultObjects
                        }
                    }
                    Else
                    {
                        If ($Paginate)
                        {
                            $ResultCount = ($Result | Measure-Object).Count
                        }
                    }
                }
                Write-Debug ('Paginate:' + [string]$Paginate + ';ResultsCount:' + [string]$ResultCount + ';Limit:' + [string]$Limit + ';')
            }
            While ($Paginate -and $ResultCount -eq $Limit)
            Write-Verbose ('Returned ' + [string]($Results | Measure-Object).Count + ' total results.')
        }
        Catch
        {
            Invoke-Command -ScriptBlock:($ScriptBlock_TryCatchError) -ArgumentList:($_, $true) -NoNewScope
        }
    }
    End
    {
        # List values to add to results
        $HiddenProperties = @('httpMetaData')
        # Validate that all fields passed into the function exist in the output
        If ($Results)
        {
            # Append meta info to each result record
            Get-Variable -Name:($HiddenProperties) |
            ForEach-Object {
                $Variable = $_
                $Results |
                ForEach-Object {
                    Add-Member -InputObject:($_) -MemberType:('NoteProperty') -Name:($Variable.Name) -Value:($Variable.Value)
                }
            }
            # Validate results properties returned
            $Fields | ForEach-Object {
                If ($_ -notin ($Results | ForEach-Object { $_.PSObject.Properties.Name } | Select-Object -Unique))
                {
                    Write-Warning ('API output does not contain the field "' + $_ + '". Please refer to https://docs.jumpcloud.com for API endpoint field names.')
                }
            }
        }
        Else
        {
            $Results += [PSCustomObject]@{
                'NoContent'    = $null;
                'httpMetaData' = $httpMetaData;
            }
        }
        # Set the meta info to be hidden by default
        Return Hide-ObjectProperty -Object:($Results) -HiddenProperties:($HiddenProperties)
    }
}