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 { 
    <#
    .SYNOPSIS
    Removes empty values from a hashtable recursively.
 
    .DESCRIPTION
    This function removes empty values from a given hashtable. It can be used to clean up a hashtable by removing keys with null, empty string, empty array, or empty dictionary values. The function supports recursive removal of empty values.
 
    .PARAMETER Hashtable
    The hashtable from which empty values will be removed.
 
    .PARAMETER ExcludeParameter
    An array of keys to exclude from the removal process.
 
    .PARAMETER Recursive
    Indicates whether to recursively remove empty values from nested hashtables.
 
    .PARAMETER Rerun
    Specifies the number of times to rerun the removal process recursively.
 
    .PARAMETER DoNotRemoveNull
    If specified, null values will not be removed.
 
    .PARAMETER DoNotRemoveEmpty
    If specified, empty string values will not be removed.
 
    .PARAMETER DoNotRemoveEmptyArray
    If specified, empty array values will not be removed.
 
    .PARAMETER DoNotRemoveEmptyDictionary
    If specified, empty dictionary values will not be removed.
 
    .EXAMPLE
    $hashtable = @{
        'Key1' = '';
        'Key2' = $null;
        'Key3' = @();
        'Key4' = @{}
    }
    Remove-EmptyValue -Hashtable $hashtable -Recursive
 
    Description
    -----------
    This example removes empty values from the $hashtable recursively.
 
    #>

    [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 { 
    <#
    .SYNOPSIS
    Converts a binary string to an IP address format.
 
    .DESCRIPTION
    This function takes a binary string as input and converts it to an IP address format. The binary string must be evenly divisible by 8.
 
    .PARAMETER Binary
    The binary string to convert to an IP address format.
 
    .EXAMPLE
    Convert-BinaryToIP -Binary "01000001000000100000000100000001"
    Output: 65.0.1.1
    #>

    [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 { 
    <#
    .SYNOPSIS
    Converts an IPv4 address to binary format.
 
    .DESCRIPTION
    This function takes an IPv4 address as input and converts it to binary format.
 
    .PARAMETER IP
    Specifies the IPv4 address to convert to binary format.
 
    .EXAMPLE
    Convert-IPToBinary -IP "192.168.1.1"
    Converts the IPv4 address "192.168.1.1" to binary format.
 
    .EXAMPLE
    Convert-IPToBinary -IP "10.0.0.1"
    Converts the IPv4 address "10.0.0.1" to binary format.
 
    #>

    [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 { 
    <#
    .SYNOPSIS
    Generates a list of IP addresses within a specified binary range.
 
    .DESCRIPTION
    This function takes two binary strings representing the start and end IP addresses and generates a list of IP addresses within that range.
 
    .PARAMETER StartBinary
    Specifies the starting IP address in binary format.
 
    .PARAMETER EndBinary
    Specifies the ending IP address in binary format.
 
    .EXAMPLE
    Get-IPRange -StartBinary '11000000' -EndBinary '11000010'
    Description:
    Generates a list of IP addresses between '192.0.0.0' and '192.0.2.0'.
 
    .EXAMPLE
    Get-IPRange -StartBinary '10101010' -EndBinary '10101100'
    Description:
    Generates a list of IP addresses between '170.0.0.0' and '172.0.0.0'.
    #>

    [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 { 
    <#
    .SYNOPSIS
    Checks if an IP address falls within a specified range defined by binary start and end values.
 
    .DESCRIPTION
    This function compares the binary representation of an IP address with the binary start and end values to determine if the IP address falls within the specified range.
 
    .EXAMPLE
    Test-IPIsInNetwork -IP "192.168.1.10" -StartBinary "11000000101010000000000100000000" -EndBinary "11000000101010000000000111111111"
 
    Description:
    Checks if the IP address 192.168.1.10 falls within the range defined by the binary start and end values.
 
    #>

    [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-InfobloxDHCPRange {
    <#
    .SYNOPSIS
    Adds a DHCP range to Infoblox.
 
    .DESCRIPTION
    This function adds a DHCP range to Infoblox. It requires an established connection to an Infoblox server, which can be done using the Connect-Infoblox function.
 
    .PARAMETER StartAddress
    The starting IP address of the DHCP range. This parameter is mandatory.
 
    .PARAMETER EndAddress
    The ending IP address of the DHCP range. This parameter is mandatory.
 
    .PARAMETER Name
    The name of the DHCP range.
 
    .PARAMETER Comment
    A comment for the DHCP range.
 
    .PARAMETER NetworkView
    The network view in which the DHCP range will be added. The default is 'default'.
 
    .PARAMETER MSServer
    The Microsoft server to which the DHCP range will be added.
 
    .PARAMETER ReturnOutput
    If this switch is present, the function will return the output of the operation.
 
    .PARAMETER ExtensibleAttribute
    An extensible attribute to be added to the DHCP range.
 
    .PARAMETER FailoverAssociation
    The failover association for the DHCP range.
 
    .PARAMETER Options
    An array of options to be added to the DHCP range.
 
    .PARAMETER MSOptions
    An array of Microsoft options to be added to the DHCP range.
 
    .PARAMETER ServerAssociationType
    The server association type for the DHCP range. The possible values are 'MEMBER', 'MS_FAILOVER', 'NONE', 'MS_SERVER', 'FAILOVER'.
 
    .PARAMETER Exclude
    An array of IP addresses or IP address ranges to be excluded from the DHCP range.
 
    .PARAMETER AlwaysUpdateDns
    If this switch is present, DNS will always be updated for the DHCP range.
 
    .PARAMETER Disable
    If this switch is present, the DHCP range will be disabled.
 
    .EXAMPLE
    Add-InfobloxDHCPRange -StartAddress '192.168.1.100' -EndAddress '192.168.1.200' -Name 'DHCP Range 1' -Comment 'This is a DHCP range.'
    Adds a DHCP range from 192.168.1.100 to 192.168.1.200 with the name 'DHCP Range 1' and a comment 'This is a DHCP range.'.
 
    .EXAMPLE
    Add-InfobloxDHCPRange -StartAddress '10.22.41.15' -EndAddress '10.22.41.30'
    Adds a reserved range from 10.22.41.15 to 10.22.41.30
 
    .EXAMPLE
    $addInfobloxDHCPRangeSplat = @{
        StartAddress = '10.22.41.51'
        EndAddress = '10.22.41.60'
        Verbose = $true
        MSServer = 'dhcp2016.evotec.pl'
        Name = 'DHCP Range Me?'
        ServerAssociationType = 'MS_SERVER'
    }
 
    Add-InfobloxDHCPRange @addInfobloxDHCPRangeSplat
 
    .EXAMPLE
    $addInfobloxDHCPRangeSplat = @{
        StartAddress = '10.22.41.70'
        EndAddress = '10.22.41.90'
        Verbose = $true
        MSServer = 'dhcp2019.evotec.pl'
        Name = 'DHCP Range Me2?'
        ServerAssociationType = 'MS_SERVER'
        Exclude = '10.22.41.75-10.22.41.79'
    }
 
    Add-InfobloxDHCPRange @addInfobloxDHCPRangeSplat
 
    .EXAMPLE
    $addInfobloxDHCPRangeSplat = @{
        StartAddress = '10.10.12.5'
        EndAddress = '10.10.12.10'
        Options = @(
            New-InfobloxOption -Name "dhcp-lease-time" -Number 51 -UseOption -Value '86400' -VendorClass 'DHCP'
            New-InfobloxOption -Name "domain-name-servers" -Number 6 -UseOption -Value '192.168.0.15' -VendorClass 'DHCP'
            New-InfobloxOption -Name 'routers' -Number 3 -UseOption -Value '192.168.11.12' -VendorClass 'DHCP'
            New-InfobloxOption -Name 'time-servers' -Number 4 -UseOption -Value '11' -VendorClass 'DHCP'
        )
        Verbose = $true
    }
 
    Add-InfobloxDHCPRange @addInfobloxDHCPRangeSplat
 
    .NOTES
    You must first connect to an Infoblox server using Connect-Infoblox before running this function.
    Please note that when using MSServer parameter you need to provide a valid server name that is already added to Infoblox,
    and it also needs to be part of Members in Add-InfobloxNetwork.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)][string] $StartAddress,
        [Parameter(Mandatory)][string] $EndAddress,
        [string] $Name,
        [string] $Comment,
        [string] $NetworkView = 'default',
        [string] $MSServer,
        [switch] $ReturnOutput,
        [System.Collections.IDictionary] $ExtensinbleAttribute,
        [Array] $Options,
        [Alias('ms_options')][Array] $MSOptions,
        [alias('failover_association')][string] $FailoverAssociation,
        [ValidateSet('MEMBER', 'MS_FAILOVER', 'NONE', 'MS_SERVER', 'FAILOVER')] [string] $ServerAssociationType,
        [Array] $Exclude,
        [switch] $AlwaysUpdateDns,
        [switch] $Disable
    )
    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Add-InfobloxDHCPRange - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    $Body = [ordered] @{
        "start_addr"   = $StartAddress
        "end_addr"     = $EndAddress
        "network_view" = $NetworkView
    }
    if ($Name) {
        $Body["name"] = $Name
    }
    if ($Comment) {
        $Body["comment"] = $Comment
    }
    if ($ServerAssociationType) {
        $Body["server_association_type"] = $ServerAssociationType
    }
    if ($MSServer) {
        $Body["ms_server"] = [PSCustomObject] @{
            "_struct"  = "msdhcpserver"
            "ipv4addr" = $MSServer
        }
    }
    if ($ExtensinbleAttribute) {
        $Body["extattrs"] = [ordered] @{}
        foreach ($Key in $ExtensinbleAttribute.Keys) {
            if ($ExtensinbleAttribute[$Key] -is [System.Collections.IDictionary]) {
                $Body["extattrs"][$Key] = $ExtensinbleAttribute[$Key]
            }
            else {
                $Body["extattrs"][$Key] = @{
                    value = $ExtensinbleAttribute[$Key]
                }
            }
        }
    }
    if ($Options) {
        $Body["options"] = @(
            foreach ($Option in $Options) {
                $Option
            }
        )
    }

    if ($MSOptions) {
        $Body["ms_options"] = @(
            foreach ($MSOption in $MSOptions) {
                $MSOption
            }
        )
    }

    if ($FailoverAssociation) {
        $Body["failover_association"] = $FailoverAssociation
    }
    if ($Exclude) {
        $Body["exclude"] = @(
            foreach ($ExcludeItem in $Exclude) {
                if ($ExcludeItem -is [string]) {
                    if ($ExcludeItem -like "*-*") {
                        $ExcludeItem = $ExcludeItem -split '-'
                        $ExcludeItem = @{
                            StartAddress = $ExcludeItem[0]
                            EndAddress   = $ExcludeItem[1]
                        }
                    }
                    else {
                        $ExcludeItem = @{
                            StartAddress = $ExcludeItem
                            EndAddress   = $ExcludeItem
                        }
                    }
                }
                [ordered] @{
                    "start_address" = $ExcludeItem.StartAddress
                    "end_address"   = $ExcludeItem.EndAddress
                }
            }
        )
    }

    if ($PSBoundParameters.ContainsKey('AlwaysUpdateDns')) {
        $Body["always_update_dns"] = $AlwaysUpdateDns.IsPresent
    }
    if ($PSBoundParameters.ContainsKey('Disable')) {
        $Body["disable"] = $Disable.IsPresent
    }

    Remove-EmptyValue -Hashtable $Body

    $invokeInfobloxQuerySplat = @{
        RelativeUri = "range"
        Method      = 'POST'
        Body        = $Body
    }

    $Output = Invoke-InfobloxQuery @invokeInfobloxQuerySplat
    if ($Output) {
        Write-Verbose -Message "Add-InfobloxDHCPRange - $Output"
        if ($ReturnOutput) {
            $Output
        }
    }
}
function Add-InfobloxDHCPRangeOptions {
    <#
    .SYNOPSIS
    Adds DHCP range options to an Infoblox server.
 
    .DESCRIPTION
    This function adds specified DHCP range options to an Infoblox server. It allows adding options and Microsoft-specific options for a given network or reference ID.
 
    .PARAMETER Type
    Specifies the type of options to add. Valid values are 'Options' and 'MsOptions'.
 
    .PARAMETER Network
    The network for which to add DHCP options.
 
    .PARAMETER ReferenceID
    The unique identifier for the DHCP range to be modified.
 
    .PARAMETER Name
    The name of the DHCP option to be added.
 
    .PARAMETER Number
    The number of the DHCP option.
 
    .PARAMETER Value
    The value of the DHCP option.
 
    .PARAMETER VendorClass
    The vendor class of the DHCP option.
 
    .PARAMETER UseOption
    Indicates whether to use the option.
 
    .EXAMPLE
    Add-InfobloxDHCPRangeOptions -Type 'Options' -Network '192.168.1.0/24' -Name 'domain-name-servers' -Number 6 -Value '192.168.0.15' -VendorClass 'DHCP' -UseOption
 
    .EXAMPLE
    Add-InfobloxDHCPRangeOptions -Type 'MsOptions' -ReferenceID 'DHCPRange-1' -Name 'time-servers' -Number 4 -Value '11' -VendorClass 'DHCP'
 
    .NOTES
    Ensure you are connected to an Infoblox server using Connect-Infoblox before running this function.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory)]
        [ValidateSet('Options', 'MsOptions')]
        [string] $Type,

        [parameter(ParameterSetName = 'NetworkOption', Mandatory)]
        [string] $Network,

        [parameter(ParameterSetName = 'ReferenceOption', Mandatory)]
        [string] $ReferenceID,

        [parameter(ParameterSetName = 'NetworkOption', Mandatory)]
        [parameter(ParameterSetName = 'ReferenceOption', Mandatory)]
        [string] $Name,

        [parameter(ParameterSetName = 'NetworkOption')]
        [parameter(ParameterSetName = 'ReferenceOption')]
        [alias('Num')][System.Nullable[int]] $Number,

        [parameter(ParameterSetName = 'NetworkOption')]
        [parameter(ParameterSetName = 'ReferenceOption')]
        [string] $Value,


        [parameter(ParameterSetName = 'NetworkOption')]
        [parameter(ParameterSetName = 'ReferenceOption')]
        [string] $VendorClass,


        [parameter(ParameterSetName = 'NetworkOption')]
        [parameter(ParameterSetName = 'ReferenceOption')]
        [switch] $UseOption
    )

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


    $Object = [ordered] @{
        "name"         = $Name
        "num"          = $Number
        "use_option"   = if ($PSBoundParameters.ContainsKey('UseOption')) {
            $UseOption.IsPresent 
        }
        else {
            $null 
        }
        "value"        = $Value
        "vendor_class" = $VendorClass
    }

    if ($Type -eq 'options') {
    }
    else {
        if ($UseOption.IsPresent) {
            Write-Warning -Message 'Add-InfobloxDHCPRangeOptions - use_option is not a valid parameter for MSOptions'
            $Object.Remove('use_option')
        }
    }

    Remove-EmptyValue -Hashtable $Object

    if ($Network) {
        $DHCPRange = Get-InfobloxDHCPRange -Network $Network -ReturnFields 'options', 'ms_options'
    }
    elseif ($ReferenceID) {
        $DHCPRange = Get-InfobloxDHCPRange -ReferenceID $ReferenceID -ReturnFields 'options', 'ms_options'
    }
    else {
        Write-Warning -Message 'You must specify either a Network or a ReferenceID'
        return
    }
    if (-not $DHCPRange -or $null -eq $DHCPRange._ref) {
        Write-Warning -Message 'Add-InfobloxDHCPRangeOptions - No DHCP Range found'
        return
    }

    $OptionFound = $false
    [Array] $NewOptions = @(
        if ($Type -eq 'options') {
            $Options = $DHCPRange.options | Select-Object -Property name, num, use_option, value, vendor_class
            foreach ($Option in $Options) {
                if ($Option.name -eq $Name) {
                    $OptionFound = $true
                    break
                }
                else {
                    $Option
                }
            }
            if (-not $OptionFound) {
                Write-Verbose -Message "Add-InfobloxDHCPRangeOptions - Changes required for $Name. Does not exist yet!"
                $Object
            }
        }
        else {
            $MSOptions = $DHCPRange.ms_options | Select-Object -Property name, num, value, vendor_class
            foreach ($MSOption in $MSOptions) {
                if ($MSOption.name -eq $Name) {
                    $OptionFound = $true
                    break
                }
                else {
                    $MSOption
                }
            }
            if (-not $OptionFound) {
                Write-Verbose -Message "Add-InfobloxDHCPRangeOptions - Changes required for $Name. Does not exist yet!"
                $Object
            }
        }
    )
    if (-not $OptionFound) {
        if ($Type -eq 'options') {
            Set-InfobloxDHCPRange -ReferenceID $DHCPRange._ref -Options $NewOptions
        }
        else {
            Set-InfobloxDHCPRange -ReferenceID $DHCPRange._ref -MSOptions $NewOptions
        }
    }
    else {
        Write-Warning -Message 'Add-InfobloxDHCPRangeOptions - No changes required'
    }
}
function Add-InfobloxDHCPReservation {
    <#
    .SYNOPSIS
    Add a DHCP reservation to an Infoblox server
 
    .DESCRIPTION
    Add a DHCP reservation to an Infoblox server
 
    .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
 
    .PARAMETER Network
    Subnet to add the reservation to
 
    .PARAMETER MicrosoftServer
    Microsoft server to use for the fixed address
 
    .EXAMPLE
    Add-InfobloxDHCPReservation -IPv4Address '10.2.2.18' -MacAddress '00:50:56:9A:00:01' -Name 'MyReservation' -Network '10.2.2.0/24' -Comment 'This is a test reservation' -MicrosoftServer 'myserver'
 
    .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,
        [ValidateNotNullOrEmpty()][parameter(Mandatory)][string] $Name,
        [ValidateNotNullOrEmpty()][parameter(Mandatory)][string] $Network,
        [string] $Comment,
        [alias('ms_server')][string] $MicrosoftServer
    )
    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Add-InfobloxDHCPReservation - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    $invokeInfobloxQuerySplat = @{
        RelativeUri    = 'record:dhcpreservation'
        Method         = 'POST'
        QueryParameter = @{
            ipv4addr = $IPv4Address
            mac      = $MacAddress.ToLower()
            network  = $Network
        }
    }
    if ($Name) {
        $invokeInfobloxQuerySplat.QueryParameter.name = $Name
    }
    if ($Comment) {
        $invokeInfobloxQuerySplat.QueryParameter.comment = $Comment
    }
    if ($MicrosoftServer) {
        $invokeInfobloxQuerySplat.QueryParameter.ms_server = $MicrosoftServer
    }
    $Output = Invoke-InfobloxQuery @invokeInfobloxQuerySplat
    if ($Output) {
        Write-Verbose -Message "Add-InfobloxDHCPReservation - Added $($Output.ipv4addr) with mac address $($Output.mac) / $Output"
    }
}
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
 
    .PARAMETER MicrosoftServer
    Microsoft server to use 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,
        [alias('ms_server')][string] $MicrosoftServer
    )

    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
    }
    if ($MicrosoftServer) {
        $invokeInfobloxQuerySplat.QueryParameter.ms_server = $MicrosoftServer
    }
    $Output = Invoke-InfobloxQuery @invokeInfobloxQuerySplat #-WarningAction SilentlyContinue -WarningVariable varWarning
    if ($Output) {
        Write-Verbose -Message "Add-InfobloxFixedAddress - Added $($Output.ipv4addr) with mac address $($Output.mac) / $Output"
    }
}
function Add-InfobloxNetwork {
    <#
    .SYNOPSIS
    Adds a network to Infoblox.
 
    .DESCRIPTION
    This function adds a network to Infoblox. It requires a connection to an Infoblox server, which can be established using the Connect-Infoblox function.
 
    .PARAMETER Network
    The network to add. This parameter is mandatory.
 
    .PARAMETER Comment
    An optional comment for the network.
 
    .PARAMETER NetworkView
    The network view in which to add the network. Defaults to 'default'.
 
    .PARAMETER AutoCreateReverseZone
    A switch that, when present, indicates that a reverse zone should be automatically created for the network.
 
    .PARAMETER DHCPGateway
    The DHCP gateway for the network.
 
    .PARAMETER DHCPLeaseTime
    The DHCP lease time for the network.
 
    .PARAMETER DHCPDomainNameServers
    The DHCP domain name servers for the network.
 
    .PARAMETER Options
    An array of options to be added to the DHCP range.
 
    .PARAMETER MSOptions
    An array of Microsoft options to be added to the DHCP range.
 
    .PARAMETER ExtensibleAttributeName
    The name of an extensible attribute for the network.
 
    .PARAMETER ExtensibleAttributeSite
    The site associated with the network as an extensible attribute.
 
    .PARAMETER ExtensibleAttributeState
    The state associated with the network as an extensible attribute.
 
    .PARAMETER ExtensibleAttributeCountry
    The country associated with the network as an extensible attribute.
 
    .PARAMETER ExtensibleAttributeRegion
    The region associated with the network as an extensible attribute.
 
    .PARAMETER ExtensibleAttributeVLAN
    The VLAN associated with the network as an extensible attribute.
 
    .PARAMETER ExtensibleAttribute
    A hashtable of additional extensible attributes to associate with the network.
 
    .PARAMETER Members
    An array of DHCP members to associate with the network.
 
    .PARAMETER ReturnOutput
    A switch that, when present, indicates that the output of the command should be returned.
 
    .EXAMPLE
    Add-InfobloxNetwork -Network '192.168.1.0/24' -Comment 'Test network' -DHCPGateway '192.168.1.1'
 
    .EXAMPLE
    $addInfobloxSubnetSplat = @{
        Subnet = '10.22.35.0/24'
        Comment = "Oki dokii"
        AutoCreateReverseZone = $true
        DHCPGateway = "10.22.35.1"
        DHCPLeaseTime = 5000
        DHCPDomainNameServers = "192.168.4.56,192.168.4.57"
        ExtensinbleAttributeCountry = "Poland"
        ExtensinbleAttributeName = "Test"
        ExtensinbleAttributeRegion = "Europe"
        ExtensinbleAttributeSite = "Site1"
        ExtensinbleAttributeState = "Mazowieckie"
        ExtensinbleAttributeVLAN = "810"
    }
 
    Add-InfobloxNetwork @addInfobloxSubnetSplat
 
    .EXAMPLE
    $addInfobloxSubnetSplat = @{
        Subnet = '10.22.36.0/24'
        Comment = "Oki dokii"
        AutoCreateReverseZone = $true
        DHCPGateway = "10.22.36.1"
        DHCPLeaseTime = 5000
        DHCPDomainNameServers = "192.168.4.56,192.168.4.57"
        ExtensinbleAttribute = [ordered] @{
            Name = 'Test'
            VLAN = '810'
            Country = 'Poland'
            Region = 'Europe'
            Site = 'Site1'
        }
    }
 
    Add-InfobloxNetwork @addInfobloxSubnetSplat
 
    .NOTES
    This function requires a connection to an Infoblox server, which can be established using the Connect-Infoblox function.
    #>

    [alias('Add-InfobloxSubnet')]
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)][alias('Subnet')][string] $Network,
        [string] $Comment,
        [string] $NetworkView = 'default',
        [switch] $AutoCreateReverseZone,
        [string] $DHCPGateway,
        [string] $DHCPLeaseTime,
        [string] $DHCPDomainNameServers,
        [Array] $Options,
        [Alias('ms_options')][Array] $MSOptions,
        [string[]] $Members,
        [string] $ExtensinbleAttributeName,
        [string] $ExtensinbleAttributeSite,
        [string] $ExtensinbleAttributeState,
        [string] $ExtensinbleAttributeCountry,
        [string] $ExtensinbleAttributeRegion,
        [string] $ExtensinbleAttributeVLAN,
        [System.Collections.IDictionary] $ExtensinbleAttribute,
        [switch] $ReturnOutput
    )

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

    $Body = [ordered] @{
        "network"                 = $Network
        # "members" = @(
        # @{
        # "_struct" = "msdhcpserver"
        # "ipv4addr" = ""
        # }
        # )
        "comment"                 = $Comment
        "network_view"            = $NetworkView
        "auto_create_reversezone" = $AutoCreateReverseZone.IsPresent
    }

    Remove-EmptyValue -Hashtable $Body

    if ($Members) {
        $Body["members"] = @(
            foreach ($DHCPMember in $Members) {
                [ordered] @{
                    "_struct"  = "msdhcpserver"
                    "ipv4addr" = $DHCPMember
                }
            }
        )
        #"_struct": "dhcpmember",
        #"name": "ddi.example.com"
    }

    if ($MSOptions) {
        $Body["ms_options"] = @(
            foreach ($MSOption in $MSOptions) {
                $MSOption
            }
        )
    }

    $DHCPOptions = @(
        if ($Options) {
            foreach ($Option in $Options) {
                $Option
            }
        }
        if ($DHCPLeaseTime) {
            [ordered] @{
                "name"         = "dhcp-lease-time"
                "num"          = 51
                "use_option"   = $true
                "value"        = $DHCPLeaseTime
                "vendor_class" = "DHCP"
            }
        }
        # DNS servers (we will use default ones if not provided)
        if ($DHCPDomainNameServers) {
            @{
                "name"         = "domain-name-servers"
                "num"          = 6
                "use_option"   = $true
                "value"        = $DHCPDomainNameServers
                "vendor_class" = "DHCP"
            }
        }
        # DHCP servers (we will use default ones if not provided)
        if ($DHCPGateway) {
            @{
                "name"         = "routers"
                "num"          = 3
                "use_option"   = $true
                "value"        = $DHCPGateway
                "vendor_class" = "DHCP"
            }
        }
    )

    if ($DHCPOptions.Count -gt 0) {
        $Body["options"] = $DHCPOptions
    }

    # Lets add extensible attributes
    $ExtensibleAttributeExists = $false
    foreach ($Key in $PSBoundParameters.Keys) {
        if ($Key -like "ExtensinbleAttribute*") {
            $ExtensibleAttributeExists = $true
            break
        }
    }
    if ($ExtensibleAttributeExists) {
        $Body["extattrs"] = [ordered] @{}
        if ($ExtensinbleAttributeName) {
            $Body["extattrs"]["Name"] = @{
                value = $ExtensinbleAttributeName
            }
        }
        if ($ExtensinbleAttributeSite) {
            $Body["extattrs"]["Site"] = @{
                value = $ExtensinbleAttributeSite
            }
        }
        if ($ExtensinbleAttributeState) {
            $Body["extattrs"]["State"] = @{
                value = $ExtensinbleAttributeState
            }
        }
        if ($ExtensinbleAttributeCountry) {
            $Body["extattrs"]["Country"] = @{
                value = $ExtensinbleAttributeCountry
            }
        }
        if ($ExtensinbleAttributeRegion) {
            $Body["extattrs"]["Region"] = @{
                value = $ExtensinbleAttributeRegion
            }
        }
        if ($ExtensinbleAttributeVLAN) {
            $Body["extattrs"]["VLAN"] = @{
                value = $ExtensinbleAttributeVLAN
            }
        }
        foreach ($Key in $ExtensinbleAttribute.Keys) {
            if ($ExtensinbleAttribute[$Key] -is [System.Collections.IDictionary]) {
                $Body["extattrs"][$Key] = $ExtensinbleAttribute[$Key]
            }
            else {
                $Body["extattrs"][$Key] = @{
                    value = $ExtensinbleAttribute[$Key]
                }
            }
        }
    }

    $invokeInfobloxQuerySplat = @{
        RelativeUri = "network"
        Method      = 'POST'
        Body        = $Body
    }

    $Output = Invoke-InfobloxQuery @invokeInfobloxQuerySplat
    if ($Output) {
        Write-Verbose -Message "Add-InfobloxNetwork - $Output"
        if ($ReturnOutput) {
            $Output
        }
    }
}
function Add-InfobloxNetworkExtensibleAttribute {
    <#
    .SYNOPSIS
    Adds an extensible attribute to a specified network in Infoblox.
 
    .DESCRIPTION
    This function adds an extensible attribute to a network in Infoblox.
    It requires an established connection to an Infoblox server, which can be done using the Connect-Infoblox function.
    The function checks if the specified network exists before attempting to add the extensible attribute.
 
    .PARAMETER Network
    The network to which the extensible attribute will be added. This parameter is mandatory.
 
    .PARAMETER Attribute
    The name of the extensible attribute to add. This parameter is mandatory.
 
    .PARAMETER Value
    The value of the extensible attribute to add. This parameter is mandatory.
 
    .EXAMPLE
    Add-InfobloxNetworkExtensibleAttribute -Network '192.168.1.0/24' -Attribute 'Location' -Value 'Data Center 1'
    Adds the 'Location' extensible attribute with the value 'Data Center 1' to the network '192.168.1.0/24'.
 
    .NOTES
    You must first connect to an Infoblox server using Connect-Infoblox before running this function.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)][alias('Subnet')][string] $Network,
        [Parameter(Mandatory)][alias('ExtensinbleAttribute')][string] $Attribute,
        [Parameter(Mandatory)][alias('ExtensinbleAttributeValue')][string] $Value
    )
    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Add-InfobloxNetworkExtensibleAttribute - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    $NetworkInformation = Get-InfobloxNetwork -Network $Network
    if (-not $NetworkInformation._ref) {
        Write-Warning -Message "Add-InfobloxNetworkExtensibleAttribute - Network $Network not found"
        return
    }

    $Body = [ordered] @{
        "extattrs+" = @{
            $Attribute = @{
                "value" = $Value
            }
        }
    }

    Remove-EmptyValue -Hashtable $Body

    $invokeInfobloxQuerySplat = @{
        RelativeUri = $NetworkInformation._ref
        Method      = 'PUT'
        Body        = $Body
    }

    $Output = Invoke-InfobloxQuery @invokeInfobloxQuerySplat
    if ($Output) {
        Write-Verbose -Message "Add-InfobloxNetworkExtensibleAttribute - $Output"
        if ($ReturnOutput) {
            $Output
        }
    }
}
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 = '2.11',
        [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 = $EncryptedPassword | 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()

    # The infoblox configuration is not really used anywhere. It's just a placeholder
    # It's basecause we use $PSDefaultParameterValues to pass the parameters to Invoke-InfobloxQuery
    # But this placeholder is used to check if we're connected or not in other functions
    $Script:InfobloxConfiguration = [ordered] @{
        ApiVersion = $ApiVersion
        Server     = $Server
        BaseUri    = "https://$Server/wapi/v$apiVersion"
    }

    if ($AllowSelfSignedCerts) {
        Hide-SelfSignedCerts
    }

    # we do inital query to make sure we're connected
    if (-not $SkipInitialConnection) {
        $Schema = Get-InfobloxSchema -WarningAction SilentlyContinue -WarningVariable SchemaWarning
        if (-not $Schema) {
            if ($SchemaWarning) {
                if ($ErrorActionPreference -eq 'Stop') {
                    throw $SchemaWarning
                }
                else {
                    Write-Warning -Message "Connect-Infoblox - Unable to retrieve schema. Connection failed. Error: $($SchemaWarning)"
                }
            }
            else {
                Write-Warning -Message "Connect-Infoblox - Unable to retrieve schema. Connection failed."
            }
            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,
        [switch] $FetchFromSchema,
        [int] $MaxResults = 1000000
    )
    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)]"

    if ($FetchFromSchema) {
        $ReturnFields = Get-FieldsFromSchema -SchemaObject "lease"
    }
    elseif ($ReturnFields) {
        $ReturnFields = ($ReturnFields | Sort-Object -Unique) -join ','
    }
    else {
        $ReturnFields = 'binding_state,hardware,client_hostname,fingerprint,address,network_view'
    }

    $invokeInfobloxQuerySplat = @{
        RelativeUri    = 'lease'
        Method         = 'GET'
        QueryParameter = @{
            _return_fields = $ReturnFields
            _max_results   = $MaxResults
        }
    }
    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 {
    <#
    .SYNOPSIS
    Retrieves DHCP range configuration from an Infoblox server.
 
    .DESCRIPTION
    This function retrieves the DHCP range configuration from an Infoblox server. It allows filtering by ReferenceID, Network, and other parameters.
 
    .PARAMETER ReferenceID
    The unique identifier for the DHCP range to be retrieved.
 
    .PARAMETER Network
    The network for which to retrieve DHCP ranges.
 
    .PARAMETER PartialMatch
    Indicates whether to perform a partial match on the network.
 
    .PARAMETER FetchFromSchema
    Indicates whether to fetch return fields from the schema.
 
    .PARAMETER ReturnFields
    An array of fields to be returned in the response.
 
    .PARAMETER MaxResults
    The maximum number of results to return. Default is 1,000,000.
 
    .EXAMPLE
    Get-InfobloxDHCPRange -ReferenceID 'DHCPRange-1'
 
    .EXAMPLE
    Get-InfobloxDHCPRange -Network '192.168.1' -PartialMatch
 
    .EXAMPLE
    Get-InfobloxDHCPRange -Network '192.168.1.0/24' -FetchFromSchema
 
    .NOTES
    Ensure you are connected to an Infoblox server using Connect-Infoblox before running this function.
    #>

    [CmdletBinding()]
    param(
        [string] $ReferenceID,
        [string] $Network,
        [switch] $PartialMatch,
        [switch] $FetchFromSchema,
        [string[]] $ReturnFields,
        [int] $MaxResults = 1000000
    )
    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"
    }
    elseif ($ReturnFields) {
        $ReturnFields = ($ReturnFields | Sort-Object -Unique) -join ','
    }
    else {
        $ReturnFields = $Null
    }
    $invokeInfobloxQuerySplat = @{
        RelativeUri    = 'range'
        Method         = 'GET'
        QueryParameter = @{
            _max_results   = $MaxResults
            _return_fields = $ReturnFields
        }
    }
    if ($ReferenceID) {
        $invokeInfobloxQuerySplat.RelativeUri = $ReferenceID
    }
    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,
        [switch] $FetchFromSchema,
        [string[]] $Properties
    )

    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 ($FetchFromSchema) {
        $invokeInfobloxQuerySplat.QueryParameter._return_fields = Get-FieldsFromSchema -SchemaObject "fixedaddress"
    }
    elseif ($Properties) {
        $invokeInfobloxQuerySplat.QueryParameter._return_fields = $Properties -join ','
    }
    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[]]$ReturnFields,
        [switch] $Partial,
        [switch] $All,
        [int] $MaxResults = 1000000,
        [switch] $FetchFromSchema,
        [switch] $Native
    )

    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
    }

    if ($FetchFromSchema) {
        $ReturnFields = Get-FieldsFromSchema -SchemaObject "network"
    }
    elseif ($ReturnFields) {
        $ReturnFields = ($ReturnFields | Sort-Object -Unique) -join ','
    }
    else {
        if ($Native) {
            $ReturnFields = $Null
        }
        else {
            $ReturnFields = @(
                "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 ','
        }
    }

    $QueryParameter = [ordered]@{
        _return_fields = $ReturnFields
    }
    if ($All) {
        $QueryParameter["_max_results"] = $MaxResults
    }
    elseif ($Network -and $Partial.IsPresent) {
        $QueryParameter["_max_results"] = $MaxResults
        $QueryParameter."network~" = $Network
    }
    elseif ($Network) {
        $QueryParameter.network = $Network
    }
    else {
        Write-Warning -Message "Get-InfobloxNetwork - You must provide either -Network or -All switch"
        return
    }
    $ListNetworks = Invoke-InfobloxQuery -RelativeUri "network" -Method Get -QueryParameter $QueryParameter -WhatIf:$false
    foreach ($FoundNetwork in $ListNetworks) {
        if ($Native) {
            $FoundNetwork
        }
        else {
            $FullInformation = Get-IPAddressRangeInformation -Network $FoundNetwork.network
            $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-InfobloxNetworkContainer {
    <#
    .SYNOPSIS
    Get Infoblox Network Containers
 
    .DESCRIPTION
    Get Infoblox Network Containers
 
    .PARAMETER Network
    Provide the network to search for network containers
 
    .PARAMETER PartialMatch
    Allow partial matches
 
    .PARAMETER FetchFromSchema
    Fetch fields from schema. By default, only the _ref field is returned
 
    .PARAMETER MaxResults
    Maximum number of results to return
 
    .EXAMPLE
    Get-InfoBloxNetworkContainer -Network '10.2.0.0/16' -Verbose | Format-Table
 
    .EXAMPLE
    Get-InfoBloxNetworkContainer -Network '10.2' -Verbose -PartialMatch | Format-Table
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [string] $Network,
        [switch] $PartialMatch,
        [switch] $FetchFromSchema,
        [int] $MaxResults = 1000000
    )
    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Get-InfobloxNetworkContainer - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }
    if ($Network) {
        Write-Verbose -Message "Get-InfobloxNetworkContainer - Requesting Network Containers for Network [$Network] / PartialMatch [$($PartialMatch.IsPresent)]"
    }
    else {
        Write-Verbose -Message "Get-InfobloxNetworkContainer - Requesting Network Containers for all networks"
    }

    if ($FetchFromSchema) {
        $ReturnFields = Get-FieldsFromSchema -SchemaObject "networkcontainer"
    }
    else {
        $ReturnFields = $Null
    }
    $invokeInfobloxQuerySplat = @{
        RelativeUri    = 'networkcontainer'
        Method         = 'GET'
        QueryParameter = @{
            _max_results   = $MaxResults
            _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-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-InfobloxNetworkNextAvailableNetwork {
    <#
    .SYNOPSIS
    Get the next available network from a network container
 
    .DESCRIPTION
    Get the next available network from a network container
 
    .PARAMETER Network
    Provide the network container to search for the next available network
 
    .PARAMETER NetworkRef
    Provide the network container reference to search for the next available network (alternative to Network)
 
    .PARAMETER Quantity
    How many networks to return
 
    .PARAMETER Cidr
    The CIDR of the network to return
 
    .EXAMPLE
    Get-InfobloxNetworkNextAvailableNetwork -Network '10.2.0.0/16' -Quantity 5 -Cidr 27 -Verbose | Format-Table
 
    .NOTES
    General notes
    #>

    [cmdletbinding(DefaultParameterSetName = 'Network')]
    param(
        [Parameter(Mandatory, ParameterSetName = 'Network')][string] $Network,
        [Parameter(Mandatory, ParameterSetName = 'NetworkRef')][string] $NetworkRef,
        [Parameter()][alias('Count')][int] $Quantity = 1,
        [Parameter(Mandatory)][int] $Cidr
    )
    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Get-InfobloxNetworkNextAvailableNetwork - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

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

    $invokeInfobloxQuerySplat = @{
        RelativeUri    = $NetworkRef
        QueryParameter = @{
            _function = 'next_available_network'
            cidr      = $Cidr
            num       = $Quantity
        }
        Method         = 'POST'
    }
    $Query = Invoke-InfobloxQuery @invokeInfobloxQuerySplat -WarningAction SilentlyContinue -WarningVariable varWarning -WhatIf:$false
    if ($Query) {
        $Query.networks
    }
    else {
        Write-Warning -Message "Get-InfobloxNetworkNextAvailableNetwork - No network returned for network container [$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
 
    .PARAMETER ReturnReadOnlyFields
    Return only read-only fields in format suitable for use with Invoke-InfobloxQuery
 
    .PARAMETER ReturnWriteFields
    Return only write fields in format suitable for use with Invoke-InfobloxQuery
 
    .PARAMETER ReturnFields
    Return all fields in full objects
 
    .EXAMPLE
    Get-InfobloxSchema
 
    .EXAMPLE
    Get-InfobloxSchema -Object 'record:host'
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [string] $Object,
        [switch] $ReturnReadOnlyFields,
        [switch] $ReturnWriteFields,
        [switch] $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-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
        }
        elseif ($ReturnWriteFields) {
            $Fields = ((Get-InfobloxSchema -Object $Object).Fields | Where-Object { $_.supports -like "*r*" }).Name
            $Fields -join ','
        }
        elseif ($ReturnFields) {
            (Get-InfobloxSchema -Object $Object).Fields
        }
        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
    }

    $Url = Join-UriQuery @joinUriQuerySplat

    if ($Body) {
        $JSONBody = $Body | ConvertTo-Json -Depth 10
        Write-Debug -Message "Invoke-InfobloxQuery - Body: $JSONBody"
    }
    if ($PSCmdlet.ShouldProcess($Url, "Invoke-InfobloxQuery - $Method")) {
        Write-Verbose -Message "Invoke-InfobloxQuery - Querying $Url with $Method"
        try {
            $invokeRestMethodSplat = @{
                Uri         = $Url
                Method      = $Method
                Credential  = $Credential
                ContentType = 'application/json'
                ErrorAction = 'Stop'
                Verbose     = $false
                WebSession  = $WebSession
                TimeoutSec  = 600
            }
            if ($Body) {
                $invokeRestMethodSplat.Body = $JSONBody
            }
            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 New-InfobloxOption {
    <#
    .SYNOPSIS
    Creates a dummy Infoblox option to use within other cmdlets
 
    .DESCRIPTION
    This function creates a new Infoblox option. It's just syntactic sugar to make it easier to create options to use within other cmdlets.
 
    .PARAMETER Name
    The name of the Infoblox option. This parameter is mandatory.
 
    .PARAMETER Number
    The number of the Infoblox option. This parameter is mandatory.
 
    .PARAMETER UseOption
    A switch indicating whether to use the option. This parameter is mandatory.
 
    .PARAMETER Value
    The value of the Infoblox option. This parameter is mandatory.
 
    .PARAMETER VendorClass
    The vendor class of the Infoblox option. This parameter is mandatory.
 
    .EXAMPLE
    $addInfobloxDHCPRangeSplat = @{
        StartAddress = '10.10.12.5'
        EndAddress = '10.10.12.10'
        Options = @(
            New-InfobloxOption -Name "dhcp-lease-time" -Number 51 -UseOption -Value '86400' -VendorClass 'DHCP'
            New-InfobloxOption -Name "domain-name-servers" -Number 6 -UseOption -Value '192.168.0.15' -VendorClass 'DHCP'
            New-InfobloxOption -Name 'routers' -Number 3 -UseOption -Value '192.168.11.12' -VendorClass 'DHCP'
            New-InfobloxOption -Name 'time-servers' -Number 4 -UseOption -Value '11' -VendorClass 'DHCP'
        )
        MsOptions = @(
            New-InfobloxOption -Name "dhcp-lease-time" -Number 51 -UseOption -Value '86400' -VendorClass 'DHCP'
            New-InfobloxOption -Name "domain-name-servers" -Number 6 -UseOption -Value '192.168.0.15' -VendorClass 'DHCP'
            New-InfobloxOption -Name 'routers' -Number 3 -UseOption -Value '192.168.11.12' -VendorClass 'DHCP'
            New-InfobloxOption -Name 'time-servers' -Number 4 -UseOption -Value '11' -VendorClass 'DHCP'
        )
        Verbose = $true
    }
 
    Add-InfobloxDHCPRange @addInfobloxDHCPRangeSplat
 
    .NOTES
    This function is used to create a dummy Infoblox option to use within other cmdlets.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string] $Name,
        [Parameter(Mandatory)][alias('Num')][int] $Number,
        [Parameter()][switch] $UseOption,
        [Parameter(Mandatory)][string] $Value,
        [Parameter(Mandatory)][string] $VendorClass
    )

    $Object = [ordered] @{
        "name"         = $Name
        "num"          = $Number
        "use_option"   = if ($PSBoundParameters.ContainsKey('UseOption')) {
            $UseOption.IsPresent 
        }
        else {
            $null 
        }
        "value"        = $Value
        "vendor_class" = $VendorClass
    }
    Remove-EmptyValue -Hashtable $Object
    $Object
}
function Remove-InfobloxDHCPRangeOptions {
    <#
    .SYNOPSIS
    Removes DHCP range options from an Infoblox server.
 
    .DESCRIPTION
    This function removes specified DHCP range options from an Infoblox server. It allows removing options and Microsoft-specific options for a given network or reference ID.
 
    .PARAMETER Type
    Specifies the type of options to remove. Valid values are 'Options' and 'MsOptions'.
 
    .PARAMETER Network
    The network for which to remove DHCP options.
 
    .PARAMETER ReferenceID
    The unique identifier for the DHCP range to be modified.
 
    .PARAMETER Name
    The name of the DHCP option to be removed.
 
    .EXAMPLE
    Remove-InfobloxDHCPRangeOptions -Type 'Options' -Network '192.168.1.0/24' -Name 'domain-name-servers'
 
    .EXAMPLE
    Remove-InfobloxDHCPRangeOptions -Type 'MsOptions' -ReferenceID 'DHCPRange-1' -Name 'time-servers'
 
    .NOTES
    Ensure you are connected to an Infoblox server using Connect-Infoblox before running this function.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory)]
        [ValidateSet('Options', 'MsOptions')]
        [string] $Type,

        [parameter(ParameterSetName = 'NetworkOption', Mandatory)]
        [string] $Network,

        [parameter(ParameterSetName = 'ReferenceOption', Mandatory)]
        [string] $ReferenceID,

        [parameter(ParameterSetName = 'NetworkOption', Mandatory)]
        [parameter(ParameterSetName = 'ReferenceOption', Mandatory)]
        [string] $Name
    )

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

    if ($Network) {
        $DHCPRange = Get-InfobloxDHCPRange -Network $Network -ReturnFields 'options', 'ms_options'
    }
    elseif ($ReferenceID) {
        $DHCPRange = Get-InfobloxDHCPRange -ReferenceID $ReferenceID -ReturnFields 'options', 'ms_options'
    }
    else {
        Write-Warning -Message 'You must specify either a Network or a ReferenceID'
        return
    }
    if (-not $DHCPRange -or $null -eq $DHCPRange._ref) {
        Write-Warning -Message 'Remove-InfobloxDHCPRangeOptions - No DHCP Range found'
        return
    }

    $OptionFound = $false
    [Array] $NewOptions = @(
        if ($Type -eq 'options') {
            $Options = $DHCPRange.options | Select-Object -Property name, num, use_option, value, vendor_class
            foreach ($Option in $Options) {
                if ($Option.name -eq $Name) {
                    Write-Verbose -Message "Remove-InfobloxDHCPRangeOptions - Changes required for $Name. Removal required!"
                    $OptionFound = $true
                }
                else {
                    $Option
                }
            }
        }
        else {
            $MSOptions = $DHCPRange.ms_options | Select-Object -Property name, num, value, vendor_class
            foreach ($MSOption in $MSOptions) {
                if ($MSOption.name -eq $Name) {
                    Write-Verbose -Message "Remove-InfobloxDHCPRangeOptions - Changes required for $Name. Removal required!"
                    $OptionFound = $true
                }
                else {
                    $MSOption
                }
            }
        }
    )
    if (-not $OptionFound) {
        Write-Verbose -Message "Remove-InfobloxDHCPRangeOptions - Change not required. Option $Name not found"
    }
    else {
        if ($Type -eq 'options') {
            Set-InfobloxDHCPRange -ReferenceID $DHCPRange._ref -Options $NewOptions
        }
        else {
            Set-InfobloxDHCPRange -ReferenceID $DHCPRange._ref -MSOptions $NewOptions
        }
    }
}
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 {
    <#
    .SYNOPSIS
    Removes an IP address from Infoblox.
 
    .DESCRIPTION
    This function removes an IP address from Infoblox. It checks for an existing connection to an Infoblox server, established via Connect-Infoblox, before attempting the removal. The function supports verbose output for detailed operation insights.
 
    .PARAMETER IPv4Address
    The IPv4 address to be removed from Infoblox. This parameter is mandatory.
 
    .EXAMPLE
    Remove-InfobloxIPAddress -IPv4Address '192.168.1.100'
    Removes the IP address 192.168.1.100 from Infoblox.
 
    .NOTES
    Ensure you are connected to an Infoblox server using Connect-Infoblox before executing this function. The function uses Write-Verbose for detailed operation output, which can be enabled by setting $VerbosePreference or using the -Verbose switch.
    #>

    [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
        if ($Output) {
            Write-Verbose -Message "Remove-InfobloxIPAddress - Removed $($IP.ip_address) from network $($IP.network) / $Output"
        }
    }
}
function Remove-InfobloxNetworkExtensibleAttribute {
    <#
    .SYNOPSIS
    Removes an extensible attribute from a specified network in Infoblox.
 
    .DESCRIPTION
    This function removes an extensible attribute from a network in Infoblox.
    It requires an established connection to an Infoblox server, which can be done using the Connect-Infoblox function.
    The function checks if the specified network exists before attempting to remove the extensible attribute.
 
    .PARAMETER Network
    The network from which the extensible attribute will be removed. This parameter is mandatory.
 
    .PARAMETER Attribute
    The name of the extensible attribute to remove. This parameter is mandatory.
 
    .EXAMPLE
    Remove-InfobloxNetworkExtensibleAttribute -Network '192.168.1.0/24' -Attribute 'Location'
    Removes the 'Location' extensible attribute from the network '192.168.1.0/24'.
 
    .NOTES
    You must first connect to an Infoblox server using Connect-Infoblox before running this function.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)][alias('Subnet')][string] $Network,
        [Parameter(Mandatory)][alias('ExtensibleAttribute')][string] $Attribute
    )
    if (-not $Script:InfobloxConfiguration) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw 'You must first connect to an Infoblox server using Connect-Infoblox'
        }
        Write-Warning -Message 'Remove-InfobloxNetworkExtensibleAttribute - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

    $NetworkInformation = Get-InfobloxNetwork -Network $Network
    if (-not $NetworkInformation._ref) {
        Write-Warning -Message "Remove-InfobloxNetworkExtensibleAttribute - Network $Network not found"
        return
    }
    $Body = [ordered] @{
        "extattrs-" = @{
            $Attribute = @{}
        }
    }

    Remove-EmptyValue -Hashtable $Body

    $invokeInfobloxQuerySplat = @{
        RelativeUri = $NetworkInformation._ref
        Method      = 'PUT'
        Body        = $Body
    }

    $Output = Invoke-InfobloxQuery @invokeInfobloxQuerySplat
    if ($Output) {
        Write-Verbose -Message "Remove-InfobloxNetworkExtensibleAttribute - $Output"
        if ($ReturnOutput) {
            $Output
        }
    }
}
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-InfobloxDHCPRange {
    <#
    .SYNOPSIS
    Sets the DHCP range configuration on an Infoblox server.
 
    .DESCRIPTION
    This function modifies the DHCP range configuration on an Infoblox server. It allows setting comments, Microsoft options, and other DHCP options.
 
    .PARAMETER ReferenceID
    The unique identifier for the DHCP range to be modified.
 
    .PARAMETER Comment
    A comment to associate with the DHCP range.
 
    .PARAMETER MSServer
    The Microsoft DHCP server associated with the range.
 
    .PARAMETER ExtensinbleAttribute
    A hashtable of extensible attributes to associate with the DHCP range.
 
    .PARAMETER Options
    An array of general DHCP options.
 
    .PARAMETER MSOptions
    An array of Microsoft-specific DHCP options.
 
    .PARAMETER FailoverAssociation
    The failover association for the DHCP range.
 
    .PARAMETER ServerAssociationType
    The type of server association. Valid values are 'MEMBER', 'MS_FAILOVER', 'NONE', 'MS_SERVER', 'FAILOVER'.
 
    .PARAMETER Exclude
    An array of IP addresses or address ranges to exclude from the DHCP range.
 
    .PARAMETER AlwaysUpdateDns
    Indicates whether to always update DNS.
 
    .PARAMETER Disable
    Indicates whether to disable the DHCP range.
 
    .EXAMPLE
    Set-InfobloxDHCPRange -ReferenceID 'DHCPRange-1' -Comment 'This is a DHCP range.'
 
    .EXAMPLE
    Set-InfobloxDHCPRange -ReferenceID 'DHCPRange-1' -Options @(
        New-InfobloxOption -Name "dhcp-lease-time" -Number 51 -UseOption -Value '86400' -VendorClass 'DHCP'
        New-InfobloxOption -Name "domain-name-servers" -Number 6 -UseOption -Value '192.168.0.15' -VendorClass 'DHCP'
        New-InfobloxOption -Name 'routers' -Number 3 -UseOption -Value '192.168.11.12' -VendorClass 'DHCP'
        New-InfobloxOption -Name 'time-servers' -Number 4 -UseOption -Value '11' -VendorClass 'DHCP'
    )
 
    .NOTES
    Ensure you are connected to an Infoblox server using Connect-Infoblox before running this function.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(ParameterSetName = 'ReferenceID', Mandatory)][string] $ReferenceID,
        [string] $Comment,
        [string] $MSServer,
        [System.Collections.IDictionary] $ExtensinbleAttribute,
        [Array] $Options,
        [Alias('ms_options')][Array] $MSOptions,
        [alias('failover_association')][string] $FailoverAssociation,
        [ValidateSet('MEMBER', 'MS_FAILOVER', 'NONE', 'MS_SERVER', 'FAILOVER')] [string] $ServerAssociationType,
        [Array] $Exclude,
        [switch] $AlwaysUpdateDns,
        [switch] $Disable
    )

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

    $Body = [ordered] @{}
    if ($Comment) {
        $Body["comment"] = $Comment
    }
    if ($ServerAssociationType) {
        $Body["server_association_type"] = $ServerAssociationType
    }
    if ($MSServer) {
        $Body["ms_server"] = [PSCustomObject] @{
            "_struct"  = "msdhcpserver"
            "ipv4addr" = $MSServer
        }
    }
    if ($ExtensinbleAttribute) {
        $Body["extattrs"] = [ordered] @{}
        foreach ($Key in $ExtensinbleAttribute.Keys) {
            if ($ExtensinbleAttribute[$Key] -is [System.Collections.IDictionary]) {
                $Body["extattrs"][$Key] = $ExtensinbleAttribute[$Key]
            }
            else {
                $Body["extattrs"][$Key] = @{
                    value = $ExtensinbleAttribute[$Key]
                }
            }
        }
    }
    if ($Options) {
        $Body["options"] = @(
            foreach ($Option in $Options) {
                $Option | Select-Object -Property name, num, use_option, value, vendor_class
            }
        )
    }
    if ($MSOptions) {
        $Body["ms_options"] = @(
            foreach ($MSOption in $MSOptions) {
                $MSOption | Select-Object -Property name, num, value, vendor_class
            }
        )
    }
    if ($FailoverAssociation) {
        $Body["failover_association"] = $FailoverAssociation
    }
    if ($Exclude) {
        $Body["exclude"] = @(
            foreach ($ExcludeItem in $Exclude) {
                if ($ExcludeItem -is [string]) {
                    if ($ExcludeItem -like "*-*") {
                        $ExcludeItem = $ExcludeItem -split '-'
                        $ExcludeItem = @{
                            StartAddress = $ExcludeItem[0]
                            EndAddress   = $ExcludeItem[1]
                        }
                    }
                    else {
                        $ExcludeItem = @{
                            StartAddress = $ExcludeItem
                            EndAddress   = $ExcludeItem
                        }
                    }
                }
                [ordered] @{
                    "start_address" = $ExcludeItem.StartAddress
                    "end_address"   = $ExcludeItem.EndAddress
                }
            }
        )
    }
    if ($PSBoundParameters.ContainsKey('AlwaysUpdateDns')) {
        $Body["always_update_dns"] = $AlwaysUpdateDns.IsPresent
    }
    if ($PSBoundParameters.ContainsKey('Disable')) {
        $Body["disable"] = $Disable.IsPresent
    }

    if ($Body.Count -eq 0) {
        Write-Warning -Message 'Set-InfobloxDHCPRange - No changes requested'
        return
    }

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

    $Output = Invoke-InfobloxQuery @invokeInfobloxQuerySplat
    if ($Output) {
        Write-Verbose -Message "Set-InfobloxDHCPRange - Modified $Output"
    }
}
function Set-InfobloxDHCPRangeOptions {
    <#
    .SYNOPSIS
    Sets DHCP range options on an Infoblox server.
 
    .DESCRIPTION
    This function modifies the DHCP range options on an Infoblox server. It allows setting options and Microsoft-specific options for a given network or reference ID.
 
    .PARAMETER Type
    Specifies the type of options to set. Valid values are 'Options' and 'MsOptions'.
 
    .PARAMETER Network
    The network for which to set DHCP options.
 
    .PARAMETER ReferenceID
    The unique identifier for the DHCP range to be modified.
 
    .PARAMETER Name
    The name of the DHCP option.
 
    .PARAMETER Number
    The number of the DHCP option.
 
    .PARAMETER Value
    The value of the DHCP option.
 
    .PARAMETER VendorClass
    The vendor class of the DHCP option.
 
    .PARAMETER UseOption
    Indicates whether to use the option.
 
    .EXAMPLE
    Set-InfobloxDHCPRangeOptions -Type 'Options' -Network '192.168.1.0/24' -Name 'domain-name-servers' -Number 6 -Value '192.168.0.15' -VendorClass 'DHCP' -UseOption
 
    .EXAMPLE
    Set-InfobloxDHCPRangeOptions -Type 'MsOptions' -ReferenceID 'DHCPRange-1' -Name 'time-servers' -Number 4 -Value '11' -VendorClass 'DHCP'
 
    .NOTES
    Ensure you are connected to an Infoblox server using Connect-Infoblox before running this function.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory)]
        [ValidateSet('Options', 'MsOptions')]
        [string] $Type,

        [parameter(ParameterSetName = 'NetworkOption', Mandatory)]
        [string] $Network,

        [parameter(ParameterSetName = 'ReferenceOption', Mandatory)]
        [string] $ReferenceID,

        [parameter(ParameterSetName = 'NetworkOption', Mandatory)]
        [parameter(ParameterSetName = 'ReferenceOption', Mandatory)]
        [string] $Name,

        [parameter(ParameterSetName = 'NetworkOption')]
        [parameter(ParameterSetName = 'ReferenceOption')]
        [alias('Num')][System.Nullable[int]] $Number,

        [parameter(ParameterSetName = 'NetworkOption')]
        [parameter(ParameterSetName = 'ReferenceOption')]
        [string] $Value,


        [parameter(ParameterSetName = 'NetworkOption')]
        [parameter(ParameterSetName = 'ReferenceOption')]
        [string] $VendorClass,


        [parameter(ParameterSetName = 'NetworkOption')]
        [parameter(ParameterSetName = 'ReferenceOption')]
        [switch] $UseOption

    )

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


    $Object = [ordered] @{
        "name"         = $Name
        "num"          = $Number
        "use_option"   = if ($PSBoundParameters.ContainsKey('UseOption')) {
            $UseOption.IsPresent 
        }
        else {
            $null 
        }
        "value"        = $Value
        "vendor_class" = $VendorClass
    }

    if ($Type -eq 'options') {
    }
    else {
        if ($UseOption.IsPresent) {
            Write-Warning -Message 'Set-InfobloxDHCPRangeOptions - use_option is not a valid parameter for MSOptions'
            $Object.Remove('use_option')
        }
    }

    Remove-EmptyValue -Hashtable $Object

    if ($Network) {
        $DHCPRange = Get-InfobloxDHCPRange -Network $Network -ReturnFields 'options', 'ms_options'
    }
    elseif ($ReferenceID) {
        $DHCPRange = Get-InfobloxDHCPRange -ReferenceID $ReferenceID -ReturnFields 'options', 'ms_options'
    }
    else {
        Write-Warning -Message 'You must specify either a Network or a ReferenceID'
        return
    }
    if (-not $DHCPRange -or $null -eq $DHCPRange._ref) {
        Write-Warning -Message 'Set-InfobloxDHCPRangeOptions - No DHCP Range found'
        return
    }

    $ChangeRequired = $false
    $OptionFound = $false
    [Array] $NewOptions = @(
        if ($Type -eq 'options') {
            $Options = $DHCPRange.options | Select-Object -Property name, num, use_option, value, vendor_class
            foreach ($Option in $Options) {
                if ($Option.name -eq $Name) {
                    $OptionFound = $true
                    foreach ($Key in $Object.Keys) {
                        if ($Object.$Key -ne $Option.$Key) {
                            Write-Verbose -Message "Set-InfobloxDHCPRangeOptions - Preparing overwrite $Name for $Key to $($Object.$Key)"
                            $Option.$Key = $Object.$Key
                            $ChangeRequired = $true
                        }
                        else {
                            Write-Verbose -Message "Set-InfobloxDHCPRangeOptions - No changes required $Name for $Key, as it already exists with the same values"
                        }
                    }
                    if ($ChangeRequired) {
                        $Option
                    }
                }
                else {
                    $Option
                }
            }
            if (-not $OptionFound) {
                Write-Verbose -Message "Set-InfobloxDHCPRangeOptions - Changes required for $Name. Does not exist yet!"
                $Object
                $ChangeRequired = $true
            }
        }
        else {
            $MSOptions = $DHCPRange.ms_options | Select-Object -Property name, num, value, vendor_class
            foreach ($MSOption in $MSOptions) {
                if ($MSOption.name -eq $Name) {
                    $OptionFound = $true
                    foreach ($Key in $Object.Keys) {
                        if ($Object.$Key -ne $MSOption.$Key) {
                            Write-Verbose -Message "Set-InfobloxDHCPRangeOptions - Preparing overwrite $Name for $Key to $($Object.$Key)"
                            $MSOption.$Key = $Object.$Key
                            $ChangeRequired = $true
                        }
                        else {
                            Write-Verbose -Message "Set-InfobloxDHCPRangeOptions - No changes required $Name for $Key, as it already exists with the same values"
                        }
                    }
                    if ($ChangeRequired) {
                        $MSOption
                    }
                }
                else {
                    $MSOption
                }
            }
            if (-not $OptionFound) {
                Write-Verbose -Message "Set-InfobloxDHCPRangeOptions - Changes required for $Name. Does not exist yet!"
                $Object
                $ChangeRequired = $true
            }
        }
    )
    if ($ChangeRequired) {
        if ($Type -eq 'options') {
            Set-InfobloxDHCPRange -ReferenceID $DHCPRange._ref -Options $NewOptions
        }
        else {
            Set-InfobloxDHCPRange -ReferenceID $DHCPRange._ref -MSOptions $NewOptions
        }
    }
    else {
        Write-Warning -Message 'Set-InfobloxDHCPRangeOptions - No changes required'
    }
}
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
    if ($Output) {
        Write-Verbose -Message "Set-InfobloxDNSRecord - Modified $Type / $Output"
    }
}


# Export functions and aliases as required
Export-ModuleMember -Function @('Add-InfobloxDHCPRange', 'Add-InfobloxDHCPRangeOptions', 'Add-InfobloxDHCPReservation', 'Add-InfoBloxDNSRecord', 'Add-InfobloxFixedAddress', 'Add-InfobloxNetwork', 'Add-InfobloxNetworkExtensibleAttribute', '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-InfobloxNetworkContainer', 'Get-InfobloxNetworkNextAvailableIP', 'Get-InfobloxNetworkNextAvailableNetwork', 'Get-InfobloxNetworkView', 'Get-InfobloxObjects', 'Get-InfobloxPermission', 'Get-InfobloxResponsePolicyZones', 'Get-InfobloxSchema', 'Get-InfoBloxSearch', 'Get-InfobloxVDiscoveryTask', 'Invoke-InfobloxQuery', 'New-InfobloxOption', 'Remove-InfobloxDHCPRangeOptions', 'Remove-InfobloxDnsRecord', 'Remove-InfobloxFixedAddress', 'Remove-InfobloxIPAddress', 'Remove-InfobloxNetworkExtensibleAttribute', 'Remove-InfobloxObject', 'Set-InfobloxDHCPRange', 'Set-InfobloxDHCPRangeOptions', 'Set-InfobloxDNSRecord') -Alias @('Add-InfobloxSubnet', 'Get-InfobloxDHCPLeases', 'Get-InfobloxDNSAuthZones', 'Get-InfobloxDNSRecords', 'Get-InfobloxDNSRecordsAll')
# SIG # Begin signature block
# MIItqwYJKoZIhvcNAQcCoIItnDCCLZgCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCKPs7yViAqoozH
# uJM0jQ/5SJoihfVcxQGPiSG0u6Zq8KCCJq4wggWNMIIEdaADAgECAhAOmxiO+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
# v2jiJmCG6sivqf6UHedjGzqGVnhOMIIGvDCCBKSgAwIBAgIQC65mvFq6f5WHxvnp
# BOMzBDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln
# aUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5
# NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTI0MDkyNjAwMDAwMFoXDTM1MTEy
# NTIzNTk1OVowQjELMAkGA1UEBhMCVVMxETAPBgNVBAoTCERpZ2lDZXJ0MSAwHgYD
# VQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyNDCCAiIwDQYJKoZIhvcNAQEBBQAD
# ggIPADCCAgoCggIBAL5qc5/2lSGrljC6W23mWaO16P2RHxjEiDtqmeOlwf0KMCBD
# Er4IxHRGd7+L660x5XltSVhhK64zi9CeC9B6lUdXM0s71EOcRe8+CEJp+3R2O8oo
# 76EO7o5tLuslxdr9Qq82aKcpA9O//X6QE+AcaU/byaCagLD/GLoUb35SfWHh43rO
# H3bpLEx7pZ7avVnpUVmPvkxT8c2a2yC0WMp8hMu60tZR0ChaV76Nhnj37DEYTX9R
# eNZ8hIOYe4jl7/r419CvEYVIrH6sN00yx49boUuumF9i2T8UuKGn9966fR5X6kgX
# j3o5WHhHVO+NBikDO0mlUh902wS/Eeh8F/UFaRp1z5SnROHwSJ+QQRZ1fisD8UTV
# DSupWJNstVkiqLq+ISTdEjJKGjVfIcsgA4l9cbk8Smlzddh4EfvFrpVNnes4c16J
# idj5XiPVdsn5n10jxmGpxoMc6iPkoaDhi6JjHd5ibfdp5uzIXp4P0wXkgNs+CO/C
# acBqU0R4k+8h6gYldp4FCMgrXdKWfM4N0u25OEAuEa3JyidxW48jwBqIJqImd93N
# Rxvd1aepSeNeREXAu2xUDEW8aqzFQDYmr9ZONuc2MhTMizchNULpUEoA6Vva7b1X
# CB+1rxvbKmLqfY/M/SdV6mwWTyeVy5Z/JkvMFpnQy5wR14GJcv6dQ4aEKOX5AgMB
# AAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAWBgNVHSUB
# Af8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1s
# BwEwHwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8wHQYDVR0OBBYEFJ9X
# LAN3DigVkGalY17uT5IfdqBbMFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwz
# LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZUaW1l
# U3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQGCCsGAQUFBzABhhho
# dHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKGTGh0dHA6Ly9jYWNl
# cnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZU
# aW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIBAD2tHh92mVvjOIQS
# R9lDkfYR25tOCB3RKE/P09x7gUsmXqt40ouRl3lj+8QioVYq3igpwrPvBmZdrlWB
# b0HvqT00nFSXgmUrDKNSQqGTdpjHsPy+LaalTW0qVjvUBhcHzBMutB6HzeledbDC
# zFzUy34VarPnvIWrqVogK0qM8gJhh/+qDEAIdO/KkYesLyTVOoJ4eTq7gj9UFAL1
# UruJKlTnCVaM2UeUUW/8z3fvjxhN6hdT98Vr2FYlCS7Mbb4Hv5swO+aAXxWUm3Wp
# ByXtgVQxiBlTVYzqfLDbe9PpBKDBfk+rabTFDZXoUke7zPgtd7/fvWTlCs30VAGE
# sshJmLbJ6ZbQ/xll/HjO9JbNVekBv2Tgem+mLptR7yIrpaidRJXrI+UzB6vAlk/8
# a1u7cIqV0yef4uaZFORNekUgQHTqddmsPCEIYQP7xGxZBIhdmm4bhYsVA6G2WgNF
# YagLDBzpmk9104WQzYuVNsxyoVLObhx3RugaEGru+SojW4dHPoWrUhftNpFC5H7Q
# EY7MhKRyrBe7ucykW7eaCuWBsBb4HOKRFVDcrZgdwaSIqMDiCLg4D+TPVgKx2EgE
# deoHNHT9l3ZDBD+XgbF+23/zBjeCtxz+dL/9NWR6P2eZRi7zcEO1xwcdcqJsyz/J
# ceENc2Sg8h3KeFUCS7tpFk7CrDqkMIIHXzCCBUegAwIBAgIQB8JSdCgUotar/iTq
# F+XdLjANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln
# aUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBT
# aWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMB4XDTIzMDQxNjAwMDAwMFoX
# DTI2MDcwNjIzNTk1OVowZzELMAkGA1UEBhMCUEwxEjAQBgNVBAcMCU1pa2/FgsOz
# dzEhMB8GA1UECgwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMSEwHwYDVQQDDBhQ
# cnplbXlzxYJhdyBLxYJ5cyBFVk9URUMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
# ggIKAoICAQCUmgeXMQtIaKaSkKvbAt8GFZJ1ywOH8SwxlTus4McyrWmVOrRBVRQA
# 8ApF9FaeobwmkZxvkxQTFLHKm+8knwomEUslca8CqSOI0YwELv5EwTVEh0C/Daeh
# vxo6tkmNPF9/SP1KC3c0l1vO+M7vdNVGKQIQrhxq7EG0iezBZOAiukNdGVXRYOLn
# 47V3qL5PwG/ou2alJ/vifIDad81qFb+QkUh02Jo24SMjWdKDytdrMXi0235CN4Rr
# W+8gjfRJ+fKKjgMImbuceCsi9Iv1a66bUc9anAemObT4mF5U/yQBgAuAo3+jVB8w
# iUd87kUQO0zJCF8vq2YrVOz8OJmMX8ggIsEEUZ3CZKD0hVc3dm7cWSAw8/FNzGNP
# lAaIxzXX9qeD0EgaCLRkItA3t3eQW+IAXyS/9ZnnpFUoDvQGbK+Q4/bP0ib98XLf
# QpxVGRu0cCV0Ng77DIkRF+IyR1PcwVAq+OzVU3vKeo25v/rntiXCmCxiW4oHYO28
# eSQ/eIAcnii+3uKDNZrI15P7VxDrkUIc6FtiSvOhwc3AzY+vEfivUkFKRqwvSSr4
# fCrrkk7z2Qe72Zwlw2EDRVHyy0fUVGO9QMuh6E3RwnJL96ip0alcmhKABGoIqSW0
# 5nXdCUbkXmhPCTT5naQDuZ1UkAXbZPShKjbPwzdXP2b8I9nQ89VSgQIDAQABo4IC
# AzCCAf8wHwYDVR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYDVR0OBBYE
# FHrxaiVZuDJxxEk15bLoMuFI5233MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAK
# BggrBgEFBQcDAzCBtQYDVR0fBIGtMIGqMFOgUaBPhk1odHRwOi8vY3JsMy5kaWdp
# Y2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEz
# ODQyMDIxQ0ExLmNybDBToFGgT4ZNaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0Rp
# Z2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5j
# cmwwPgYDVR0gBDcwNTAzBgZngQwBBAEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3
# dy5kaWdpY2VydC5jb20vQ1BTMIGUBggrBgEFBQcBAQSBhzCBhDAkBggrBgEFBQcw
# AYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBodHRwOi8v
# Y2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmlu
# Z1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAAMA0GCSqGSIb3DQEB
# CwUAA4ICAQC3EeHXUPhpe31K2DL43Hfh6qkvBHyR1RlD9lVIklcRCR50ZHzoWs6E
# BlTFyohvkpclVCuRdQW33tS6vtKPOucpDDv4wsA+6zkJYI8fHouW6Tqa1W47YSrc
# 5AOShIcJ9+NpNbKNGih3doSlcio2mUKCX5I/ZrzJBkQpJ0kYha/pUST2CbE3JroJ
# f2vQWGUiI+J3LdiPNHmhO1l+zaQkSxv0cVDETMfQGZKKRVESZ6Fg61b0djvQSx51
# 0MdbxtKMjvS3ZtAytqnQHk1ipP+Rg+M5lFHrSkUlnpGa+f3nuQhxDb7N9E8hUVev
# xALTrFifg8zhslVRH5/Df/CxlMKXC7op30/AyQsOQxHW1uNx3tG1DMgizpwBasrx
# h6wa7iaA+Lp07q1I92eLhrYbtw3xC2vNIGdMdN7nd76yMIjdYnAn7r38wwtaJ3KY
# D0QTl77EB8u/5cCs3ShZdDdyg4K7NoJl8iEHrbqtooAHOMLiJpiL2i9Yn8kQMB6/
# Q6RMO3IUPLuycB9o6DNiwQHf6Jt5oW7P09k5NxxBEmksxwNbmZvNQ65Zn3exUAKq
# G+x31Egz5IZ4U/jPzRalElEIpS0rgrVg8R8pEOhd95mEzp5WERKFyXhe6nB6bSYH
# v8clLAV0iMku308rpfjMiQkqS3LLzfUJ5OHqtKKQNMLxz9z185UCszGCBlMwggZP
# AgEBMH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEw
# PwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2
# IFNIQTM4NCAyMDIxIENBMQIQB8JSdCgUotar/iTqF+XdLjANBglghkgBZQMEAgEF
# AKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgor
# BgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3
# DQEJBDEiBCAg/q14L0ykB1QiC8TniJV4zfuhMrRQbtL4uy4oGLWSlDANBgkqhkiG
# 9w0BAQEFAASCAgABSsKP7p64HPv7A/F0h1ejElT4/Lk+frdqcbM/kwN5kMYGv2hh
# L1bHEW11dR5/hP53ykorFofM2DbFATzMnM0Z5+BcIXFrgK1tBPxvtfLKxDVXlOXO
# hHvvlzpKEGckTAWoTPBuqS9UxprCU4PXMzCurXBdjGX03hOy3Dz39Gys+FiZwDBW
# IPNKrCXDUPsGwCQK9oDz2bgsjYpQ42SNZ37/FMh5O9jTIcWfbOvfEqrp8Oo+Az1m
# 8+6FuVScjb3+Aynf8bz0rnYp0M3G39FhuCqIdXDBWxB9UlSFX0MYCcvgABqLmtB1
# RC0tWt6VkmiVHK5EcYjxw7qTzN/RM0SWxynPB/cbQMeNDZ7dZUsE3NtZjoMxTMIQ
# TUDZZf+QeyNCLT5SdeYiOgp43DCnDNPoaM5treW0hixMWYY3g7oVAEeHUIwRE5oP
# zfKNqyj00vodwHDUvnBizCmqCqA8hLNSCzPFjeTQ4om9PG/O4M0GQtCxJmbpNkwZ
# FeSH+7x/autZc6NN6zHiHeUaXw4uqtJKJpOmukVb/rmI07uWNrHmlu1hPp8/wEcu
# IdskWzlFv1Q2//ASpyo+UWZfXsr3ZnjE4NAEd0Om87Is3mfWqi3NXw+hxeTs66uo
# ojDNfehXSf84dybYgmEckpgr5wAi626NDkJymbvuawv8VdlACDvkjpNE4qGCAyAw
# ggMcBgkqhkiG9w0BCQYxggMNMIIDCQIBATB3MGMxCzAJBgNVBAYTAlVTMRcwFQYD
# VQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBH
# NCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0ECEAuuZrxaun+Vh8b56QTj
# MwQwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwG
# CSqGSIb3DQEJBTEPFw0yNDEwMDkxMDQwNDVaMC8GCSqGSIb3DQEJBDEiBCA6E6Uk
# R9r2/9cz6b0/lkpDDlbbS0QXt6IKlk6oFs6QcDANBgkqhkiG9w0BAQEFAASCAgBm
# nPdiLZw6mifir4EvQS+icPSXDr5Iws3tPlUmNTYLyT4SC1KjRzixgmxAeRS2Gone
# lhFwWC8wwn+EGc9LrEABxe9Mqrx+j65QTy5y3yMoZ8uBl/3b4d1kZXbv5pnk47Fv
# +GuJYJIwVuX0KFFH7A7GLaBAsmLce1Bet55DCKAApE9Sh8k6PlXQHMptGINHRz0s
# orlt0wVqYQ3Fn02wrThRyad3Z/ZTBbeq+vmjTEoRMtmHaLcAMocUewF0BRIpV1Xn
# Boab99Xd1berIEIaKjB6XPjnz7GOM9PvetW2XufVB3IsKRnVXh+cV1QRnNmZbb0v
# hmSdGdhSz5d3vtXUnWo5sGXR/x2VnnA7iyNOktT5YrWswHUS1qVAN37P89t4DMx/
# B+/pqj5E8jjjSGovdNPpt2OsHYnN5HjtPp2THygJd6n5w+UUIFKUD5/EgmSMDTX6
# HYmSF5c6mQcXQY3+weyXPEAkE8esdWR/8gGg1rsf2UZFQbhcB/2fr+diplFLsiVM
# kD/IURoiOcEB+XMY5fD7G2nioGRggMywd/+BCILpzQ3lgC69EBnrV/PdyJdOV8HG
# 0fNHKOoWkJJh0pJJSF7McDdMYVyViujfIWo1GtRzU2VJs+uBWsXAR65QVKdiiivc
# AN0VQr7jtcUYQ9RP2gShOj5BeJAwWZ8fTHWGV0ElXQ==
# SIG # End signature block