Function Get-ISMBusinessObject {
<#
.SYNOPSIS
  Gets all records of a specified business object OR one particular record when a -RecID is provided.
.DESCRIPTION
  Gets all records of a specified business object OR one particular record when a -RecID is provided. Additionally can retrieve metadata for any business object.
.PARAMETER -BusinessObject
    The Business Object you are interacting with. (Incidents, CIs, CI__ActiveDirectorys, ServiceReqs, Locations, Employees, Manufacturers, StandardUserTeams, Problems, Changes, etc...)
.PARAMETER -RecID
    The Record ID (RecID) of the specific record you wish to retrieve from the business object.
.PARAMETER -Top
    Optional parameter that can be used to specify the number of records you'd like back from the business object. If the supplied number is greater than the total number of records or the -Top parameter is not specified, all records will be returned.
.PARAMETER -Metadata
    Switch that results in the function returning the Business Object metadata as opposed to any of its records. Metadata contains lots of useful information about the business object such as the fields and quick actions that are associated with that business object.
.PARAMETER -PropertyList
    Optional parameter that will query the supplied -BusinessObject metadata and return all of its fields/properties.
.PARAMETER -PropertyMatch
    Optional parameter that allows for a match search against the -PropertyList. Useful for locating specific field names/properties of the business object.
.PARAMETER -DetailedOutput
    Optional hidden parameter that includes some additional output during querying of the Business Object. Primarily used for debugging.
.INPUTS
  Business Object and optionally a -RecID.
.OUTPUTS
  The business object(s) are returned.
.NOTES
  Version:        1.0
  Author:         Sean Carlton
  Creation Date:  08/16/2022
  Purpose/Change: Initial script development
.EXAMPLE
 Get-ISMBusinessObject -BusinessObject incidents

 =======
 This example demonstrates getting all incident records.
 =======
.EXAMPLE
 Get-ISMBusinessObject -BusinessObject incidents -RecID "5788C7AB6E1E44AD8464FED18A510609"

 =======
 This example demonstrates getting a specific incident record based on its RecID.
 =======
.EXAMPLE
 Get-ISMBusinessObject -BusinessObject ci__activedirectorys -RecID "5788C7AB6E1E44AD8464FED18A510609"

 =======
 This example demonstrates getting all records of the CI__ActiveDirectorys extended business object. Extended business objects are formatted with two underscores(__) separating the parent business object from the child business object.
 =======
.EXAMPLE
 Get-ISMBusinessObject -BusinessObject cis

 =======
 This example demonstrates retrieving all ci records.
 =======
.EXAMPLE
 Get-ISMBusinessObject -BusinessObject cis -top 3

 =======
 This example demonstrates retrieving the top 3 ci records.
 =======
.EXAMPLE
 Get-ISMBusinessObject -BusinessObject employees -metadata

 =======
 This example demonstrates querying the employees business object for its metadata.
 =======
.EXAMPLE
 Get-ISMBusinessObject -BusinessObject employees -PropertyMatch "email"

 =======
 This example demonstrates querying the employees business object for any fields/properties that match "email". This is useful for quickly locating specific fields you need to interact with.
 =======
.EXAMPLE
 Get-ISMBusinessObject -BusinessObject employees -PropertyList

 =======
 This example demonstrates querying the employees business object metadata for all of its associated fields/properties. A list of all fields will be returned.
 =======

#>
    [CmdletBinding(DefaultParameterSetName = 'BusinessObj')]
    param (
        [Alias("BO")]
        [Parameter(Mandatory = $true,
            HelpMessage = 'Business Object to query for (Incidents, ServiceReqs, CIs, Changes, etc...)',
            Position = 0)]
            [Parameter(
                ParameterSetName = 'BusinessObj')]
            [Parameter(
                ParameterSetName = 'RecID')]
            [Parameter(
                ParameterSetName = 'Metadata')]
        [string]$BusinessObject,

        [Parameter(
            ParameterSetName = 'RecID')]
        [string]$RecID,

        [Parameter(
            ParameterSetName = 'BusinessObj')]
        [string]$Top,

        [Parameter(
            ParameterSetName = 'Metadata')]
        [switch]$Metadata,

        [Parameter(
            ParameterSetName = 'Metadata')]
        [string]$PropertyMatch,

        [Parameter(
            ParameterSetName = 'Metadata')]
        [switch]$PropertyList,

        [Parameter(
            ParameterSetName = 'BusinessObj')]
        [Parameter(
            ParameterSetName = 'RecID')]
        [Parameter(
            ParameterSetName = 'Metadata')]
        [Parameter(DontShow)]
        [switch]$DetailedOutput,

        ### API Connection Parameters ###
        [Parameter(HelpMessage = 'Tenant URL', DontShow)]
        [string]$Tenant = (Connect-ISM)[1],
        [Parameter(HelpMessage = 'Authorization Header', DontShow)]
        [hashtable]$Headers = (Connect-ISM)[0]
    )

   
    switch ($PSCmdlet.ParameterSetName) {
        'Metadata' {
            $URI = "$Tenant/api/odata/$BusinessObject/`$metadata"
            break
        }

        'RecId' {
            $URI = "$Tenant/api/odata/businessobject/$BusinessObject('$RecID')"
            break
        }
        
        "BusinessObj" {

            If ($BusinessObject -match "."){
               $BusinessObject = $BusinessObject.replace(".","__")
            }

            #Default the Top value to 100. (Maximum allowed record query per API call.)
            if (!$Top -or ($Top -gt 100)){
                $InitialTop = "100"
            } else {
                $InitialTop = $Top
            }

            $URI = "$Tenant/api/odata/businessobject/$BusinessObject`?`$top=$InitialTop"
            break
        }
    }

    
    try {
      
        $Query = Invoke-RestMethod -Method GET -uri $URI -headers $Headers -TimeoutSec $SparkQueryTimeoutSec
        
        if (!$Query){
            
            #Write-Host "No results returned for the $BusinessObject Business Object!" -ForegroundColor yellow

        } elseif ($Query.Edmx){

            $Query.edmx.DataServices.schema | Add-Member -Value $BusinessObject -MemberType NoteProperty -Name "QueriedBusinessObject" -Force

            #Return the DataServices.schema of the object
            $Results = $Query.edmx.DataServices.schema

        } elseif ($Query.Value){

            if ($DetailedOutput){
                Write-Host "The $BusinessObject business object has $($Query.'@odata.count') total records."
            }
           
            $Results = @()

            if (($Query.Value | Measure-Object).count -eq $Top -and ![string]::isnullorempty($Top)){
                $Query.'@odata.count' = $Top
                $Results = $Query.Value
            } elseif (($Query.Value | Measure-Object).count -gt $Top -and ![string]::isnullorempty($Top)){
                $Query.'@odata.count' = $Top
                $Results = $Query.Value | Select-Object -First $Top
            } elseif (($Query.Value | Measure-Object).count -lt $Top -and ![string]::isnullorempty($Top)){
                if (!$Top -gt $Query.'@odata.count'){
                    $Query.'@odata.count' = $Top
                }
                $Results += $Query.Value
            } else {
                $Results += $Query.Value
            }


            if ($Results.Count -ne $Query.'@odata.count'){
                
                $ExitLoop = $false

                Do {

                    $Skip = $Results.Count 
                    
                    $URI = "$Tenant/api/odata/businessobject/$BusinessObject`?`$top=100&`$skip=$Skip"

                    $SkipQuery = Invoke-RestMethod -Method GET -uri $URI -headers $Headers -TimeoutSec $SparkQueryTimeoutSec

                    if ($SkipQuery){
                        $Results += $SkipQuery.Value

                        if ($Results.count -gt $Top -and ![string]::isnullorempty($Top)){
                            $Results = $Results | Select-Object -First $Top
                            $ExitLoop = $true
                        }

                    } else {
                        write-warning "Failed to query for $BusinessObject! (Skip: $Skip | Total: $($Query.'@odata.count')) "
                        $ExitLoop = $true
                    }

                } until (($Results.Count -ge $Query.'@odata.count') -or $ExitLoop)

            }

        } else {
            $Results = $Query
        }

        #Friendly little helper loop to parse employee business object MemberOf HTML fields into a proper array.
        if ($BusinessObject -eq "employees" -and (!$Metadata)){

            Foreach ($Employee in $Results){

                $Employee | Add-Member -MemberType NoteProperty -Value @() -Name "SHS_MemberOfParsed" -force
                
                if ($Employee.SHS_MemberOf){
                    $Employee.SHS_MemberOfParsed = $Employee.SHS_MemberOf.replace("<br>","%").split("%").trim()
                    $Employee.SHS_MemberOfParsed = $Employee.SHS_MemberOfParsed | Sort-Object
                }

            }
 
        }

        if ($PropertyList -and $Results -or $PropertyMatch -and $Results){

            if ($Results.EntityType.count){
                $BO_Properties = $Results.EntityType[-1].Property | Where-Object {$_.Name -ne "RecID"} | Sort-Object "Name"
            } else {
                $BO_Properties = $Results.EntityType.Property | Where-Object {$_.Name -ne "RecID"} | Sort-Object "Name"
            }

            if ($PropertyMatch){
                $BO_Properties = $BO_Properties | Where-Object {$_.Name -match $PropertyMatch}
            }

            return $BO_Properties

        } else {
            return $Results
        }

        
}

catch {
    write-warning "Failed querying Spark!"
}




}#END FUNCTION


New-Alias -Name Get-ISMBO -Value Get-ISMBusinessObject -Force




