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 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 Convert-BinaryToIP { 
    [cmdletBinding()]
    param(
        [string] $Binary
    )
    $Binary = $Binary -replace '\s+'
    if ($Binary.Length % 8) {
        Write-Warning -Message "Convert-BinaryToIP - Binary string '$Binary' is not evenly divisible by 8."
        return $Null
    }
    [int] $NumberOfBytes = $Binary.Length / 8
    $Bytes = @(foreach ($i in 0..($NumberOfBytes - 1)) {
            try {

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

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

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

    )
    
    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,
        [alias('FirstProperty')][Parameter()][string[]] $FirstProperties,
        [alias('LastProperty')][Parameter()][string[]] $LastProperties
    )
    process {
        foreach ($O in $Object) {
            $ReturnObject = [ordered] @{}
            foreach ($Property in $FirstProperties) {
                if ($O.PSObject.Properties.Name -contains $Property) {
                    $ReturnObject[$Property] = $O.$Property
                }
            }
            foreach ($Property in $O.PSObject.Properties.Name) {
                if ($Property -notin $LastProperties -and $Property -notin $FirstProperties) {
                    $ReturnObject[$Property] = $O.$Property
                }
            }
            foreach ($Property in $LastProperties) {
                if ($O.PSObject.Properties.Name -contains $Property) {
                    $ReturnObject[$Property] = $O.$Property
                }
            }
            [pscustomobject]$ReturnObject
        }
    }
}
function Add-InfoBloxDNSRecord {
    [CmdletBinding()]
    param(
        [string] $Name,
        [string] $IPv4Address,
        [string] $CanonicalName,
        [parameter(Mandatory)][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
    )
    if (-not $Script:InfobloxConfiguration) {
        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 IPv4Address: '$IPv4Address'"
        if ($Name -and $IPv4Address) {
            $Body = [ordered] @{
                name     = $Name.ToLower()
                ipv4addr = $IPv4Address
            }
        }
        else {
            Write-Warning -Message "Add-InfoBloxDNSRecord - Name and IPv4Address are required for $Type record"
            return
        }
    }
    elseif ($Type -eq 'CNAME') {
        Write-Verbose -Message "Add-InfoBloxDNSRecord - Adding $Type record $Name with IPv4Address: '$IPv4Address'"
        if ($Name -and $CanonicalName) {
            $Body = [ordered] @{
                name      = $Name.ToLower()
                canonical = $CanonicalName.ToLower()
            }
        }
        else {
            Write-Warning -Message "Add-InfoBloxDNSRecord - Name and CanonicalName are required for $Type record"
            return
        }
    }
    else {
        # won't trigger, but lets leave it like that
        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 {
        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
 
    .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("([a-zA-Z0-9]{2}:){5}[a-zA-Z0-9]{2}")][parameter(Mandatory)][string] $MacAddress
    )

    if (-not $Script:InfobloxConfiguration) {
        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()
        }
    }
    $Output = Invoke-InfobloxQuery @invokeInfobloxQuerySplat -WarningAction SilentlyContinue -WarningVariable varWarning
    if ($Output) {
        Write-Verbose -Message "Add-InfobloxFixedAddress - Added $($Mac.ipv4addr) with mac address $($Mac.mac) / $Output"
    }
    else {
        Write-Warning -Message "Add-InfobloxFixedAddress - Failed to add $($Mac.ipv4addr) with mac address $($Mac.mac), error: $varWarning"
    }
}
function Connect-Infoblox {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ParameterSetName = 'UserName')]
        [Parameter(Mandatory, ParameterSetName = 'Credential')]
        [string] $Server,

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

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

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

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

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

    $PSDefaultParameterValues['Invoke-InfobloxQuery:Credential'] = $Credential
    $PSDefaultParameterValues['Invoke-InfobloxQuery:Server'] = $Server
    #$PSDefaultParameterValues['Invoke-InfobloxQuery:ApiVersion'] = $ApiVersion
    $PSDefaultParameterValues['Invoke-InfobloxQuery:BaseUri'] = "https://$Server/wapi/v$apiVersion"

    $Script:InfobloxConfiguration = [ordered] @{
        ApiVersion = $ApiVersion
        Credential = $Credential
        Server     = $Server
        BaseUri    = "https://$Server/wapi/v$apiVersion"
    }
}
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()]
    param()
    $Script:InfobloxConfiguration = $null
}
function Get-InfobloxDHCP {
    [CmdletBinding()]
    param(
        [string] $Network,
        [switch] $PartialMatch
    )
    if (-not $Script:InfobloxConfiguration) {
        Write-Warning -Message 'Get-InfobloxDHCP - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }
    if ($Network) {
        Write-Verbose -Message "Get-InfobloxDHCP - Requesting DHCP ranges for Network [$Network] / PartialMatch [$($PartialMatch.IsPresent)]"
    }
    else {
        Write-Verbose -Message "Get-InfobloxDHCP - Requesting DHCP ranges for all networks"
    }
    $invokeInfobloxQuerySplat = @{
        RelativeUri    = 'range'
        Method         = 'GET'
        QueryParameter = @{
            _max_results = 1000000
            # _return_fields = 'mac,ipv4addr,network_view'
        }
    }
    if ($Network) {
        if ($PartialMatch) {
            $invokeInfobloxQuerySplat.QueryParameter."network~" = $Network.ToLower()
        }
        else {
            $invokeInfobloxQuerySplat.QueryParameter.network = $Network.ToLower()
        }
    }
    Invoke-InfobloxQuery @invokeInfobloxQuerySplat
}
function Get-InfobloxDHCPLeases {
    [CmdletBinding()]
    param(
        [string] $Network,
        [string] $IPv4Address,
        [string] $Hostname,
        [switch] $PartialMatch
    )
    if (-not $Script:InfobloxConfiguration) {
        Write-Warning -Message 'Get-InfobloxDHCPLeases - You must first connect to an Infoblox server using Connect-Infoblox'
        return
    }

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

    $invokeInfobloxQuerySplat = @{
        RelativeUri    = 'lease'
        Method         = 'GET'
        QueryParameter = @{
            _return_fields = 'binding_state,hardware,client_hostname,fingerprint,address,network_view'
            _max_results   = 1000000
        }
    }
    if ($Network) {
        if ($PartialMatch) {
            $invokeInfobloxQuerySplat.QueryParameter."network~" = $Network.ToLower()
        }
        else {
            $invokeInfobloxQuerySplat.QueryParameter.network = $Network.ToLower()
        }
    }
    if ($IPv4Address) {
        if ($PartialMatch) {
            $invokeInfobloxQuerySplat.QueryParameter."ipv4addr~" = $IPv4Address.ToLower()
        }
        else {
            $invokeInfobloxQuerySplat.QueryParameter.ipv4addr = $IPv4Address.ToLower()
        }
    }
    if ($Hostname) {
        if ($PartialMatch) {
            $invokeInfobloxQuerySplat.QueryParameter."name~" = $Hostname.ToLower()
        }
        else {
            $invokeInfobloxQuerySplat.QueryParameter.name = $Hostname.ToLower()
        }
    }
    Invoke-InfobloxQuery @invokeInfobloxQuerySplat
}
function Get-InfobloxDNSAuthZone {
    [alias('Get-InfobloxDNSAuthZones')]
    [cmdletbinding()]
    param(
        [string] $FQDN,
        [string] $View
    )

    if (-not $Script:InfobloxConfiguration) {
        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 | Select-ObjectByProperty -LastProperty '_ref'
}
function Get-InfobloxDNSDelegatedZone {
    [cmdletbinding()]
    param(
        [string] $Name,
        [string] $View
    )

    if (-not $Script:InfobloxConfiguration) {
        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 | Select-ObjectByProperty -LastProperty '_ref'
}
function Get-InfobloxDNSForwardZone {
    [cmdletbinding()]
    param(
        [string] $Name,
        [string] $View
    )

    if (-not $Script:InfobloxConfiguration) {
        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 | 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) {
        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'
    }
    else {
        if ($FetchFromSchema) {
            if (-not $Script:InfobloxSchemaFields) {
                $Script:InfobloxSchemaFields = [ordered] @{}
            }
            if ($Script:InfobloxSchemaFields["record:$Type"]) {
                $invokeInfobloxQuerySplat.QueryParameter._return_fields = ($Script:InfobloxSchemaFields["record:$Type"])
            }
            else {
                $Schema = Get-InfobloxSchema -Object "record:$Type"
                if ($Schema -and $Schema.fields.name) {
                    $invokeInfobloxQuerySplat.QueryParameter._return_fields = ($Schema.fields.Name -join ',')
                    $Script:InfobloxSchemaFields["record:$Type"] = ($Schema.fields.Name -join ',')
                }
                else {
                    Write-Warning -Message "Get-InfobloxDNSRecord - Failed to fetch schema for record type $($Type). Using defaults"
                }
            }
        }
        else {
            # will fetch defaults
        }
    }
    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
    if ($Type -eq 'Host') {
        foreach ($Record in $Output) {
            [pscustomobject]@{
                name               = $Record.name
                dns_name           = $Record.dns_name
                aliases            = $Record.aliases
                dns_aliases        = $Record.dns_aliases
                view               = $Record.view
                configure_for_dns  = $Record.configure_for_dns
                configure_for_dhcp = $Record.ipv4addrs.configure_for_dhcp
                host               = $Record.ipv4addrs.host
                ipv4addr           = $Record.ipv4addrs.ipv4addr
                ipv4addr_ref       = $Record.ipv4addrs._ref
                _ref               = $Record._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) {
        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"
            }
        }
    }
    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
    $Output | Select-ObjectByProperty -LastProperty '_ref' -FirstProperty 'zone', 'type', 'name'
}
function Get-InfobloxDNSView {
    [cmdletbinding()]
    param(

    )

    if (-not $Script:InfobloxConfiguration) {
        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
}
function Get-InfobloxFixedAddress {
    [cmdletbinding()]
    param(
        [parameter(Mandatory)][string] $MacAddress,
        [switch] $PartialMatch
    )

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

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

    $invokeInfobloxQuerySplat = @{
        RelativeUri    = 'fixedaddress'
        Method         = 'GET'
        QueryParameter = @{
            _max_results   = 1000000
            _return_fields = 'mac,ipv4addr,network_view'
        }
    }
    if ($PartialMatch) {
        $invokeInfobloxQuerySplat.QueryParameter."mac~" = $MacAddress.ToLower()
    }
    else {
        $invokeInfobloxQuerySplat.QueryParameter.mac = $MacAddress.ToLower()
    }
    Invoke-InfobloxQuery @invokeInfobloxQuerySplat
}
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) {
        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 | Select-Object -First $Count | Select-ObjectByProperty -LastProperty '_ref'
    }
    else {
        Invoke-InfobloxQuery @invokeInfobloxQuerySplat | Select-ObjectByProperty -LastProperty '_ref'
    }
}
function Get-InfobloxMember {
    [cmdletbinding()]
    param(

    )

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

    $invokeInfobloxQuerySplat = @{
        RelativeUri    = 'member'
        Method         = 'GET'
        QueryParameter = @{
            _max_results   = 1000000
            _return_fields = 'config_addr_type,host_name,platform,service_type_configuration,vip_setting,node_info,service_status'
        }
    }
    $Output = Invoke-InfobloxQuery @invokeInfobloxQuerySplat
    $Output | Select-ObjectByProperty -LastProperty '_ref'
}
function Get-InfobloxNetwork {
    [OutputType([system.object[]])]
    [cmdletbinding()]
    param(
        [string] $Network = '*',
        [string[]]$Properties,
        [switch] $Partial,
        [switch] $All
    )

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

    if ($All) {
        $ListNetworks = Invoke-InfobloxQuery -RelativeUri "network" -Method Get -QueryParameter @{
            _return_fields = 'extattrs,network,network_view'
        }
    }
    elseif ($Network -and $Partial.IsPresent) {
        $ListNetworks = Invoke-InfobloxQuery -RelativeUri "network" -Method Get -QueryParameter @{
            "network~"     = $Network
            _return_fields = 'extattrs,network,network_view'
        }
    }
    elseif ($Network) {
        $ListNetworks = Invoke-InfobloxQuery -RelativeUri "network" -Method Get -QueryParameter @{
            network        = $Network
            _return_fields = 'extattrs,network,network_view'
        }
    }
    else {
        return
    }

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

        $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
            #BinaryIP = $FullInformation.BinaryIP # : 00001010000000100000101000000000
            #BinarySubnetMask = $FullInformation.BinarySubnetMask # : 11111111111111111111111100000000
            #BinaryNetworkAddress = $FullInformation.BinaryNetworkAddress # : 00001010000000100000101000000000
            #BinaryBroadcast = $FullInformation.BinaryBroadcast # : 00001010000000100000101011111111
        }
        foreach ($Extra in $FoundNetwork.extattrs.psobject.properties) {
            $ExtraProperties = $true
            $OutputData.Add($Extra.Name, $Extra.Value.value)
        }
        [PSCustomObject]$OutputData
    }
    if ($ExtraProperties) {
        $Properties = Select-Properties -Objects $AllNetworks -AllProperties
        $AllNetworks | Select-Object -Property $Properties
    }
    else {
        $AllNetworks
    }
}
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) {
        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
    if ($Query) {
        $Query.ips
    }
    else {
        Write-Warning -Message "Get-InfobloxNetworkNextAvailableIP - No IP returned for network [$NetworkRef], error: $varWarning"
    }
}
function Get-InfobloxNetworkView {
    [cmdletbinding()]
    param(

    )

    if (-not $Script:InfobloxConfiguration) {
        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"

    $invokeInfobloxQuerySplat = @{
        RelativeUri    = 'networkview'
        Method         = 'GET'
        QueryParameter = @{
            _max_results = 1000000
            # _return_fields = 'mac,ipv4addr,network_view'
        }
    }
    Invoke-InfobloxQuery @invokeInfobloxQuerySplat
}
function Get-InfobloxSchema {
    <#
    .SYNOPSIS
    Get the schema for Infoblox as a whole or a specific object
 
    .DESCRIPTION
    Get the schema for Infoblox as a whole or a specific object
 
    .PARAMETER Object
    The object to get the schema for
 
    .EXAMPLE
    Get-InfobloxSchema
 
    .EXAMPLE
    Get-InfobloxSchema -Object 'record:host'
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [string] $Object
    )
    if (-not $Script:InfobloxConfiguration) {
        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
    if ($Query) {
        $Query
    }
    else {
        Write-Warning -Message 'Get-InfobloxSchema - No schema returned'
    }
}
function Get-InfoBloxSearch {
    [CmdletBinding()]
    param(
        [parameter(ParameterSetName = 'IPv4')][string] $IPv4Address
    )

    if (-not $Script:InfobloxConfiguration) {
        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
}
function Invoke-InfobloxQuery {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory)][string] $BaseUri,
        [parameter(Mandatory)][string] $RelativeUri,
        [parameter(Mandatory)][pscredential] $Credential,
        [parameter()][System.Collections.IDictionary] $QueryParameter,
        [parameter()][string] $Method = 'GET',
        [parameter()][System.Collections.IDictionary] $Body
    )

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

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

    $Url = Join-UriQuery @joinUriQuerySplat

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


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

        try {
            $invokeRestMethodSplat = @{
                Uri         = $Url
                Method      = $Method
                Credential  = $Credential
                ContentType = 'application/json'
                ErrorAction = 'Stop'
                Verbose     = $false
                #WebSession = $WebSession
                TimeoutSec  = 600
            }
            if ($Body) {
                $invokeRestMethodSplat.Body = $Body | ConvertTo-Json -Depth 10
            }
            Invoke-RestMethod @invokeRestMethodSplat
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                throw
            }
            Write-Warning -Message "Invoke-InfobloxQuery - Querying $Url failed. Error: $($_.Exception.Message)"
        }
    }
}
function Remove-InfobloxFixedAddress {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory)][string] $MacAddress,
        [parameter()][string] $IPv4Address
    )

    if (-not $Script:InfobloxConfiguration) {
        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 {
            Write-Warning -Message "Remove-InfobloxFixedAddress - Failed to remove $($Mac.ipv4addr) with mac address $($Mac.mac), error: $varWarning"
        }
    }
}
function Remove-InfobloxIPAddress {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [parameter()][string] $IPv4Address
    )

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

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

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

    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'ReferenceID')]
    param(
        [Parameter(Mandatory, ParameterSetName = 'Array')][Array] $Objects,
        [parameter(Mandatory, ParameterSetName = 'ReferenceID')][string] $ReferenceID
    )
    if (-not $Script:InfobloxConfiguration) {
        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"
        }
        else {
            Write-Warning -Message "Remove-InfobloxObject - Failed to remove $ReferenceID, error: $varWarning"
        }
    }
}


# Export functions and aliases as required
Export-ModuleMember -Function @('Add-InfoBloxDNSRecord', 'Add-InfobloxFixedAddress', 'Connect-Infoblox', 'Disconnect-Infoblox', 'Get-InfobloxDHCP', 'Get-InfobloxDHCPLeases', 'Get-InfobloxDNSAuthZone', 'Get-InfobloxDNSDelegatedZone', 'Get-InfobloxDNSForwardZone', 'Get-InfobloxDNSRecord', 'Get-InfobloxDNSRecordAll', 'Get-InfobloxDNSView', 'Get-InfobloxFixedAddress', 'Get-InfobloxIPAddress', 'Get-InfobloxMember', 'Get-InfobloxNetwork', 'Get-InfobloxNetworkNextAvailableIP', 'Get-InfobloxNetworkView', 'Get-InfobloxSchema', 'Get-InfoBloxSearch', 'Invoke-InfobloxQuery', 'Remove-InfobloxFixedAddress', 'Remove-InfobloxIPAddress', 'Remove-InfobloxObject') -Alias @('Get-InfobloxDNSAuthZones', 'Get-InfobloxDNSRecords', 'Get-InfobloxDNSRecordsAll')