PowerInfoblox.psm1

function Get-IPAddressRangeInformation { 
    <#
    .SYNOPSIS
    Provides information about IP Address range
 
    .DESCRIPTION
    Provides information about IP Address range
 
    .PARAMETER Network
    Network in form of IP/NetworkLength (e.g. 10.2.10.0/24')
 
    .PARAMETER IPAddress
    IP Address to use
 
    .PARAMETER NetworkLength
    Network length to use
 
    .PARAMETER CIDRObject
    CIDRObject to use
 
    .EXAMPLE
    $CidrObject = @{
        Ip = '10.2.10.0'
        NetworkLength = 24
    }
    Get-IPAddressRangeInformation -CIDRObject $CidrObject | Format-Table
 
    .EXAMPLE
    Get-IPAddressRangeInformation -Network '10.2.10.0/24' | Format-Table
 
    .EXAMPLE
    Get-IPAddressRangeInformation -IPAddress '10.2.10.0' -NetworkLength 24 | Format-Table
 
    .NOTES
    General notes
    #>

    [cmdletBinding(DefaultParameterSetName = 'Network')]
    param(
        [Parameter(ParameterSetName = 'Network', Mandatory)][string] $Network,
        [Parameter(ParameterSetName = 'IPAddress', Mandatory)][string] $IPAddress,
        [Parameter(ParameterSetName = 'IPAddress', Mandatory)][int] $NetworkLength,
        [Parameter(ParameterSetName = 'CIDR', Mandatory)][psobject] $CIDRObject
    )
    $IPv4Regex = '(?:(?:0?0?\d|0?[1-9]\d|1\d\d|2[0-5][0-5]|2[0-4]\d)\.){3}(?:0?0?\d|0?[1-9]\d|1\d\d|2[0-5][0-5]|2[0-4]\d)'

    if ($Network) {
        $CIDRObject = @{
            Ip            = $Network.Split('/')[0]
            NetworkLength = $Network.Split('/')[1]
        }
    }
    elseif ($IPAddress -and $NetworkLength) {
        $CIDRObject = @{
            Ip            = $IPAddress
            NetworkLength = $NetworkLength
        }
    }
    elseif ($CIDRObject) {
    }
    else {
        Write-Error "Get-IPAddressRangeInformation - Invalid parameters specified"
        return
    }

    $o = [ordered] @{}
    $o.IP = [string] $CIDRObject.IP
    $o.BinaryIP = Convert-IPToBinary $o.IP
    if (-not $o.BinaryIP) {
        return
    }
    $o.NetworkLength = [int32] $CIDRObject.NetworkLength
    $o.SubnetMask = Convert-BinaryToIP ('1' * $o.NetworkLength).PadRight(32, '0')
    $o.BinarySubnetMask = ('1' * $o.NetworkLength).PadRight(32, '0')
    $o.BinaryNetworkAddress = $o.BinaryIP.SubString(0, $o.NetworkLength).PadRight(32, '0')
    if ($Contains) {
        if ($Contains -match "\A${IPv4Regex}\z") {

            return Test-IPIsInNetwork $Contains $o.BinaryNetworkAddress $o.BinaryNetworkAddress.SubString(0, $o.NetworkLength).PadRight(32, '1')
        }
        else {
            Write-Error "Get-IPAddressRangeInformation - Invalid IPv4 address specified with -Contains"
            return
        }
    }
    $o.NetworkAddress = Convert-BinaryToIP $o.BinaryNetworkAddress
    if ($o.NetworkLength -eq 32 -or $o.NetworkLength -eq 31) {
        $o.HostMin = $o.IP
    }
    else {
        $o.HostMin = Convert-BinaryToIP ([System.Convert]::ToString(([System.Convert]::ToInt64($o.BinaryNetworkAddress, 2) + 1), 2)).PadLeft(32, '0')
    }
    [string] $BinaryBroadcastIP = $o.BinaryNetworkAddress.SubString(0, $o.NetworkLength).PadRight(32, '1') 
    $o.BinaryBroadcast = $BinaryBroadcastIP
    [int64] $DecimalHostMax = [System.Convert]::ToInt64($BinaryBroadcastIP, 2) - 1
    [string] $BinaryHostMax = [System.Convert]::ToString($DecimalHostMax, 2).PadLeft(32, '0')
    $o.HostMax = Convert-BinaryToIP $BinaryHostMax
    $o.TotalHosts = [int64][System.Convert]::ToString(([System.Convert]::ToInt64($BinaryBroadcastIP, 2) - [System.Convert]::ToInt64($o.BinaryNetworkAddress, 2) + 1))
    $o.UsableHosts = $o.TotalHosts - 2

    if ($o.NetworkLength -eq 32) {
        $o.Broadcast = $Null
        $o.UsableHosts = [int64] 1
        $o.TotalHosts = [int64] 1
        $o.HostMax = $o.IP
    }
    elseif ($o.NetworkLength -eq 31) {
        $o.Broadcast = $Null
        $o.UsableHosts = [int64] 2
        $o.TotalHosts = [int64] 2

        [int64] $DecimalHostMax2 = [System.Convert]::ToInt64($BinaryBroadcastIP, 2) 
        [string] $BinaryHostMax2 = [System.Convert]::ToString($DecimalHostMax2, 2).PadLeft(32, '0')
        $o.HostMax = Convert-BinaryToIP $BinaryHostMax2
    }
    elseif ($o.NetworkLength -eq 30) {
        $o.UsableHosts = [int64] 2
        $o.TotalHosts = [int64] 4
        $o.Broadcast = Convert-BinaryToIP $BinaryBroadcastIP
    }
    else {
        $o.Broadcast = Convert-BinaryToIP $BinaryBroadcastIP
    }
    if ($Enumerate) {
        $IPRange = @(Get-IPRange $o.BinaryNetworkAddress $o.BinaryNetworkAddress.SubString(0, $o.NetworkLength).PadRight(32, '1'))
        if ((31, 32) -notcontains $o.NetworkLength ) {
            $IPRange = $IPRange[1..($IPRange.Count - 1)] 
            $IPRange = $IPRange[0..($IPRange.Count - 2)] 
        }
        $o.IPEnumerated = $IPRange
    }
    else {
        $o.IPEnumerated = @()
    }
    [PSCustomObject]$o
}
function Join-UriQuery { 
    <#
    .SYNOPSIS
    Provides ability to join two Url paths together including advanced querying
 
    .DESCRIPTION
    Provides ability to join two Url paths together including advanced querying which is useful for RestAPI/GraphApi calls
 
    .PARAMETER BaseUri
    Primary Url to merge
 
    .PARAMETER RelativeOrAbsoluteUri
    Additional path to merge with primary url (optional)
 
    .PARAMETER QueryParameter
    Parameters and their values in form of hashtable
 
    .PARAMETER EscapeUriString
    If set, will escape the url string
 
    .EXAMPLE
    Join-UriQuery -BaseUri 'https://evotec.xyz/' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts' -QueryParameter @{
        page = 1
        per_page = 20
        search = 'SearchString'
    }
 
    .EXAMPLE
    Join-UriQuery -BaseUri 'https://evotec.xyz/wp-json/wp/v2/posts' -QueryParameter @{
        page = 1
        per_page = 20
        search = 'SearchString'
    }
 
    .EXAMPLE
    Join-UriQuery -BaseUri 'https://evotec.xyz' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts'
 
    .NOTES
    General notes
    #>

    [alias('Join-UrlQuery')]
    [CmdletBinding()]
    param (
        [parameter(Mandatory)][uri] $BaseUri,
        [parameter(Mandatory = $false)][uri] $RelativeOrAbsoluteUri,
        [Parameter()][System.Collections.IDictionary] $QueryParameter,
        [alias('EscapeUrlString')][switch] $EscapeUriString
    )
    Begin {
        Add-Type -AssemblyName System.Web
    }
    Process {

        if ($BaseUri -and $RelativeOrAbsoluteUri) {
            $Url = Join-Uri -BaseUri $BaseUri -RelativeOrAbsoluteUri $RelativeOrAbsoluteUri
        }
        else {
            $Url = $BaseUri
        }

        if ($QueryParameter) {
            $Collection = [System.Web.HttpUtility]::ParseQueryString([String]::Empty)
            foreach ($key in $QueryParameter.Keys) {
                $Collection.Add($key, $QueryParameter.$key)
            }
        }

        $uriRequest = [System.UriBuilder] $Url
        if ($Collection) {
            $uriRequest.Query = $Collection.ToString()
        }
        if (-not $EscapeUriString) {
            $uriRequest.Uri.AbsoluteUri
        }
        else {
            [System.Uri]::EscapeUriString($uriRequest.Uri.AbsoluteUri)
        }
    }
}
function Remove-EmptyValue { 
    [alias('Remove-EmptyValues')]
    [CmdletBinding()]
    param(
        [alias('Splat', 'IDictionary')][Parameter(Mandatory)][System.Collections.IDictionary] $Hashtable,
        [string[]] $ExcludeParameter,
        [switch] $Recursive,
        [int] $Rerun,
        [switch] $DoNotRemoveNull,
        [switch] $DoNotRemoveEmpty,
        [switch] $DoNotRemoveEmptyArray,
        [switch] $DoNotRemoveEmptyDictionary
    )
    foreach ($Key in [string[]] $Hashtable.Keys) {
        if ($Key -notin $ExcludeParameter) {
            if ($Recursive) {
                if ($Hashtable[$Key] -is [System.Collections.IDictionary]) {
                    if ($Hashtable[$Key].Count -eq 0) {
                        if (-not $DoNotRemoveEmptyDictionary) {
                            $Hashtable.Remove($Key)
                        }
                    }
                    else {
                        Remove-EmptyValue -Hashtable $Hashtable[$Key] -Recursive:$Recursive
                    }
                }
                else {
                    if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) {
                        $Hashtable.Remove($Key)
                    }
                    elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') {
                        $Hashtable.Remove($Key)
                    }
                    elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) {
                        $Hashtable.Remove($Key)
                    }
                }
            }
            else {
                if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) {
                    $Hashtable.Remove($Key)
                }
                elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') {
                    $Hashtable.Remove($Key)
                }
                elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) {
                    $Hashtable.Remove($Key)
                }
            }
        }
    }
    if ($Rerun) {
        for ($i = 0; $i -lt $Rerun; $i++) {
            Remove-EmptyValue -Hashtable $Hashtable -Recursive:$Recursive
        }
    }
}
function Select-Properties { 
    <#
    .SYNOPSIS
    Allows for easy selecting property names from one or multiple objects
 
    .DESCRIPTION
    Allows for easy selecting property names from one or multiple objects. This is especially useful with using AllProperties parameter where we want to make sure to get all properties from all objects.
 
    .PARAMETER Objects
    One or more objects
 
    .PARAMETER Property
    Properties to include
 
    .PARAMETER ExcludeProperty
    Properties to exclude
 
    .PARAMETER AllProperties
    All unique properties from all objects
 
    .PARAMETER PropertyNameReplacement
    Default property name when object has no properties
 
    .EXAMPLE
    $Object1 = [PSCustomobject] @{
        Name1 = '1'
        Name2 = '3'
        Name3 = '5'
    }
    $Object2 = [PSCustomobject] @{
        Name4 = '2'
        Name5 = '6'
        Name6 = '7'
    }
 
    Select-Properties -Objects $Object1, $Object2 -AllProperties
 
    #OR:
 
    $Object1, $Object2 | Select-Properties -AllProperties -ExcludeProperty Name6 -Property Name3
 
    .EXAMPLE
    $Object3 = [Ordered] @{
        Name1 = '1'
        Name2 = '3'
        Name3 = '5'
    }
    $Object4 = [Ordered] @{
        Name4 = '2'
        Name5 = '6'
        Name6 = '7'
    }
 
    Select-Properties -Objects $Object3, $Object4 -AllProperties
 
    $Object3, $Object4 | Select-Properties -AllProperties
 
    .NOTES
    General notes
    #>

    [CmdLetBinding()]
    param(
        [Array][Parameter(Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] $Objects,
        [string[]] $Property,
        [string[]] $ExcludeProperty,
        [switch] $AllProperties,
        [string] $PropertyNameReplacement = '*'
    )
    Begin {
        function Select-Unique {
            [CmdLetBinding()]
            param(
                [System.Collections.IList] $Object
            )
            [Array] $CleanedList = foreach ($O in $Object) {
                if ($null -ne $O) {
                    $O
                }
            }
            $New = $CleanedList.ToLower() | Select-Object -Unique
            $Selected = foreach ($_ in $New) {
                $Index = $Object.ToLower().IndexOf($_)
                if ($Index -ne -1) {
                    $Object[$Index]
                }
            }
            $Selected
        }
        $ObjectsList = [System.Collections.Generic.List[Object]]::new()
    }
    Process {
        foreach ($Object in $Objects) {
            $ObjectsList.Add($Object)
        }
    }
    End {
        if ($ObjectsList.Count -eq 0) {
            Write-Warning 'Select-Properties - Unable to process. Objects count equals 0.'
            return
        }
        if ($ObjectsList[0] -is [System.Collections.IDictionary]) {
            if ($AllProperties) {
                [Array] $All = foreach ($_ in $ObjectsList) {
                    $_.Keys
                }

                $FirstObjectProperties = Select-Unique -Object $All
            }
            else {
                $FirstObjectProperties = $ObjectsList[0].Keys
            }
            if ($Property.Count -gt 0 -and $ExcludeProperty.Count -gt 0) {

                $FirstObjectProperties = foreach ($_ in $FirstObjectProperties) {
                    if ($Property -contains $_ -and $ExcludeProperty -notcontains $_) {
                        $_
                        continue
                    }
                }
            }
            elseif ($Property.Count -gt 0) {

                $FirstObjectProperties = foreach ($_ in $FirstObjectProperties) {
                    if ($Property -contains $_) {
                        $_
                        continue
                    }
                }
            }
            elseif ($ExcludeProperty.Count -gt 0) {

                $FirstObjectProperties = foreach ($_ in $FirstObjectProperties) {
                    if ($ExcludeProperty -notcontains $_) {
                        $_
                        continue
                    }
                }
            }
        }
        elseif ($ObjectsList[0].GetType().Name -match 'bool|byte|char|datetime|decimal|double|ExcelHyperLink|float|int|long|sbyte|short|string|timespan|uint|ulong|URI|ushort') {
            $FirstObjectProperties = $PropertyNameReplacement
        }
        else {
            if ($Property.Count -gt 0 -and $ExcludeProperty.Count -gt 0) {
                $ObjectsList = $ObjectsList | Select-Object -Property $Property -ExcludeProperty $ExcludeProperty
            }
            elseif ($Property.Count -gt 0) {
                $ObjectsList = $ObjectsList | Select-Object -Property $Property 
            }
            elseif ($ExcludeProperty.Count -gt 0) {
                $ObjectsList = $ObjectsList | Select-Object -Property '*' -ExcludeProperty $ExcludeProperty
            }
            if ($AllProperties) {
                [Array] $All = foreach ($_ in $ObjectsList) {
                    $ListProperties = $_.PSObject.Properties.Name
                    if ($null -ne $ListProperties) {
                        $ListProperties
                    }
                }

                $FirstObjectProperties = Select-Unique -Object $All
            }
            else {
                $FirstObjectProperties = $ObjectsList[0].PSObject.Properties.Name
            }
        }
        $FirstObjectProperties
    }
}
function Write-Color { 
    <#
    .SYNOPSIS
    Write-Color is a wrapper around Write-Host delivering a lot of additional features for easier color options.
 
    .DESCRIPTION
    Write-Color is a wrapper around Write-Host delivering a lot of additional features for easier color options.
 
    It provides:
    - Easy manipulation of colors,
    - Logging output to file (log)
    - Nice formatting options out of the box.
    - Ability to use aliases for parameters
 
    .PARAMETER Text
    Text to display on screen and write to log file if specified.
    Accepts an array of strings.
 
    .PARAMETER Color
    Color of the text. Accepts an array of colors. If more than one color is specified it will loop through colors for each string.
    If there are more strings than colors it will start from the beginning.
    Available colors are: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
 
    .PARAMETER BackGroundColor
    Color of the background. Accepts an array of colors. If more than one color is specified it will loop through colors for each string.
    If there are more strings than colors it will start from the beginning.
    Available colors are: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
 
    .PARAMETER StartTab
    Number of tabs to add before text. Default is 0.
 
    .PARAMETER LinesBefore
    Number of empty lines before text. Default is 0.
 
    .PARAMETER LinesAfter
    Number of empty lines after text. Default is 0.
 
    .PARAMETER StartSpaces
    Number of spaces to add before text. Default is 0.
 
    .PARAMETER LogFile
    Path to log file. If not specified no log file will be created.
 
    .PARAMETER DateTimeFormat
    Custom date and time format string. Default is yyyy-MM-dd HH:mm:ss
 
    .PARAMETER LogTime
    If set to $true it will add time to log file. Default is $true.
 
    .PARAMETER LogRetry
    Number of retries to write to log file, in case it can't write to it for some reason, before skipping. Default is 2.
 
    .PARAMETER Encoding
    Encoding of the log file. Default is Unicode.
 
    .PARAMETER ShowTime
    Switch to add time to console output. Default is not set.
 
    .PARAMETER NoNewLine
    Switch to not add new line at the end of the output. Default is not set.
 
    .PARAMETER NoConsoleOutput
    Switch to not output to console. Default all output goes to console.
 
    .EXAMPLE
    Write-Color -Text "Red ", "Green ", "Yellow " -Color Red,Green,Yellow
 
    .EXAMPLE
    Write-Color -Text "This is text in Green ",
                      "followed by red ",
                      "and then we have Magenta... ",
                      "isn't it fun? ",
                      "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan
 
    .EXAMPLE
    Write-Color -Text "This is text in Green ",
                      "followed by red ",
                      "and then we have Magenta... ",
                      "isn't it fun? ",
                      "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan -StartTab 3 -LinesBefore 1 -LinesAfter 1
 
    .EXAMPLE
    Write-Color "1. ", "Option 1" -Color Yellow, Green
    Write-Color "2. ", "Option 2" -Color Yellow, Green
    Write-Color "3. ", "Option 3" -Color Yellow, Green
    Write-Color "4. ", "Option 4" -Color Yellow, Green
    Write-Color "9. ", "Press 9 to exit" -Color Yellow, Gray -LinesBefore 1
 
    .EXAMPLE
    Write-Color -LinesBefore 2 -Text "This little ","message is ", "written to log ", "file as well." `
                -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt" -TimeFormat "yyyy-MM-dd HH:mm:ss"
    Write-Color -Text "This can get ","handy if ", "want to display things, and log actions to file ", "at the same time." `
                -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt"
 
    .EXAMPLE
    Write-Color -T "My text", " is ", "all colorful" -C Yellow, Red, Green -B Green, Green, Yellow
    Write-Color -t "my text" -c yellow -b green
    Write-Color -text "my text" -c red
 
    .EXAMPLE
    Write-Color -Text "TestujÄ™ czy siÄ™ Å‚adnie zapisze, czy bÄ™dÄ… problemy" -Encoding unicode -LogFile 'C:\temp\testinggg.txt' -Color Red -NoConsoleOutput
 
    .NOTES
    Understanding Custom date and time format strings: https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings
    Project support: https://github.com/EvotecIT/PSWriteColor
    Original idea: Josh (https://stackoverflow.com/users/81769/josh)
 
    #>

    [alias('Write-Colour')]
    [CmdletBinding()]
    param (
        [alias ('T')] [String[]]$Text,
        [alias ('C', 'ForegroundColor', 'FGC')] [ConsoleColor[]]$Color = [ConsoleColor]::White,
        [alias ('B', 'BGC')] [ConsoleColor[]]$BackGroundColor = $null,
        [alias ('Indent')][int] $StartTab = 0,
        [int] $LinesBefore = 0,
        [int] $LinesAfter = 0,
        [int] $StartSpaces = 0,
        [alias ('L')] [string] $LogFile = '',
        [Alias('DateFormat', 'TimeFormat')][string] $DateTimeFormat = 'yyyy-MM-dd HH:mm:ss',
        [alias ('LogTimeStamp')][bool] $LogTime = $true,
        [int] $LogRetry = 2,
        [ValidateSet('unknown', 'string', 'unicode', 'bigendianunicode', 'utf8', 'utf7', 'utf32', 'ascii', 'default', 'oem')][string]$Encoding = 'Unicode',
        [switch] $ShowTime,
        [switch] $NoNewLine,
        [alias('HideConsole')][switch] $NoConsoleOutput
    )
    if (-not $NoConsoleOutput) {
        $DefaultColor = $Color[0]
        if ($null -ne $BackGroundColor -and $BackGroundColor.Count -ne $Color.Count) {
            Write-Error "Colors, BackGroundColors parameters count doesn't match. Terminated."
            return
        }
        if ($LinesBefore -ne 0) {
            for ($i = 0; $i -lt $LinesBefore; $i++) {
                Write-Host -Object "`n" -NoNewline 
            } 
        } # Add empty line before
        if ($StartTab -ne 0) {
            for ($i = 0; $i -lt $StartTab; $i++) {
                Write-Host -Object "`t" -NoNewline 
            } 
        }  # Add TABS before text
        if ($StartSpaces -ne 0) {
            for ($i = 0; $i -lt $StartSpaces; $i++) {
                Write-Host -Object ' ' -NoNewline 
            } 
        }  # Add SPACES before text
        if ($ShowTime) {
            Write-Host -Object "[$([datetime]::Now.ToString($DateTimeFormat))] " -NoNewline 
        } # Add Time before output
        if ($Text.Count -ne 0) {
            if ($Color.Count -ge $Text.Count) {
                # the real deal coloring
                if ($null -eq $BackGroundColor) {
                    for ($i = 0; $i -lt $Text.Length; $i++) {
                        Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -NoNewline 
                    }
                }
                else {
                    for ($i = 0; $i -lt $Text.Length; $i++) {
                        Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -BackgroundColor $BackGroundColor[$i] -NoNewline 
                    }
                }
            }
            else {
                if ($null -eq $BackGroundColor) {
                    for ($i = 0; $i -lt $Color.Length ; $i++) {
                        Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -NoNewline 
                    }
                    for ($i = $Color.Length; $i -lt $Text.Length; $i++) {
                        Write-Host -Object $Text[$i] -ForegroundColor $DefaultColor -NoNewline 
                    }
                }
                else {
                    for ($i = 0; $i -lt $Color.Length ; $i++) {
                        Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -BackgroundColor $BackGroundColor[$i] -NoNewline 
                    }
                    for ($i = $Color.Length; $i -lt $Text.Length; $i++) {
                        Write-Host -Object $Text[$i] -ForegroundColor $DefaultColor -BackgroundColor $BackGroundColor[0] -NoNewline 
                    }
                }
            }
        }
        if ($NoNewLine -eq $true) {
            Write-Host -NoNewline 
        }
        else {
            Write-Host 
        } # Support for no new line
        if ($LinesAfter -ne 0) {
            for ($i = 0; $i -lt $LinesAfter; $i++) {
                Write-Host -Object "`n" -NoNewline 
            } 
        }  # Add empty line after
    }
    if ($Text.Count -and $LogFile) {
        # Save to file
        $TextToFile = ""
        for ($i = 0; $i -lt $Text.Length; $i++) {
            $TextToFile += $Text[$i]
        }
        $Saved = $false
        $Retry = 0
        Do {
            $Retry++
            try {
                if ($LogTime) {
                    "[$([datetime]::Now.ToString($DateTimeFormat))] $TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop -WhatIf:$false
                }
                else {
                    "$TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop -WhatIf:$false
                }
                $Saved = $true
            }
            catch {
                if ($Saved -eq $false -and $Retry -eq $LogRetry) {
                    Write-Warning "Write-Color - Couldn't write to log file $($_.Exception.Message). Tried ($Retry/$LogRetry))"
                }
                else {
                    Write-Warning "Write-Color - Couldn't write to log file $($_.Exception.Message). Retrying... ($Retry/$LogRetry)"
                }
            }
        } Until ($Saved -eq $true -or $Retry -ge $LogRetry)
    }
}
function Convert-BinaryToIP { 
    [cmdletBinding()]
    param(
        [string] $Binary
    )
    $Binary = $Binary -replace '\s+'
    if ($Binary.Length % 8) {
        Write-Warning -Message "Convert-BinaryToIP - Binary string '$Binary' is not evenly divisible by 8."
        return $Null
    }
    [int] $NumberOfBytes = $Binary.Length / 8
    $Bytes = @(foreach ($i in 0..($NumberOfBytes - 1)) {
            try {

                [System.Convert]::ToByte($Binary.Substring(($i * 8), 8), 2)
            }
            catch {
                Write-Warning -Message "Convert-BinaryToIP - Error converting '$Binary' to bytes. `$i was $i."
                return $Null
            }
        })
    return $Bytes -join '.'
}
function Convert-IPToBinary { 
    [cmdletBinding()]
    param(
        [string] $IP
    )
    $IPv4Regex = '(?:(?:0?0?\d|0?[1-9]\d|1\d\d|2[0-5][0-5]|2[0-4]\d)\.){3}(?:0?0?\d|0?[1-9]\d|1\d\d|2[0-5][0-5]|2[0-4]\d)'
    $IP = $IP.Trim()
    if ($IP -match "\A${IPv4Regex}\z") {
        try {
            return ($IP.Split('.') | ForEach-Object { [System.Convert]::ToString([byte] $_, 2).PadLeft(8, '0') }) -join ''
        }
        catch {
            Write-Warning -Message "Convert-IPToBinary - Error converting '$IP' to a binary string: $_"
            return $Null
        }
    }
    else {
        Write-Warning -Message "Convert-IPToBinary - Invalid IP detected: '$IP'. Conversion failed."
        return $Null
    }
}
function Get-IPRange { 
    [cmdletBinding()]
    param(
        [string] $StartBinary,
        [string] $EndBinary
    )
    [int64] $StartInt = [System.Convert]::ToInt64($StartBinary, 2)
    [int64] $EndInt = [System.Convert]::ToInt64($EndBinary, 2)
    for ($BinaryIP = $StartInt; $BinaryIP -le $EndInt; $BinaryIP++) {
        Convert-BinaryToIP ([System.Convert]::ToString($BinaryIP, 2).PadLeft(32, '0'))
    }
}
function Join-Uri { 
    <#
    .SYNOPSIS
    Provides ability to join two Url paths together
 
    .DESCRIPTION
    Provides ability to join two Url paths together
 
    .PARAMETER BaseUri
    Primary Url to merge
 
    .PARAMETER RelativeOrAbsoluteUri
    Additional path to merge with primary url
 
    .EXAMPLE
    Join-Uri 'https://evotec.xyz/' '/wp-json/wp/v2/posts'
 
    .EXAMPLE
    Join-Uri 'https://evotec.xyz/' 'wp-json/wp/v2/posts'
 
    .EXAMPLE
    Join-Uri -BaseUri 'https://evotec.xyz/' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts'
 
    .EXAMPLE
    Join-Uri -BaseUri 'https://evotec.xyz/test/' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts'
 
    .NOTES
    General notes
    #>

    [alias('Join-Url')]
    [cmdletBinding()]
    param(
        [parameter(Mandatory)][uri] $BaseUri,
        [parameter(Mandatory)][uri] $RelativeOrAbsoluteUri
    )

    return ($BaseUri.OriginalString.TrimEnd('/') + "/" + $RelativeOrAbsoluteUri.OriginalString.TrimStart('/'))
}
function Test-IPIsInNetwork { 
    [cmdletBinding()]
    param(
        [string] $IP,
        [string] $StartBinary,
        [string] $EndBinary
    )
    $TestIPBinary = Convert-IPToBinary $IP
    [int64] $TestIPInt64 = [System.Convert]::ToInt64($TestIPBinary, 2)
    [int64] $StartInt64 = [System.Convert]::ToInt64($StartBinary, 2)
    [int64] $EndInt64 = [System.Convert]::ToInt64($EndBinary, 2)
    if ($TestIPInt64 -ge $StartInt64 -and $TestIPInt64 -le $EndInt64) {
        return $True
    }
    else {
        return $False
    }
}
function Convert-IpAddressToPtrString {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$IPAddress
    )

    # Split the IP address into its octets
    $octets = $IPAddress -split "\."

    # Reverse the octets
    [array]::Reverse($octets)

    # Join the reversed octets with dots and append the standard PTR suffix
    $ptrString = ($octets -join ".") + ".in-addr.arpa"

    $ptrString
}
function Get-FieldsFromSchema {
    [CmdletBinding()]
    param(
        [PSCustomobject] $Schema,
        [string] $SchemaObject
    )
    if (-not $Script:InfobloxSchemaFields) {
        $Script:InfobloxSchemaFields = [ordered] @{}
    }
    if ($Script:InfobloxSchemaFields[$SchemaObject]) {
        $Script:InfobloxSchemaFields[$SchemaObject]
    }
    else {
        if (-not $Schema) {
            $Schema = Get-InfobloxSchema -Object $SchemaObject
        }
        if ($Schema -and $Schema.fields.name) {
            $FilteredFields = foreach ($Field in $Schema.fields) {
                if ($Field.supports -like "*r*") {
                    $Field.Name
                }
            }
            $Script:InfobloxSchemaFields[$SchemaObject] = ($FilteredFields -join ',')
            $Script:InfobloxSchemaFields[$SchemaObject]
        }
        else {
            Write-Warning -Message "Get-FieldsFromSchema - Failed to fetch schema for record type 'allrecords'. Using defaults"
        }
    }
}
function Hide-SelfSignedCerts {
    [cmdletbinding()]
    param(

    )
    if ($PSVersionTable.PSVersion.Major -gt 5) {
        #Write-Warning -Message "Hide-SelfSignedCerts - This function is only supported in PowerShell 6 and later"
        $Script:InfobloxConfiguration['SkipCertificateValidation'] = $true
        return
    }
    try {
        Add-Type -TypeDefinition @"
        using System.Net;
        using System.Security.Cryptography.X509Certificates;
        public class TrustAllCertsPolicy : ICertificatePolicy {
            public bool CheckValidationResult(
                ServicePoint srvPoint, X509Certificate certificate,
                WebRequest request, int certificateProblem) {
                return true;
            }
        }
"@


        [System.Net.ServicePointManager]::CertificatePolicy = [TrustAllCertsPolicy]::new()
    }
    catch {
        Write-Warning -Message "Hide-SelfSignedCerts - Error when trying to hide self-signed certificates: $($_.Exception.Message)"
    }
}
function New-WebSession {
    [cmdletbinding()]
    param(
        [System.Collections.IDictionary]$Cookies,
        [Uri]$For
    )

    $newSession = [Microsoft.PowerShell.Commands.WebRequestSession]::new()

    foreach ($entry in $Cookies.GetEnumerator()) {
        $cookie = [System.Net.Cookie]::new($entry.Name, $entry.Value)
        if ($For) {
            $newSession.Cookies.Add([uri]::new($For, '/'), $cookie)
        }
        else {
            $newSession.Cookies.Add($cookie)
        }
    }

    $newSession
}
function Select-ObjectByProperty {
    [CmdletBinding()]
    param(
        [Parameter(Position = 0, ValueFromPipeline)][Array] $Object,
        [System.Collections.IDictionary] $AddObject,
        [alias('FirstProperty')][Parameter()][string[]] $FirstProperties,
        [alias('LastProperty')][Parameter()][string[]] $LastProperties,
        [string[]] $AllProperties
    )
    process {
        foreach ($O in $Object) {
            # If we have an object, we can get the properties from it
            # we assume user can provide AllProperties instead of current object properties
            $Properties = if ($AllProperties) {
                $AllProperties 
            }
            else {
                $O.PSObject.Properties.Name 
            }

            $ReturnObject = [ordered] @{}
            foreach ($Property in $FirstProperties) {
                if ($Properties -contains $Property) {
                    $ReturnObject[$Property] = $O.$Property
                }
            }
            if ($AddObject) {
                foreach ($Property in $AddObject.Keys) {
                    $ReturnObject[$Property] = $AddObject[$Property]
                }
            }
            foreach ($Property in $Properties) {
                if ($Property -notin $LastProperties -and $Property -notin $FirstProperties) {
                    $ReturnObject[$Property] = $O.$Property
                }
            }
            foreach ($Property in $LastProperties) {
                if ($Properties -contains $Property) {
                    $ReturnObject[$Property] = $O.$Property
                }
            }
            [pscustomobject]$ReturnObject
        }
    }
}
function Add-InfoBloxDNSRecord {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Name
    Parameter description
 
    .PARAMETER IPAddress
    Parameter description
 
    .PARAMETER CanonicalName
    Parameter description
 
    .PARAMETER PtrName
    Parameter description
 
    .PARAMETER Type
    Parameter description
 
    .EXAMPLE
    Add-InfoBloxDNSRecord -Name 'Test' -IPv4Address '10.10.10.10' -Type 'A'
 
    .EXAMPLE
    Add-InfoBloxDNSRecord -Name 'Test' -IPv4Address '10.10.10.10' -Type 'HOST'
 
    .EXAMPLE
    Add-InfoBloxDNSRecord -Name 'Test' -CanonicalName 'test2.mcdonalds.com' -Type 'CNAME'
 
    .NOTES
    General notes
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [string] $Name,
        [Alias('IPv4Address', 'IPv6Address')][string] $IPAddress,
        [string] $CanonicalName,
        [string] $PtrName,
        [string] $Text,
        [parameter(Mandatory)][ValidateSet(
            'A',
            #'AAAA',
            'CNAME',
            'HOST',
            'PTR'
            #'DName',
            #'DNSKEY', 'DS', 'Host', 'host_ipv4addr', 'host_ipv6addr',
            #'LBDN', 'MX', 'NAPTR', 'NS', 'NSEC',
            #'NSEC3', 'NSEC3PARAM', 'PTR', 'RRSIG', 'SRV', 'TXT'
        )]
        [string] $Type
    )
    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Add-InfoBloxDNSRecord - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    # Lets convert it to lowercase, since Infoblox is case sensitive
    $Type = $Type.ToLower()
    if ($Type -eq 'A') {
        Write-Verbose -Message "Add-InfoBloxDNSRecord - Adding $Type record $Name with IPAddress: '$IPAddress'"
        if ($Name -and $IPAddress) {
            $Body = [ordered] @{
                name     = $Name.ToLower()
                ipv4addr = $IPAddress
            }
        }
        else {
            if ($ErrorActionPreference -eq 'Stop') {
                throw "Add-InfoBloxDNSRecord - 'Name' and 'IPAddress' are required for $Type record"
            }
            Write-Warning -Message "'Name' and 'IPAddress' are required for $Type record"
            return
        }
    }
    elseif ($Type -eq 'CNAME') {
        Write-Verbose -Message "Add-InfoBloxDNSRecord - Adding $Type record $Name with IPAddress: '$IPAddress'"
        if ($Name -and $CanonicalName) {
            $Body = [ordered] @{
                name      = $Name.ToLower()
                canonical = $CanonicalName.ToLower()
            }
        }
        else {
            if ($ErrorActionPreference -eq 'Stop') {
                throw "'Name' and 'CanonicalName' are required for $Type record"
            }
            Write-Warning -Message "Add-InfoBloxDNSRecord - 'Name' and 'CanonicalName' are required for $Type record"
            return
        }
    }
    elseif ($Type -eq 'AAAA') {
        Write-Verbose -Message "Add-InfoBloxDNSRecord - Adding $Type record $Name with IPAddress: '$IPAddress'"
        if ($Name -and $IPAddress) {
            $Body = [ordered] @{
                name     = $Name.ToLower()
                ipv6addr = $IPAddress
            }
        }
        else {
            if ($ErrorActionPreference -eq 'Stop') {
                throw "'Name' and 'IPAddress' are required for $Type record"
            }
            Write-Warning -Message "Add-InfoBloxDNSRecord - 'Name' and 'IPAddress' are required for $Type record"
            return
        }
    }
    elseif ($Type -eq 'HOST') {
        Write-Verbose -Message "Add-InfoBloxDNSRecord - Adding $Type record $Name with IPAddress: '$IPAddress'"
        if ($Name -and $IPAddress) {
            $Body = [ordered] @{
                name      = $Name.ToLower()
                ipv4addrs = @(
                    @{
                        ipv4addr = $IPAddress
                    }
                )
            }
        }
        else {
            if ($ErrorActionPreference -eq 'Stop') {
                throw "'Name' and 'IPAddress' are required for '$Type' record"
            }
            Write-Warning -Message "Add-InfoBloxDNSRecord - 'Name' and 'IPAddress' are required for '$Type' record"
            return
        }
    }
    elseif ($Type -eq 'PTR') {
        Write-Verbose -Message "Add-InfoBloxDNSRecord - Adding $Type record $Name with IPAddress: '$IPAddress'"
        if ($Name -and $IPAddress -and $PtrName) {
            $Body = [ordered] @{
                name     = $Name.ToLower()
                ptrdname = $PtrName.ToLower()
                ipv4addr = $IPAddress
            }
        }
        else {
            if ($ErrorActionPreference -eq 'Stop') {
                throw "'Name' and 'IPAddress' and 'PtrName' are required for '$Type' record"
            }
            Write-Warning -Message "Add-InfoBloxDNSRecord - 'Name' and 'IPAddress' and 'PtrName' are required for '$Type' record"
            return
        }
    }
    elseif ($Type -eq 'TXT') {
        Write-Verbose -Message "Add-InfoBloxDNSRecord - Adding $Type record $Name with TEXT: '$Text'"
        if ($Name -and $IPAddress) {
            $Body = [ordered] @{
                name = $Name.ToLower()
                text = $Text.ToLower()
            }
        }
        else {
            if ($ErrorActionPreference -eq 'Stop') {
                throw "'Name' and 'Text' are required for '$Type' record"
            }
            Write-Warning -Message "Add-InfoBloxDNSRecord - 'Name' and 'Text' are required for '$Type' record"
            return
        }
    }
    else {
        # won't trigger, but lets leave it like that
        if ($ErrorActionPreference -eq 'Stop') {
            throw "Add-InfoBloxDNSRecord - Type $Type not supported"
        }
        Write-Warning -Message "Add-InfoBloxDNSRecord - Type $Type not supported"
        return
    }
    $invokeInfobloxQuerySplat = @{
        RelativeUri = "record:$Type"
        Method      = 'POST'
        Body        = $Body
    }

    $Output = Invoke-InfobloxQuery @invokeInfobloxQuerySplat #-WarningAction SilentlyContinue -WarningVariable varWarning
    if ($Output) {
        Write-Verbose -Message "Add-InfoBloxDNSRecord - Added $Type / $Output"
    }
    #else {
    # if (-not $WhatIfPreference) {
    #Write-Warning -Message "Add-InfoBloxDNSRecord - Failed to add $Type, error: $varWarning"
    # }
    #}
}
function Add-InfobloxFixedAddress {
    <#
    .SYNOPSIS
    Add a fixed mac address to an IP address on an Infoblox server
 
    .DESCRIPTION
    Add a fixed mac address to an IP address on an Infoblox server
    A fixed address is a specific IP address that a DHCP server always assigns when a lease request comes from
    a particular MAC address of the client. For example, if you have a printer in your network, you can reserve a
    particular IP address to be assigned to it every time it is turned on.
 
    .PARAMETER IPv4Address
    IPv4 address to add the mac address to
 
    .PARAMETER MacAddress
    Mac address to add to the IPv4 address
 
    .PARAMETER Name
    Name of the fixed address
 
    .PARAMETER Comment
    Comment for the fixed address
 
    .EXAMPLE
    Add-InfobloxFixedAddress -IPv4Address '10.2.2.18' -MacAddress '00:50:56:9A:00:01'
 
    .NOTES
    General notes
    #>

    [cmdletbinding(SupportsShouldProcess)]
    param(
        [ValidateNotNullOrEmpty()][parameter(Mandatory)][string] $IPv4Address,
        [ValidatePattern("([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$")][parameter(Mandatory)][string] $MacAddress,
        [string] $Name,
        [string] $Comment
    )

    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Add-InfobloxFixedAddress - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    Write-Verbose -Message "Add-InfobloxFixedAddress - Adding IPv4Address $IPv4Address to MacAddress $MacAddress"

    $invokeInfobloxQuerySplat = @{
        RelativeUri    = 'fixedaddress'
        Method         = 'POST'
        QueryParameter = @{
            ipv4addr = $IPv4Address
            mac      = $MacAddress.ToLower()
        }
    }
    if ($Name) {
        $invokeInfobloxQuerySplat.QueryParameter.name = $Name
    }
    if ($Comment) {
        $invokeInfobloxQuerySplat.QueryParameter.comment = $Comment
    }
    $Output = Invoke-InfobloxQuery @invokeInfobloxQuerySplat #-WarningAction SilentlyContinue -WarningVariable varWarning
    if ($Output) {
        Write-Verbose -Message "Add-InfobloxFixedAddress - Added $($Mac.ipv4addr) with mac address $($Mac.mac) / $Output"
    }
    #else {
    # if (-not $WhatIfPreference) {
    # Write-Warning -Message "Add-InfobloxFixedAddress - Failed to add $($Mac.ipv4addr) with mac address $($Mac.mac), error: $varWarning"
    # }
    #}
}
function Connect-Infoblox {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ParameterSetName = 'UserName')]
        [Parameter(Mandatory, ParameterSetName = 'Credential')]
        [string] $Server,

        [Parameter(Mandatory, ParameterSetName = 'UserName')][string] $Username,
        [alias('SecurePassword')][Parameter(Mandatory, ParameterSetName = 'UserName')][string] $EncryptedPassword,

        [Parameter(Mandatory, ParameterSetName = 'Credential')][pscredential] $Credential,

        [Parameter(ParameterSetName = 'UserName')]
        [Parameter(ParameterSetName = 'Credential')]
        [string] $ApiVersion = '1.0',
        [Parameter(ParameterSetName = 'UserName')]
        [Parameter(ParameterSetName = 'Credential')]
        [switch] $EnableTLS12,
        [Parameter(ParameterSetName = 'UserName')]
        [Parameter(ParameterSetName = 'Credential')]
        [switch] $AllowSelfSignedCerts,
        [Parameter(ParameterSetName = 'UserName')]
        [Parameter(ParameterSetName = 'Credential')]
        [switch] $SkipInitialConnection,
        [switch] $ReturnObject
    )

    if ($EnableTLS12) {
        [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
    }

    # lets clear sessions if any exists
    Disconnect-Infoblox

    if ($Username -and $EncryptedPassword) {
        try {
            $Password = $SecurePassword | ConvertTo-SecureString -ErrorAction Stop
            $Credential = [pscredential]::new($Username, $Password)
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                throw
            }
            Write-Warning -Message "Connect-Infoblox - Unable to convert password to secure string. Error: $($_.Exception.Message)"
            return
        }
    }

    $PSDefaultParameterValues['Invoke-InfobloxQuery:Credential'] = $Credential
    $PSDefaultParameterValues['Invoke-InfobloxQuery:Server'] = $Server
    $PSDefaultParameterValues['Invoke-InfobloxQuery:BaseUri'] = "https://$Server/wapi/v$apiVersion"
    $PSDefaultParameterValues['Invoke-InfobloxQuery:WebSession'] = [Microsoft.PowerShell.Commands.WebRequestSession]::new()

    # $WebSession = New-WebSession -Cookies @{
    # security_setting = @{
    # session_timeout = 60000
    # }
    # } -For $BaseUri

    # The infoblox configuration is not really used anywhere. It's just a placeholder
    $Script:InfobloxConfiguration = [ordered] @{
        ApiVersion = $ApiVersion
        #Credential = $Credential
        Server     = $Server
        BaseUri    = "https://$Server/wapi/v$apiVersion"
        # Create a WebSession object to store cookies
        # Session = New-Object Microsoft.PowerShell.Commands.WebRequestSession
        #WebSession = $WebSession
    }

    if ($AllowSelfSignedCerts) {
        Hide-SelfSignedCerts
    }

    # we do inital query to make sure we're connected
    if (-not $SkipInitialConnection) {
        $Schema = Get-InfobloxSchema -WarningAction SilentlyContinue
        if (-not $Schema) {
            Disconnect-Infoblox
            return
        }
    }

    if ($ReturnObject) {
        $Script:InfobloxConfiguration
    }
}
function Disconnect-Infoblox {
    <#
    .SYNOPSIS
    Disconnects from an InfoBlox server
 
    .DESCRIPTION
    Disconnects from an InfoBlox server
    As this is a REST API it doesn't really disconnect, but it does clear the script variable to clear the credentials from memory
 
    .EXAMPLE
    Disconnect-Infoblox
 
    .NOTES
    General notes
    #>

    [cmdletbinding(SupportsShouldProcess)]
    param(
        [switch] $ForceLogOut
    )

    if ($ForceLogOut) {
        $invokeInfobloxQuerySplat = @{
            RelativeUri = "logout"
            Method      = 'POST'
        }
        Invoke-InfobloxQuery @invokeInfobloxQuerySplat
    }
    # lets remove the default parameters so that user has to connect again
    $Script:InfobloxConfiguration = $null
    $PSDefaultParameterValues.Remove('Invoke-InfobloxQuery:Credential')
    $PSDefaultParameterValues.Remove('Invoke-InfobloxQuery:Server')
    $PSDefaultParameterValues.Remove('Invoke-InfobloxQuery:BaseUri')
    $PSDefaultParameterValues.Remove('Invoke-InfobloxQuery:WebSession')
}
function Get-InfobloxDHCPLease {
    [alias('Get-InfobloxDHCPLeases')]
    [CmdletBinding()]
    param(
        [string] $Network,
        [string] $IPv4Address,
        [string] $Hostname,
        [switch] $PartialMatch
    )
    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Get-InfobloxDHCPLease - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    Write-Verbose -Message "Get-InfobloxDHCPLease - Requesting DHCP leases for Network [$Network] / IPv4Address [$IPv4Address] / Hostname [$Hostname] / PartialMatch [$($PartialMatch.IsPresent)]"

    $invokeInfobloxQuerySplat = @{
        RelativeUri    = 'lease'
        Method         = 'GET'
        QueryParameter = @{
            _return_fields = 'binding_state,hardware,client_hostname,fingerprint,address,network_view'
            _max_results   = 1000000
        }
    }
    if ($Network) {
        if ($PartialMatch) {
            $invokeInfobloxQuerySplat.QueryParameter."network~" = $Network.ToLower()
        }
        else {
            $invokeInfobloxQuerySplat.QueryParameter.network = $Network.ToLower()
        }
    }
    if ($IPv4Address) {
        if ($PartialMatch) {
            $invokeInfobloxQuerySplat.QueryParameter."ipv4addr~" = $IPv4Address.ToLower()
        }
        else {
            $invokeInfobloxQuerySplat.QueryParameter.ipv4addr = $IPv4Address.ToLower()
        }
    }
    if ($Hostname) {
        if ($PartialMatch) {
            $invokeInfobloxQuerySplat.QueryParameter."name~" = $Hostname.ToLower()
        }
        else {
            $invokeInfobloxQuerySplat.QueryParameter.name = $Hostname.ToLower()
        }
    }
    Invoke-InfobloxQuery @invokeInfobloxQuerySplat -WhatIf:$false
}
function Get-InfobloxDHCPRange {
    [CmdletBinding()]
    param(
        [string] $Network,
        [switch] $PartialMatch,
        [switch] $FetchFromSchema
    )
    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Get-InfobloxDHCPRange - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }
    if ($Network) {
        Write-Verbose -Message "Get-InfobloxDHCPRange - Requesting DHCP ranges for Network [$Network] / PartialMatch [$($PartialMatch.IsPresent)]"
    }
    else {
        Write-Verbose -Message "Get-InfobloxDHCPRange - Requesting DHCP ranges for all networks"
    }

    if ($FetchFromSchema) {
        $ReturnFields = Get-FieldsFromSchema -SchemaObject "range"
    }
    else {
        $ReturnFields = $Null
    }
    $invokeInfobloxQuerySplat = @{
        RelativeUri    = 'range'
        Method         = 'GET'
        QueryParameter = @{
            _max_results   = 1000000
            _return_fields = $ReturnFields
        }
    }
    if ($Network) {
        if ($PartialMatch) {
            $invokeInfobloxQuerySplat.QueryParameter."network~" = $Network.ToLower()
        }
        else {
            $invokeInfobloxQuerySplat.QueryParameter.network = $Network.ToLower()
        }
    }
    Invoke-InfobloxQuery @invokeInfobloxQuerySplat -WhatIf:$false
}
function Get-InfobloxDiscoveryTask {
    [cmdletbinding()]
    param(
        [switch] $FetchFromSchema
    )

    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Get-InfobloxDiscoveryTask - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    # defalt return fields
    if ($FetchFromSchema) {
        $ReturnFields = Get-FieldsFromSchema -SchemaObject "discoverytask"
    }

    $invokeInfobloxQuerySplat = @{
        RelativeUri    = 'discoverytask'
        Method         = 'GET'
        QueryParameter = @{
            _max_results   = 1000000
            _return_fields = $ReturnFields
        }
    }
    $Output = Invoke-InfobloxQuery @invokeInfobloxQuerySplat -WhatIf:$false
    $Output | Select-ObjectByProperty -LastProperty '_ref'
}
function Get-InfobloxDNSAuthZone {
    [alias('Get-InfobloxDNSAuthZones')]
    [cmdletbinding()]
    param(
        [string] $FQDN,
        [string] $View
    )

    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Get-InfobloxDNSAuthZones - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    $invokeInfobloxQuerySplat = @{
        RelativeUri    = 'zone_auth'
        Method         = 'GET'
        QueryParameter = @{
            _max_results   = 1000000
            _return_fields = @(
                'address'
                'allow_active_dir'
                'allow_fixed_rrset_order'
                'allow_gss_tsig_for_underscore_zone'
                'allow_gss_tsig_zone_updates'
                'allow_query'
                'allow_transfer'
                'allow_update'
                'allow_update_forwarding'
                'aws_rte53_zone_info'
                'cloud_info'
                'comment'
                'copy_xfer_to_notify'
                'create_underscore_zones'
                'ddns_force_creation_timestamp_update'
                'ddns_principal_group'
                'ddns_principal_tracking'
                'ddns_restrict_patterns'
                'ddns_restrict_patterns_list'
                'ddns_restrict_protected'
                'ddns_restrict_secure'
                'ddns_restrict_static'
                'disable'
                'disable_forwarding'
                'display_domain'
                'dns_fqdn'
                'dns_integrity_enable'
                'dns_integrity_frequency'
                'dns_integrity_member'
                'dns_integrity_verbose_logging'
                'dns_soa_email'
                'dnssec_key_params'
                'dnssec_keys'
                'dnssec_ksk_rollover_date'
                'dnssec_zsk_rollover_date'
                'effective_check_names_policy'
                'effective_record_name_policy'
                'extattrs'
                'external_primaries'
                'external_secondaries'
                'fqdn'
                'grid_primary'
                'grid_primary_shared_with_ms_parent_delegation'
                'grid_secondaries'
                'is_dnssec_enabled'
                'is_dnssec_signed'
                'is_multimaster'
                'last_queried'
                'locked'
                'locked_by'
                'mask_prefix'
                'member_soa_mnames'
                'member_soa_serials'
                'ms_ad_integrated'
                'ms_allow_transfer'
                'ms_allow_transfer_mode'
                'ms_dc_ns_record_creation'
                'ms_ddns_mode'
                'ms_managed'
                'ms_primaries'
                'ms_read_only'
                'ms_secondaries'
                'ms_sync_disabled'
                'ms_sync_master_name'
                'network_associations'
                'network_view'
                'notify_delay'
                'ns_group'
                'parent'
                'prefix'
                'primary_type'
                'record_name_policy'
                'records_monitored'
                'rr_not_queried_enabled_time'
                'scavenging_settings'
                'soa_default_ttl'
                'soa_email'
                'soa_expire'
                'soa_negative_ttl'
                'soa_refresh'
                'soa_retry'
                'soa_serial_number'
                'srgs'
                'update_forwarding'
                'use_allow_active_dir'
                'use_allow_query'
                'use_allow_transfer'
                'use_allow_update'
                'use_allow_update_forwarding'
                'use_check_names_policy'
                'use_copy_xfer_to_notify'
                'use_ddns_force_creation_timestamp_update'
                'use_ddns_patterns_restriction'
                'use_ddns_principal_security'
                'use_ddns_restrict_protected'
                'use_ddns_restrict_static'
                'use_dnssec_key_params'
                'use_external_primary'
                'use_grid_zone_timer'
                'use_import_from'
                'use_notify_delay'
                'use_record_name_policy'
                'use_scavenging_settings'
                'use_soa_email'
                'using_srg_associations'
                'view'
                'zone_format'
                'zone_not_queried_enabled_time'
            ) -join ','
        }
    }
    if ($View) {
        $invokeInfobloxQuerySplat.QueryParameter.view = $View.ToLower()
    }
    if ($FQDN) {
        $invokeInfobloxQuerySplat.QueryParameter.fqdn = $FQDN.ToLower()
    }
    Invoke-InfobloxQuery @invokeInfobloxQuerySplat -WhatIf:$false | Select-ObjectByProperty -LastProperty '_ref'
}
function Get-InfobloxDNSDelegatedZone {
    [cmdletbinding()]
    param(
        [string] $Name,
        [string] $View
    )

    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Get-InfobloxDNSDelgatedZone - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    $invokeInfobloxQuerySplat = @{
        RelativeUri    = 'zone_delegated'
        Method         = 'GET'
        QueryParameter = @{
            _max_results   = 1000000
            _return_fields = "address,comment,delegate_to,delegated_ttl,disable,display_domain,dns_fqdn,enable_rfc2317_exclusion,extattrs,fqdn,locked,locked_by,mask_prefix,ms_ad_integrated,ms_ddns_mode,ms_managed,ms_read_only,ms_sync_master_name,ns_group,parent,prefix,use_delegated_ttl,using_srg_associations,view,zone_format"
        }
    }
    if ($View) {
        $invokeInfobloxQuerySplat.QueryParameter.view = $View.ToLower()
    }
    if ($Name) {
        $invokeInfobloxQuerySplat.QueryParameter.fqdn = $Name.ToLower()
    }
    Invoke-InfobloxQuery @invokeInfobloxQuerySplat -WhatIf:$false | Select-ObjectByProperty -LastProperty '_ref'
}
function Get-InfobloxDNSForwardZone {
    [cmdletbinding()]
    param(
        [string] $Name,
        [string] $View
    )

    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Get-InfobloxDNSForwardZone - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    $invokeInfobloxQuerySplat = @{
        RelativeUri    = 'zone_forward'
        Method         = 'GET'
        QueryParameter = @{
            _max_results = 1000000
        }
    }
    if ($View) {
        $invokeInfobloxQuerySplat.QueryParameter.view = $View.ToLower()
    }
    if ($Name) {
        $invokeInfobloxQuerySplat.QueryParameter.fqdn = $Name.ToLower()
    }
    Invoke-InfobloxQuery @invokeInfobloxQuerySplat -WhatIf:$false | Select-ObjectByProperty -LastProperty '_ref'
}
function Get-InfobloxDNSRecord {
    [alias('Get-InfobloxDNSRecords')]
    [cmdletbinding()]
    param(
        [string] $Name,
        [string] $Zone,
        [string] $View,
        [switch] $PartialMatch,
        [ValidateSet(
            'A', 'AAAA', 'CName', 'DName',
            'DNSKEY', 'DS', 'Host', 'host_ipv4addr', 'host_ipv6addr',
            'LBDN', 'MX', 'NAPTR', 'NS', 'NSEC',
            'NSEC3', 'NSEC3PARAM', 'PTR', 'RRSIG', 'SRV', 'TXT'
        )]
        [string] $Type = 'Host',
        [switch] $FetchFromSchema
    )

    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Get-InfobloxDNSRecord - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    $invokeInfobloxQuerySplat = @{
        RelativeUri    = "record:$($Type.ToLower())"
        Method         = 'GET'
        QueryParameter = @{
            _max_results = 1000000
        }
    }
    if ($Type -eq 'Host') {
        $invokeInfobloxQuerySplat.QueryParameter._return_fields = 'name,dns_name,aliases,dns_aliases,ipv4addrs,configure_for_dns,view'
    }
    elseif ($Type -eq 'PTR') {
        $invokeInfobloxQuerySplat.QueryParameter._return_fields = 'aws_rte53_record_info,cloud_info,comment,creation_time,creator,ddns_principal,ddns_protected,disable,discovered_data,dns_name,dns_ptrdname,extattrs,forbid_reclamation,ipv4addr,ipv6addr,last_queried,ms_ad_user_data,name,ptrdname,reclaimable,shared_record_group,ttl,use_ttl,view,zone'
    }
    elseif ($Type -eq 'A') {
        $invokeInfobloxQuerySplat.QueryParameter._return_fields = 'ipv4addr,name,view,zone,cloud_info,comment,creation_time,creator,ddns_principal,ddns_protected,disable,discovered_data,dns_name,last_queried,ms_ad_user_data,reclaimable,shared_record_group,ttl,use_ttl'
    }
    if ($FetchFromSchema) {
        $invokeInfobloxQuerySplat.QueryParameter._return_fields = Get-FieldsFromSchema -SchemaObject "record:$Type"
    }
    if ($Zone) {
        if ($PartialMatch) {
            $invokeInfobloxQuerySplat.QueryParameter."zone~" = $Zone.ToLower()
        }
        else {
            $invokeInfobloxQuerySplat.QueryParameter.zone = $Zone.ToLower()
        }
    }
    if ($View) {
        if ($PartialMatch) {
            $invokeInfobloxQuerySplat.QueryParameter."view~" = $View.ToLower()
        }
        else {
            $invokeInfobloxQuerySplat.QueryParameter.view = $View.ToLower()
        }
    }
    if ($Name) {
        if ($PartialMatch) {
            $invokeInfobloxQuerySplat.QueryParameter."name~" = $Name.ToLower()
        }
        else {
            $invokeInfobloxQuerySplat.QueryParameter.name = $Name.ToLower()
        }
    }
    $Output = Invoke-InfobloxQuery @invokeInfobloxQuerySplat -WhatIf:$false
    if ($Type -eq 'A') {
        $Output | Select-ObjectByProperty -LastProperty '_ref' -FirstProperty 'name', 'ipv4addr', 'view', 'zone', 'cloud_info', 'comment', 'creation_time', 'creator', 'ddns_principal', 'ddns_protected', 'disable', 'discovered_data', 'dns_name', 'last_queried', 'ms_ad_user_data', 'reclaimable', 'shared_record_group', 'ttl', 'use_ttl'
    }
    elseif ($Type -eq 'HOST') {
        $Output | Select-ObjectByProperty -LastProperty '_ref' -FirstProperty 'name', 'dns_name', 'aliases', 'dns_aliases', 'view', 'configure_for_dns', 'configure_for_dhcp', 'host', 'ipv4addr', 'ipv4addr_ref'
    }
    else {
        $Output | Select-ObjectByProperty -LastProperty '_ref'
    }
}
function Get-InfobloxDNSRecordAll {
    [alias('Get-InfobloxDNSRecordsAll')]
    [cmdletbinding()]
    param(
        [string] $Name,
        [string] $Zone,
        [string] $View,
        [switch] $PartialMatch,
        [switch] $FetchFromSchema
    )

    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Get-InfobloxDNSRecordAll - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    $invokeInfobloxQuerySplat = @{
        RelativeUri    = "allrecords"
        Method         = 'GET'
        QueryParameter = @{
            _max_results = 1000000
        }
    }

    $invokeInfobloxQuerySplat.QueryParameter._return_fields = 'address,comment,creator,ddns_principal,ddns_protected,disable,dtc_obscured,name,reclaimable,record,ttl,type,view,zone'

    if ($FetchFromSchema) {
        <#
        if (-not $Script:InfobloxSchemaFields) {
            $Script:InfobloxSchemaFields = [ordered] @{}
        }
        if ($Script:InfobloxSchemaFields["allrecords"]) {
            $invokeInfobloxQuerySplat.QueryParameter._return_fields = ($Script:InfobloxSchemaFields["allrecords"])
        } else {
            $Schema = Get-InfobloxSchema -Object "allrecords"
            if ($Schema -and $Schema.fields.name) {
                $invokeInfobloxQuerySplat.QueryParameter._return_fields = ($Schema.fields.Name -join ',')
                $Script:InfobloxSchemaFields["allrecords"] = ($Schema.fields.Name -join ',')
            } else {
                Write-Warning -Message "Get-InfobloxDNSRecordAll - Failed to fetch schema for record type 'allrecords'. Using defaults"
            }
        }
        #>

        $invokeInfobloxQuerySplat.QueryParameter._return_fields = Get-FieldsFromSchema -SchemaObject "allrecords"
    }
    if ($Zone) {
        if ($PartialMatch) {
            $invokeInfobloxQuerySplat.QueryParameter."zone~" = $Zone.ToLower()
        }
        else {
            $invokeInfobloxQuerySplat.QueryParameter.zone = $Zone.ToLower()
        }
    }
    if ($View) {
        if ($PartialMatch) {
            $invokeInfobloxQuerySplat.QueryParameter."view~" = $View.ToLower()
        }
        else {
            $invokeInfobloxQuerySplat.QueryParameter.view = $View.ToLower()
        }
    }
    if ($Name) {
        if ($PartialMatch) {
            $invokeInfobloxQuerySplat.QueryParameter."name~" = $Name.ToLower()
        }
        else {
            $invokeInfobloxQuerySplat.QueryParameter.name = $Name.ToLower()
        }
    }
    $Output = Invoke-InfobloxQuery @invokeInfobloxQuerySplat -WhatIf:$false
    $AllProperties = Select-Properties -AllProperties -Object $Output
    $Output | Select-ObjectByProperty -LastProperty '_ref' -FirstProperty 'zone', 'type', 'name', 'address', 'disable', 'creator' -AllProperties $AllProperties
}
function Get-InfobloxDNSView {
    [cmdletbinding()]
    param(

    )

    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Get-InfobloxDNSView - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    Write-Verbose -Message "Get-InfobloxDNSView - Requesting DNS View"

    $invokeInfobloxQuerySplat = @{
        RelativeUri    = 'view'
        Method         = 'GET'
        QueryParameter = @{
            _max_results = 1000000
            # _return_fields = 'mac,ipv4addr,network_view'
        }
    }
    Invoke-InfobloxQuery @invokeInfobloxQuerySplat -WhatIf:$false
}
function Get-InfobloxFixedAddress {
    [cmdletbinding()]
    param(
        [parameter(Mandatory)][string] $MacAddress,
        [switch] $PartialMatch
    )

    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Get-InfobloxFixedAddress - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    Write-Verbose -Message "Get-InfobloxFixedAddress - Requesting MacAddress [$MacAddress] / PartialMatch [$($PartialMatch.IsPresent)]"

    $invokeInfobloxQuerySplat = @{
        RelativeUri    = 'fixedaddress'
        Method         = 'GET'
        QueryParameter = @{
            _max_results   = 1000000
            _return_fields = 'mac,ipv4addr,network_view'
        }
    }
    if ($PartialMatch) {
        $invokeInfobloxQuerySplat.QueryParameter."mac~" = $MacAddress.ToLower()
    }
    else {
        $invokeInfobloxQuerySplat.QueryParameter.mac = $MacAddress.ToLower()
    }
    Invoke-InfobloxQuery @invokeInfobloxQuerySplat -WhatIf:$false
}
function Get-InfobloxGrid {
    [cmdletbinding()]
    param(
        [switch] $FetchFromSchema
    )

    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Get-InfobloxGrid - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    # defalt return fields
    $ReturnFields = 'allow_recursive_deletion,audit_log_format,audit_to_syslog_enable,automated_traffic_capture_setting,consent_banner_setting,csp_api_config,csp_grid_setting,deny_mgm_snapshots,descendants_action,dns_resolver_setting,dscp,email_setting,enable_gui_api_for_lan_vip,enable_lom,enable_member_redirect,enable_recycle_bin,enable_rir_swip,external_syslog_backup_servers,external_syslog_server_enable,http_proxy_server_setting,informational_banner_setting,is_grid_visualization_visible,lockout_setting,lom_users,mgm_strict_delegate_mode,ms_setting,name,nat_groups,ntp_setting,objects_changes_tracking_setting,password_setting,restart_banner_setting,restart_status,rpz_hit_rate_interval,rpz_hit_rate_max_query,rpz_hit_rate_min_query,scheduled_backup,security_banner_setting,security_setting,service_status,snmp_setting,support_bundle_download_timeout,syslog_facility,syslog_servers,syslog_size,threshold_traps,time_zone,token_usage_delay,traffic_capture_auth_dns_setting,traffic_capture_chr_setting,traffic_capture_qps_setting,traffic_capture_rec_dns_setting,traffic_capture_rec_queries_setting,trap_notifications,updates_download_member_config,vpn_port'
    if ($FetchFromSchema) {
        $ReturnFields = Get-FieldsFromSchema -SchemaObject "grid"
    }

    $invokeInfobloxQuerySplat = @{
        RelativeUri    = 'grid'
        Method         = 'GET'
        QueryParameter = @{
            _max_results   = 1000000
            _return_fields = $ReturnFields
        }
    }
    $Output = Invoke-InfobloxQuery @invokeInfobloxQuerySplat -WhatIf:$false
    $Output | Select-ObjectByProperty -LastProperty '_ref'
}
function Get-InfobloxIPAddress {
    <#
    .SYNOPSIS
    Get Infoblox IP Address information for given network or IP address
 
    .DESCRIPTION
    Get Infoblox IP Address information for given network or IP address
 
    .PARAMETER Network
    Find IP address information for a specific network
 
    .PARAMETER IPv4Address
    Find IP address information for a specific IP address
 
    .PARAMETER Status
    Get IP addresses with a specific status, either Used or Unused
 
    .PARAMETER Name
    Get IP addresses with a specific name
 
    .PARAMETER Count
    Limit the number of results returned
 
    .EXAMPLE
    Get-InfobloxIPAddress -Network '10.2.2.0/24'
 
    .EXAMPLE
    Get-InfobloxIPAddress -Network '10.2.2.0/24' -Status Used -Verbose | Format-Table
 
    .EXAMPLE
    Get-InfobloxIPAddress -Network '10.2.2.0' -Verbose | Format-Table
 
    .NOTES
    General notes
    #>

    [cmdletbinding()]
    param(
        [parameter(ParameterSetName = 'Network')][string] $Network,
        [parameter(ParameterSetName = 'IPv4')][string] $IPv4Address,

        [parameter(ParameterSetName = 'Network')]
        [parameter(ParameterSetName = 'IPv4')]
        [parameter()][ValidateSet('Used', 'Unused')][string] $Status,

        [parameter(ParameterSetName = 'Network')]
        [parameter(ParameterSetName = 'IPv4')]
        [parameter()][string] $Name,

        [parameter(ParameterSetName = 'Network')]
        [parameter(ParameterSetName = 'IPv4')]
        [alias('Quantity')][parameter()][int] $Count
    )
    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Get-InfobloxIPAddress - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    if ($Network) {
        Write-Verbose -Message "Get-InfobloxIPAddress - Requesting Network [$Network] Status [$Status]"
    }
    else {
        Write-Verbose -Message "Get-InfobloxIPAddress - Requesting IPv4Address [$IPv4Address] Status [$Status]"
    }

    $invokeInfobloxQuerySplat = [ordered]@{
        RelativeUri    = 'ipv4address'
        Method         = 'GET'
        QueryParameter = [ordered]@{
            _max_results = 1000000
        }
    }
    if ($Network) {
        $invokeInfobloxQuerySplat.QueryParameter.network = $Network
    }
    if ($Status) {
        $invokeInfobloxQuerySplat.QueryParameter.status = $Status.ToUpper()
    }
    if ($Name) {
        $invokeInfobloxQuerySplat.QueryParameter.names = $Name
    }
    if ($IPv4Address) {
        $invokeInfobloxQuerySplat.QueryParameter.ip_address = $IPv4Address
    }
    if ($Count) {
        Invoke-InfobloxQuery @invokeInfobloxQuerySplat -WhatIf:$false | Select-Object -First $Count | Select-ObjectByProperty -LastProperty '_ref'
    }
    else {
        Invoke-InfobloxQuery @invokeInfobloxQuerySplat -WhatIf:$false | Select-ObjectByProperty -LastProperty '_ref'
    }
}
function Get-InfobloxMember {
    [cmdletbinding()]
    param(
        [switch] $FetchFromSchema
    )

    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Get-InfobloxMember - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    # defalt return fields
    $ReturnFields = 'config_addr_type,host_name,platform,service_type_configuration,vip_setting,node_info,service_status'
    if ($FetchFromSchema) {
        $ReturnFields = Get-FieldsFromSchema -SchemaObject "member"
    }

    $invokeInfobloxQuerySplat = @{
        RelativeUri    = 'member'
        Method         = 'GET'
        QueryParameter = @{
            _max_results   = 1000000
            _return_fields = $ReturnFields
        }
    }
    $Output = Invoke-InfobloxQuery @invokeInfobloxQuerySplat -WhatIf:$false
    $Output | Select-ObjectByProperty -FirstProperty 'host_name' -LastProperty '_ref'
}
function Get-InfobloxNetwork {
    [OutputType([system.object[]])]
    [cmdletbinding()]
    param(
        [string] $Network,
        [string[]]$Properties,
        [switch] $Partial,
        [switch] $All
    )

    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Get-InfobloxNetwork - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    $QueryParameter = [ordered]@{
        _max_results   = 1000000
        _return_fields = @(
            "authority", "bootfile", "bootserver", "cloud_info", "comment", "conflict_count", "ddns_domainname", "ddns_generate_hostname", "ddns_server_always_updates", "ddns_ttl", "ddns_update_fixed_addresses", "ddns_use_option81", "deny_bootp", "dhcp_utilization", "dhcp_utilization_status", "disable", "discover_now_status", "discovered_bgp_as", "discovered_bridge_domain", "discovered_tenant", "discovered_vlan_id", "discovered_vlan_name", "discovered_vrf_description", "discovered_vrf_name", "discovered_vrf_rd", "discovery_basic_poll_settings", "discovery_blackout_setting", "discovery_engine_type", "discovery_member", "dynamic_hosts", "email_list", "enable_ddns", "enable_dhcp_thresholds", "enable_discovery", "enable_email_warnings", "enable_ifmap_publishing", "enable_pxe_lease_time", "enable_snmp_warnings", "endpoint_sources", "extattrs", "high_water_mark", "high_water_mark_reset", "ignore_dhcp_option_list_request", "ignore_id", "ignore_mac_addresses", "ipam_email_addresses", "ipam_threshold_settings", "ipam_trap_settings", "ipv4addr", "last_rir_registration_update_sent", "last_rir_registration_update_status", "lease_scavenge_time", "logic_filter_rules", "low_water_mark", "low_water_mark_reset", "members", "mgm_private", "mgm_private_overridable", "ms_ad_user_data", "netmask", "network", "network_container", "network_view", "nextserver", "options", "port_control_blackout_setting", "pxe_lease_time", "recycle_leases", "rir", "rir_organization", "rir_registration_status", "same_port_control_discovery_blackout", "static_hosts", "subscribe_settings", "total_hosts", "unmanaged", "unmanaged_count", "update_dns_on_lease_renewal", "use_authority", "use_blackout_setting", "use_bootfile", "use_bootserver", "use_ddns_domainname", "use_ddns_generate_hostname", "use_ddns_ttl", "use_ddns_update_fixed_addresses", "use_ddns_use_option81", "use_deny_bootp", "use_discovery_basic_polling_settings", "use_email_list", "use_enable_ddns", "use_enable_dhcp_thresholds", "use_enable_discovery", "use_enable_ifmap_publishing", "use_ignore_dhcp_option_list_request", "use_ignore_id", "use_ipam_email_addresses", "use_ipam_threshold_settings", "use_ipam_trap_settings", "use_lease_scavenge_time", "use_logic_filter_rules", "use_mgm_private", "use_nextserver", "use_options", "use_pxe_lease_time", "use_recycle_leases", "use_subscribe_settings", "use_update_dns_on_lease_renewal", "use_zone_associations", "utilization", "utilization_update", "vlans", "zone_associations"
        ) -join ','
    }
    if ($All) {
        $ListNetworks = Invoke-InfobloxQuery -RelativeUri "network" -Method Get -QueryParameter $QueryParameter -WhatIf:$false
    }
    elseif ($Network -and $Partial.IsPresent) {
        $QueryParameter."network~" = $Network
    }
    elseif ($Network) {
        $QueryParameter.network = $Network
    }
    else {
        return
    }
    $ListNetworks = Invoke-InfobloxQuery -RelativeUri "network" -Method Get -QueryParameter $QueryParameter -WhatIf:$false

    #$ExtraProperties = $false
    foreach ($FoundNetwork in $ListNetworks) {

        $FullInformation = Get-IPAddressRangeInformation -Network $FoundNetwork.network

        <#
        $FoundNetwork.options
 
 
        name : dhcp-lease-time
        num : 51
        use_option : False
        value : 43200
        vendor_class : DHCP
        #>


        $OutputData = [ordered] @{
            Network                              = $FoundNetwork.network
            NetworkRef                           = $FoundNetwork._ref
            IP                                   = $FullInformation.IP                   # : 10.2.10.0
            NetworkLength                        = $FullInformation.NetworkLength        # : 24
            SubnetMask                           = $FullInformation.SubnetMask           # : 255.255.255.0
            NetworkAddress                       = $FullInformation.NetworkAddress       # : 10.2.10.0
            HostMin                              = $FullInformation.HostMin              # : 10.2.10.1
            HostMax                              = $FullInformation.HostMax              # : 10.2.10.254
            TotalHosts                           = $FullInformation.TotalHosts           # : 256
            UsableHosts                          = $FullInformation.UsableHosts          # : 254
            Broadcast                            = $FullInformation.Broadcast            # : 10.2.10.255

            authority                            = $FoundNetwork.authority                            #: False
            cloud_info                           = $FoundNetwork.cloud_info                           #: @{authority_type=GM; delegated_scope=NONE; mgmt_platform=; owned_by_adaptor=False}
            comment                              = $FoundNetwork.comment                              #: found in CMDB
            conflict_count                       = $FoundNetwork.conflict_count                       #: 0
            ddns_generate_hostname               = $FoundNetwork.ddns_generate_hostname               #: False
            ddns_server_always_updates           = $FoundNetwork.ddns_server_always_updates           #: True
            ddns_ttl                             = $FoundNetwork.ddns_ttl                             #: 0
            ddns_update_fixed_addresses          = $FoundNetwork.ddns_update_fixed_addresses          #: False
            ddns_use_option81                    = $FoundNetwork.ddns_use_option81                    #: False
            deny_bootp                           = $FoundNetwork.deny_bootp                           #: False
            dhcp_utilization                     = $FoundNetwork.dhcp_utilization                     #: 0
            dhcp_utilization_status              = $FoundNetwork.dhcp_utilization_status              #: LOW
            disable                              = $FoundNetwork.disable                              #: False
            discover_now_status                  = $FoundNetwork.discover_now_status                  #: NONE
            discovered_bgp_as                    = $FoundNetwork.discovered_bgp_as                    #:
            discovered_bridge_domain             = $FoundNetwork.discovered_bridge_domain             #:
            discovered_tenant                    = $FoundNetwork.discovered_tenant                    #:
            discovered_vlan_id                   = $FoundNetwork.discovered_vlan_id                   #:
            discovered_vlan_name                 = $FoundNetwork.discovered_vlan_name                 #:
            discovered_vrf_description           = $FoundNetwork.discovered_vrf_description           #:
            discovered_vrf_name                  = $FoundNetwork.discovered_vrf_name                  #:
            discovered_vrf_rd                    = $FoundNetwork.discovered_vrf_rd                    #:
            discovery_basic_poll_settings        = $FoundNetwork.discovery_basic_poll_settings        #: @{auto_arp_refresh_before_switch_port_polling=True; cli_collection=True; complete_ping_sweep=False;
            # = $FoundNetwork. # credential_group=default; device_profile=False; netbios_scanning=False; port_scanning=False;
            # = $FoundNetwork. # smart_subnet_ping_sweep=False; snmp_collection=True; switch_port_data_collection_polling=PERIODIC;
            # = $FoundNetwork. # switch_port_data_collection_polling_interval=3600}
            discovery_blackout_setting           = $FoundNetwork.discovery_blackout_setting           #: @{enable_blackout=False}
            discovery_engine_type                = $FoundNetwork.discovery_engine_type                #: NONE
            dynamic_hosts                        = $FoundNetwork.dynamic_hosts                        #: 0
            email_list                           = $FoundNetwork.email_list                           #: {}
            enable_ddns                          = $FoundNetwork.enable_ddns                          #: False
            enable_dhcp_thresholds               = $FoundNetwork.enable_dhcp_thresholds               #: False
            enable_discovery                     = $FoundNetwork.enable_discovery                     #: False
            enable_email_warnings                = $FoundNetwork.enable_email_warnings                #: False
            enable_ifmap_publishing              = $FoundNetwork.enable_ifmap_publishing              #: False
            enable_pxe_lease_time                = $FoundNetwork.enable_pxe_lease_time                #: False
            enable_snmp_warnings                 = $FoundNetwork.enable_snmp_warnings                 #: False
            #extattrs = $FoundNetwork.extattrs #: @{Country=; Name=; Region=}
            high_water_mark                      = $FoundNetwork.high_water_mark                      #: 95
            high_water_mark_reset                = $FoundNetwork.high_water_mark_reset                #: 85
            ignore_dhcp_option_list_request      = $FoundNetwork.ignore_dhcp_option_list_request      #: False
            ignore_id                            = $FoundNetwork.ignore_id                            #: NONE
            ignore_mac_addresses                 = $FoundNetwork.ignore_mac_addresses                 #: {}
            ipam_email_addresses                 = $FoundNetwork.ipam_email_addresses                 #: {}
            ipam_threshold_settings              = $FoundNetwork.ipam_threshold_settings              #: @{reset_value=85; trigger_value=95}
            ipam_trap_settings                   = $FoundNetwork.ipam_trap_settings                   #: @{enable_email_warnings=False; enable_snmp_warnings=True}
            ipv4addr                             = $FoundNetwork.ipv4addr                             #: 172.23.0.0
            lease_scavenge_time                  = $FoundNetwork.lease_scavenge_time                  #: -1
            logic_filter_rules                   = $FoundNetwork.logic_filter_rules                   #: {}
            low_water_mark                       = $FoundNetwork.low_water_mark                       #: 0
            low_water_mark_reset                 = $FoundNetwork.low_water_mark_reset                 #: 10
            members                              = $FoundNetwork.members                              #: {}
            mgm_private                          = $FoundNetwork.mgm_private                          #: False
            mgm_private_overridable              = $FoundNetwork.mgm_private_overridable              #: True
            netmask                              = $FoundNetwork.netmask                              #: 27
            network_container                    = $FoundNetwork.network_container                    #: 172.23.0.0/16
            network_view                         = $FoundNetwork.network_view                         #: default
            options                              = $FoundNetwork.options                              #: {@{name=dhcp-lease-time; num=51; use_option=False; value=43200; vendor_class=DHCP}}
            port_control_blackout_setting        = $FoundNetwork.port_control_blackout_setting        #: @{enable_blackout=False}
            recycle_leases                       = $FoundNetwork.recycle_leases                       #: True
            rir                                  = $FoundNetwork.rir                                  #: NONE
            rir_registration_status              = $FoundNetwork.rir_registration_status              #: NOT_REGISTERED
            same_port_control_discovery_blackout = $FoundNetwork.same_port_control_discovery_blackout #: False
            static_hosts                         = $FoundNetwork.static_hosts                         #: 0
            subscribe_settings                   = $FoundNetwork.subscribe_settings                   #:
            total_hosts                          = $FoundNetwork.total_hosts                          #: 0
            unmanaged                            = $FoundNetwork.unmanaged                            #: False
            unmanaged_count                      = $FoundNetwork.unmanaged_count                      #: 0
            update_dns_on_lease_renewal          = $FoundNetwork.update_dns_on_lease_renewal          #: False
            use_authority                        = $FoundNetwork.use_authority                        #: False
            use_blackout_setting                 = $FoundNetwork.use_blackout_setting                 #: False
            use_bootfile                         = $FoundNetwork.use_bootfile                         #: False
            use_bootserver                       = $FoundNetwork.use_bootserver                       #: False
            use_ddns_domainname                  = $FoundNetwork.use_ddns_domainname                  #: False
            use_ddns_generate_hostname           = $FoundNetwork.use_ddns_generate_hostname           #: False
            use_ddns_ttl                         = $FoundNetwork.use_ddns_ttl                         #: False
            use_ddns_update_fixed_addresses      = $FoundNetwork.use_ddns_update_fixed_addresses      #: False
            use_ddns_use_option81                = $FoundNetwork.use_ddns_use_option81                #: False
            use_deny_bootp                       = $FoundNetwork.use_deny_bootp                       #: False
            use_discovery_basic_polling_settings = $FoundNetwork.use_discovery_basic_polling_settings #: False
            use_email_list                       = $FoundNetwork.use_email_list                       #: False
            use_enable_ddns                      = $FoundNetwork.use_enable_ddns                      #: False
            use_enable_dhcp_thresholds           = $FoundNetwork.use_enable_dhcp_thresholds           #: False
            use_enable_discovery                 = $FoundNetwork.use_enable_discovery                 #: False
            use_enable_ifmap_publishing          = $FoundNetwork.use_enable_ifmap_publishing          #: False
            use_ignore_dhcp_option_list_request  = $FoundNetwork.use_ignore_dhcp_option_list_request  #: False
            use_ignore_id                        = $FoundNetwork.use_ignore_id                        #: False
            use_ipam_email_addresses             = $FoundNetwork.use_ipam_email_addresses             #: False
            use_ipam_threshold_settings          = $FoundNetwork.use_ipam_threshold_settings          #: False
            use_ipam_trap_settings               = $FoundNetwork.use_ipam_trap_settings               #: False
            use_lease_scavenge_time              = $FoundNetwork.use_lease_scavenge_time              #: False
            use_logic_filter_rules               = $FoundNetwork.use_logic_filter_rules               #: False
            use_mgm_private                      = $FoundNetwork.use_mgm_private                      #: False
            use_nextserver                       = $FoundNetwork.use_nextserver                       #: False
            use_options                          = $FoundNetwork.use_options                          #: False
            use_pxe_lease_time                   = $FoundNetwork.use_pxe_lease_time                   #: False
            use_recycle_leases                   = $FoundNetwork.use_recycle_leases                   #: False
            use_subscribe_settings               = $FoundNetwork.use_subscribe_settings               #: False
            use_update_dns_on_lease_renewal      = $FoundNetwork.use_update_dns_on_lease_renewal      #: False
            use_zone_associations                = $FoundNetwork.use_zone_associations                #: False
            utilization                          = $FoundNetwork.utilization                          #: 0
            utilization_update                   = $FoundNetwork.utilization_update                   #: 1707318915
            vlans                                = $FoundNetwork.vlans                                #: {}
            zone_associations                    = $FoundNetwork.zone_associations                    #: {}
            _ref                                 = $FoundNetwork._ref                                 #: network/ZG
        }
        foreach ($Extra in $FoundNetwork.extattrs.psobject.properties) {
            $OutputData[$Extra.Name] = $Extra.Value.value
        }
        [PSCustomObject]$OutputData
    }
}
function Get-InfobloxNetworkNextAvailableIP {
    [cmdletbinding(DefaultParameterSetName = 'Network')]
    param(
        [Parameter(Mandatory, ParameterSetName = 'Network')][string] $Network,
        [Parameter(Mandatory, ParameterSetName = 'NetworkRef')][string] $NetworkRef,
        [alias('Count')][int] $Quantity = 1
    )
    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Get-InfobloxNetworkNextAvailableIP - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    if ($Network) {
        $NetworkInformation = Get-InfobloxNetwork -Network $Network
        if ($NetworkInformation) {
            $NetworkRef = $NetworkInformation.NetworkRef
        }
        else {
            Write-Warning -Message "Get-InfobloxNetworkNextAvailableIP - No network found for [$Network]"
            return
        }
    }

    $invokeInfobloxQuerySplat = @{
        RelativeUri    = $NetworkRef
        QueryParameter = @{
            _function = 'next_available_ip'
            num       = $Quantity
        }
        Method         = 'POST'
    }

    $Query = Invoke-InfobloxQuery @invokeInfobloxQuerySplat -WarningAction SilentlyContinue -WarningVariable varWarning -WhatIf:$false
    if ($Query) {
        $Query.ips
    }
    else {
        Write-Warning -Message "Get-InfobloxNetworkNextAvailableIP - No IP returned for network [$NetworkRef], error: $varWarning"
    }
}
function Get-InfobloxNetworkView {
    [cmdletbinding()]
    param(
        [switch] $FetchFromSchema
    )

    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Get-InfobloxNetworkView - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    Write-Verbose -Message "Get-InfobloxNetworkView - Requesting Network View"

    if ($FetchFromSchema) {
        $ReturnFields = Get-FieldsFromSchema -SchemaObject "networkview"
    }

    $invokeInfobloxQuerySplat = @{
        RelativeUri    = 'networkview'
        Method         = 'GET'
        QueryParameter = @{
            _max_results   = 1000000
            _return_fields = $ReturnFields
        }
    }
    $Output = Invoke-InfobloxQuery @invokeInfobloxQuerySplat -WhatIf:$false
    $Output | Select-ObjectByProperty -LastProperty '_ref'
}
function Get-InfobloxObjects {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ParameterSetName = 'ReferenceID')]
        [string[]] $ReferenceID,
        [Parameter(Mandatory, ParameterSetName = 'Objects')]
        [ValidateSet(
            "ad_auth_service",
            "admingroup",
            "adminrole",
            "adminuser",
            "allendpoints",
            "allnsgroup",
            "allrecords",
            "allrpzrecords",
            "approvalworkflow",
            "authpolicy",
            "awsrte53taskgroup",
            "awsuser",
            "bfdtemplate",
            "bulkhost",
            "bulkhostnametemplate",
            "cacertificate",
            "capacityreport",
            "captiveportal",
            "certificate:authservice",
            "csvimporttask",
            "db_objects",
            "dbsnapshot",
            "ddns:principalcluster",
            "ddns:principalcluster:group",
            "deleted_objects",
            "dhcp:statistics",
            "dhcpfailover",
            "dhcpoptiondefinition",
            "dhcpoptionspace",
            "discovery",
            "discovery:credentialgroup",
            "discovery:device",
            "discovery:devicecomponent",
            "discovery:deviceinterface",
            "discovery:deviceneighbor",
            "discovery:devicesupportbundle",
            "discovery:diagnostictask",
            "discovery:gridproperties",
            "discovery:memberproperties",
            "discovery:sdnnetwork",
            "discovery:status",
            "discovery:vrf",
            "discoverytask",
            "distributionschedule",
            "dns64group",
            "dtc",
            "dtc:allrecords",
            "dtc:certificate",
            "dtc:lbdn",
            "dtc:monitor",
            "dtc:monitor:http",
            "dtc:monitor:icmp",
            "dtc:monitor:pdp",
            "dtc:monitor:sip",
            "dtc:monitor:snmp",
            "dtc:monitor:tcp",
            "dtc:object",
            "dtc:pool",
            "dtc:record:a",
            "dtc:record:aaaa",
            "dtc:record:cname",
            "dtc:record:naptr",
            "dtc:record:srv",
            "dtc:server",
            "dtc:topology",
            "dtc:topology:label",
            "dtc:topology:rule",
            "dxl:endpoint",
            "extensibleattributedef",
            "fileop",
            "filterfingerprint",
            "filtermac",
            "filternac",
            "filteroption",
            "filterrelayagent",
            "fingerprint",
            "fixedaddress",
            "fixedaddresstemplate",
            "ftpuser",
            "grid",
            "grid:cloudapi",
            "grid:cloudapi:cloudstatistics",
            "grid:cloudapi:tenant",
            "grid:cloudapi:vm",
            "grid:cloudapi:vmaddress",
            "grid:dashboard",
            "grid:dhcpproperties",
            "grid:dns",
            "grid:filedistribution",
            "grid:license_pool",
            "grid:license_pool_container",
            "grid:maxminddbinfo",
            "grid:member:cloudapi",
            "grid:servicerestart:group",
            "grid:servicerestart:group:order",
            "grid:servicerestart:request",
            "grid:servicerestart:request:changedobject",
            "grid:servicerestart:status",
            "grid:threatanalytics",
            "grid:threatprotection",
            "grid:x509certificate",
            "hostnamerewritepolicy",
            "hsm:allgroups",
            "hsm:safenetgroup",
            "hsm:thalesgroup",
            "ipam:statistics",
            "ipv4address",
            "ipv6address",
            "ipv6dhcpoptiondefinition",
            "ipv6dhcpoptionspace",
            "ipv6fixedaddress",
            "ipv6fixedaddresstemplate",
            "ipv6network",
            "ipv6networkcontainer",
            "ipv6networktemplate",
            "ipv6range",
            "ipv6rangetemplate",
            "ipv6sharednetwork",
            "kerberoskey",
            "ldap_auth_service",
            "lease",
            "license:gridwide",
            "localuser:authservice",
            "macfilteraddress",
            "mastergrid",
            "member",
            "member:dhcpproperties",
            "member:dns",
            "member:filedistribution",
            "member:license",
            "member:parentalcontrol",
            "member:threatanalytics",
            "member:threatprotection",
            "memberdfp",
            "msserver",
            "msserver:adsites:domain",
            "msserver:adsites:site",
            "msserver:dhcp",
            "msserver:dns",
            "mssuperscope",
            "namedacl",
            "natgroup",
            "network",
            "network_discovery",
            "networkcontainer",
            "networktemplate",
            "networkuser",
            "networkview",
            "notification:rest:endpoint",
            "notification:rest:template",
            "notification:rule",
            "nsgroup",
            "nsgroup:delegation",
            "nsgroup:forwardingmember",
            "nsgroup:forwardstubserver",
            "nsgroup:stubmember",
            "orderedranges",
            "orderedresponsepolicyzones",
            "outbound:cloudclient",
            "parentalcontrol:avp",
            "parentalcontrol:blockingpolicy",
            "parentalcontrol:subscriber",
            "parentalcontrol:subscriberrecord",
            "parentalcontrol:subscribersite",
            "permission",
            "pxgrid:endpoint",
            "radius:authservice",
            "range",
            "rangetemplate",
            "record:a",
            "record:aaaa",
            "record:alias",
            "record:caa",
            "record:cname",
            "record:dhcid",
            "record:dname",
            "record:dnskey",
            "record:ds",
            "record:dtclbdn",
            "record:host",
            "record:host_ipv4addr",
            "record:host_ipv6addr",
            "record:mx",
            "record:naptr",
            "record:ns",
            "record:nsec",
            "record:nsec3",
            "record:nsec3param",
            "record:ptr",
            "record:rpz:a",
            "record:rpz:a:ipaddress",
            "record:rpz:aaaa",
            "record:rpz:aaaa:ipaddress",
            "record:rpz:cname",
            "record:rpz:cname:clientipaddress",
            "record:rpz:cname:clientipaddressdn",
            "record:rpz:cname:ipaddress",
            "record:rpz:cname:ipaddressdn",
            "record:rpz:mx",
            "record:rpz:naptr",
            "record:rpz:ptr",
            "record:rpz:srv",
            "record:rpz:txt",
            "record:rrsig",
            "record:srv",
            "record:tlsa",
            "record:txt",
            "record:unknown",
            "recordnamepolicy",
            "request",
            "restartservicestatus",
            "rir",
            "rir:organization",
            "roaminghost",
            "ruleset",
            "saml:authservice",
            "scavengingtask",
            "scheduledtask",
            "search",
            "sharednetwork",
            "sharedrecord:a",
            "sharedrecord:aaaa",
            "sharedrecord:cname",
            "sharedrecord:mx",
            "sharedrecord:srv",
            "sharedrecord:txt",
            "sharedrecordgroup",
            "smartfolder:children",
            "smartfolder:global",
            "smartfolder:personal",
            "snmpuser",
            "superhost",
            "superhostchild",
            "syslog:endpoint",
            "tacacsplus:authservice",
            "taxii",
            "tftpfiledir",
            "threatanalytics:analytics_whitelist",
            "threatanalytics:moduleset",
            "threatanalytics:whitelist",
            "threatinsight:cloudclient",
            "threatprotection:grid:rule",
            "threatprotection:profile",
            "threatprotection:profile:rule",
            "threatprotection:rule",
            "threatprotection:rulecategory",
            "threatprotection:ruleset",
            "threatprotection:ruletemplate",
            "threatprotection:statistics",
            "upgradegroup",
            "upgradeschedule",
            "upgradestatus",
            "userprofile",
            "vdiscoverytask",
            "view",
            "vlan",
            "vlanrange",
            "vlanview",
            "zone_auth",
            "zone_auth_discrepancy",
            "zone_delegated",
            "zone_forward",
            "zone_rp",
            "zone_stub"
        )][string] $Object,
        [int] $MaxResults,
        [switch] $FetchFromSchema,
        [string[]] $ReturnFields
    )

    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Get-InfobloxObjects - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }
    if ($Object) {
        Write-Verbose -Message "Get-InfobloxObjects - Requesting $Object"

        if ($FetchFromSchema) {
            $ReturnFields = Get-FieldsFromSchema -SchemaObject "$Object"
        }

        $invokeInfobloxQuerySplat = @{
            RelativeUri    = $Object.ToLower()
            Method         = 'GET'
            QueryParameter = @{
                _return_fields = $ReturnFields
            }
        }
        if ($MaxResults) {
            $invokeInfobloxQuerySplat.QueryParameter._max_results = $MaxResults
        }
        $Output = Invoke-InfobloxQuery @invokeInfobloxQuerySplat -WhatIf:$false
        $Output | Select-ObjectByProperty -LastProperty '_ref'
    }
    else {
        foreach ($Ref in $ReferenceID) {
            Write-Verbose -Message "Get-InfobloxObjects - Requesting $Ref"
            if ($FetchFromSchema) {
                $ObjectType = $Ref.Split('/')[0]
                $ReturnFields = Get-FieldsFromSchema -SchemaObject "$ObjectType"
                if ($ReturnFields) {
                    Write-Verbose -Message "Get-InfobloxObjects - Requesting $ObjectType with fields $ReturnFields"
                }
                else {
                    Write-Warning -Message "Get-InfobloxObjects - Failed to get fields for $ObjectType"
                }
            }

            $invokeInfobloxQuerySplat = @{
                RelativeUri    = $Ref
                Method         = 'GET'
                QueryParameter = @{
                    _return_fields = $ReturnFields
                }
            }
            if ($MaxResults) {
                $invokeInfobloxQuerySplat.QueryParameter._max_results = $MaxResults
            }

            $Output = Invoke-InfobloxQuery @invokeInfobloxQuerySplat -WhatIf:$false
            $Output | Select-ObjectByProperty -LastProperty '_ref'
        }
    }
}
function Get-InfobloxPermission {
    [cmdletbinding()]
    param(
        [switch] $FetchFromSchema
    )

    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Get-InfobloxPermissions - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    # defalt return fields
    if ($FetchFromSchema) {
        $ReturnFields = Get-FieldsFromSchema -SchemaObject "permission"
    }

    $invokeInfobloxQuerySplat = @{
        RelativeUri    = 'permission'
        Method         = 'GET'
        QueryParameter = @{
            _max_results   = 1000000
            _return_fields = $ReturnFields
        }
    }
    $Output = Invoke-InfobloxQuery @invokeInfobloxQuerySplat -WhatIf:$false
    $Output | Select-ObjectByProperty -LastProperty '_ref'
}
function Get-InfobloxResponsePolicyZones {
    [cmdletbinding()]
    param(
        [switch] $FetchFromSchema
    )

    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Get-InfobloxResponsePolicyZones - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    # defalt return fields
    if ($FetchFromSchema) {
        $ReturnFields = Get-FieldsFromSchema -SchemaObject "zone_rp"
    }

    $invokeInfobloxQuerySplat = @{
        RelativeUri    = 'zone_rp'
        Method         = 'GET'
        QueryParameter = @{
            _max_results   = 1000000
            _return_fields = $ReturnFields
        }
    }
    $Output = Invoke-InfobloxQuery @invokeInfobloxQuerySplat -WhatIf:$false
    $Output | Select-ObjectByProperty -LastProperty '_ref'
}
function Get-InfobloxSchema {
    <#
    .SYNOPSIS
    Get the schema for Infoblox as a whole or a specific object
 
    .DESCRIPTION
    Get the schema for Infoblox as a whole or a specific object
 
    .PARAMETER Object
    The object to get the schema for
 
    .EXAMPLE
    Get-InfobloxSchema
 
    .EXAMPLE
    Get-InfobloxSchema -Object 'record:host'
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [string] $Object,
        [switch] $ReturnReadOnlyFields
    )
    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Get-InfobloxSchema - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }
    if ($Object) {
        $invokeInfobloxQuerySplat = @{
            RelativeUri = "$($Object.ToLower())"
            Method      = 'Get'
            Query       = @{
                _schema = $true
            }
        }
    }
    else {
        $invokeInfobloxQuerySplat = @{
            RelativeUri = "?_schema"
            Method      = 'Get'
        }
    }
    $Query = Invoke-InfobloxQuery @invokeInfobloxQuerySplat -WhatIf:$false
    if ($Query) {
        if ($ReturnReadOnlyFields) {
            Get-FieldsFromSchema -Schema $Query -SchemaObject $Object
        }
        else {
            $Query
        }
    }
    else {
        Write-Warning -Message 'Get-InfobloxSchema - No schema returned'
    }
}
function Get-InfoBloxSearch {
    [CmdletBinding()]
    param(
        [parameter(ParameterSetName = 'IPv4')][string] $IPv4Address
    )

    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Get-InfoBloxSearch - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }
    Write-Verbose -Message "Get-InfoBloxSearch - Requesting IPv4Address [$IPv4Address]"

    $invokeInfobloxQuerySplat = [ordered]@{
        RelativeUri    = 'search'
        Method         = 'GET'
        QueryParameter = [ordered]@{
            _max_results = 1000000
        }
    }
    if ($IPv4Address) {
        $invokeInfobloxQuerySplat.QueryParameter.address = $IPv4Address
    }

    Invoke-InfobloxQuery @invokeInfobloxQuerySplat -WhatIf:$false
}
function Get-InfobloxVDiscoveryTask {
    [cmdletbinding()]
    param(
        [switch] $FetchFromSchema
    )

    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Get-InfobloxVDiscoveryTask - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    # defalt return fields
    if ($FetchFromSchema) {
        $ReturnFields = Get-FieldsFromSchema -SchemaObject "vdiscoverytask"
    }

    $invokeInfobloxQuerySplat = @{
        RelativeUri    = 'vdiscoverytask'
        Method         = 'GET'
        QueryParameter = @{
            _max_results   = 1000000
            _return_fields = $ReturnFields
        }
    }
    $Output = Invoke-InfobloxQuery @invokeInfobloxQuerySplat -WhatIf:$false
    $Output | Select-ObjectByProperty -LastProperty '_ref'
}
function Invoke-InfobloxQuery {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory)][string] $BaseUri,
        [parameter(Mandatory)][string] $RelativeUri,
        [parameter()][pscredential] $Credential,
        [Parameter()][Microsoft.PowerShell.Commands.WebRequestSession] $WebSession,
        [parameter()][System.Collections.IDictionary] $QueryParameter,
        [parameter()][string] $Method = 'GET',
        [parameter()][System.Collections.IDictionary] $Body
    )

    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Invoke-InfobloxQuery - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }
    if (-not $Credential -and -not $WebSession) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'Invoke-InfobloxQuery - You must provide either a Credential or a WebSession with a cookie from Connect-Infoblox'
        }
        Write-Warning -Message 'Invoke-InfobloxQuery - You must provide either a Credential or a WebSession with a cookie from Connect-Infoblox'
        return
    }

    $joinUriQuerySplat = @{
        BaseUri               = $BaseUri
        RelativeOrAbsoluteUri = $RelativeUri
    }
    if ($QueryParameter) {
        $joinUriQuerySplat['QueryParameter'] = $QueryParameter
    }

    # if ($Method -eq 'GET') {
    # if (-not $QueryParameter) {
    # $joinUriQuerySplat['QueryParameter'] = [ordered] @{}
    # }
    # #_paging = 1
    # #_return_as_object = 1
    # #_max_results = 100000
    # $joinUriQuerySplat['QueryParameter']._max_results = 1000000
    # }

    $Url = Join-UriQuery @joinUriQuerySplat

    if ($PSCmdlet.ShouldProcess($Url, "Invoke-InfobloxQuery - $Method")) {
        Write-Verbose -Message "Invoke-InfobloxQuery - Querying $Url with $Method"


        # $WebSession = New-WebSession -Cookies @{
        # timeout = 600
        # mtime = 144631868
        # client = 'API'
        # } -For $BaseUri

        try {
            $invokeRestMethodSplat = @{
                Uri         = $Url
                Method      = $Method
                Credential  = $Credential
                ContentType = 'application/json'
                ErrorAction = 'Stop'
                Verbose     = $false
                WebSession  = $WebSession #$PSDefaultParameterValues['Invoke-InfobloxQuery:WebSession']
                TimeoutSec  = 600
            }
            if ($Body) {
                $invokeRestMethodSplat.Body = $Body | ConvertTo-Json -Depth 10
            }
            if ($Script:InfobloxConfiguration['SkipCertificateValidation'] -eq $true) {
                $invokeRestMethodSplat.SkipCertificateCheck = $true
            }
            Remove-EmptyValue -Hashtable $invokeRestMethodSplat -Recursive -Rerun 2
            Invoke-RestMethod @invokeRestMethodSplat
            # we connected to the server, so we can reset the default Credentials value
            $PSDefaultParameterValues['Invoke-InfobloxQuery:Credential'] = $null
        }
        catch {
            if ($PSVersionTable.PSVersion.Major -gt 5) {
                $OriginalError = $_.Exception.Message
                if ($_.ErrorDetails.Message) {
                    try {
                        $JSONError = ConvertFrom-Json -InputObject $_.ErrorDetails.Message -ErrorAction Stop
                    }
                    catch {
                        if ($ErrorActionPreference -eq 'Stop') {
                            throw $OriginalError
                        }
                        Write-Warning -Message "Invoke-InfobloxQuery - Querying $Url failed. Error: $OriginalError"
                        return
                    }
                    if ($JSONError -and $JSONError.text) {
                        if ($ErrorActionPreference -eq 'Stop') {
                            throw $JSONError.text
                        }
                        Write-Warning -Message "Invoke-InfobloxQuery - Querying $Url failed. $($JSONError.text)"
                        return
                    }
                    else {
                        if ($ErrorActionPreference -eq 'Stop') {
                            throw $OriginalError
                        }
                        Write-Warning -Message "Invoke-InfobloxQuery - Querying $Url failed. Error: $OriginalError"
                    }
                }
                else {
                    if ($ErrorActionPreference -eq 'Stop') {
                        throw
                    }
                    Write-Warning -Message "Invoke-InfobloxQuery - Querying $Url failed. Error: $OriginalError"
                }
            }
            else {
                if ($ErrorActionPreference -eq 'Stop') {
                    throw
                }
                Write-Warning -Message "Invoke-InfobloxQuery - Querying $Url failed. Error: $($_.Exception.Message)"
            }
        }
    }
}
function Remove-InfobloxDnsRecord {
    <#
    .SYNOPSIS
    Remove Infoblox DNS records
 
    .DESCRIPTION
    Remove Infoblox DNS records
 
    .PARAMETER Name
    Name of the record to remove
 
    .PARAMETER Type
    Type of the record to remove
 
    .PARAMETER SkipPTR
    Skip PTR record removal, when removing A record
 
    .PARAMETER LogPath
    Path to log file. Changes are logged to this file
 
    .EXAMPLE
    Remove-InfobloxDnsRecord -Name 'test.example.com' -Type 'A' -WhatIf
 
    .EXAMPLE
    Remove-InfobloxDnsRecord -Name 'test.example.com' -Type 'A' -SkipPTR -WhatIf
 
    .NOTES
    General notes
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)][string[]] $Name,
        [ValidateSet(
            'A', 'CNAME', 'AAAA', 'PTR'
        )]
        [Parameter(Mandatory)][string] $Type,
        [switch] $SkipPTR,
        [string] $LogPath
    )
    [Array] $ToBeDeleted = foreach ($Record in $Name) {
        $FoundRecord = Get-InfobloxDNSRecord -Name $Record -Type $Type -Verbose:$false
        if ($FoundRecord) {
            $FoundRecord
            if ($LogPath) {
                Write-Color -Text "Found $($FoundRecord.name) with type $Type to be removed" -LogFile $LogPath -NoConsoleOutput
            }
        }
        else {
            Write-Verbose -Message "Remove-InfobloxDnsRecord - No record for $Record were found. Skipping"
            if ($LogPath) {
                Write-Color -Text "No record for $Record were found. Skipping" -LogFile $LogPath -NoConsoleOutput
            }
        }
    }

    Write-Verbose -Message "Remove-InfobloxDnsRecord - Found $($ToBeDeleted.Count) records to delete"

    [Array] $ToBeDeletedPTR = @(
        if ($Type -eq 'A' -and -not $SkipPTR) {
            foreach ($Record in $ToBeDeleted) {
                if ($null -eq $Record.ipv4addr) {
                    continue
                }
                try {
                    $PTRAddress = Convert-IpAddressToPtrString -IPAddress $Record.ipv4addr -ErrorAction Stop
                }
                catch {
                    Write-Warning -Message "Remove-InfobloxDnsRecord - Failed to convert $($Record.ipv4addr) to PTR"
                    if ($LogPath) {
                        Write-Color -Text "Failed to convert $($Record.ipv4addr) to PTR" -NoConsoleOutput -LogFile $LogPath
                    }
                }
                if ($PTRAddress) {
                    $PTRRecord = Get-InfobloxDNSRecord -Type PTR -Name $PTRAddress -Verbose:$false
                    if ($PTRRecord) {
                        $PTRRecord
                        if ($LogPath) {
                            Write-Color -Text "Found $($PTRRecord.name) with type PTR to be removed" -NoConsoleOutput -LogFile $LogPath
                        }
                    }
                    else {
                        Write-Verbose -Message "Remove-InfobloxDnsRecord - No PTR record for $($Record.name) were found. Skipping"
                        if ($LogPath) {
                            Write-Color -Text "No PTR record for $($Record.name) were found. Skipping" -NoConsoleOutput -LogFile $LogPath
                        }
                    }
                }
            }
        }
    )

    if ($ToBeDeletedPTR.Count -gt 0) {
        Write-Verbose -Message "Remove-InfobloxDnsRecord - Found $($ToBeDeletedPTR.Count) PTR records to delete"
    }

    foreach ($Record in $ToBeDeleted) {
        if (-not $Record._ref) {
            Write-Warning -Message "Remove-InfobloxDnsRecord - Record does not have a reference ID, skipping"
            if ($LogPath) {
                Write-Color -Text "Record does not have a reference ID, skipping" -NoConsoleOutput -LogFile $LogPath
            }
            continue
        }
        Write-Verbose -Message "Remove-InfobloxDnsRecord - Removing $($Record.name) with type $Type / WhatIf:$WhatIfPreference"
        if ($LogPath) {
            Write-Color -Text "Removing $($Record.name) with type $Type" -NoConsoleOutput -LogFile $LogPath
        }
        try {
            $Success = Remove-InfobloxObject -ReferenceID $Record._ref -WhatIf:$WhatIfPreference -ErrorAction Stop -ReturnSuccess -Verbose:$false
            if ($Success -eq $true -or $WhatIfPreference) {
                Write-Verbose -Message "Remove-InfobloxDnsRecord - Removed $($Record.name) with type $Type / WhatIf: $WhatIfPreference"
                if ($LogPath) {
                    Write-Color -Text "Removed $($Record.name) with type $Type" -NoConsoleOutput -LogFile $LogPath
                }
            }
            else {
                # this shouldn't really happen as the error action is set to stop
                Write-Warning -Message "Remove-InfobloxDnsRecord - Failed to remove $($Record.name) with type $Type / WhatIf: $WhatIfPreference"
                if ($LogPath) {
                    Write-Color -Text "Failed to remove $($Record.name) with type $Type / WhatIf: $WhatIfPreference" -NoConsoleOutput -LogFile $LogPath
                }
            }
        }
        catch {
            Write-Warning -Message "Remove-InfobloxDnsRecord - Failed to remove $($Record.name) with type $Type, error: $($_.Exception.Message)"
            if ($LogPath) {
                Write-Color -Text "Failed to remove $($Record.name) with type $Type, error: $($_.Exception.Message)" -NoConsoleOutput -LogFile $LogPath
            }
        }
    }
    foreach ($Record in $ToBeDeletedPTR) {
        if (-not $Record._ref) {
            Write-Warning -Message "Remove-InfobloxDnsRecord - PTR record does not have a reference ID, skipping"
            if ($LogPath) {
                Write-Color -Text "PTR record does not have a reference ID, skipping" -NoConsoleOutput -LogFile $LogPath
            }
            continue
        }
        Write-Verbose -Message "Remove-InfobloxDnsRecord - Removing $($Record.name) with type PTR / WhatIf:$WhatIfPreference"
        if ($LogPath) {
            Write-Color -Text "Removing $($Record.name) with type PTR / WhatIf: $WhatIfPreference" -NoConsoleOutput -LogFile $LogPath
        }
        try {
            $Success = Remove-InfobloxObject -ReferenceID $Record._ref -WhatIf:$WhatIfPreference -ErrorAction Stop -ReturnSuccess -Verbose:$false
            if ($Success -eq $true -or $WhatIfPreference) {
                Write-Verbose -Message "Remove-InfobloxDnsRecord - Removed $($Record.name) with type PTR / WhatIf: $WhatIfPreference"
                if ($LogPath) {
                    Write-Color -Text "Removed $($Record.name) with type PTR / WhatIf: $WhatIfPreference" -NoConsoleOutput -LogFile $LogPath
                }
            }
            else {
                # this shouldn't really happen as the error action is set to stop
                Write-Warning -Message "Remove-InfobloxDnsRecord - Failed to remove $($Record.name) with type PTR / WhatIf: $WhatIfPreference"
                if ($LogPath) {
                    Write-Color -Text "Failed to remove $($Record.name) with type PTR / WhatIf: $WhatIfPreference" -NoConsoleOutput -LogFile $LogPath
                }
            }
        }
        catch {
            Write-Warning -Message "Remove-InfobloxDnsRecord - Failed to remove $($Record.name) with type PTR, error: $($_.Exception.Message)"
            if ($LogPath) {
                Write-Color -Text "Failed to remove $($Record.name) with type PTR, error: $($_.Exception.Message)" -NoConsoleOutput -LogFile $LogPath
            }
        }
    }
}
function Remove-InfobloxFixedAddress {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory)][string] $MacAddress,
        [parameter()][string] $IPv4Address
    )

    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Remove-InfobloxFixedAddress - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    if (-not $IPv4Address) {
        Write-Verbose -Message "Remove-InfobloxFixedAddress - Removing $MacAddress"
    }
    else {
        Write-Verbose -Message "Remove-InfobloxFixedAddress - Removing $MacAddress from $IPv4Address"
    }

    $ListMacaddresses = Get-InfobloxFixedAddress -MacAddress $MacAddress

    if ($IPv4Address) {
        $ListMacaddresses = $ListMacaddresses | Where-Object -Property ipv4addr -EQ -Value $IPv4Address
    }
    foreach ($Mac in $ListMacaddresses) {
        $invokeInfobloxQuerySplat = @{
            RelativeUri = "$($Mac._ref)"
            Method      = 'DELETE'
        }
        $Output = Invoke-InfobloxQuery @invokeInfobloxQuerySplat #-WarningAction SilentlyContinue -WarningVariable varWarning
        if ($Output) {
            Write-Verbose -Message "Remove-InfobloxFixedAddress - Removed $($Mac.ipv4addr) with mac address $($Mac.mac) / $Output"
        }
        #else {
        #if (-not $WhatIfPreference) {
        # Write-Warning -Message "Remove-InfobloxFixedAddress - Failed to remove $($Mac.ipv4addr) with mac address $($Mac.mac), error: $varWarning"
        #}
        #}
    }
}
function Remove-InfobloxIPAddress {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [parameter()][string] $IPv4Address
    )

    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Remove-InfobloxIPAddress - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    Write-Verbose -Message "Remove-InfobloxIPAddress - Removing $IPv4Address"

    $ListIP = Get-InfobloxIPAddress -IPv4Address $IPv4Address
    foreach ($IP in $ListIP) {
        $invokeInfobloxQuerySplat = @{
            RelativeUri = "$($IP._ref)"
            Method      = 'DELETE'
        }
        $Output = Invoke-InfobloxQuery @invokeInfobloxQuerySplat #-WarningAction SilentlyContinue -WarningVariable varWarning
        if ($Output) {
            Write-Verbose -Message "Remove-InfobloxIPAddress - Removed $($IP.ip_address) from network $($IP.network) / $Output"
        }
        #else {
        # if (-not $WhatIfPreference) {
        # Write-Warning -Message "Remove-InfobloxIPAddress - Failed to remove $($IP.ip_address) with mac address $($IP.network), error: $varWarning"
        # }
        #}
    }
}
function Remove-InfobloxObject {
    <#
    .SYNOPSIS
    Remove an Infoblox object by reference ID
 
    .DESCRIPTION
    Remove an Infoblox object by reference ID
    It can be used to remove any object type, but it is recommended to use the more specific cmdlets
 
    .PARAMETER Objects
    An array of objects to remove
 
    .PARAMETER ReferenceID
    The reference ID of the object to remove
 
    .EXAMPLE
    Remove-InfobloxObject -ReferenceID 'record:host/ZG5zLmhvc3QkLl9kZWZhdWx0LmNvbS5pbmZvLmhvc3Q6MTcyLjI2LjEuMjAu:'
 
    .NOTES
    General notes
    #>

    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'ReferenceID')]
    param(
        [Parameter(Mandatory, ParameterSetName = 'Array')][Array] $Objects,
        [parameter(Mandatory, ParameterSetName = 'ReferenceID')][string] $ReferenceID,
        [switch] $ReturnSuccess
    )
    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Remove-InfobloxObject - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    if ($Objects) {
        $Objects | ForEach-Object {
            if ($_._ref) {
                $ReferenceID = $_._ref
                Remove-InfobloxObject -ReferenceID $ReferenceID
            }
            else {
                Write-Warning -Message "Remove-InfobloxObject - Object does not have a reference ID: $_"
            }
        }
    }
    else {
        $invokeInfobloxQuerySplat = @{
            RelativeUri = $ReferenceID
            Method      = 'DELETE'
        }
        $Output = Invoke-InfobloxQuery @invokeInfobloxQuerySplat #-WarningAction SilentlyContinue -WarningVariable varWarning
        if ($Output) {
            Write-Verbose -Message "Remove-InfobloxObject - Removed $($ReferenceID) / $Output"
            if ($ReturnSuccess) {
                $true
            }
        }
        else {
            if ($ReturnSuccess) {
                $false
            }
        }
    }
}
function Set-InfobloxDNSRecord {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER ReferenceID
    Parameter description
 
    .PARAMETER Object
    Parameter description
 
    .PARAMETER Type
    Parameter description
 
    .EXAMPLE
    Set-InfobloxDNSRecord -ReferenceID 'record:host/ZG5zLmhvc3QkLl9kZWZhdWx0LmNvbS5teW5ldC5jb20kMTAuMTAuMTAuMTA=' -Name 'xyz' -Type 'A'
 
    .EXAMPLE
    Set-InfobloxDNSRecord -ReferenceID 'record:host/ZG5zLmhvc3QkLl9kZWZhdWx0LmNvbS5teW5ldC5jb20kMTAuMTAuMTAuMTA=' -PTRName 'xyz -Type 'PTR'
 
    .EXAMPLE
    Set-InfobloxDNSRecord -ReferenceID 'record:host/ZG5zLmhvc3QkLl9kZWZhdWx0LmNvbS5teW5ldC5jb20kMTAuMTAuMTAuMTA=' -Name 'test2.mcdonalds.com' -Type 'CNAME'
 
    .NOTES
    General notes
    #>
#
    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'ReferenceID')]
    param(
        [parameter(ParameterSetName = 'ReferenceID', Mandatory)][string] $ReferenceID,
        [Alias("Name", 'PtrName', 'PTR', 'NameServer', 'Text')][parameter(ParameterSetName = 'Object', Mandatory)][string] $Object,
        [parameter(Mandatory)][ValidateSet(
            'A',
            'AAAA',
            'CNAME',
            'HOST',
            'PTR',
            'MX',
            'NS',
            'TXT'
        )][string] $Type
    )
    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Set-InfobloxDNSRecord - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    # Lets convert it to lowercase, since Infoblox is case sensitive
    $Type = $Type.ToLower()
    if ($Type -in 'A', 'AAAA', 'HOST', 'CNAME', 'MX') {
        $Body = [ordered] @{
            name = $Object.ToLower()
        }
    }
    elseif ($Type -eq 'PTR') {
        $Body = [ordered] @{
            ptrdname = $Object.ToLower()
        }
    }
    elseif ($Type -eq 'NS') {
        $Body = [ordered] @{
            nameserver = $Object.ToLower()
        }
    }
    elseif ($Type -eq 'TXT') {
        $Body = [ordered] @{
            text = $Object.ToLower()
        }
    }
    else {
        if ($ErrorActionPreference -eq 'Stop') {
            throw "Set-InfobloxDNSRecord - Unsupported type: $Type"
        }
        Write-Warning -Message "Set-InfobloxDNSRecord - Unsupported type: $Type"
        return
    }

    $invokeInfobloxQuerySplat = @{
        RelativeUri = $ReferenceID
        Method      = 'PUT'
        Body        = $Body
    }

    $Output = Invoke-InfobloxQuery @invokeInfobloxQuerySplat #-WarningAction SilentlyContinue -WarningVariable varWarning
    if ($Output) {
        Write-Verbose -Message "Set-InfobloxDNSRecord - Modified $Type / $Output"
    }
    #else {
    #if (-not $WhatIfPreference) {
    # Write-Warning -Message "Set-InfobloxDNSRecord - Failed to modify $Type, error: $varWarning"
    #}
    #}
}


# Export functions and aliases as required
Export-ModuleMember -Function @('Add-InfoBloxDNSRecord', 'Add-InfobloxFixedAddress', 'Connect-Infoblox', 'Disconnect-Infoblox', 'Get-InfobloxDHCPLease', 'Get-InfobloxDHCPRange', 'Get-InfobloxDiscoveryTask', 'Get-InfobloxDNSAuthZone', 'Get-InfobloxDNSDelegatedZone', 'Get-InfobloxDNSForwardZone', 'Get-InfobloxDNSRecord', 'Get-InfobloxDNSRecordAll', 'Get-InfobloxDNSView', 'Get-InfobloxFixedAddress', 'Get-InfobloxGrid', 'Get-InfobloxIPAddress', 'Get-InfobloxMember', 'Get-InfobloxNetwork', 'Get-InfobloxNetworkNextAvailableIP', 'Get-InfobloxNetworkView', 'Get-InfobloxObjects', 'Get-InfobloxPermission', 'Get-InfobloxResponsePolicyZones', 'Get-InfobloxSchema', 'Get-InfoBloxSearch', 'Get-InfobloxVDiscoveryTask', 'Invoke-InfobloxQuery', 'Remove-InfobloxDnsRecord', 'Remove-InfobloxFixedAddress', 'Remove-InfobloxIPAddress', 'Remove-InfobloxObject', 'Set-InfobloxDNSRecord') -Alias @('Get-InfobloxDHCPLeases', 'Get-InfobloxDNSAuthZones', 'Get-InfobloxDNSRecords', 'Get-InfobloxDNSRecordsAll')
# SIG # Begin signature block
# MIItsQYJKoZIhvcNAQcCoIItojCCLZ4CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCN/TJDwz0rnyaw
# XNmfiV55g2yHJGdi7b+6t5zULhWy2qCCJrQwggWNMIIEdaADAgECAhAOmxiO+dAt
# 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV
# BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa
# Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy
# dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD
# ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
# ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E
# MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy
# unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF
# xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1
# 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB
# MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR
# WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6
# nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB
# YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S
# UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x
# q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB
# NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP
# TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC
# AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
# Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv
# bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0
# aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB
# LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc
# Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov
# Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy
# oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW
# juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF
# mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z
# twGpn1eqXijiuZQwggWQMIIDeKADAgECAhAFmxtXno4hMuI5B72nd3VcMA0GCSqG
# SIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx
# GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy
# dXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGIx
# CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
# dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH
# NDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo3rvkXUo8MCIw
# aTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutWxpdtHauyefLK
# EdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQRBAu34LzB4Tm
# dDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nPzaw0QF+xembu
# d8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6PgNq2kZhAkHnD
# eMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEBfCFM1LyuGwN1
# XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3wWmIdph2PVld
# QnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2mkQZK37AlLTS
# YW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2Yn72gLD76GSm
# M9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF13nqsX40/ybzT
# QRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+dIPyhHsXAj6Kx
# fgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD
# VR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwPTzANBgkq
# hkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNkaA9Wz3eucPn9mkqZucl4
# XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjSPMFDQK4dUPVS/JA7u5iZ
# aWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK7VB6fWIhCoDIc2bRoAVg
# X+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eBcg3AFDLvMFkuruBx8lbk
# apdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp5aPNoiBB19GcZNnqJqGL
# FNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msgdDDS4Dk0EIUhFQEI6FUy
# 3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vriRbgjU2wGb2dVf0a1TD9u
# KFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ79ARj6e/CVABRoIoqyc54
# zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5nLGbsQAe79APT0JsyQq8
# 7kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3i0objwG2J5VT6LaJbVu8
# aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0HEEcRrYc9B9F1vM/zZn4w
# ggauMIIElqADAgECAhAHNje3JFR82Ees/ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIx
# CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
# dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH
# NDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAzMjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVT
# MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1
# c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqG
# SIb3DQEBAQUAA4ICDwAwggIKAoICAQDGhjUGSbPBPXJJUVXHJQPE8pE3qZdRodbS
# g9GeTKJtoLDMg/la9hGhRBVCX6SI82j6ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9
# /UO0hNoR8XOxs+4rgISKIhjf69o9xBd/qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXn
# HwZljZQp09nsad/ZkIdGAHvbREGJ3HxqV3rwN3mfXazL6IRktFLydkf3YYMZ3V+0
# VAshaG43IbtArF+y3kp9zvU5EmfvDqVjbOSmxR3NNg1c1eYbqMFkdECnwHLFuk4f
# sbVYTXn+149zk6wsOeKlSNbwsDETqVcplicu9Yemj052FVUmcJgmf6AaRyBD40Nj
# gHt1biclkJg6OBGz9vae5jtb7IHeIhTZgirHkr+g3uM+onP65x9abJTyUpURK1h0
# QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZGs506o9UD4L/wojzKQtwYSH8UNM/STKvv
# mz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHzNklNiyDSLFc1eSuo80VgvCONWPfcYd6T
# /jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk
# 42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJASgADoRU7s7pXcheMBK9Rp6103a50g5r
# mQzSM7TNsQIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4E
# FgQUuhbZbU2FL3MpdpovdYxqII+eyG8wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5n
# P+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcG
# CCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu
# Y29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln
# aUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8v
# Y3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNV
# HSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIB
# AH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4QTRPPMFPOvxj7x1Bd4ksp+3CKDaopafxp
# wc8dB+k+YMjYC+VcW9dth/qEICU0MWfNthKWb8RQTGIdDAiCqBa9qVbPFXONASIl
# zpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1gtqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQ
# cAp876i8dU+6WvepELJd6f8oVInw1YpxdmXazPByoyP6wCeCRK6ZJxurJB4mwbfe
# Kuv2nrF5mYGjVoarCkXJ38SNoOeY+/umnXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+j
# Sbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+UzTl63f8lY5knLD0/a6fxZsNBzU+2QJsh
# IUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhzq6YBT70/O3itTK37xJV77QpfMzmHQXh6
# OOmc4d0j/R0o08f56PGYX/sr2H7yRp11LB4nLCbbbxV7HhmLNriT1ObyF5lZynDw
# N7+YAN8gFk8n+2BnFqFmut1VwDophrCYoCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR
# 81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvkdgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2
# VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3OBqhK/bt1nz8MIIGsDCCBJigAwIBAgIQ
# CK1AsmDSnEyfXs2pvZOu2TANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQGEwJVUzEV
# MBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29t
# MSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjEwNDI5MDAw
# MDAwWhcNMzYwNDI4MjM1OTU5WjBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln
# aUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBT
# aWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMIICIjANBgkqhkiG9w0BAQEF
# AAOCAg8AMIICCgKCAgEA1bQvQtAorXi3XdU5WRuxiEL1M4zrPYGXcMW7xIUmMJ+k
# jmjYXPXrNCQH4UtP03hD9BfXHtr50tVnGlJPDqFX/IiZwZHMgQM+TXAkZLON4gh9
# NH1MgFcSa0OamfLFOx/y78tHWhOmTLMBICXzENOLsvsI8IrgnQnAZaf6mIBJNYc9
# URnokCF4RS6hnyzhGMIazMXuk0lwQjKP+8bqHPNlaJGiTUyCEUhSaN4QvRRXXegY
# E2XFf7JPhSxIpFaENdb5LpyqABXRN/4aBpTCfMjqGzLmysL0p6MDDnSlrzm2q2AS
# 4+jWufcx4dyt5Big2MEjR0ezoQ9uo6ttmAaDG7dqZy3SvUQakhCBj7A7CdfHmzJa
# wv9qYFSLScGT7eG0XOBv6yb5jNWy+TgQ5urOkfW+0/tvk2E0XLyTRSiDNipmKF+w
# c86LJiUGsoPUXPYVGUztYuBeM/Lo6OwKp7ADK5GyNnm+960IHnWmZcy740hQ83eR
# Gv7bUKJGyGFYmPV8AhY8gyitOYbs1LcNU9D4R+Z1MI3sMJN2FKZbS110YU0/EpF2
# 3r9Yy3IQKUHw1cVtJnZoEUETWJrcJisB9IlNWdt4z4FKPkBHX8mBUHOFECMhWWCK
# ZFTBzCEa6DgZfGYczXg4RTCZT/9jT0y7qg0IU0F8WD1Hs/q27IwyCQLMbDwMVhEC
# AwEAAaOCAVkwggFVMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFGg34Ou2
# O/hfEYb7/mF7CIhl9E5CMB8GA1UdIwQYMBaAFOzX44LScV1kTN8uZz/nupiuHA9P
# MA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDAzB3BggrBgEFBQcB
# AQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggr
# BgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1
# c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybDMuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmwwHAYDVR0gBBUwEzAH
# BgVngQwBAzAIBgZngQwBBAEwDQYJKoZIhvcNAQEMBQADggIBADojRD2NCHbuj7w6
# mdNW4AIapfhINPMstuZ0ZveUcrEAyq9sMCcTEp6QRJ9L/Z6jfCbVN7w6XUhtldU/
# SfQnuxaBRVD9nL22heB2fjdxyyL3WqqQz/WTauPrINHVUHmImoqKwba9oUgYftzY
# gBoRGRjNYZmBVvbJ43bnxOQbX0P4PpT/djk9ntSZz0rdKOtfJqGVWEjVGv7XJz/9
# kNF2ht0csGBc8w2o7uCJob054ThO2m67Np375SFTWsPK6Wrxoj7bQ7gzyE84FJKZ
# 9d3OVG3ZXQIUH0AzfAPilbLCIXVzUstG2MQ0HKKlS43Nb3Y3LIU/Gs4m6Ri+kAew
# Q3+ViCCCcPDMyu/9KTVcH4k4Vfc3iosJocsL6TEa/y4ZXDlx4b6cpwoG1iZnt5Lm
# Tl/eeqxJzy6kdJKt2zyknIYf48FWGysj/4+16oh7cGvmoLr9Oj9FpsToFpFSi0HA
# SIRLlk2rREDjjfAVKM7t8RhWByovEMQMCGQ8M4+uKIw8y4+ICw2/O/TOHnuO77Xr
# y7fwdxPm5yg/rBKupS8ibEH5glwVZsxsDsrFhsP2JjMMB0ug0wcCampAMEhLNKhR
# ILutG4UI4lkNbcoFUCvqShyepf2gpx8GdOfy1lKQ/a+FSCH5Vzu0nAPthkX0tGFu
# v2jiJmCG6sivqf6UHedjGzqGVnhOMIIGwjCCBKqgAwIBAgIQBUSv85SdCDmmv9s/
# X+VhFjANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln
# aUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5
# NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTIzMDcxNDAwMDAwMFoXDTM0MTAx
# MzIzNTk1OVowSDELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMu
# MSAwHgYDVQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMzCCAiIwDQYJKoZIhvcN
# AQEBBQADggIPADCCAgoCggIBAKNTRYcdg45brD5UsyPgz5/X5dLnXaEOCdwvSKOX
# ejsqnGfcYhVYwamTEafNqrJq3RApih5iY2nTWJw1cb86l+uUUI8cIOrHmjsvlmbj
# aedp/lvD1isgHMGXlLSlUIHyz8sHpjBoyoNC2vx/CSSUpIIa2mq62DvKXd4ZGIX7
# ReoNYWyd/nFexAaaPPDFLnkPG2ZS48jWPl/aQ9OE9dDH9kgtXkV1lnX+3RChG4PB
# uOZSlbVH13gpOWvgeFmX40QrStWVzu8IF+qCZE3/I+PKhu60pCFkcOvV5aDaY7Mu
# 6QXuqvYk9R28mxyyt1/f8O52fTGZZUdVnUokL6wrl76f5P17cz4y7lI0+9S769Sg
# LDSb495uZBkHNwGRDxy1Uc2qTGaDiGhiu7xBG3gZbeTZD+BYQfvYsSzhUa+0rRUG
# FOpiCBPTaR58ZE2dD9/O0V6MqqtQFcmzyrzXxDtoRKOlO0L9c33u3Qr/eTQQfqZc
# ClhMAD6FaXXHg2TWdc2PEnZWpST618RrIbroHzSYLzrqawGw9/sqhux7UjipmAmh
# cbJsca8+uG+W1eEQE/5hRwqM/vC2x9XH3mwk8L9CgsqgcT2ckpMEtGlwJw1Pt7U2
# 0clfCKRwo+wK8REuZODLIivK8SgTIUlRfgZm0zu++uuRONhRB8qUt+JQofM604qD
# y0B7AgMBAAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAW
# BgNVHSUBAf8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglg
# hkgBhv1sBwEwHwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8wHQYDVR0O
# BBYEFKW27xPn783QZKHVVqllMaPe1eNJMFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6
# Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEy
# NTZUaW1lU3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQGCCsGAQUF
# BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKGTGh0dHA6
# Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZT
# SEEyNTZUaW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIBAIEa1t6g
# qbWYF7xwjU+KPGic2CX/yyzkzepdIpLsjCICqbjPgKjZ5+PF7SaCinEvGN1Ott5s
# 1+FgnCvt7T1IjrhrunxdvcJhN2hJd6PrkKoS1yeF844ektrCQDifXcigLiV4JZ0q
# BXqEKZi2V3mP2yZWK7Dzp703DNiYdk9WuVLCtp04qYHnbUFcjGnRuSvExnvPnPp4
# 4pMadqJpddNQ5EQSviANnqlE0PjlSXcIWiHFtM+YlRpUurm8wWkZus8W8oM3NG6w
# QSbd3lqXTzON1I13fXVFoaVYJmoDRd7ZULVQjK9WvUzF4UbFKNOt50MAcN7MmJ4Z
# iQPq1JE3701S88lgIcRWR+3aEUuMMsOI5ljitts++V+wQtaP4xeR0arAVeOGv6wn
# LEHQmjNKqDbUuXKWfpd5OEhfysLcPTLfddY2Z1qJ+Panx+VPNTwAvb6cKmx5Adza
# ROY63jg7B145WPR8czFVoIARyxQMfq68/qTreWWqaNYiyjvrmoI1VygWy2nyMpqy
# 0tg6uLFGhmu6F/3Ed2wVbK6rr3M66ElGt9V/zLY4wNjsHPW2obhDLN9OTH0eaHDA
# dwrUAuBcYLso/zjlUlrWrBciI0707NMX+1Br/wd3H3GXREHJuEbTbDJ8WC9nR2Xl
# G3O2mflrLAZG70Ee8PBf4NvZrZCARK+AEEGKMIIHXzCCBUegAwIBAgIQB8JSdCgU
# otar/iTqF+XdLjANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJVUzEXMBUGA1UE
# ChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQg
# Q29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMB4XDTIzMDQxNjAw
# MDAwMFoXDTI2MDcwNjIzNTk1OVowZzELMAkGA1UEBhMCUEwxEjAQBgNVBAcMCU1p
# a2/FgsOzdzEhMB8GA1UECgwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMSEwHwYD
# VQQDDBhQcnplbXlzxYJhdyBLxYJ5cyBFVk9URUMwggIiMA0GCSqGSIb3DQEBAQUA
# A4ICDwAwggIKAoICAQCUmgeXMQtIaKaSkKvbAt8GFZJ1ywOH8SwxlTus4McyrWmV
# OrRBVRQA8ApF9FaeobwmkZxvkxQTFLHKm+8knwomEUslca8CqSOI0YwELv5EwTVE
# h0C/Daehvxo6tkmNPF9/SP1KC3c0l1vO+M7vdNVGKQIQrhxq7EG0iezBZOAiukNd
# GVXRYOLn47V3qL5PwG/ou2alJ/vifIDad81qFb+QkUh02Jo24SMjWdKDytdrMXi0
# 235CN4RrW+8gjfRJ+fKKjgMImbuceCsi9Iv1a66bUc9anAemObT4mF5U/yQBgAuA
# o3+jVB8wiUd87kUQO0zJCF8vq2YrVOz8OJmMX8ggIsEEUZ3CZKD0hVc3dm7cWSAw
# 8/FNzGNPlAaIxzXX9qeD0EgaCLRkItA3t3eQW+IAXyS/9ZnnpFUoDvQGbK+Q4/bP
# 0ib98XLfQpxVGRu0cCV0Ng77DIkRF+IyR1PcwVAq+OzVU3vKeo25v/rntiXCmCxi
# W4oHYO28eSQ/eIAcnii+3uKDNZrI15P7VxDrkUIc6FtiSvOhwc3AzY+vEfivUkFK
# RqwvSSr4fCrrkk7z2Qe72Zwlw2EDRVHyy0fUVGO9QMuh6E3RwnJL96ip0alcmhKA
# BGoIqSW05nXdCUbkXmhPCTT5naQDuZ1UkAXbZPShKjbPwzdXP2b8I9nQ89VSgQID
# AQABo4ICAzCCAf8wHwYDVR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYD
# VR0OBBYEFHrxaiVZuDJxxEk15bLoMuFI5233MA4GA1UdDwEB/wQEAwIHgDATBgNV
# HSUEDDAKBggrBgEFBQcDAzCBtQYDVR0fBIGtMIGqMFOgUaBPhk1odHRwOi8vY3Js
# My5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQw
# OTZTSEEzODQyMDIxQ0ExLmNybDBToFGgT4ZNaHR0cDovL2NybDQuZGlnaWNlcnQu
# Y29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAy
# MUNBMS5jcmwwPgYDVR0gBDcwNTAzBgZngQwBBAEwKTAnBggrBgEFBQcCARYbaHR0
# cDovL3d3dy5kaWdpY2VydC5jb20vQ1BTMIGUBggrBgEFBQcBAQSBhzCBhDAkBggr
# BgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBo
# dHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2Rl
# U2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAAMA0GCSqG
# SIb3DQEBCwUAA4ICAQC3EeHXUPhpe31K2DL43Hfh6qkvBHyR1RlD9lVIklcRCR50
# ZHzoWs6EBlTFyohvkpclVCuRdQW33tS6vtKPOucpDDv4wsA+6zkJYI8fHouW6Tqa
# 1W47YSrc5AOShIcJ9+NpNbKNGih3doSlcio2mUKCX5I/ZrzJBkQpJ0kYha/pUST2
# CbE3JroJf2vQWGUiI+J3LdiPNHmhO1l+zaQkSxv0cVDETMfQGZKKRVESZ6Fg61b0
# djvQSx510MdbxtKMjvS3ZtAytqnQHk1ipP+Rg+M5lFHrSkUlnpGa+f3nuQhxDb7N
# 9E8hUVevxALTrFifg8zhslVRH5/Df/CxlMKXC7op30/AyQsOQxHW1uNx3tG1DMgi
# zpwBasrxh6wa7iaA+Lp07q1I92eLhrYbtw3xC2vNIGdMdN7nd76yMIjdYnAn7r38
# wwtaJ3KYD0QTl77EB8u/5cCs3ShZdDdyg4K7NoJl8iEHrbqtooAHOMLiJpiL2i9Y
# n8kQMB6/Q6RMO3IUPLuycB9o6DNiwQHf6Jt5oW7P09k5NxxBEmksxwNbmZvNQ65Z
# n3exUAKqG+x31Egz5IZ4U/jPzRalElEIpS0rgrVg8R8pEOhd95mEzp5WERKFyXhe
# 6nB6bSYHv8clLAV0iMku308rpfjMiQkqS3LLzfUJ5OHqtKKQNMLxz9z185UCszGC
# BlMwggZPAgEBMH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ
# bmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBS
# U0E0MDk2IFNIQTM4NCAyMDIxIENBMQIQB8JSdCgUotar/iTqF+XdLjANBglghkgB
# ZQMEAgEFAKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJ
# AzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8G
# CSqGSIb3DQEJBDEiBCDnlQdvlTPN/dzKhFhi6Gh0ClhvY4oZ1GQeimA3c9ouZjAN
# BgkqhkiG9w0BAQEFAASCAgBGZLbj8uJ2y5Tt1IeucVZZAtPj9eU5r7wYeSJQFKPt
# YgZdxi4JBJ2kxL8ElJK5IV9ea0crYSh32yU8jBJGUOQT6KxQHszkDeZnhZPFuJd/
# 83vnbMB/qsGTfnlBXG6ABCL1hOuOehbzmjYVKVK7jw4jAv222RpIheiMrrYPDdNG
# jJncAhYZr+X+ELWCgw59sDKwVjX4q9C1q2f7gP9w8xkPoSX9yJ4mHQAPHSfmzpXX
# OvDUp1KLQlRahd061+t9OriaB7VRA2GtXl9yF/ILtCgzFsHhQtA5SmkeJfh0hE0L
# TQpkkN1rlgBzqigukhfio7FPeACLJgTUz76TJ+KpINjr1YQbNumD4G/QBYCmcMqn
# pLdPTsUkl+T9sjLBRNpJ7K7LdADR+iGnhO6ehEYle4Pidv5QDPBioDauA/hhym0l
# GsLwK1PJLj4RthQkwLBWScKX6MEmv1dUxM5YZJGC4IIExkHrCoAHk7l+DPkuyYf+
# o4s7+AhX55e4sxeNWCcuFiij6wKvohKK2xY35+MnH7+VreLhruJ1GQCcCWaL4M6q
# ol47Wolq0TyAtX3e3tR2T7sDxyWTXqtI9MocFtv8ss9hLGrXAbRoEOkDsK3TpomB
# x36KTLjwrJetudDarkQFGiYD57BhoHzvgnsrgT8+IM0O5BojeZNs0tk32LMo0iHx
# 2qGCAyAwggMcBgkqhkiG9w0BCQYxggMNMIIDCQIBATB3MGMxCzAJBgNVBAYTAlVT
# MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1
# c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0ECEAVEr/OUnQg5
# pr/bP1/lYRYwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcN
# AQcBMBwGCSqGSIb3DQEJBTEPFw0yNDA0MTYwNzEwNTBaMC8GCSqGSIb3DQEJBDEi
# BCBWXlYkijKq3/Jqp1IFcwaOCK8xy//lXu3drc0G2jFinTANBgkqhkiG9w0BAQEF
# AASCAgBWj/DJ6hsrhD2RQ0ZAGEWWaersbv+SW5da3hKU4/pstlFwrxmJwAxO/QbQ
# 1WB4/5qOSTTjtj/7pkkbbx6SsRD/iIOXPDgVznTWPXfSPHoZU1SsK27qy/3Ujbje
# GkcSOpoA11CERvJKxzS/9EOK74eI82fvAZAcNVJotl2e+5AqtxnlLNIZFz7TejuN
# y6Hz9Cg8GCXaEEE1RmP/4W9qZ9lGB+hTmWqgyZQ2cXnhur7b7suBIriX9oGNZwyi
# uTZ+VYbTDOWEqCyfTQgmGZiYpzYOm0zOECfUz2mkXsZoY19+nvd+xNt52I3GeJRV
# rmeY1bApohe2dzRoSvDR1YpTyEeUdK3/4sz/sFx8PiqjMY2BEVt2BxzCMTkM9k8S
# TEaF5VK7cxp7L9Mt6TBpG9bB8F7Zli3R0UUvgaDiqY8Hi5mh3seqDyp86ulDYZyc
# Ailb0/v+NdFz19/0atrPQ0ZkuSb5e5uZLhKSR/OJn7yX/x8kRJU/vkPpZ1LHzKjC
# Fc1Q2pitE8afdHv5dO54enXtEFVxM7+jfDHRfa1RWArPqgSbYT+9bNFlK4iuSyOz
# e8/sHaCTl3i1v2JvZDbyRoQD5msWh2AI6X7sUnmMMtwIe1iFyeJFqwkmZMpuBJI6
# OAsPjHq4vKlgXpcTTnaCw+6uaSShRgWpyfqmu0SpgPuhd9pKKg==
# SIG # End signature block