public/Products/Appliances.ps1

#Meraki Appliance Functions
using namespace System.Management
using namespace System.Collections.Generic

#region ContentFiltering
function Get-MerakiApplianceContentFilteringCategories() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$id
    )

    $Uri = "{0}/networks/{1}/appliance/contentFiltering/categories" -f $BaseURI, $id
    $Headers = Get-Headers

    try {
        $response = Invoke-RestMethod -Method GET -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect

        return $response
    } catch {
        throw $_
    }
    <#
    .SYNOPSIS
    Returns the content filtering categories for this network.*
    .PARAMETER id
    The Network ID.
    .OUTPUTS
    An array of content filtering categories.
    .NOTES
    Filtering category IDs are different between MX devices. You cannot use category IDs from an MX84 to set categories on an MX100.
    It is best practice to pull the list of categories from the device before attempting to set any new categories.
    #>

}

Set-Alias -Name GMNetAppCFCats -Value Get-MerakiApplianceContentFilteringCategories -Option ReadOnly
Set-Alias -Name Get-MerakiNetworkApplianceContentFilteringCategories -Value Get-MerakiApplianceContentFilteringCategories -Option ReadOnly
Set-Alias -Name GMAppCFCats -Value Get-MerakiApplianceContentFilteringCategories -Option ReadOnly

<#
.Description
Retrieve content filtering Rules for a network
#>

function Get-MerakiApplianceContentFiltering() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)]
        [string]$id
    )


    $Uri = "{0}/networks/{1}/appliance/contentFiltering" -f $BaseURI, $id
    $headers = Get-Headers

    try {
        $response = Invoke-RestMethod -Method GET -Uri $Uri -Headers $headers -PreserveAuthorizationOnRedirect

        return $response    
    } catch {
        throw $_
    }
    <#
    .SYNOPSIS
    Get the content filtering settings for this appliance.
    .PARAMETER id
    The network Id.
    .OUTPUTS
    An array of Meraki content filtering objects.
    #>

}

Set-Alias -Name GMNetCF -Value Get-MerakiApplianceContentFiltering -Option ReadOnly
Set-Alias -Name Get-MerakiNetworkApplianceContentFiltering -Value Get-MerakiApplianceContentFiltering -Option ReadOnly
Set-Alias -Name GMAppCF -Value Get-MerakiApplianceContentFiltering -Option ReadOnly

function Update-MerakiApplianceContentFiltering() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$id,
        [Parameter(ParameterSetName = "values")]
        [string[]]$allowedURLPatterns,
        [Parameter(ParameterSetName = "values")]
        [string[]]$blockedURLPatterns,
        [Parameter(ParameterSetName = "values")]
        [string[]]$blockedUrlCategories,
        [string]$urlCategoryListSize,
        [Parameter(ParameterSetName = "rules")]
        [psObject]$ContentFilteringRules
    )

    $Uri = "{0}/networks/{1}/appliance/contentFiltering" -f $BaseURI, $id
    $Headers = Get-Headers

    
    if ($ContentFilteringRules) {
        if ($ContentFilteringRules.allowedUrlPatterns) {
            $allowedURLPatterns = $ContentFilteringRules.allowedUrlPatterns
        }
        if ($ContentFilteringRules.blockedUrlPatterns) {
            $blockedURLPatterns = $ContentFilteringRules.blockedUrlPatterns
        }
        if ($ContentFilteringRules.urlCategoryListSize) {
            $urlCategoryListSize = $ContentFilteringRules.urlCategoryListSize
        }
        if ($ContentFilteringRules.blockedUrlCategories) {
            $ContentFilteringRules.blockedUrlCategories | ForEach-Object {                
                $blockedUrlCategories += $_.Id
            }
        }
    } 
    $properties = [ordered]@{}
    if ($allowedURLPatterns) {$properties.Add("allowedUrlPatterns", $allowedURLPatterns) }
    if ($blockedURLPatterns) {$properties.Add("blockedUrlPatterns", $blockedURLPatterns) }
    if ($urlCategoryListSize) {$properties.Add("urlCategoryListSize", $urlCategoryListSize) }
    if ($blockedUrlCategories) {$properties.Add("blockedUrlCategories", $blockedUrlCategories) }

    $psBody = [PSCustomObject]$properties
    
    $body = $psBody | ConvertTo-Json -Compress -Depth 6

    try {
        $response = Invoke-RestMethod -Method PUT -Uri $Uri -Body $body -Headers $Headers -PreserveAuthorizationOnRedirect
        return  $response
    } catch {
        throw $_
    }
    <#
    .SYNOPSIS
    Update the content filtering rules.
    .PARAMETER id
    The Network Id.
    .PARAMETER allowedURLPatterns
    An array of allowed URL patterns.
    .PARAMETER blockedURLPatterns
    AM array of blocked URL patterns.
    .PARAMETER blockedUrlCategories
    An array of blocked URL Categories.
    .PARAMETER urlCategoryListSize
    The list size of the category list. Note this parameter is not supported on MX100 and above devices.
    .PARAMETER ContentFilteringRules
    A Meraki content filtering rule object (not supported with other parameters).
    .OUTPUTS
    The updated Meraki content filtering object.
    .NOTES
    .EXAMPLE
    You must pull the Content Filtering Rules using the function Get-MerakiNetworkApplianceContentFiltering and then modify the properties of that object.
    Adding a new URL to the blocked URL Pattern
    PS> $cfr = Get-MerakiNetworks | Where-Object {$_.Name -like "Dallas} | Get-MerakiNetworkApplianceContentFiltering
    PS> $cfr.blockedUrlPatterns += "example.com"
    PS> Get-MerakiNetworks | Where-Object {$_.Name -like "Dallas"} | Update-MerakiNetworkApplianceContentFiltering -allowedUrlPatterns $cfr.allowedUrlPattern -blockedUrlPatterns $cfr.blockedUrlPatterns -blockedUrlCategories $cfr.blockedUrlCategories -urlCategoryListSize $cfr.urlCategoryListSize
    or
    PS > Get-MerakiNetworks | Where-Object {$_.like "Dallas"} | Update-MerakiNetworkApplianceContentFiltering -ContentFilteringRules $cfr
    .EXAMPLE
    Updating Templates
    If you have networks bound to templates, you should must the template and allow the template to trickle the changes down to the bound network.
    PS> $cfr = Get-MerakiOrganizationConfigTemplates | Where-object {$_.Name -eq "Org-Template"} | Get-MerakiNetworkApplianceContentFiltering
    PS> $cfr.clockedUrlPatterns += "example.com"
    PS> Get-MerakiOrganizationConfigTemplates | Where-Object ($_.Name -eq "Org-Template"} Update-MerakiNetworkApplianceContentFiltering -ContentFilteringRules $cfr
    #>

}

Set-Alias -Name UMNetAppCF -value Update-MerakiApplianceContentFiltering -Option ReadOnly
Set-Alias -Name Update-MerakiNetworkApplianceContentFiltering -Value Update-MerakiApplianceContentFiltering -Option ReadOnly
Set-Alias -Name UMAppCF -value Update-MerakiApplianceContentFiltering -Option ReadOnly

function Add-MerakiApplianceContentFilteringRules() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param (
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [String]$Id,
        [String[]]$allowedURLPatterns,
        [String[]]$blockedURLPatterns
    )

    Begin {

    }
    
    Process {
        if ((-not $allowedURLPatterns) -and (-not $blockedURLPatterns) ) {
            Write-Host "You must provide al least one of the content filtering patterns" -ForegroundColor Red
            exit
        }
        $cfr = Get-MerakiNetworkApplianceContentFiltering -Id $Id
        if ($allowedURLPatterns) {
            $allowedURLPatterns | ForEach-Object {
                $cfr.allowedUrlPatterns += $_
            }
        }
        If ($blockedURLPatterns) {
            $blockedURLPatterns | ForEach-Object {
                $cfr.blockedURLPatterns += $_
            }
        }
    
        Update-MerakiApplianceContentFiltering -Id $Id -ContentFilteringRules $Cfr
    }
    <#
    .SYNOPSIS
    Add new URL patterns.
    .DESCRIPTION
    Add the provided allowed and blocked URL patterns to the content filtering rule.
    .PARAMETER Id
    The Network ID.
    .PARAMETER allowedURLPatterns
    An array of allowed URL patterns.
    .PARAMETER blockedURLPatterns
    An array of blocked URL patterns.
    .OUTPUTS
    The updated content filtering rules.
    .EXAMPLE
    Add sites to the allowed and blocked URL patterns.
    PS> $Network | Add-MerakiNetworkApplianceContentFilteringRule -allowedUrlPatterns "xtz.com" -blockedUrlPatterns "badsite.com"
    #>

}

Set-Alias -Name AddMNetAppCFR -Value Add-MerakiApplianceContentFilteringRules -Option ReadOnly
Set-Alias -Name Add-MerakiNetworkApplianceContentFilteringRules -Value Add-MerakiApplianceContentFilteringRules -Option ReadOnly
Set-Alias -Name AddMAppCFR -Value Add-MerakiApplianceContentFilteringRules -Option ReadOnly

function Remove-MerakiApplianceContentFilteringRules () {
    [CmdletBinding(
        DefaultParameterSetName = 'default', 
        SupportsShouldProcess,
        ConfirmImpact = 'high'
    )]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [Alias('NetworkId')]
        [string]$id,
        [string[]]$allowedURLPatterns,
        [string[]]$blockedURLPatterns
    )

    Process {
        If ((-not $allowedURLPatterns) -and (-not $blockedURLPatterns)) {
            Write-Host "You must provide al least one fo the content filtering patterns" -ForegroundColor Red
            exit
        }
        $cfr = Get-MerakiNetworkApplianceContentFiltering -Id $id
        if ($allowedURLPatterns) {
            $AUPList = [System.Collections.ArrayList]::New($cfr.allowedUrlPatterns)
            $allowedURLPatterns | Foreach-Object {
                $AUPList.Remove($_)
            }
            $cfr.allowedUrlPatterns = $AUPList.ToArray()
        }

        if ($blockedURLPatterns) {
            $BUPList = [System.Collections.ArrayList]::New($cfr.blockedUrlPatterns)
            $blockedURLPatterns | ForEach-Object {
                $BUPList.remove($_)
            }
            $cfr.blockedUrlPatterns = $BUPList.ToArray()
        }
        #
        # Try and get the network name. If this fails, see if the ID is for a template.

        if ($PSCmdlet.ShouldProcess("Network with ID: $Id", "Remove content filtering rules")) {
            Update-MerakiNetworkApplianceContentFiltering -id $id -ContentFilteringRules $cfr
        }
    }
    <#
    .SYNOPSIS
    Remove allowed and blocked URL patterns.
    .DESCRIPTION
    Removes the provided allowed and blocked URL patterns to the content filtering rule.
    .PARAMETER id
    The Network Id.
    .PARAMETER allowedURLPatterns
    Allowed URL patterns to remove.
    .PARAMETER blockedURLPatterns
    Blocked URL patterns to remove.
    .OUTPUTS
    The updated content filtering rules.
    .EXAMPLE
    Remove sites from the allowed and blocked URL patterns.
    PS> $Network | Remove-MerakiNetworkApplianceContentFilteringRule -allowedUrlPatterns "xtz.com" -blockedUrlPatterns "badsite.com"
    #>

}

Set-Alias -Name RemoveMNetAppCfr -Value Remove-MerakiNetworkApplianceContentFilteringRules -Option ReadOnly
Set-Alias -name Remove-MerakiNetworkApplianceContentFilteringRules -Value Remove-MerakiApplianceContentFilteringRules -Option ReadOnly
Set-Alias -Name RemoveMAppCfr -Value Remove-MerakiNetworkApplianceContentFilteringRules -Option ReadOnly
#endregion

#region Ports
function Get-MerakiAppliancePorts() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$id
    )

    Begin {

        $Headers = Get-Headers
    }

    Process {
        $Uri = "{0}/networks/{1}/appliance/ports" -f $BaseURI, $id

        try {
            $response = Invoke-RestMethod -Method GET -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect
            $response | ForEach-Object {
                $_ | Add-Member -MemberType NoteProperty -Name NetworkId -Value $Id
            }
            return $response
        }catch {
            throw $_
        }
    }
    <#
    .SYNOPSIS
    Returns the port configuration for the Network Appliance.
    .PARAMETER id
    The network Id.
    .OUTPUTS
    An array of Meraki port objects.
    #>

}

Set-Alias -Name GMAppPorts -value Get-MerakiAppliancePorts -Option ReadOnly

function Get-MerakiAppliancePort() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('Id')]
        [string]$NetworkId,
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [string]$Number
    )

    Begin {
        $Headers = Get-Headers
    }

    Process {
        $Uri = "{0}/networks/{1}/appliance/ports/{2}" -f $BaseURI, $NetworkId, $number

        try {
            $response = Invoke-RestMethod -Method Get -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect
            return $response
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Return per-port VLAN settings for a single MX port.
    .PARAMETER id
    The ID of the Network
    .PARAMETER Number
    The port number
    .OUTPUTS
    An Appliance port object.
    #>

}

Set-Alias -Name Get-MerakiNetworkAppliancePort -Value Get-MerakiAppliancePort -Option ReadOnly


function Set-MerakiAppliancePort() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('Id')]
        [string]$NetworkId,
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [string]$Number,
        [int]$VlanId,
        [Parameter(Mandatory)]
        [ValidateSet('access','trunk')]
        [string]$Type,
        [ValidateSet('open', '8021x-radius', 'mac-radius', 'hybris-radius')]
        [ValidateScript({
            $_ -and $Type -eq 'access'
        }, ErrorMessage = 'Parameter AccessPolicy Can only be used with a access port.')]
        [string]$AccessPolicy,
        [ValidateScript({
            $_ -and $Type -eq 'trunk'
        }, ErrorMessage = "Parameter AllowedVLANS can only be used with a trunk port.")]
        [string]$AllowedVLANS,
        [ValidateScript({
            $_ -and $Type -eq 'trunk'
        }, ErrorMessage = 'Parameter DropUnTaggedTraffic can only be used with a trunk port. ')]
        [switch]$DropUntaggedTraffic,
        [switch]$Enabled
    )

    Begin {
        $Headers = Get-Headers

        $_Body = @{}

        if ($VlanId) {$_Body.Add("vlan", $VlanId)}
        if ($Type) {$_Body.Add("type", $Type)}
        if ($AccessPolicy) {$_Body.Add("accessPolicy", $AccessPolicy)}
        if ($AllowedVLANS) {$_Body.add("allowedVlans", $AllowedVLANS)}
        if ($DropUntaggedTraffic) {$_Body.Add("dropUntaggedTraffic", $true)}
        if ($Enabled) {$_Body.Add("enabled", $true)}

        $Body = $_Body | ConvertTo-Json -Depth 3 -Compress
    }

    Process{
        $Uri = "{0}/networks/{1}/appliance/ports/{2}" -f $BaseURI, $NetworkId, $Number

        try {
            $response = Invoke-RestMethod -Method PUT -Uri $Uri -Headers $Headers -Body $Body
            return $response
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Update the per-port VLAN settings for a single MX port.
    .PARAMETER NetworkId
    The Id of the network
    .PARAMETER Number
    The port number to be updated.
    .PARAMETER VlanId
    Native VLAN when the port is in Trunk mode. Access VLAN when the port is in Access mode.
    .PARAMETER Type
    The type of the port: 'access' or 'trunk'.
    .PARAMETER AccessPolicy
    The name of the policy. Only applicable to Access ports. Valid values are: 'open', '8021x-radius', 'mac-radius', 'hybris-radius' for MX64 or Z3 or any MX supporting the per port authentication feature. Otherwise, 'open' is the only valid value and 'open' is the default value if the field is missing.
    .PARAMETER AllowedVLANS
    Comma-delimited list of the VLAN ID's allowed on the port, or 'all' to permit all VLAN's on the port.
    .PARAMETER DropUntaggedTraffic
    Trunk port can Drop all Untagged traffic. When true, no VLAN is required. Access ports cannot have dropUntaggedTraffic set to true.
    .OUTPUTS
    A port object.
    #>

}

Set-Alias -Name Set-MerakiNetworkAppliancePort -Value Set-MerakiAppliancePort -Option ReadOnly
#endregion

#region Static Routes

function Get-MerakiApplianceStaticRoutes() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$id
    )

    $uri = "{0}/networks/{1}/appliance/staticRoutes" -f $BaseURI, $id
    $Headers = Get-Headers

    try {
        $response = Invoke-RestMethod -Method GET -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect        
        return $response
    } catch {
        throw $_
    }
    <#
    .SYNOPSIS
    Returns the static routes for this network appliance.
    .PARAMETER id
    The Network Id.
    .OUTPUTS
    An array of Meraki static route objects.
    #>

}

Set-Alias -Name GMNetAppRoutes -Value Get-MerakiNetworkApplianceStaticRoutes -Option ReadOnly
Set-Alias -Name Get-MerakiNetworkApplianceStaticRoutes -Value Get-MerakiApplianceStaticRoutes -Option ReadOnly
Set-Alias -Name GMAppRoutes -Value Get-MerakiNetworkApplianceStaticRoutes -Option ReadOnly

function Add-MerakiApplianceStaticRoute() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$id,
        [Parameter(Mandatory)]
        [string]$Name,
        [Parameter(Mandatory)]
        [string]$Subnet,
        [Parameter(Mandatory)]
        [Alias('GatewayIp')]
        [string]$NextHop,
        [Alias('GatewayVlanID')]
        [string]$NextHopVlanID
    )

    Begin {
        $Headers = Get-Headers

        $_Body = @{
            name = $Name
            subnet = $Subnet
            gatewayIp = $NextHop
        }

        if ($NextHopVlanID) {
            $_Body.Add("gatewayVlanId", $NextHopVlanID)
        }

        $body = $_Body | ConvertTo-Json -Depth 3
    }

    Process {
        $Uri = "{0}/networks/{1}/appliance/staticRoutes" -f $BaseURI, $Id
        try {
            $response = Invoke-RestMethod -Method Post -Uri $Uri -Headers $Headers -Body $body            
            return $response
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Adds a static routes for an MX or teleworker network.
    .PARAMETER id
    The Id of the Network.
    .PARAMETER Name
    The name of the new static route
    .PARAMETER Subnet
    The subnet of the static route
    .PARAMETER GatewayIp
    The gateway IP (next hop) of the static route.
    .PARAMETER GatewayVlanIDGIthub
    .OUTPUTS
    An array of static route objects.
    #>
    
}

function Set-MerakiApplianceStaticRoute() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [string]$NetworkId,
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('RouteId')]
        [string]$Id,        
        [string]$Name,
        [switch]$Disable,
        [string]$Subnet,
        [Alias('GatewayIp')]
        [string]$NextHopIp,
        [Alias('GatewayVlanID')]
        [string]$NextHopVlanID
    )

    $Headers = Get-Headers

    $_Body = @{}
    if ($Name) {
        $_Body.Add("name", $Name)
    }
    if ($Disable.IsPresent) {
        $_Body.Add("enabled", $false)
    } else {
        $_Body.Add("enabled", $true)
    }
    if ($subnet) {
        $_Body.Add("subnet", $subnet)
    }
    if ($NextHopIp) {
        $_Body.Add("gatewayIp", $NextHopIp)
    }
    if ($NextHopVlanID) {
        $_Body.Add("gatewayVlanId", $NextHopVlanID)
    }

    $body = $_Body | ConvertTo-Json -Depth 3 -Compress

    $Uri = "{0}/networks/{1}/appliance/staticRoutes/{0}" -F $BaseURI, $NetworkId, $ID

    try {
        $response = Invoke-RestMethod -Method Put -Uri $Uri -Headers $Headers -Body $body -PreserveAuthorizationOnRedirect
        return $response
    } catch {
        throw $_
    }
    <#
    .DESCRIPTION
    Update a static route for an MX or teleworker network.
    .PARAMETER NetworkId
    The ID of the network
    .PARAMETER Id
    The Id of the static route.
    .PARAMETER Name
    The name of the static route.
    .PARAMETER Disable
    Disable the static route. Omitting this parameter will enable the static route.
    .PARAMETER Subnet
    The subnet of the static route
    .PARAMETER NextHopIp
    The next hop IP of the static route.
    .PARAMETER NextHopVlanID
    The next hop IP VLAN ID of the static route.
    #>

}

function Remove-MerakiApplianceStaticRoute() {
    [CmdletBinding(
        SupportsShouldProcess,
        ConfirmImpact = 'high'
    )]
    Param (
        [Parameter(Mandatory)]
        [Alias('NetworkId')]
        [string]$NetworkId,
        [Parameter(Mandatory)]
        [Alias('StaticRouteId')]
        [string]$id
    )

    $Headers = Get-Headers

    $Uri = "{0}/networks/{1}/appliance/staticRoutes/{2}" -f $BaseURI, $NetworkId, $Id

    if ($PSCmdlet.ShouldProcess("Static route with Id: $Id", "Delete")) {
        try {
            $response = Invoke-RestMethod -Method Delete -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect
            return $response
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Delete a static route from an MX or teleworker network.
    .PARAMETER NetworkId
    The id of the network.
    .PARAMETER id
    The Id of the static route.
    #>

}
#endregion

#region VLANs
function Get-MerakiApplianceVLANS() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)]
        [string]$id
    )

    Begin {
    
        $Headers = Get-Headers       
    }

    Process {
        $Uri = "{0}/networks/{1}/appliance/vlans" -f $BaseURI, $id

        try {
            $response =  Invoke-RestMethod -Method GET -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect            
            return $response
        } catch {
            throw $_
        }
    }
    <#
    .SYNOPSIS
    Returns the VLANs for the network appliance.
    .PARAMETER id
    The network Id.
    .OUTPUTS
    AN array of Meraki VLAN objects.
    #>

}

Set-Alias -Name GMNetAppVLANs -Value Get-MerakiNetworkApplianceVLANS -Option ReadOnly
Set-Alias -Name Get-MerakiNetworkApplianceVLANS -Value Get-MerakiApplianceVLANS -Option ReadOnly
Set-Alias -Name GMAppVLANs -Value Get-MerakiApplianceVLANS -Option ReadOnly

function Get-MerakiApplianceVLAN() {
    [CmdletBinding(DefaultParameterSetName = 'default')]    
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)]
        [String]$networkId,
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)]
        [string]$id
    )
   
    $Uri = "{0}/networks/{1}/appliance/vlans/{2}" -f $BaseURI, $networkId, $id
    $Headers = Get-Headers

    try {
        $response = Invoke-RestMethod -Method GET -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect

        return $response
    } catch {
        throw $_
    }
    <#
    .SYNOPSIS
    Returns a network appliance VLAN
    .PARAMETER networkId
    The Network Id.
    .PARAMETER id
    The VLAN Id.
    .OUTPUTS
    A Meraki VLAN object.
    #>

}

Set-Alias -Name GMNetAppVLAN -Value Get-MerakiApplianceVLAN -Option ReadOnly
Set-Alias -Name Get-MerakiNetworkApplianceVLAN -Value Get-MerakiApplianceVLAN -Option ReadOnly
Set-Alias -Name GMAppVLAN -Value Get-MerakiApplianceVLAN -Option ReadOnly

function Add-MerakiApplianceVlan() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param(
        [Parameter(Mandatory = $true)]
        [string]$NetworkId,
        [Parameter(Mandatory = $true)]
        [string]$VlanId,
        [Parameter(Mandatory = $true)]
        [string]$Name,
        [string]$Subnet,
        [string]$ApplianceIp,
        [string]$GroupPolicyId,
        [ValidateSet('same','unique')]
        [string]$TemplateVlanType,
        [string]$CIDR,
        [int]$Mask,
        [switch]$Ipv6Enabled,
        [hashtable[]]$Ipv6PrefixAssignments,
        [switch]$MandatoryDHCP
    )

    $Headers = Get-Headers

    $Uri = "{0}/networks/{1}/appliance/vlans" -f $BaseURI, $NetworkId

    $_Body = @{
        "id" = $VlanId
        "name" = $Name
    }
    if ($Subnet) { $_Body.Add("subnet", $Subnet) }
    if ($ApplianceIp) { $_Body.Add("applianceIp", $ApplianceIp) }
    if ($GroupPolicyId) { $_Body.Add("groupPolicyId", $GroupPolicyId) }
    if ($TemplateVlanType) { $_Body.Add("templateVlanType", $TemplateVlanType) }
    if ($CIDR) { $_Body.Add("cidr", $CIDR) }
    if ($Mask) { $_Body.Add("mask", $Mask ) }
    if ($Ipv6Enabled.IsPresent) {
        $_Body.Add("ipv6", @{
            "enabled" = $true
            "PrefixAssignments" = $Ipv6PrefixAssignments
        })        
    }
    if ($MandatoryDHCP.IsPresent) {
        $_Body.Add(
            @{
                "enabled" = $true
            }
        )
    }

    $body = $_Body | ConvertTo-Json -Depth 5 -Compress

    try {
        $response = Invoke-RestMethod -Method POST -Uri $Uri -Headers $Headers -Body $body -PreserveAuthorizationOnRedirect
        return $response
    } catch {
        throw $_
    }
    <#
    .DESCRIPTION
    Creates a VLAN on the appliance in the specified network.
    .PARAMETER NetworkId
    The ID of the network to create the VLAN.
    .PARAMETER VlanId
    The VLAN ID of the new VLAN (must be between 1 and 4094).
    .PARAMETER Name
    The name of the new VLAN.
    .PARAMETER Subnet
    The subnet of the VLAN.
    .PARAMETER ApplianceIp
    The local IP of the appliance on the VLAN
    .PARAMETER GroupPolicyId
    The id of the desired group policy to apply to the VLAN
    .PARAMETER TemplateVlanType
    Type of subnetting of the VLAN. Applicable only for template network. Valid values are 'same', 'unique'.
    .PARAMETER CIDR
    CIDR of the pool of subnets. Applicable only for template network. Each network bound to the template will automatically pick a subnet from this pool to build its own VLAN.
    .PARAMETER Mask
    Mask used for the subnet of all bound to the template networks. Applicable only for template network.
    .PARAMETER Ipv6Enabled
    Enable IPv6 on VLAN.
    .PARAMETER Ipv6PrefixAssignments
    Prefix assignments on the VLAN
    .PARAMETER MandatoryDHCP
    Mandatory DHCP will enforce that clients connecting to this VLAN must use the IP address assigned by the DHCP server. Clients who use a static IP address won't be able to associate. Only available on firmware versions 17.0 and above
    #>

}
Set-Alias -Name Add-MerakiNetworkApplianceVlan -Value Add-MerakiApplianceVlan

Function Remove-MerakiApplianceVlan() {
    [CmdletBinding(
        SupportsShouldProcess, 
        ConfirmImpact = 'High',
        DefaultParameterSetName = 'default'
    )]
    Param(
        [Parameter(Mandatory = $true)]
        [string]$NetworkId,
        [Parameter(Mandatory)]
        [string]$VlanId
    )

    $Headers = Get-Headers

    $Uri = "{0}/networks/{1}/appliance/vlans/{2}" -f $BaseURI, $NetworkId, $VlanId

    if ($PSCmdlet.ShouldProcess("VLAN ID $VlanId", "Delete")) {
        try {
            $response = Invoke-RestMethod -Method DELETE -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect
            return $response
        } catch {
            throw $_
        }           
    }
    <#
    .SYNOPSIS
    Remove a VLAN
    .DESCRIPTION
    Remove a Meraki Appliance VLAN
    .PARAMETER NetworkId
    The Id of the network
    .PARAMETER VlanId
    The VLAN ID to remove
    #>

}

Set-Alias -Name Remove-MerakiNetworkApplianceVlan -Value Remove-MerakiApplianceVlan -Option ReadOnly

function Set-MerakiApplianceVLAN() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param(
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [string]$NetworkId,
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName)]
        [Alias('id')]
        [string]$VlanId,
        [string]$VlanName,
        [String]$ApplianceIp,
        [string]$Subnet,
        [string]$GroupPolicyId,
        [ValidateSet("same","unique")]
        [string]$TemplateVlanType,
        [string]$VpnNatSUbnet,
        [string]$CIDR,
        [string]$Mask,
        [ValidateScript({$_ -is [hashtable]})]
        [hashtable[]]$fixedIpAssignments,
        [hashtable[]]$ReservedIpRanges,
        [string]$DnsNameServers,
        [ValidateSet("Run a DHCP Server","Relay DHCP to another Server","Do not respond to DHCP requests")]
        [string]$DhcpHandling,
        [ValidateScript({
            $_ -and ($DhcpHandling -eq 'Relay DHCP to another Server"')
        }, ErrorMessage = "Parameter DhcpRelayServers is not valid when parameter DhcpHandling is 'Relay DHCP to another Server'")]
        [string[]]$DhcpRelayServerIPs,
        [ValidateSet(
            '30 minutes', '1 hour', '4 hours', '12 hours', '1 day', '1 week'
        )]
        [string]$DhcpLeaseTime,
        [bool]$DhcpBootOptionEnabled,
        [string]$DhcpBootNextServer,
        [string]$DhcpBootFilename,
        [hashtable[]]$DhcpOptions,
        [switch]$Ipv6Enabled,
        [hashtable[]]$IPv6PrefixAssignments,
        [bool]$MandatoryDhcp
    )

    Begin {
    
        $Headers = Get-Headers

        $_body = @{}
        if ($name) {
            $_.Body.Add("name", $Name)
        }
        if ($ApplianceIp) {
            $_body.Add("applianceIp", $ApplianceIp)
        }
        if ($subnet) {
            $_body.Add("subnet", $subnet)
        }
        if ($GroupPolicyId) {
            $_body.Add("groupPolicyId", $GroupPolicyId)
        }
        if ($TemplateVlanType) {
            $_body.Add("templateVlanType", $TemplateVlanType)
        }
        if ($CIDR) {
            $_body.Add("cidr", $CIDR)
        }
        if ($Mask) {
            $_body.Add("mask", $Mask)
        }
        if ($fixedIpAssignments) {
            $_body.Add("fixedIpAssignments", $fixedIpAssignments)
        }
        if ($ReservedIpRanges) {
            $_body.Add("reservedIpRanges", $ReservedIpRanges)
        }
        if ($DnsNameServers) {
            $_body.Add("dnsNameservers", $DnsNameServers.Replace(",",[char]10))
        }
        if ($DhcpHandling) {
            $_body.Add("dhcpHandling", $DhcpHandling)
        }
        if ($DhcpRelayServerIPs) {
            $_body.Add("dhcpRelayServerIps", $DhcpRelayServerIPs)
        }
        if ($DhcpLeaseTime) {
            $_body.Add("dhcpLeaseTime", $DhcpLeaseTime)
        }
        if ($DhcpBootOptionEnabled) {
            $_body.Add("dhcpBootOptionsEnabled", $DhcpBootOptionEnabled)
        }
        if ($DhcpBootNextServer) {
            $_body.Add("dhcpBootNextServer", $DhcpBootNextServer)
        }
        if ($DhcpBootFilename) {
            $_body.Add("dhcpBoofFilename", $DhcpBootFilename)
        }
        if ($Ipv6Enabled) {
            $_Body.Add("ipv6", @{
                "enabled" = $true
                "prefixAssignments" = $Ipv6PrefixAssignments
            })        
        }
        if ($MandatoryDhcp) {
            $_body.Add("mandatoryDhcp", $MandatoryDhcp)
        }
        if ($VpnNatSubnet) {
            $_body.Add("vpnNatSubnet", $VpnNatSubnet)
        }

        $body = $_body | ConvertTo-Json -Depth 10 -Compress
    }

    Process {
        # Return the network Info so we can determine is this network is assigned to a template.
        $Network = Get-MerakiNetwork -networkID $NetworkId

        # check for Template only parameters.
        if ($mask -or $CIDR -or $TemplateVlanType) {
            if ($Network.$_isBoundToConfigTemplate -eq $false) {
                Throw "Parameters 'mask', 'CIDR' and TemplateVLanType are only applicable to template networks."
            }
        }

        $Uri = "{0}/networks/{1}/appliance/vlans/{2}" -f $BaseURI, $NetworkId, $VlanId

        Try {
            $response = Invoke-RestMethod -Method PUT -Uri $Uri -Headers $Headers -Body $body -PreserveAuthorizationOnRedirect
            return $response
        } catch {
            Throw $_
        }
    }
    <#
    .SYNOPSIS
    "Update Appliance VLAN"
    .DESCRIPTION
    "Updates settings for a Meraki Appliance VLAN"
    .PARAMETER id
    "Network Id"
    .PARAMETER VlanId
    "VLAN ID to be updated"
    .PARAMETER VlanName
    "Name of the VLAN"
    .PARAMETER ApplianceIp
    "Appliance IP for this VLAN (Default Gateway)"
    .PARAMETER Subnet
    "Subnet for this VLAN"
    .PARAMETER GroupPolicyId
    .ID of the group policy to apply to this VLAN"
    .PARAMETER TemplateVlanType
    "Type of subnetting of the VLAN. Applicable only for template network"
    .PARAMETER VpnNatSUbnet
    The translated VPN subnet if VPN and VPN subnet translation are enabled on the VLAN
    .PARAMETER CIDR
    CIDR of the pool of subnets. Applicable only for template network. Each network bound to the template will automatically pick a subnet from this pool to build its own VLAN.
    .PARAMETER Mask
    Mask used for the subnet of all bound to the template networks. Applicable only for template network.
    .PARAMETER fixedIpAssignments
    The DHCP fixed IP assignments on the VLAN. This should be an object that contains mappings from MAC addresses to objects that themselves each contain "ip" and "name" string fields. See the sample request/response for more details.
    A hash table that contains the following name value pairs.
    MAC: The MAC address of the device to assign the IP to.
    IP: the IP to assign. This must be withing the IP pool of the DHCP server.
    Name: A descriptive name for the device.
    .PARAMETER ReservedIpRanges
    The DHCP reserved IP ranges on the VLAN
    A hash table with the following name value pairs.
    comment: A text comment for the reserved range
    start: The first IP in the reserved range
    end: The last IP in the reserved range
    .PARAMETER DnsNameServers
    The DNS nameservers used for DHCP responses, either "upstream_dns", "google_dns", "opendns", or a newline separated string of IP addresses or domain names
    .PARAMETER DhcpHandling
    The appliance's handling of DHCP requests on this VLAN. One of: 'Run a DHCP server', 'Relay DHCP to another server' or 'Do not respond to DHCP requests'
    .PARAMETER DhcpRelayServerIPs
    The IPs of the DHCP servers that DHCP requests should be relayed to. Must be an array od strings.
    .PARAMETER DhcpLeaseTime
    The term of DHCP leases if the appliance is running a DHCP server on this VLAN. One of: '30 minutes', '1 hour', '4 hours', '12 hours', '1 day' or '1 week'
    .PARAMETER DhcpBootOptionEnabled
    Use DHCP boot options specified in other properties.
    .PARAMETER DhcpBootNextServer
    DHCP boot option to direct boot clients to the server to load the boot file from
    .PARAMETER DhcpBootFilename
    DHCP boot option for boot filename
    .PARAMETER DhcpOptions
    An array of HashTables with the following name/value pairs.
    .PARAMETER Ipv6Enabled
    Enable IPv6 on VLAN.
    .PARAMETER IPv6PrefixAssignments
    An array of HashTables containing IPv6 prefix assignments.
    The hashtable must have the following name/value pairs.
    prefixAssignments: array[] Prefix assignments on the VLAN
    staticApplianceIp6: string Manual configuration of the IPv6 Appliance IP
    staticPrefix: string Manual configuration of a /64 prefix on the VLAN
    autonomous: boolean Auto assign a /64 prefix from the origin to the VLAN
    .PARAMETER MandatoryDhcp
    Mandatory DHCP will enforce that clients connecting to this VLAN must use the IP address assigned by the DHCP server. Clients who use a static IP address won't be able to associate. Only available on firmware versions 17.0 and above
    #>

}

Set-Alias -Name SetMNAppVLAN  -Value Set-MerakiNetworkApplianceVLAN -Option ReadOnly
Set-Alias -Name Set-MerakiNetworkApplianceVLAN -Value Set-MerakiApplianceVLAN -Option ReadOnly
#endregion

#region Monitoring
function Get-MerakiApplianceDhcpSubnets() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$serial
    )

    Begin {        
        $Headers = Get-Headers
    }

    Process {
        $Url = "{0}/devices/{1}/appliance/dhcp/subnets" -f $BaseURI, $Serial
        try {
            $response = Invoke-RestMethod -Method GET -Uri $Url -Headers $Headers -PreserveAuthorizationOnRedirect
            return $response
        } catch {
            throw $_
        }
    }
    <#
    .SYNOPSIS
    Returns DHCP Subnets for an appliance.
    .DESCRIPTION
    Returns information and statistics for an appliances DHCP subnets. Including used count and free count.
    .PARAMETER serial
    The serial number of the appliance.
    .OUTPUTS
    A collection of Subnet objects.
    #>

}

function Get-MerakiApplianceClientSecurityEvents() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [string]$ClientId,
        [Parameter(
            Mandatory,
            ParameterSetName = 'dates'
        )]
        [ValidateScript({
            $_ -gt (Get-Date).AddDays(-791)
        }, ErrorMessage = "StartDate cannot be more that 791 days prior to today")]
        [datetime]$StartDate,
        [Parameter(
            Mandatory,
            ParameterSetName = 'dates'
        )]
        [ValidateScript({
            $_ -lt $StartDate.AddDays(791)
        }, ErrorMessage = "End date cannot be more than 791 days after StartDate")]
        [datetime]$EndDate,
        [Parameter(
            Mandatory,
            ParameterSetName = 'days'
        )]
        [ValidateScript({
            $_ -lt 791
        })]
        [int]$Days,
        [int]$PerPage       
    )

    Begin {

        $Headers = Get-Headers

        $Results = [List[PsObject]]::New()
        
        if (StartDate) {
            $query = "?t0={0}" -f ($StartDate.ToString("0"))
        }
        if ($EndDate) {
            if ($query) {
                $query = "{0}&" -f $query
            } else {
                $Query = "?"
            }
            $query = "{0}t1={1}" -f $query, ($EndDate.ToString("0"))
        }
        if ($Days) {
            $seconds = [TimeSpan]::FromDays($Days).TotalSeconds
            if ($query) {
                $query = "{0}&" -f $query
            } else {
                $query = "?"
            }
            $Query = "{0}timespan={1}" -f $Query, $seconds
        }
        if ($PerPage) {
            if ($query) {
                $query = "{0}&" -f $query
            } else {
                $query = "?"
            }
            $query = "{0}perPage={1}" -f $query, $PerPage
        }
    }

    Process {
        $Uri = "{0}/networks/{1}/appliance/clients/{2}/security/events" -f $BaseURI, $Id, $ClientId
        if($query) {
            $Uri = "{0}{1}" -f $Uri, $query
        }

        try {

            $response = Invoke-WebRequest -Method Get -Uri $Uri -Headers -PreserveAuthorizationOnRedirect

            [List[PsObject]]$result = $response.Content | ConvertFrom-Json
            if ($result) {
                $Results.AddRange($result)
            }
            $page = 1
            if ($Pages -ne 1) {
                $done = $false
                do {
                    if ($response.RelationLink['next']) {
                        $Uri = $response.RelationLink['next']
                        $response = Invoke-WebRequest -Method Get -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect
                        [List[PsObject]]$result = $response.Content | ConvertFrom-Json
                        if ($result) {
                            $Results.AddRange($result)
                        }
                        $page += 1
                        if ($page -gt $Pages) {
                            $done = $true
                        }
                    } else {
                        $done = $true
                    }
                } until ($done)
            }
            return $Results
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Returns the security events for a client. Clients can be identified by a client key or either the MAC or IP depending on whether the network uses Track-by-IP.
    .PARAMETER Id
    The ID of the network.
    .PARAMETER ClientId
    The ID of the client.
    .PARAMETER StartDate
    The beginning of the timespan for the data. Data is gathered after the specified StartDate value. The maximum look back period is 791 days from today.
    .PARAMETER EndDate
    The end of the timespan for the data. t1 can be a maximum of 791 days after StartDate.
    .PARAMETER Days
    The timespan for which the information will be fetched. If specifying timespan, do not specify parameters StartDate and EndDate. The value must be less than or equal to 791 days. The default is 31 days.
    .PARAMETER PerPage
    The number of entries per page returned. Acceptable range is 3 - 1000. Default is 100.
    .OUTPUTS
    An array of security event objects.
    #>

}

Function Get-MerakiApplianceSecurityEvents() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param(
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [Parameter(
            Mandatory,
            ParameterSetName = 'dates'
        )]
        [ValidateScript({
            $_ -gt (Get-Date).AddDays(-791)
        }, ErrorMessage = "StartDate cannot be more that 791 days prior to today")]
        [datetime]$StartDate,
        [Parameter(
            Mandatory,
            ParameterSetName = 'dates'
        )]
        [ValidateScript({
            $_ -lt $StartDate.AddDays(791)
        }, ErrorMessage = "End date cannot be more than 791 days after StartDate")]
        [datetime]$EndDate,
        [Parameter(
            Mandatory,
            ParameterSetName = 'days'
        )]
        [ValidateScript({
            $_ -lt 791
        })]
        [int]$Days,
        [int]$PerPage       
        
    )

    Begin {

        $Headers = Get-Headers

        $Results = [List[PsObject]]::New()

        if ($StartDate) {
            $Query = "?t0={0}" -f ($StartDate.ToString("0"))
        }
        if ($EndDate) {
            if ($Query) {
                $Query = "{0}&" -f $Query
            } else {
                $Query = "?"
            }
            $Query = "{0}t1={1}" -f $Query, ($EndDate.ToString("0"))
        }
        if ($Days) {
            $seconds = [TimeSpan]::FromDays($Days).TotalSeconds
            if ($Query) {
                $Query = "{0}&" -f $Query
            } else {
                $Query = "?"
            }
            $Query = "{0}timespan={1}" -f $Query, $seconds
        }
        if ($PerPage) {
            if ($query) {
                $query = "{0}&" -f $query
            } else {
                $query = "?"
            }
            $query = "{0}perPage={1}" -f $query, $PerPage
        }
    }

    Process {
        $Uri = "{0}/networks/{networkId}/appliance/security/events" -f $BaseURI, $Id
        if ($Query) {
            $Uri = "{0}{1}" -f $Uri, $Query
        }

        try {
            $response = Invoke-WebRequest -Method Get -Uri $Uri -Headers -PreserveAuthorizationOnRedirect

            [List[PsObject]]$result = $response.Content | ConvertFrom-Json
            if ($result) {
                $Results.AddRange($result)
            }
            $page = 1
            if ($Pages -ne 1) {
                $done = $false
                do {
                    if ($response.RelationLink['next']) {
                        $Uri = $response.RelationLink['next']
                        $response = Invoke-WebRequest -Method Get -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect
                        [List[PsObject]]$result = $response.Content | ConvertFrom-Json
                        if ($result) {
                            $Results.AddRange($result)
                        }
                        $page += 1
                        if ($page -gt $Pages) {
                            $done = $true
                        }
                    } else {
                        $done = $true
                    }
                } until ($done)
            }
            return $Results

        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    List the security events for a network
    .PARAMETER Id
    The ID of the network
    .PARAMETER StartDate
    The beginning of the timespan for the data. Data is gathered after the specified StartDate value. The maximum look back period is 791 days from today.
    .PARAMETER EndDate
    The end of the timespan for the data. t1 can be a maximum of 791 days after StartDate.
    .PARAMETER Days
    The timespan for which the information will be fetched. If specifying timespan, do not specify parameters StartDate and EndDate. The value must be less than or equal to 791 days. The default is 31 days.
    .PARAMETER PerPage
    The number of entries per page returned. Acceptable range is 3 - 1000. Default is 100.
    .OUTPUTS
    An array of security event objects.
    #>


}

#endregion

Set-Alias -Name GMNetAppDhcpSubnet -Value Get-MerakiApplianceDhcpSubnets -Option ReadOnly
Set-ALias -Name Get-MerakiNetworkApplianceDhcpSubnets -Value Get-MerakiNetworkApplianceDhcpSubnets -Option ReadOnly
Set-Alias -Name GMAppDhcpSubnet -Value Get-MerakiApplianceDhcpSubnets -Option ReadOnly


#region VPN
function Get-MerakiApplianceSiteToSiteVPN() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$id,
        [switch]$hr
    )


    $Uri = "{0}/networks/{1}/appliance/vpn/siteToSiteVpn" -f $BaseURI, $id
    $Headers = Get-Headers

    try {
        $response = Invoke-RestMethod -Method GET -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect
        if ($hr) {
            $heading = [PsCustomObject][ordered]@{
                network = (Get-MerakiNetwork -networkID $id).name
                mode = $response.mode
            }
            Write-Output $heading | Format-List
            Write-Output "Hubs:"
            if ($response.mode = "spoke") {
                $hubs = @()
                $response.hubs | ForEach-Object {
                    $Hub = [PSCustomObject][ordered]@{
                        Name = (Get-MerakiNetwork -networkID $_.HubId).name
                        DefaultRoute = $_.useDefaultRoute
                    }
                    $Hubs += $Hub
                }
                Write-Output $hubs | Format-Table
            }
            Write-Output "Subnets:"
            $subnets = @()
            $response.subnets | ForEach-Object {
                $subnet = [PSCustomObject][ordered]@{
                    localSubnet = $_.localSubnet
                    useVpn = $_.useVpn
                }
                $subnets += $subnet
            }
            Write-Output $subnets | Format-Table
        } else {
            return $response
        }
    } catch {
        throw $_
    }
    <#
    .SYNOPSIS
    Returns a Meraki Network appliance Site-to-Site VPN configuration.
    .PARAMETER id
    The network Id.
    .PARAMETER hr
    Formats the output into 2 tables. Hubs and Subnets.
    .OUTPUTS
    If -hr is specified outputs tables to the console.
    If -hr is omitted, outputs a Meraki Site-to-Site VPN object.
    #>

}

Set-Alias -Name GMNetAppSSVpn -Value Get-MerakiNetworkApplianceSiteToSiteVPN -Option ReadOnly
Set-Alias -Name Get-MerakiNetworkApplianceSiteToSiteVPN -Value Get-MerakiApplianceSiteToSiteVPN -Option ReadOnly
Set-Alias -Name GMAppSSVpn -Value Get-MerakiApplianceSiteToSiteVPN -Option ReadOnly

function Set-MerakiApplianceSiteToSiteVpn() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param(
        [Parameter(Mandatory = $true)]        
        [string]$NetworkId,
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [ValidateSet('none', 'spoke', 'hub')]
        [string]$Mode,
        [Parameter(ValueFromPipelineByPropertyName)]
        [psobject[]]$Hubs,
        [Parameter(ValueFromPipelineByPropertyName)]
        [psobject[]]$Subnets
    )

    $Headers = Get-Headers

    $Uri = "{0}/networks/{1}/appliance/vpn/siteToSiteVpn" -f $BaseURI, $NetworkId

    If ($VpnSettings) {
        $body = $VpnSettings | ConvertTo-Json -Depth 6 -Compress
    } else {

        $_Body = @{
            mode = $mode
        }

        if ($Hubs) {
            foreach ($Hub in $hubs) {
                if (-not $Hub.HubId) {
                    throw "Property hubId is required for all Hubs"
                }
            }
            $_Body.Add("hubs", $Hubs)
        }

        if ($Subnets) {
            foreach($Subnet in $Subnets) {
                if (-not $Subnet.localSubnet) {
                    throw "Property localSubnet is required for all Subnets"
                }
            }
            $_Body.Add("subnets", $Subnets)
        }

        $body = $_Body | ConvertTo-Json -Depth 6 -Compress
    }

    try {
        $response = Invoke-RestMethod -Method PUT -Uri $Uri -Headers $Headers -Body $Body -PreserveAuthorizationOnRedirect
        return $response
    }
    catch {
        throw $_
    }
    <#
    .SYNOPSIS
    Update Network Site-to-Site VPN
    .DESCRIPTION
    Update the Meraki Network Site to Site VPN Settings.
    .PARAMETER NetworkId
    The ID of the to update
    .PARAMETER VpnSettings
    A Object containing the VPN Settings to apply. This parameter must be used without other parameters.
    .PARAMETER Mode
    The site-to-site VPN mode. Can be one of 'none', 'spoke' or 'hub'
    .PARAMETER Hubs
    The list of VPN hubs, in order of preference. In spoke mode, at least 1 hub is required.
    Hub objects contain the following properties.
    hubId:string (Required) - The network ID of the hub.

    useDefaultRoute:boolean - Only valid in 'spoke' mode. Indicates whether default route traffic should be sent to this hub.
    .PARAMETER Subnets
    The list of subnets and their VPN presence.
    Subnet object contain the following properties:
    localSubnet:string (required) - The CIDR notation subnet used within the VPN
    useVpn: boolean - Indicates the presence of the subnet in the VPN
    .EXAMPLE
    Updating an existing network configured as a spoke.
    # The easiest way to do this is to get the current von settings in an object.
    $VpnSettings = Get-MerakiNetworkApplianceSiteToSiteCpn -id N_1246598574
    # Modify the settings in this object
    # Set the 1st hub destination
    $VpnSettings.hubs[0].hubId = N_5452659857
    $VpnSettings.hubs[0].useDefaultRoute = $false
    # Modify the 2nd hub destination
    $VpnSettings.hubs[0].hubId = N_4585965254
    $VpnSettings.hubs[0].useDefaultRoute = $false
    # Modify the subnet settings if necessary
    $VpnSettings.Subnets[0].localSubnet = 10.5.5.5/24
    $VpnSettings.Subnets[0].useVpn = $true
    # Update the VPN Settings
    Set-MerakiNetworkApplianceSiteToSiteVpn -NetworkId N_1246598574 -VpnSettings $VpnSettings
    .EXAMPLE
    In this example we are going to convert a Hub (mess) network to a Spoke network
    In this instance the subnet is already set as we want it so we will only change the mode and add in the remote hub networks.
    
    # Create an array of hubs object
    $Hubs = @(
        @{
            hubId = "N_54265629254"
            useDefaultRoute = $False
        },
        @{
            hubId = "N_75485962345"
            useDefaultRoute = $false
        }
    )
    Set-MerakiNetworkApplianceSiteToSiteVpn -NetworkId N_845926352 -Mode Spoke -Hubs $Hubs
    #>

}

Set-Alias -Name Set-MerakiNetworkApplianceSiteToSiteVpn -Value Set-MerakiApplianceSiteToSiteVpn -Option ReadOnly
#endregion




#region CellularFirewall
function Get-MerakiApplianceCellularFirewallRules () {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param(
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName)]
        [Alias('NetworkId')]
        [string]$Id
    )

    Begin {
        $Headers = Get-Headers
    }
    
    Process {
        $Uri = "{0}/networks/{1}/appliance/firewall/cellularFirewallRules" -f $BaseURI, $Id        

        $Network = Get-MerakiNetwork -networkID $Id

        try {
            $response = Invoke-RestMethod -Method GET -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect
            $rules = $response.rules
            $number = 1
            $rules | ForEach-Object {
                $_ | Add-Member -MemberType NoteProperty -Name "NetworkId" -Value $Id
                $_ | Add-Member -MemberType NoteProperty -Name "NetworkName" -Value $Network.Name
                $_ | Add-Member -MemberType NoteProperty -Name "RuleNumber" -Value $number
                $number += 1
            }
            return $rules
        }
        catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Returns the Cellular firewall rules for this network.
    .PARAMETER Id
    The Network ID to retrieve the rules from.
    #>

}

function Set-MerakiApplianceCellularFirewallRules() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param(
        [Parameter(Mandatory = $true)]
        [Alias('NetworkId')]
        [string]$Id,
        [Parameter(Mandatory = $true)]
        [psObject[]]$Rules
    )

    $Headers = Get-Headers

    $Uri = "{0}/networks/{1}/appliance/firewall/cellularFirewallRules" -f $BaseURI, $Id

    # Remove the default rule if it exists.
    $Rules = $Rules.where({$_.comment -ne 'Default rule'})

    # Remove the RuleNumber, NetworkName, and NetworkId properties if it exists
    $Rules = $Rules | Select-Object -ExcludeProperty RuleNumber, NetworkId, NetworkName

    $_Rules = @{
        rules = $Rules
    }

    $body = $_Rules | ConvertTo-Json -Depth 4 

    $Network = Get-MerakiNetwork -Id $Id
    try {
        $Rules = Invoke-RestMethod -Method Put -Uri $Uri -Headers $Headers -Body $body -PreserveAuthorizationOnRedirect
        $Number = 1
        $Rules | ForEach-Object {
            $_ | Add-Member -MemberType NoteProperty -Name 'NetworkId' -Value $Id
            $_ | Add-Member -MemberType NoteProperty -Name 'NetworkName' -Value $Network.Name
            $_ | Add-Member -MemberType NoteProperty -Name 'RuleNumber' -Value $Number
            $Number += 1
        }
        return $response
    } catch {
        throw $_
    }
    <#
    .DESCRIPTION
    Update the network's cellular firewall rules
    .PARAMETER Id
    The network ID to update the rules on.
    .PARAMETER Rules
    An array of objects containing the following properties.
    comment: string Description of the rule (optional)
    destCidr*: string Comma-separated list of destination IP address(es) (in IP or CIDR notation), fully-qualified domain names (FQDN) or 'any'
    destPort: string Comma-separated list of destination port(s) (integer in the range 1-65535), or 'any'
    policy*: string 'allow' or 'deny' traffic specified by this rule
    protocol*: string The type of protocol (must be 'tcp', 'udp', 'icmp', 'icmp6' or 'any')
    srcCidr*: string Comma-separated list of source IP address(es) (in IP or CIDR notation), or 'any' (note: FQDN not supported for source addresses)
    srcPort: string Comma-separated list of source port(s) (integer in the range 1-65535), or 'any'
    syslogEnabled: boolean Log this rule to syslog (true or false, boolean value) - only applicable if a syslog has been configured (optional)
    .NOTES
    The Rules returned from Get-MerakiApplianceCellularFirewallRules contain the properties RuleNumber, NetworkId, and NetworkName.
    These properties are removed by this function before being sent to the API endpoint.
    If you create the rules array manually you do not need to include these properties.
    Changes to these rules should be done with the associated Add-, Set-, and Remove- functions.
    #>

}

function Set-MerakiApplianceCellularFirewallRule() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param(
        [Parameter(Mandatory = $true)]
        [Alias('NetworkId')]
        [string]$Id,
        [Parameter(Mandatory = $true)]
        [int]$RuleNumber,
        [ValidateSet('allow','deny')]
        [string]$Policy,
        [ValidateSet('tcp', 'udp', 'icmp', 'icmp6', 'any')]
        [string]$Protocol,
        [Alias('srcPort')]
        [string]$SourcePort,
        [ALias('srcCidr')]
        [string]$SourceCidr,
        [Alias('DestCidr')]
        [string]$DestinationCidr,
        [Alias('destPort')]
        [string]$DestinationPort,
        [switch]$SyslogEnabled,
        [string]$Comment
    )

    $Rules = @{}
    Get-MerakiApplianceCellularFirewallRules -id $Id | ForEach-Object {
        $Rules.Add($_.RuleNumber, $_)
    }

    If ($Policy) {
        $Rules[$RuleNumber].Policy = $Policy
    }
    if ($Protocol) {
        $Rules[$RuleNumber].Protocol = $Protocol
    }
    if ($SourceCidr) {
        $Rules[$RuleNumber].srcCidr = $SourceCidr
    }
    if ($SourcePort) {
        $Rules[$RuleNumber].srcPort = $SourcePort
    }
    if ($DestinationCidr) {
        $Rules[$RuleNumber].destCidr = $DestinationCidr
    }
    if ($DestinationPort) {
        $Rules[$RuleNumber].destPort = $DestinationPort
    }
    if ($Comment) {
        $Rules[$RuleNumber].comment = $Comment
    }
    if ($SyslogEnabled) {
        $Rule[$RuleNumber].syslogEnabled = $true
    }

    $Rules = $Rules.Values 

    
    try {
        $Rules = Set-MerakiApplianceCellularFirewallRules -Id $Id -rules $Rules
        return $Rules
    }
    catch {
        throw $_
    }
    <#
    .DESCRIPTION
    Updates a single cellular firewall rule in the specified network.
    .PARAMETER Id
    The ID of the network.
    .PARAMETER RuleNumber
    The RuleNumber to update.
    .PARAMETER Policy
    'allow' or 'deny' traffic specified by this rule
    .PARAMETER protocol
    The type of protocol (must be 'tcp', 'udp', 'icmp', 'icmp6' or 'any')
    .PARAMETER SourcePort
    Comma-separated list of source port(s) (integer in the range 1-65535), or 'any'
    .PARAMETER SourceCidr
    Comma-separated list of source IP address(es) (in IP or CIDR notation), or 'any' (note: FQDN not supported for source addresses)
    .PARAMETER DestinationCidr
    Comma-separated list of destination IP address(es) (in IP or CIDR notation), fully-qualified domain names (FQDN) or 'any'
    .PARAMETER DestinationPort
    Comma-separated list of destination port(s) (integer in the range 1-65535), or 'any'
    .PARAMETER SyslogEnabled
    Log this rule to syslog (true or false, boolean value) - only applicable if a syslog has been configured (optional)
    .PARAMETER Comment
    Description of the rule (optional)
    #>

}

function Add-MerakiApplianceCellularFirewallRule() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param(
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [Parameter(Mandatory = $true)]
        [ValidateSet('allow','deny')]
        [string]$Policy,
        [Parameter(Mandatory = $true)]
        [ValidateSet('tcp', 'udp', 'icmp', 'icmp6', 'any')]
        [string]$protocol,
        [Alias('srcPort')]
        [string]$SourcePort = 'any',
        [Parameter(Mandatory = $true)]
        [ALias('srcCidr')]
        [string]$SourceCidr,
          [Parameter(Mandatory = $true)]
        [Alias('DestCidr')]
        [string]$DestinationCidr,
        [Alias('destPort')]
        [string]$DestinationPort = 'any',
        [switch]$SyslogEnabled,
        [Parameter(Mandatory = $true)]
        [string]$Comment
    )

    Begin {

    }

    Process {
        $Rules = Get-MerakiApplianceCellularFirewallRules -Id $Id

        # Remove the default rule

        
        $Properties = @{
            "policy"        = $Policy
            "srcCidr"       = $SourceCidr
            "srcPort"       = $SourcePort
            "protocol"      = $protocol
            "destCidr"      = $DestinationCidr
            "destPort"      = $DestinationPort
            "comment"       = $Comment
            "RuleNUmber"    = -1
        }
        if ($SyslogEnabled.IsPresent) { $properties.Add("syslogEnabled", $SyslogEnabled) }
        $rule = [PSCustomObject]$Properties

        $Rules += $rule

        try {
            $response = Set-MerakiNetworkApplianceCellularFirewallRules -Id $id -Rules $Rules
            return $response
        }
        catch {
            throw $_
        }
    }
    <#
    Adds a cellular firewall rule to the network
    .PARAMETER Id
    The ID of the network.
    .PARAMETER Policy
    'allow' or 'deny' traffic specified by this rule
    .PARAMETER protocol
    The type of protocol (must be 'tcp', 'udp', 'icmp', 'icmp6' or 'any')
    .PARAMETER SourcePort
    Comma-separated list of source port(s) (integer in the range 1-65535), or 'any'
    .PARAMETER SourceCidr
    Comma-separated list of source IP address(es) (in IP or CIDR notation), or 'any' (note: FQDN not supported for source addresses)
    .PARAMETER DestinationCidr
    Comma-separated list of destination IP address(es) (in IP or CIDR notation), fully-qualified domain names (FQDN) or 'any'
    .PARAMETER DestinationPort
    Comma-separated list of destination port(s) (integer in the range 1-65535), or 'any'
    .PARAMETER SyslogEnabled
    Log this rule to syslog (true or false, boolean value) - only applicable if a syslog has been configured (optional)
    .PARAMETER Comment
    Description of the rule (optional)
    #>

}

function Remove-MerakiApplianceCellularFirewallRule() {
    [CmdletBinding(
        SupportsShouldProcess,
        ConfirmImpact = 'High'
    )]
    Param(
        [Parameter(Mandatory = $true)]
        [string]$NetworkId,
        [Parameter(Mandatory = $true)]
        [int]$RuleNumber
    )

    $Rules = Get-MerakiApplianceCellularFirewallRules -id $NetworkId | Where-Object {$_.RuleNumber -ne $RuleNumber} | `
        Select-Object -ExcludeProperty RuleNumber


    if ($PSCmdlet.ShouldProcess("Cellular Firewall Rule number $RuleNumber", 'DELETE')) {
        try {
            $response = Set-MerakiApplianceCellularFirewallRules -Id $NetworkId -Rules $Rules
            return $response
        }
        catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Deletes a cellular firewall rule.
    .PARAMETER NetworkId
    The ID of the network to remove the rule.
    .PARAMETER RuleIndex
    The rule number to be removed.
    #>

}

function Get-MerakiApplianceInboundCellularFirewallRules() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id
    )

    Begin {
        $Headers = Get-Headers
    }

    Process {
        $Uri = "{0}/networks/appliance/firewall/inboundCellularFirewallRule" -f $BaseURI, $Id
        
        $Network = Get-MerakiNetwork -networkID $Id

        try {
            $response = Invoke-RestMethod -Method GET -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect
            $Number = 1
            $response | ForEach-Object {
                $_ | Add-Member -MemberType NoteProperty -Name "NetworkId" -Value $Id
                $_ | Add-Member -MemberType NoteProperty -Name "NetworkName" -Value $Network.Name
                $_ | Add-Member -MemberType NoteProperty -Name "RuleNumber" -Value $Number
                $Number += 1
            }
            return $response
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Return the inbound cellular firewall rules for an MX network
    .PARAMETER Id
    The Id of the network.
    .OUTPUTS
    An array of inbound cellular firewall rule objects.
    #>

}

function Set-MerakiApplianceInboundCellularFirewallRules () {
    [CmdletBinding()]
    param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [Parameter(Mandatory)]
        [PsObject[]]$Rules
    )

    Begin {
        $Headers = Get-Headers

        # Remove the default rule if it exists.
        $Rules = $Rules.Where({$_.comment -ne "Default rule"})

        # Remove the RuleNumber, NetworkId, amd NetworkName properties if they exists
        $Rules = $Rules | Select-Object -ExcludeProperty RuleNumber, NetworkId, NetworkName

        $rules = @{
            rules = $Rules
        }

        $Body = $rules | ConvertTo-Json -Depth 4 -Compress
    }

    Process {
        $Uri = "{0}/networks/{1}/appliance/firewall/inboundCellularFirewallRules" -f $BaseURI, $Id

        $Network = Get-MerakiNetwork -Id $Id

        try {
            $Rules = Invoke-RestMethod -Method PUT -Uri $Uri -Headers $Headers -Body $Body
            $Number = 1
            $Rules | ForEach-Object {
                $_ | Add-Member -MemberType NoteProperty -Name 'NetworkId' -Value $Id
                $_ | Add-Member -MemberType NoteProperty -Name 'NetworkName' -Value $Network.Name
                $_ | Add-Member -MemberType NoteProperty -Name 'RuleNumber' -Value $Number
                $Number += 1
            }
            return $response
        } catch {
            throw $_
        }

    }
    <#
    .DESCRIPTION
    Update the inbound cellular firewall rules of an MX network
    .PARAMETER Id
    The Id of the network.
    .PARAMETER Rules
    An array of inbound cellular firewall rules that include the following properties
    comment: string Description of the rule (optional)
    destCidr*: string Comma-separated list of destination IP address(es) (in IP or CIDR notation), fully-qualified domain names (FQDN) or 'any'
    destPort: string Comma-separated list of destination port(s) (integer in the range 1-65535), or 'any'
    policy*: string 'allow' or 'deny' traffic specified by this rule
    protocol*: string The type of protocol (must be 'tcp', 'udp', 'icmp', 'icmp6' or 'any')
    srcCidr*: string Comma-separated list of source IP address(es) (in IP or CIDR notation), or 'any' (note: FQDN not supported for source addresses)
    srcPort: string Comma-separated list of source port(s) (integer in the range 1-65535), or 'any'
    syslogEnabled: boolean Log this rule to syslog (true or false, boolean value) - only applicable if a syslog has been configured (optional)
    .NOTES
    The Rules returned from Get-MerakiApplianceInboundFirewallRules contain the properties RuleNumber, NetworkId, and NetworkName.
    These properties are removed by this function before being sent to the API endpoint.
    If you create the rules array manually you do not need to include these properties.
    Changes to these rules should be done with the associated Add-, Set-, and Remove- functions.
    .OUTPUTS
    An array of updated inbound cellular firewall rules.
    #>

}

function Add-MerakiApplianceInboundCellularFirewallRule() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [string]$Comment,
        [Parameter(Mandatory)]
        [ValidateSet('allow','deny' )]
        [string]$Policy,
        [Alias('srcCidr')]
        [string]$SourceCIDR,
        [ALias('srcPort')]
        [string]$SourcePort = 'any',
        [ValidateSet('tcp', 'udp', 'icmp', 'icmp6', 'any')]
        [string]$Protocol = 'any',
        [Parameter(Mandatory)]
        [Alias('destCidr')]
        [string]$DestinationCIDR,
        [Alias('destPort')]
        [string]$DestinationPort = 'any',
        [switch]$SyslogEnabled
    )

    Begin {

        $Rules = Get-MerakiApplianceInboundCellularFirewallRules -Id $Id

        $Rules = $Rules.Where({$_.Comment -ne 'Default rule'}) | Select-Object -ExcludeProperty RuleNumber

        $Properties = @{
            "policy"    = $Policy
            "srcCidr"   = $SourceCidr
            "srcPort"   = $SourcePort
            "protocol"  = $protocol
            "destCidr"  = $DestinationCidr
            "destPort"  = $DestinationPort
            "comment"   = $Comment
        }

        if ($SyslogEnabled.IsPresent) {$Properties.Add("syslogEnabled", $true)}
        $Rule = [PSCustomObject]$Properties

        $Rules += $Rule

        try {
            $response = Set-MerakiNetworkApplianceInboundCellularFirewallRules -Id $id -Rules $Rules
            return $response
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Add an inbound cellular firewall rule to an MX network.
    .PARAMETER Id
    The ID of the network.
    .PARAMETER Policy
    'allow' or 'deny' traffic specified by this rule
    .PARAMETER protocol
    The type of protocol (must be 'tcp', 'udp', 'icmp', 'icmp6' or 'any')
    .PARAMETER SourcePort
    Comma-separated list of source port(s) (integer in the range 1-65535), or 'any'
    .PARAMETER SourceCidr
    Comma-separated list of source IP address(es) (in IP or CIDR notation), or 'any' (note: FQDN not supported for source addresses)
    .PARAMETER DestinationCidr
    Comma-separated list of destination IP address(es) (in IP or CIDR notation), fully-qualified domain names (FQDN) or 'any'
    .PARAMETER DestinationPort
    Comma-separated list of destination port(s) (integer in the range 1-65535), or 'any'
    .PARAMETER SyslogEnabled
    Log this rule to syslog (true or false, boolean value) - only applicable if a syslog has been configured (optional)
    .PARAMETER Comment
    Description of the rule (optional)
    #>

}

function Set-MerakiApplianceInboundCellularFirewallRule() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [Parameter(Mandatory = $true)]
        [int]$RuleNumber,
        [ValidateSet('allow','deny')]
        [string]$Policy,
        [ValidateSet('tcp', 'udp', 'icmp', 'icmp6', 'any')]
        [string]$Protocol,
        [Alias('srcPort')]
        [string]$SourcePort,
        [ALias('srcCidr')]
        [string]$SourceCidr,
        [Alias('DestCidr')]
        [string]$DestinationCidr,
        [Alias('destPort')]
        [string]$DestinationPort,
        [switch]$SyslogEnabled,
        [string]$Comment
    )

    Begin {
        $Rules = @{}
    }

    Process {
        Get-MerakiApplianceInboundCellularFirewallRules -id $Id | Foreach-Object {
            $Rules.Add($_.RuleNumber, $_)
        }

        If ($Policy) {
            $Rules[$RuleNumber].Policy = $Policy
        }
        if ($Protocol) {
            $Rules[$RuleNumber].Protocol = $Protocol
        }
        if ($SourceCidr) {
            $Rules[$RuleNumber].srcCidr = $SourceCidr
        }
        if ($SourcePort) {
            $Rules[$RuleNumber].srcPort = $SourcePort
        }
        if ($DestinationCidr) {
            $Rules[$RuleNumber].destCidr = $DestinationCidr
        }
        if ($DestinationPort) {
            $Rules[$RuleNumber].destPort = $DestinationPort
        }
        if ($Comment) {
            $Rules[$RuleNumber].comment = $Comment
        }
        if ($SyslogEnabled) {
            $Rule[$RuleNumber].syslogEnabled = $true
        }        

        $Rules = $Rules.Values

        try {
            $Rules = Set-MerakiApplianceInboundCellularFirewallRules -id -Rules $Rules
            return $Rules
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Updates a single inbound cellular firewall rule in the specified network.
    You must get the rule index using the Get-MerakiNetworkApplianceCellularFirewallRule function.
    .PARAMETER Id
    The ID of the network.
    .PARAMETER RuleIndex
    The RuleIndex to update.
    .PARAMETER Policy
    'allow' or 'deny' traffic specified by this rule
    .PARAMETER protocol
    The type of protocol (must be 'tcp', 'udp', 'icmp', 'icmp6' or 'any')
    .PARAMETER SourcePort
    Comma-separated list of source port(s) (integer in the range 1-65535), or 'any'
    .PARAMETER SourceCidr
    Comma-separated list of source IP address(es) (in IP or CIDR notation), or 'any' (note: FQDN not supported for source addresses)
    .PARAMETER DestinationCidr
    Comma-separated list of destination IP address(es) (in IP or CIDR notation), fully-qualified domain names (FQDN) or 'any'
    .PARAMETER DestinationPort
    Comma-separated list of destination port(s) (integer in the range 1-65535), or 'any'
    .PARAMETER SyslogEnabled
    Log this rule to syslog (true or false, boolean value) - only applicable if a syslog has been configured (optional)
    .PARAMETER Comment
    Description of the rule (optional)
    #>

}

function Remove-MerakiApplianceInboundCellularFirewallRule() {
    [CmdletBinding(
        SupportsShouldProcess,
        ConfirmImpact = 'High'
    )]
    Param (
        [Parameter(Mandatory)]
        [string]$NetworkId,
        [Parameter(Mandatory)]
        [int]$RuleNumber
    )

    $Rules = Get-MerakiApplianceInboundCellularFirewallRules -id $NetworkId | Where-Object {$_.RuleNumber -ne $RuleNumber} | `
        Select-Object -ExcludeProperty RuleNumber

    if ($PSCmdlet.ShouldProcess("Inbound Cellular Firewall Rule Number $ruleNumber", "Delete")) {
        try {
            $response = Set-MerakiApplianceInboundCellularFirewallRules -Id $NetworkId -Rules $Rules
            return $response
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Deleted an inbound cellular firewall rule.
    .PARAMETER NetworkId
    The Id of the network.
    .PARAMETER RuleNumber
    The rule number to be removed.
    #>

}
#endregion

#region InboundFirewallRules
function Get-MerakiApplianceInboundFirewallRules() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id
    )

    Begin {
        $Headers = get-Headers
    }

    Process{
        $Uri = "{0}/networks/{1}/appliance/firewall/inboundFirewallRules" -f $BaseURI, $Id

        $Network = Get-MerakiNetwork -id $Id

        try {
            $response = Invoke-RestMethod -Method GET -Uri $Uri -Headers $Headers
            $Rules = $response.rules
            $Number = 1
            $Rules | Foreach-Object {
                $_ | Add-Member -MemberType NoteProperty -Name "NetworkId" -Value $id
                $_ | Add-Member -MemberName NoteProperty -Name "NetworkName" -Value $Network.Name
                $_ | Add-Member -MemberType NoteProperty -Name "RuleNumber" -Value $Number
                $number += 1
            }
            return $Rules
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Return the inbound firewall rules for an MX network
    .PARAMETER Id
    The Id of the network.
    .OUTPUTS
    An array of inbound firewall rules.
    #>

}

function Set-MerakiApplianceInboundFirewallRules() {
    [CmdletBinding()]
    Param(
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [Parameter(Mandatory)]
        [PsObject[]]$Rules
    )

    Begin {
        $Headers = Get-Headers

        # Remove the default rule if it exists
        $Rules = $Rules.Where({$_.comment -ne 'Default rule'})

        #Remove the RuleNumber, NetworkId, and NetworkName properties if they exist.
        $Rules = $Rules | Select-Object -ExcludeProperty RuleNumber, NetworkName, NetworkId

        $_Rules = @{
            rules = $Rules
        }

        $body = $_Rules |ConvertTo-Json -Depth 4 -Compress
    }

    Process {
        $Uri = "{0}/networks/{1}/appliance/firewall/inboundFirewallRules" -f $BaseURI, $id

        try {
            $response = Invoke-RestMethod -Method PUT -Uri $Uri -Headers $Headers -Body $body -PreserveAuthorizationOnRedirect
            $rules = $response.range
            return $response.rules
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Update the inbound firewall rules of an MX network
    .PARAMETER Id
    The Id of the network.
    .PARAMETER Rules
    An array of inbound firewall rules objects.
    .OUTPUTS
    An array of updated inbound firewall rules.
    #>

}

 function Add-MerakiApplianceInboundFirewallRule() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [Parameter(Mandatory = $true)]
        [ValidateSet('allow','deny')]
        [string]$Policy,
        [Parameter(Mandatory = $true)]
        [ValidateSet('tcp', 'udp', 'icmp', 'icmp6', 'any')]
        [string]$protocol,
        [Alias('srcPort')]
        [string]$SourcePort = 'any',
        [Parameter(Mandatory = $true)]
        [ALias('srcCidr')]
        [string]$SourceCidr,
          [Parameter(Mandatory = $true)]
        [Alias('DestCidr')]
        [string]$DestinationCidr,
        [Alias('destPort')]
        [string]$DestinationPort = 'any',
        [switch]$SyslogEnabled,
        [Parameter(Mandatory = $true)]
        [string]$Comment
    )

    Begin {
        $Rules = Get-MerakiApplianceInboundFirewallRules -Id $Id Select-Object -ExcludeProperty RuleNumber

        $Rules = $Rules | Where-Object {$_.comment -ne 'Default rule'}

        $Properties = @{
            "policy"        = $Policy
            "srcCidr"       = $SourceCidr
            "srcPort"       = $SourcePort
            "protocol"      = $protocol
            "destCidr"      = $DestinationCidr
            "destPort"      = $DestinationPort
            "comment"       = $Comment
            "RuleNumber"    = -1
            "NetworkId"     = "NA"
            "NetworkName"   = "NA"
        }
        if ($SyslogEnabled.IsPresent) { $Properties.Add("syslogEnabled", $true)}
        $rule = [PSCustomObject]$Properties

        $Rules += $rule

        try {
            $response = Set-MerakiApplianceInboundCellularFirewallRules -id $id -Rules $Rules
            return $response.rules
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Adds an inbound firewall rule to the network.
    .PARAMETER Policy
    'allow' or 'deny' traffic specified by this rule
    .PARAMETER protocol
    The type of protocol (must be 'tcp', 'udp', 'icmp', 'icmp6' or 'any')
    .PARAMETER SourcePort
    Comma-separated list of source port(s) (integer in the range 1-65535), or 'any'
    .PARAMETER SourceCidr
    Comma-separated list of source IP address(es) (in IP or CIDR notation), or 'any' (note: FQDN not supported for source addresses)
    .PARAMETER DestinationCidr
    Comma-separated list of destination IP address(es) (in IP or CIDR notation), fully-qualified domain names (FQDN) or 'any'
    .PARAMETER DestinationPort
    Comma-separated list of destination port(s) (integer in the range 1-65535), or 'any'
    .PARAMETER SyslogEnabled
    Log this rule to syslog (true or false, boolean value) - only applicable if a syslog has been configured (optional)
    .PARAMETER Comment
    Description of the rule (optional)
    #>

 }

 function Set-MerakiApplianceInboundFirewallRule() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [Parameter(Mandatory = $true)]
        [int]$RuleNumber,
        [ValidateSet('allow','deny')]
        [string]$Policy,
        [ValidateSet('tcp', 'udp', 'icmp', 'icmp6', 'any')]
        [string]$Protocol,
        [Alias('srcPort')]
        [string]$SourcePort,
        [ALias('srcCidr')]
        [string]$SourceCidr,
        [Alias('DestCidr')]
        [string]$DestinationCidr,
        [Alias('destPort')]
        [string]$DestinationPort,
        [switch]$SyslogEnabled,
        [string]$Comment
    )

    Begin {
        $Rules = @{}
    }

    Process {
        Get-MerakiApplianceInboundFirewallRules -id $id | ForEach-Object {
            $Rules.Add($_.RuleNumber, $_)
        }

        If ($Policy) {
            $Rules[$RuleNumber].Policy = $Policy
        }
        if ($Protocol) {
            $Rules[$RuleNumber].Protocol = $Protocol
        }
        if ($SourceCidr) {
            $Rules[$RuleNumber].srcCidr = $SourceCidr
        }
        if ($SourcePort) {
            $Rules[$RuleNumber].srcPort = $SourcePort
        }
        if ($DestinationCidr) {
            $Rules[$RuleNumber].destCidr = $DestinationCidr
        }
        if ($DestinationPort) {
            $Rules[$RuleNumber].destPort = $DestinationPort
        }
        if ($Comment) {
            $Rules[$RuleNumber].comment = $Comment
        }
        if ($SyslogEnabled) {
            $Rule[$RuleNumber].syslogEnabled = $true
        }

        $Rules = $Rules.Values

        try {
            $response = Set-MerakiApplianceInboundFirewallRules -id $Id -Rules $Rules
            return $response
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Updates a single inbound firewall rule in the specified network.
    .PARAMETER Id
    The ID of the network.
    .PARAMETER RuleNumber
    The RuleNumber to update.
    .PARAMETER Policy
    'allow' or 'deny' traffic specified by this rule
    .PARAMETER protocol
    The type of protocol (must be 'tcp', 'udp', 'icmp', 'icmp6' or 'any')
    .PARAMETER SourcePort
    Comma-separated list of source port(s) (integer in the range 1-65535), or 'any'
    .PARAMETER SourceCidr
    Comma-separated list of source IP address(es) (in IP or CIDR notation), or 'any' (note: FQDN not supported for source addresses)
    .PARAMETER DestinationCidr
    Comma-separated list of destination IP address(es) (in IP or CIDR notation), fully-qualified domain names (FQDN) or 'any'
    .PARAMETER DestinationPort
    Comma-separated list of destination port(s) (integer in the range 1-65535), or 'any'
    .PARAMETER SyslogEnabled
    Log this rule to syslog (true or false, boolean value) - only applicable if a syslog has been configured (optional)
    .PARAMETER Comment
    Description of the rule (optional)
    #>

 }

 function Remove-MerakiApplianceInboundFirewallRule() {
    [CmdletBinding(
        SupportsShouldProcess,
        ConfirmImpact = 'High'
    )]
    Param (
        [Parameter(Mandatory)]
        [string]$NetworkId,
        [Parameter(Mandatory)]
        [int]$RuleNumber
    )

    $Rules = Get-MerakiApplianceInboundFirewallRules -id $NetworkId | Where-Object {$_.RuleNUmber -ne $RuleNumber}

    if ($PSCmdlet.ShouldProcess("Inbound Firewall Rule number $RuleNumber", "Delete")) {
        try {
            $response = Set-MerakiApplianceInboundFirewallRules -id $NetworkId -Rules $Rules
            return $response
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Deletes an inbound firewall rule.
    .PARAMETER NetworkId
    The ID of the network.
    .PARAMETER RuleNumber
    The rule number to remove.
    #>

 }
#endregion

#region Firewalled Services
function Get-MerakiApplianceFirewalledService() {
    [CmdletBinding()]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [ValidateSet('ICMP','web','SNMP')]
        [string]$Service
    )

    Begin {

        $Headers = Get-Headers
    }

    Process {
        $Uri = "{0}/networks/{1}/appliance/firewall/firewalledServices" -f $BaseURI, $Id
        if ($Service) {
            $Uri = "{0}/{1}" -f $Uri, $Service
        }
        try {
            $response = Invoke-RestMethod -Method Get -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect
            $response | Add-Member -MemberType NoteProperty -Name "NetworkId" -Value $Id
            return $response
        }
        catch {
            Throw $_
        }
    }
    <#
    .DESCRIPTION
    Retrieve the Appliance firewalled services.
    .PARAMETER Id
    The Id of the network.
    #>

}

function Set-MerakiApplianceFirewalledService() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [string]$Service,
        [Parameter(Mandatory)]
        [ValidateSet('blocked', 'restricted', 'unrestricted')]
        [string]$Access,
        [ValidateScript({
            ($_ -and $Access -eq 'restricted') -or
            ((-not $_) -and ($Access -in 'blocked','unrestricted'))
        })]
        [string[]]$AllowedIps
    )

    Begin {
        $Headers = Get-Headers

        $_Body = @{
            access = $Access
        }
        if ($Access -eq 'restricted') {
            $_Body.Add("allowedIps", $AllowedIps)
        }

        $body = $_Body | ConvertTo-Json -Depth 3 -Compress
    }

    Process {
        $Uri = "{0}/networks/{1}/appliance/firewall/firewalledServices/{2}" -f $BaseURI, $Id, $Service

        try {
            $response = Invoke-RestMethod -Method Put -Uri $Uri -Headers $Headers -Body $body -PreserveAuthorizationOnRedirect
            return $response
        } catch {
            throw $_
        }
    }
}
#endregion

#region L3 Firewall Rules
Function Get-MerakiApplianceL3FirewallRules() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param(
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$id
    )

    $Uri = "{0}/networks/{1}/appliance/firewall/l3FirewallRules" -f $BaseURI, $Id

    $Headers = Get-Headers

    try {
        $response = Invoke-RestMethod -Method GET -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect
        $rules = $response.Rules
        $ruleId = 1
        $rules | Foreach-Object {
            $_ | Add-Member -MemberType NoteProperty -Name "RuleId" -Value $ruleId
            $RuleId += 1
        }
        return $rules
    } catch {
        Throw $_
    }
    <#
    .DESCRIPTION
    Retrieve the network appliance level 3 firewall rules.
    .PARAMETER id
    The network Id.
    #>

}

function Set-MerakiApplianceL3FirewallRules() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param(
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [Parameter(
            Mandatory
        )]
        [psobject[]]$Rules
    )

    Begin {

        $Headers = Get-Headers

        # The below statements are used if rules are being copied from another network/

        # Remove the RuleNumber, NetworkId, and NetworkName property if they exist.
        $Rules = $Rules | Select-Object -Property * -ExcludeProperty RuleNumber, NetworkId, NetworkName
        

        # Remove the default rule if it exists.
        $Rules = $Rules | Where-Object {$_.comment -ne 'Default rule'}
    }

    Process {
    
        $Uri = "{0}/networks/{1}/appliance/firewall/l3FirewallRules" -f $BaseUri, $id
        $_body = @{
            "rules" = $Rules
        }
        $body = $_body | ConvertTo-Json -Depth 5 -Compress

        $Network = Get-MerakiNetwork -Id $Id
        try {
            $response = Invoke-RestMethod -Method PUT -Uri $Uri -Headers $Headers -Body $body -PreserveAuthorizationOnRedirect
            $Rules = $response.rules
            $Number = 1
            $Rules | ForEach-Object {
                $_ | Add-Member -MemberType NoteProperty -Name 'NetworkId' -Value $Id
                $_ | Add-Member -MemberType NoteProperty -Name 'NetworkName' -Value $Network.Name
                $_ | Add-Member -MemberType NoteProperty -Name 'RuleNumber' -Value $Number
                $Number += 1
            }
            return $response.rules
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Removes the specified level 3 firewall rules from a network. (This is irreversible!)
    .PARAMETER Id
    The ID of the network.
    .PARAMETER Rules
    An array of firewall rules objects.
    .PARAMETER PassThru
    Returns the updated list of rules.
    #>

}

function Add-MerakiApplianceL3FirewallRule() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param(
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [ValidateSet('allow','deny')]
        [string]$Policy,
        [string]$Comment,
        [ValidateSet('tcp', 'udp', 'icmp', 'icmp6', 'any')]
        [string]$Protocol,
        [ValidateScript(
            {
                $subnetPart = $_.split("/")
                $_ -eq "any" -or ([IPAddress]$subnetPart[0] -is [IPAddress] -and $SubnetPart[1] -In 0..32)
            }
        )]
        [string]$SourceCIDR,
        [string]$SourcePort = 'any',
        [ValidateScript(
            {
                $subnetPart = $_.split("/")
                $_ -eq "any" -or [IPAddress]$subnetPart[0] -is [IPAddress] -and $SubnetPart[1] -In 0..32
            }
        )]
        [string]$DestinationCIDR,
        [string]$DestinationPort = 'any',
        [switch]$SyslogEnabled,
        [switch]$PassThru
    )
    
    Process {
        $Rules = Get-MerakiApplianceL3FirewallRules -id $Id 
        
        $Properties = [PSCustomObject]@{
            policy = $policy
            comment = $Comment
            protocol = $Protocol
            destPort = $DestinationPort
            destCidr = $DestinationCIDR
            srcPort = $SourcePort
            srcCidr = $SourceCIDR
            $RuleNumber = -1
            $NetworkId = "NA"
            $NetworkName = "NA"
        }
        If ($SyslogEnabled) {$Properties.Add("syslogEnabled", $true)}
        $Rule = [PSCustomObject]$Properties
        
        $Rules += $Rule
        try {
            $Rules = Set-MerakiApplianceL3FirewallRules -Id $Id -Rules $Rules
            return $Rules
        } catch {
            throw $_
        }
    }
    <#
    .SYNOPSIS
    Add a Level 3 Firewall rule.
    .DESCRIPTION
    Adds a Level 3 firewall rule to a Meraki Appliance.
    .PARAMETER Id
    The Network ID.
    .PARAMETER Policy
    The Policy for this rule.
    .PARAMETER Comment
    Description of the rule.
    .PARAMETER Protocol
    The protocol to use
    .PARAMETER SourceCIDR
    Comma-separated list of source IP address(es) (in IP or CIDR notation), or 'any' (note: FQDN not supported for source addresses)
    .PARAMETER SourcePort
    Comma-separated list of source port(s) (integer in the range 1-65535), or 'any'
    .PARAMETER DestinationCIDR
    Comma-separated list of destination IP address(es) (in IP or CIDR notation), fully-qualified domain names (FQDN) or 'any'
    .PARAMETER DestinationPort
    Comma-separated list of destination port(s) (integer in the range 1-65535), or 'any'
    .PARAMETER SyslogEnabled
    Log this rule to syslog - only applicable if a syslog has been configured (optional)
    .PARAMETER PassThru
    Return the newly created rule.
    #>

}

function Set-MerakiApplianceL3FirewallRule() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param(
        [Parameter(
            Mandatory
        )]
        [string]$NetworkId,
        [int]$RuleNumber,
        [ValidateSet('allow','deny')]
        [string]$Policy,
        [string]$Comment,
        [ValidateSet('tcp', 'udp', 'icmp', 'icmp6', 'any')]
        [string]$Protocol,
        [ValidateScript(
            {
                $subnetPart = $_.split("/")
                [IPAddress]$subnetPart[0] -is [IPAddress] -and $SubnetPart[1] -In 0..32
            }
        )]
        [string]$SourceCIDR,
        [string]$SourcePort,
        [ValidateScript(
            {
                $subnetPart = $_.split("/")
                [IPAddress]$subnetPart[0] -is [IPAddress] -and $SubnetPart[1] -In 0..32
            }
        )]
        [string]$DestinationCIDR,
        [string]$DestinationPort,
        [switch]$SyslogEnabled
    )

    $Rules_ = @{}
    Get-MerakiApplianceL3FirewallRules -id $NetworkId | ForEach-Object {
        $Rules_.Add($_.RuleNumber, $_)
    }

    if (-not $Rule_[$RuleNumber]) {
        throw "Invalid Rule Id"
    }

    If ($Policy) {
        $Rules_[$RuleNumber].policy = $Policy
    }
    if ($Comment) {
        $Rules_[$RuleNumber].comment = $Comment
    }
    if ($Protocol) {
        $Rules_[$RuleNumber].protocol = $Protocol
    }
    if ($SourceCIDR) {
        $Rule_[$RuleNumber].srcCIDR - $SourceCIDR
    }
    if ($SourcePort) {
        $Rule_[$RuleNumber].srcPort = $SourcePort
    }
    if ($DestinationCIDR) {
        $Rule_[$RuleNumber].destCIDR = $DestinationCIDR
    }
    if ($DestinationPort) {
        $Rule_[$RuleNumber].destPort = $DestinationPort
    }
    if ($SyslogEnabled) {
        $Rule_[$RuleNumber].syslogEnabled = $SyslogEnabled
    }

    $Rules = $Rules_.Values

    try {
        $Rules = Set-MerakiApplianceL3FirewallRules -Id $Id -Rules $Rules
        return $Rules  
    } catch {
        throw $_
    }

    <#
    .SYNOPSIS
    Update an existing level 3 firewall rule.
    .DESCRIPTION
    Update an existing Level 3 firewall rule on a meraki Appliance.
    .PARAMETER NetworkId
    The Id of the network
    .PARAMETER RuleId
    The Id of the rule to update.
    .PARAMETER Policy
    allow' or 'deny' traffic specified by this rule
    .PARAMETER Comment
    Description of the rule (optional)
    .PARAMETER Protocol
    The type of protocol (must be 'tcp', 'udp', 'icmp', 'icmp6' or 'any')
    .PARAMETER SourceCIDR
    Comma-separated list of source IP address(es) (in IP or CIDR notation), or 'any' (note: FQDN not supported for source addresses)
    .PARAMETER SourcePort
    Comma-separated list of source port(s) (integer in the range 1-65535), or 'any'
    .PARAMETER DestinationCIDR
    Comma-separated list of destination IP address(es) (in IP or CIDR notation), fully-qualified domain names (FQDN) or 'any'
    .PARAMETER DestinationPort
    Comma-separated list of destination port(s) (integer in the range 1-65535), or 'any'
    .PARAMETER SyslogEnabled
    Log this rule to syslog (true or false, boolean value) - only applicable if a syslog has been configured (optional)
    .PARAMETER
    #>

}

function Remove-MerakiApplianceL3FirewallRule() {
    [CmdletBinding(
        SupportsShouldProcess, 
        DefaultParameterSetName = 'default',
        ConfirmImpact = 'High'
    )]
    Param(
        [Parameter(
            Mandatory
        )]
        [string]$NetworkId,
        [Parameter(Mandatory)]
        [string]$RuleId,
        [switch]$PassThru
    )

    $Uri = "{0}/networks/{1}/appliance/firewall/l3FirewallRules" -f $BaseUri, $NetworkId

    $Headers = Get-Headers

    $Rules = Get-MerakiApplianceL3FirewallRules -id $NetworkId

    # Remove the Default Rule
    $Rules = $Rules | Where-Object {$_.comment -ne "Default rule"}

    $Rule = $Rules | Where-Object {$_.RuleId -eq $RuleId}
    if (-not $Rule) {
        throw "Invalid Rule Id"
    }

    # Remove the Rule to be deleted
    $Rules = $Rules | Where-Object {$_.RuleId -ne $RuleId}

    # Remove the Rule Property
    $Rules = $Rules | Select-Object -Property * -ExcludeProperty RuleId

    $_Body = @{rules = $Rules}

    $body = $_Body | ConvertTo-Json -Depth 5 -Compress
    if ($PSCmdlet.ShouldProcess("Rule:$($Rule.Comment)", "Delete")) {
        try {
            $response = Invoke-RestMethod -Method PUT -Uri $Uri -Headers $Headers -Body $body -PreserveAuthorizationOnRedirect
            return $response.rules
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Delete the specified level 3 firewall rule. (This cannot be undone!)
    .PARAMETER NetworkId
    The Id of the network.
    .PARAMETER RuleId
    The Rule Id to be deleted.
    .PARAMETER PassThru
    return the updated rules.
    #>

}
#endregion

#region L7 firewall rules
function Get-MerakiApplianceL7FirewallRules() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id
    )

    Begin {
        $Headers = Get-Headers
    }

    Process {
        $Uri = "{0}/networks/{1}/appliance/firewall/l7FirewallRules" -f $BaseURI, $Id

        try {
            $response = Invoke-RestMethod -Method GET -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect
            $Rules = $response.rules
            $Number = 1
            $Rules | ForEach-Object {
                $_ | Add-Member -MemberType NoteProperty -Name 'NetworkId' -Value $Id
                $_ | Add-Member -MemberType NoteProperty -Name "RuleNumber" -Value $Number
                $Number += 1
            }
            return $Rules
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Returns the MX L7 firewall rules for an MX network.
    .PARAMETER Id
    The Id of the network.
    .OUTPUTS
    An array of level 7 firewall rules.
    #>

}

function Set-MerakiApplianceL7FirewallRules() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [PsObject[]]$Rules
    )

    Begin {
        $Headers = Get-Headers
    }

    Process {
        $Uri = "{0}/networks/{1}/appliance/firewall/l7FirewallRules" -f $BaseURI, $Id

        # Remove the default rule if it exists
        $Rules = $Rules.where({$_.comment -ne 'Default rule'})

        # Remove the RuleNumber property
        $Rules = $Rules | Select-Object -ExcludeProperty RuleNumber

        $_Rules = @{
            rules = $Rules
        }

        $Body = $_Rules | ConvertTo-Json -Depth 4 -Compress

        try {
            $response = Invoke-RestMethod -Method PUT -Uri $Uri -Headers $Headers -Body $Body -PreserveAuthorizationOnRedirect
            return $response.rule
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Update the MX L7 firewall rules for an MX network
    .PARAMETER Id
    The ID of the network.
    .PARAMETER Rules
    An array of level 7 firewall rules.
    .OUTPUTS
    AN array of updated level 7 firewall rules.
    #>

}

function Add-MerakiApplianceL7FirewallRule() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [Parameter(Mandatory)]
        [ValidateSet('application', 'applicationCategory', 'host', 'port', 'ipRange')]
        [string]$Type,
        [Parameter(Mandatory)]
        [string]$Value
    )

    Begin {
        $Properties = @{
            policy      = 'deny'
            type        = $Type
            value       = $Value
            RuleNumber  = -1
            NetworkId = "NA"
            NetworkName = "NA"

        }
        $Rule = [PsCustomObject]$Properties        
    }

    Process {
        [array]$Rules = Get-MerakiApplianceL7FirewallRules -id $Id
        $Rules += $Rule

        try {
            $Rules = Set-MerakiApplianceL7FirewallRules -Id $Id -Rules $Rules
            return $Rules
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Add a new MX L7 firewall rules for an MX network.
    .PARAMETER Id
    The Id if the Network.
    .PARAMETER Type
    Type of the L7 rule. One of: 'application', 'applicationCategory', 'host', 'port', 'ipRange'
    .PARAMETER Value
    The 'value' of what you want to block. Format of 'value' varies depending on type of the rule.
    The application categories and application ids can be retrieved from the the
    'Get-MerakiNetworkApplianceApplicationCategories' endpoint. The countries follow the two-letter ISO 3166-1 alpha-2 format
    .OUTPUTS
    An array of Level 7 Firewall Rules
    #>

}

function Set-MerakiApplianceL7FirewallRule () {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [int]$RuleNumber,        
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateSet('application', 'applicationCategory', 'host', 'port', 'ipRange')]
        [string]$Type,
        [Parameter(ValueFromPipelineByPropertyName)]
        [string]$Value
    )

    Process {
        $Rules_ = @{}
        Get-MerakiApplianceL7FirewallRules -id $Id| ForEach-Object {
            $Rules_.Add($_.RuleNumber, $_)            
        }

        if ($type) {
            $Rules_[$RuleNumber].type = $Type
        }
        if ($Value) {
            $Rules_[$RuleNumber].value = $Value
        }

        $Rules = $Rules_.Values

        try {
            $Rules = Set-MerakiApplianceL7FirewallRules -Id $Id -Rules $Rules
            return $Rules
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Update an MX L7 firewall rule for an MX network.
    .PARAMETER Id
    The ID of the network.
    .PARAMETER RuleNumber
    The rule number to update.
    .PARAMETER Type
    Type of the L7 rule. One of: 'application', 'applicationCategory', 'host', 'port', 'ipRange'
    .PARAMETER Value
    The 'value' of what you want to block. Format of 'value' varies depending on type of the rule.
    The application categories and application ids can be retrieved from the the
    'Get-MerakiNetworkApplianceApplicationCategories' endpoint. The countries follow the two-letter ISO 3166-1 alpha-2 format
    .OUTPUTS
    An array of Level 7 Firewall Rules
    #>

}

function Remove-MerakiApplianceL7FirewallRule() {
    [CmdletBinding(
        SupportsShouldProcess,
        ConfirmImpact = 'high'
    )]
    Param (
        [Parameter(Mandatory)]
        [string]$NetworkId,
        [Parameter(Mandatory)]
        [string]$RuleNumber
    )

    $Rules = Get-MerakiApplianceL7FirewallRules -Id $NetworkId | Where-Object {$_.RuleNumber -ne $RuleNumber}

    if ($PSCmdlet.ShouldProcess("Level7 firewall rule number $RuleNumber", "Delete")) {
        try {
            $Rules = Set-MerakiApplianceL7FirewallRules -Id $NetworkId -Rules $Rules
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Deletes an MX L7 firewall rule for an MX network.
    .PARAMETER Id
    The ID of the network.
    .PARAMETER RuleNumber
    The rule number to update.
    .OUTPUTS
    An array of Level 7 Firewall Rules
    #>

}

Function Get-MerakiApplianceL7ApplicationCategories() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id
    )

    Begin {
        $Headers = Get-Headers
    }

    Process {
        $Uri = "{0}/networks/{1}/appliance/firewall/l7FirewallRules/applicationCategories" -f $BaseURI, $Id

        try {
            $response = Invoke-RestMethod -Method Get -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect
            return $response.ApplicationCategories
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Return the L7 firewall application categories and their associated applications for an MX network.
    .PARAMETER Id
    The ID of the Network.
    .OUTPUTS
    An array of application category objects.
    #>

}
#endregion

#region Firewall NAR Rules
function Get-MerakiApplianceFirewallNatRules() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [Parameter(Mandatory)]
        [ValidateSet('OneToMany','OneToOne','PortForwarding')]
        [string]$Type
    )

    Begin {
        $Headers = Get-Headers
        switch ($type) {
            'OneToMany' {
                $Uri = "{0}/networks/{1}/appliance/firewall/oneToManyNatRules"
            }
            'OneToOne' {
                $Uri = "{0}/networks/{1}/appliance/firewall/oneToOneNatRules"
            }
            'PortForwarding'{
                $Uri = "{0}/networks/{networkId}/appliance/firewall/portForwardingRules"
            }
        }
    }

    Process {
        $Uri = $Uri -f $BaseURI, $Id

        $Network = Get-MerakiNetwork -NetworkId $Id

        try {
            $response = Invoke-RestMethod -Method Get -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect
            [array]$Rules = $response.rules
            $number = 1
            $Rules | ForEach-Object {
                $_ | Add-Member -MemberType NoteProperty -Name 'NetworkId' -Value $Id
                $_ | Add-Member -MemberType NoteProperty -Name 'NetworkName' -Value $Network.Name
                $_ | Add-Member -MemberType NoteProperty -Name 'RuleNumber' -Value $number
                $Number += 1
            }
            return $Rules
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Return the NAT mapping rules for an MX network
    .PARAMETER Id
    The ID of the network.
    .PARAMETER Type
    The type of mapping rule. One of OneToMany, OneToOne, or PortForwarding
    .OUTPUTS
    An array of NAT mapping rules.\
    #>

}

function Set-MerakiApplianceFirewallNatRules() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [Parameter(Mandatory)]
        [PsObject]$Rules,
        [Parameter(Mandatory)]
        [ValidateSet('OneToMany','OneToOne','PortForwarding')]
        [string]$Type
    )

    Begin {
        $Headers = Get-headers
        
        switch ($type) {
            'OneToMany' {
                $Uri = "{0}/networks/{1}/appliance/firewall/oneToManyNatRules"
            }
            'OneToOne' {
                $Uri = "{0}/networks/{1}/appliance/firewall/oneToOneNatRules"
            }
            'PortForwarding'{
                $Uri = "{0}/networks/{networkId}/appliance/firewall/portForwardingRules"
            }
        }
    }

    Process {
        $Uri = $Uri -f $BaseURI, $Id

        # Remove the RuleNumber, NetworkId, and NetworkName properties.
        [array]$Rules = $Rules | Select-Object -ExcludeProperty RuleNumber, NetworkName, NetworkId
        $Rules_ = @{
            rules = $Rules
        }

        $Body = $Rules_ | ConvertTo-Json -Depth 10 #-Compress

        $Network = Get-MerakiNetwork -networkID $Id

        try {
            $response = Invoke-RestMethod -Method PUT -Uri $Uri -Headers $Headers -Body $Body
            $Rules = $response.rules
            $Number = 1
            $Rules | ForEach-Object {
                $_ | Add-Member -MemberType NoteProperty -Name 'NetworkId' -Value $Id
                $_ | Add-Member -MemberType NoteProperty -Name 'NetworkName' -Value $Network.name
                $_ | Add-Member -MemberType NoteProperty -Name 'RuleNumber' -Value $number
                $number += 1
            }

            Return $Rules
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Set the one to many NAT mapping rules for an MX network/
    .PARAMETER Id
    The Id of the network.
    .PARAMETER Rules
    An array of mapping rules. This object depends on the rule type. Incompatible rule(s) fo the specified type will throw an error.
    .PARAMETER Type
    The type of mapping rule. One of OneToMany, OneToOne, or PortForwarding
    .OUTPUTS an array of NAT Mapping Rules
    .NOTES
    The Rules collection varies depending on the type of rules being written.
    This parameter can accept either a PSCustomObject or a HashTable

    One to Many rules consist of the following Schema: * = required
    publicIp*: string The IP address that will be used to access the internal resource from the WAN
    uplink*: string The physical WAN interface on which the traffic will arrive ('internet1' or, if available, 'internet2')
    portRules*: array[] An array of associated forwarding rules with the following properties:
        localIp: string Local IP address to which traffic will be forwarded
        localPort: string Destination port of the forwarded traffic that will be sent from the MX to the specified host on the LAN. If you simply wish to forward the traffic without translating the port, this should be the same as the Public port
        name: string A description of the rule
        protocol: string 'tcp' or 'udp'
        publicPort: string Destination port of the traffic that is arriving on the WAN
        allowedIps: array[] Remote IP addresses or ranges that are permitted to access the internal resource via this port forwarding rule, or 'any'

    One To One Mapping rules consist of the following schema: * = required
    lanIp*: string The IP address of the server or device that hosts the internal resource that you wish to make available on the WAN
    name: string A descriptive name for the rule
    publicIp: string The IP address that will be used to access the internal resource from the WAN
    uplink: string The physical WAN interface on which the traffic will arrive ('internet1' or, if available, 'internet2')
    allowedInbound: array[] The ports this mapping will provide access on, and the remote IPs that will be allowed access to the resource
        protocol: string Either of the following: 'tcp', 'udp', 'icmp-ping' or 'any'
        allowedIps: array[] An array of ranges of WAN IP addresses that are allowed to make inbound connections on the specified ports or port ranges, or 'any'
        destinationPorts: array[] An array of ports or port ranges that will be forwarded to the host on the LAN

    Port Forwarding rules consist of the following schema: * = required
    lanIp*: string The IP address of the server or device that hosts the internal resource that you wish to make available on the WAN
    localPort*: string A port or port ranges that will receive the forwarded traffic from the WAN
    name: string A descriptive name for the rule
    protocol*: string TCP or UDP. Valid values are 'tcp', 'udp'
    publicPort*: string A port or port ranges that will be forwarded to the host on the LAN
    uplink: string The physical WAN interface on which the traffic will arrive ('internet1' or, if available, 'internet2' or 'both')
    allowedIps*: array[] An array of ranges of WAN IP addresses that are allowed to make inbound connections on the specified ports or port ranges (or any)
    #>

}

function Add-MerakiApplianceFirewallNatRule() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [Parameter(Mandatory)]
        [ValidateSet('OneToMany','OneToOne','PortForwarding')]
        [string]$Type,
        [ValidateScript({
            ($_ -and $Type -eq 'OneToMany') -or ($_ -and $Type -eq 'OneToOne')
        }, ErrorMessage = 'Property PublicIp is invalid for a PortForwarding rule')]
        [string]$PublicIp,
        [Parameter(Mandatory)]
        [ValidateSet('internet1', 'internet2')]
        [string]$Uplink,
        [ValidateScript({
            $_ -and $Type -eq 'OneToMany'
        }, ErrorMessage = 'Parameter PortRules is only valid for a OneToMany Rule')]        
        [PsObject[]]$PortRules,
        [ValidateScript({
            ($_ -and $Type -eq 'OneToOne') -or ($_ -and $type -eq 'PortForwarding')
        }, ErrorMessage = 'Parameter LanIp is invalid for a OneToMany rule.')]
        [string]$LanIp,
        [ValidateScript({
            ($_ -and $Type -eq 'OneToOne') -or ($_ -and $Type -eq 'PortForwarding')
        }, ErrorMessage = 'Parameter Name is invalid for a OneToMany rule.')]
        [string]$Name,
        [ValidateScript({
            $_ -and $Type -eq 'OneToOne'
        }, ErrorMessage = 'Parameter AllowedInbound is only valid for a OneToMany rule')]
        [PsObject]$AllowedInbound,
        [ValidateScript({
            $_ -and $Type -eq 'PortForwarding'
        }, ErrorMessage = 'Parameter Protocol is only valid for a PortForwarding rule.')]
        [ValidateSet('tcp','udp')]
        [string]$Protocol,
        [ValidateScript({
            $_ -and $Type -eq 'PortForwarding'
        }, ErrorMessage = 'Parameter PublicPort is only valid for a PortForwarding rule.')]
        [string]$PublicPort,
        [ValidateScript({
            $_ -and $Type -eq 'PortForwarding'
        }, ErrorMessage = 'Parameter LocalPort is only valid for a PortForwarding rule.')]
        [string]$LocalPort,
        [ValidateScript({
            $_ -and $Type -eq 'PortForwarding'
        }, ErrorMessage = 'Parameter AllowedIps is only valid for a PortForwarding rule.')]
        [string[]]$AllowedIps
    )

    Begin {

        switch ($Type) {
            'OneToMany' {
                $Properties = @{
                    publicIp    = $PublicIP
                    uplink      = $Uplink
                    portRules   = $PortRules
                    RuleNumber  = -1
                    NetworkId   = 'NA'
                    NetworkName = 'NA'
                }
                $Rule = [PSCustomObject]$Properties
            }
            'OneToOne' {
                $Properties = @{
                    publicIp        = $PublicIP
                    uplink          = $Uplink
                    lanIp           = $lanIp
                    name            = $Name
                    allowedInbound  = $AllowedInbound
                    RuleNNumber     = -1
                    NetworkId       = 'NA'
                    NetworkName     = 'NA'
                }
                $Rule = [PSCustomObject]$Properties
            }
            'PortForwarding' {
                $Properties = @{
                    lanIp       = $lanIp
                    localPort   = $LocalPort
                    name        = $Name
                    protocol    = $Protocol
                    publicPort  = $PublicPort
                    uplink      = $Uplink
                    allowedIps  = $AllowedIps
                    RuleNumber  = -1
                    NetworkId   = 'NA'
                    NetworkName = 'NA'
                }
            }
        }        
    }

    Process {
        $Rules = Get-MerakiApplianceFirewallNatRules -Id $Id -Type $Type
        if ($Rules) {
            $Rules += $Rule
        } else {
            $Rules = @($Rule)
        }
        try{
            $Rules = Set-MerakiApplianceFirewallNatRules -Id $Id -Type $Type -Rules $Rules
            return $Rules
        } catch {
            throw $_
        }    
    }
    <#
    .DESCRIPTION
    Add a One to Many NAT mapping rule to an MX network
    .PARAMETER Id
    The Id of tye network.
    .PARAMETER Type
    The type of rule, can be one of: 'OneToMany','OneToOne',or 'PortForwarding'
    .PARAMETER PublicIP
    The IP address that will be used to access the internal resource from the WAN.
    This parameter is only valid for for OneToMany and OneToOne rules.
    .PARAMETER Uplink
    The physical WAN interface on which the traffic will arrive ('internet1' or, if available, 'internet2')
    .PARAMETER PortRules
    An array of associated forwarding rules with the following properties:
        localIp: string Local IP address to which traffic will be forwarded
        localPort: string Destination port of the forwarded traffic that will be sent from the MX to the specified host on the LAN. If you simply wish to forward the traffic without translating the port, this should be the same as the Public port
        name: string A description of the rule
        protocol: string 'tcp' or 'udp'
        publicPort: string Destination port of the traffic that is arriving on the WAN
        allowedIps: array[] Remote IP addresses or ranges that are permitted to access the internal resource via this port forwarding rule, or 'any'

        This property can be either a PsObject or a HashTable
        This property is only valid for OneToMany rules.
    .PARAMETER LanIp
    The IP address of the server or device that hosts the internal resource that you wish to make available on the WAN
    This parameter is only valid for OneToOne or PortForwarding rules.
    .PARAMETER Name
    A descriptive name for the rule.
    This parameter is only valid for OneToOne or PortForwarding rules.
    .PARAMETER AllowedInbound
    The ports this mapping will provide access on, and the remote IPs that will be allowed access to the resource
    The parameter is only valid for OneToMany rules.
    .PARAMETER Protocol
    TCP or UDP. Valid values are 'tcp', 'udp'
    The parameter is only valid for PortForwarding rules.
    .PARAMETER PublicPort
    A port or port ranges that will be forwarded to the host on the LAN.
    The parameter is only valid for PortForwarding rules.
    .PARAMETER LocalPort
    A port or port ranges that will receive the forwarded traffic from the WAN
    The parameter is only valid for PortForwarding rules.
    .PARAMETER AllowedIps
    An array of ranges of WAN IP addresses that are allowed to make inbound connections on the specified ports or port ranges (or any)
    The parameter is only valid for PortForwarding rules.
    .OUTPUTS
    An array of One top Many Firewall NAT rules
    #>

}

Function Set-MerakiApplianceFirewallNatRule() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [Parameter(Mandatory)]
        [int]$RuleNumber,
        [Parameter(Mandatory)]
        [ValidateSet('OneToMany','OneToOne','PortForwarding')]
        [string]$Type,
        [ValidateScript({
            ($_ -and $Type -eq 'OneToMany') -or ($_ -and $Type -eq 'OneToOne')
        }, ErrorMessage = 'Property PublicIp is invalid for a PortForwarding rule')]
        [string]$PublicIp,
        [Parameter(Mandatory)]
        [ValidateSet('internet1', 'internet2')]
        [string]$Uplink,
        [ValidateScript({
            $_ -and $Type -eq 'OneToMany'
        }, ErrorMessage = 'Parameter PortRules is only valid for a OneToMany Rule')]
        [Parameter(Mandatory)]
        [PsObject[]]$PortRules,
        [ValidateScript({
            ($_ -and $Type -eq 'OneToOne') -or ($_ -and $type -eq 'PortForwarding')
        }, ErrorMessage = 'Parameter LanIp is invalid for a OneToMany rule.')]
        [string]$LanIp,
        [ValidateScript({
            ($_ -and $Type -eq 'OneToOne') -or ($_ -and $Type -eq 'PortForwarding')
        }, ErrorMessage = 'Parameter Name is invalid for a OneToMany rule.')]
        [string]$Name,
        [ValidateScript({
            $_ -and $Type -eq 'OneToMany'
        }, ErrorMessage = 'Parameter AllowedInbound is only valid for a OneToMany rule')]
        [PsObject]$AllowedInbound,
        [ValidateScript({
            $_ -and $Type -eq 'PortForwarding'
        }, ErrorMessage = 'Parameter Protocol is only valid for a PortForwarding rule.')]
        [ValidateSet('tcp','udp')]
        [string]$Protocol,
        [ValidateScript({
            $_ -and $Type -eq 'PortForwarding'
        }, ErrorMessage = 'Parameter PublicPort is only valid for a PortForwarding rule.')]
        [string]$PublicPort,
        [ValidateScript({
            $_ -and $Type -eq 'PortForwarding'
        },ErrorMessage = 'Parameter LocalPort is only valid for a PortForwarding rule.')]
        [string]$LocalPort,
        [ValidateScript({
            $_ -and $Type -eq 'PortForwarding'
        }, ErrorMessage = 'Parameter AllowedIps is only valid for a PortForwarding rule.')]
        [string[]]$AllowedIps

    )

    $Rules_ = @{}
    Get-MerakiApplianceFirewallNatRules -Id $NetworkId -Type $Type | ForEach-Object {
        $Rules_.Add($_.RuleNumber, $_)
    }
    if ($PublicIP) {
        $Rules_[$RuleNumber].publicIp = $PublicIP
    }
    if ($Uplink) {
        $Rules_[$RuleNumber].Uplink = $Uplink
    }
    if ($PortRules) {
        $Rules_[$RuleNumber].portRules = $PortRules
    }                
    if ($PublicIp) {
        $Rules_[$RuleNumber].PublicIp = $PublicIp
    }
    if ($Uplink) {
        $Rules_[$RuleNumber].Uplink = $Uplink
    }
    if ($LanIp) {
        $Rules_[$RuleNumber].lanIp = $LanIp
    }
    if ($Name) {
        $Rules_[$RuleNumber].Name = $Name
    }
    if ($AllowedInbound) {
        $Rules_[$RuleNumber].allowedInbound = $AllowedInbound
    }
    if ($LocalPort) {
        $Rules_[$RuleNumber].localPort = $LocalPort
    }
    if ($PublicPort) {
        $Rules_[$RuleNumber].publicPort = $PublicPort
    }
    if ($AllowedIps) {
        $Rules_[$RuleNumber].allowedIps = $AllowedIps
    }

    try {
        $Rules = Set-MerakiApplianceFirewallNatRules -Id $Id -Type $Type -Rules $Rules.Values
        return $Rules
    } catch {
        throw $_
    }
    <#
    .DESCRIPTION
    Add a One to Many NAT mapping rule to an MX network
    .PARAMETER Id
    The Id of tye network.
    .PARAMETER RuleNumber
    The rule number oto be updated.
    .PARAMETER Type
    The type of rule, can be one of: 'OneToMany','OneToOne',or 'PortForwarding'
    .PARAMETER PublicIP
    The IP address that will be used to access the internal resource from the WAN.
    This parameter is only valid for for OneToMany and OneToOne rules.
    .PARAMETER Uplink
    The physical WAN interface on which the traffic will arrive ('internet1' or, if available, 'internet2')
    .PARAMETER PortRules
    An array of associated forwarding rules with the following properties:
        localIp: string Local IP address to which traffic will be forwarded
        localPort: string Destination port of the forwarded traffic that will be sent from the MX to the specified host on the LAN. If you simply wish to forward the traffic without translating the port, this should be the same as the Public port
        name: string A description of the rule
        protocol: string 'tcp' or 'udp'
        publicPort: string Destination port of the traffic that is arriving on the WAN
        allowedIps: array[] Remote IP addresses or ranges that are permitted to access the internal resource via this port forwarding rule, or 'any'

        This property can be either a PsObject or a HashTable
        This property is only valid for OneToMany rules.
    .PARAMETER LanIp
    The IP address of the server or device that hosts the internal resource that you wish to make available on the WAN
    This parameter is only valid for OneToOne or PortForwarding rules.
    .PARAMETER Name
    A descriptive name for the rule.
    This parameter is only valid for OneToOne or PortForwarding rules.
    .PARAMETER AllowedInbound
    The ports this mapping will provide access on, and the remote IPs that will be allowed access to the resource
    The parameter is only valid for OneToMany rules.
    .PARAMETER Protocol
    TCP or UDP. Valid values are 'tcp', 'udp'
    The parameter is only valid for PortForwarding rules.
    .PARAMETER PublicPort
    A port or port ranges that will be forwarded to the host on the LAN.
    The parameter is only valid for PortForwarding rules.
    .PARAMETER LocalPort
    A port or port ranges that will receive the forwarded traffic from the WAN
    The parameter is only valid for PortForwarding rules.
    .PARAMETER AllowedIps
    An array of ranges of WAN IP addresses that are allowed to make inbound connections on the specified ports or port ranges (or any)
    The parameter is only valid for PortForwarding rules.
    .OUTPUTS
    An array of One top Many Firewall NAT rules
    #>

}

function Remove-MerakiApplianceFirewallNatRule {
    [CmdletBinding(
        SupportsShouldProcess,
        ConfirmImpact = 'High'
    )]
    Param (
        [Parameter(Mandatory)]
        [string]$NetworkId,
        [Parameter(Mandatory)]
        [ValidateSet('OneToMany','OneToOne','PortForwarding')]
        [string]$Type,
        [Parameter(Mandatory)]
        [int]$RuleNumber
    )

    $Rules = Get-MerakiApplianceFirewallNatRules -Id $Id -Type $Type
    
    $Rules = $Rules.Where({$_.RuleNumber -ne $RuleNumber})

    if ($PSCmdlet.ShouldProcess("$Type rule number $RuleNumber", "Delete")) {
        try {
            $Rules = Set-MerakiApplianceFirewallNatRules -Id $Id -Type OneToMany -Rules $Rules
            return $Rules
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Deletes a One To Many firewall NAT rule
    .PARAMETER NetworkId
    The Id of the network.
    .PARAMETER RuleNumber
    The rule number to delete
    #>

}
#endregion

#region prefixes
function Get-MerakiApplianceDelegatesStaticPrefixes() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id
    )

    Begin {
        $Headers = Get-Headers
    }

    Process {
        $Uri = "{0}/networks/{1}/appliance/prefixes/delegated/statics" -f $BaseURI, $Id

        Try {
            $response = Invoke-RestMethod -Method GET -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect
            $response | ForEach-Object {
                $_ | Add-Member -MemberType NoteProperty -Name 'NetworkId' -Value $Id
            }
            return $response
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Retrieves static delegated prefixes for a network.
    .PARAMETER Id
    The Id of the network.
    .OUTPUTS
    An array of static prefixes.
    #>


 
}
function Get-MerakiApplianceDelegatesStaticPrefix() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [string]$PrefixId
    )

    Begin {
        $Headers = Get-Headers
    }

    Process {
        $Uri = "{0}/networks/{1}/appliance/prefixes/delegated/statics/{2}" -f $BaseURI, $id, $PrefixId

        try {
            $response = Invoke-RestMethod -Method Get -URI $Uri -Headers $Headers -PreserveAuthorizationOnRedirect
            $response | ForEach-Object {
                $_ | Add-Member -MemberType NoteProperty -Name 'NetworkId' -Value $Id                
            }
            return $response
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Return a static delegated prefix from a network
    .PARAMETER Id
    The ID of the network.
    .PARAMETER PrefixId
    The Static Prefix Id.
    .OUTPUTS
    A Delegated Static prefix object.
    #>

}

function Add-MerakiApplianceDelegatedStaticPrefix() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [string]$Description,
        [Parameter(Mandatory)]
        [string]$Prefix,
        [Parameter(Mandatory)]
        [ValidateSet('independent', 'internet')]
        [string]$Type,
        [Parameter(Mandatory)]
        [string[]]$Interfaces
    )

    Begin {
        $Headers = Get-Headers

        $_Body = @{
            prefix = $Prefix
            origin = @{
                type = $Type
                interfaces = $Interfaces
            }
        }

        if ($Description) {
            $_Body.Add("description", $Description)
        }

        $body = $_Body | ConvertTo-Json -Depth 4 -Compress
    }

    Process{
        $Uri = "{0}/networks/{1}/appliance/prefixes/delegated/statics" -f $BaseURI, $Id 

        try {
            $response = Invoke-RestMethod -Method POST -Uri $Uri -Headers $Headers -Body $body -PreserveAuthorizationOnRedirect
            $response | Add-Member -MemberType NoteProperty -Name 'NetworkId' -Value $Id
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Add a static delegated prefix from a network
    .PARAMETER Id
    The Id of the network.
    .PARAMETER Description
    A name or description for the prefix
    .PARAMETER Prefix
    A static IPv6 prefix
    .PARAMETER Type
    Type of the origin. Valid values are 'independent', 'internet'
    .PARAMETER Interfaces
    An array of interfaces associated with the prefix. i.e. 'wan1'
    .OUTPUTS
    A delegated static prefix object.
    #>
    
}

function Set-MerakiApplianceDelegatedStaticPrefix() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [string]$PrefixId,
        [string]$Description,
        [string]$Prefix,
        [ValidateSet('independent', 'internet')]
        [string]$Type,
        [string[]]$Interfaces
    )

    Begin {
        $Headers = Get-Headers
        
        $_Body = @{}
        if ($Description) {
            $_Body.Add("description", $Description)
        }
        if ($Prefix) {
            $_Body.Add("prefix", $Prefix)
        }
        if ($Type) {
            $_Body['origin'].type = $Type
        }
        if ($Interfaces) {
            $_Body['origin'].interfaces = $Interfaces
        }

        $body = $_Body | ConvertTo-Json -Depth 4 -Compress
    }

    Process {
        $Uri = "{0}/networks/{networkId}/appliance/prefixes/delegated/statics/{staticDelegatedPrefixId}" -f $BaseURI, $Id, $PrefixId

        try {
            $response = Invoke-RestMethod -Method Put -Uri $Uri -Headers $Headers -Body $body -PreserveAuthorizationOnRedirect
            $response | Add-Member -MemberType NoteProperty -Name 'NetworkId' -Value $Id
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Update a static delegated prefix from a network.
    .PARAMETER Id
    The ID of the network.
    .PARAMETER PrefixId
    The Prefix Id.
    .PARAMETER Prefix
    A static IPv6 prefix
    .PARAMETER Type
    Type of the origin. Valid values are 'independent', 'internet'
    .PARAMETER Interfaces
    An array of interfaces associated with the prefix. i.e. 'wan1'
    .OUTPUTS
    A delegated static prefix object.
    #>

}

function Remove-MerakiApplianceDelegatedStaticPrefix() {
    [CmdletBinding(
        SupportsShouldProcess,
        ConfirmImpact = 'High'
    )]
    Param (
        [Parameter(Mandatory)]
        [string]$NetworkId,
        [Parameter(Mandatory)]
        [string]$PrefixId
    )

    $Headers = Get-Headers

    $Uri = "{0}/networks/{1}/appliance/prefixes/delegated/statics/{2}" -f $BaseURI, $Id, $PrefixId

    if ($PSCmdlet.ShouldProcess("Static Prefix ID $PrefixId", "Delete")) {
        $response = Invoke-RestMethod -Method Delete -Uri $Uri -Headers $Headers
        return $response
    } catch {
        throw $_
    }
    <#
    .DESCRIPTION
    Delete a static delegated prefix from a network
    .PARAMETER NetworkId
    The ID of te network
    .PARAMETER PrefixId
    The Prefix Id
    .OUTPUTS
    HTTP response, response status 204 = success.
    #>

}
#endregion

#region IntrusionMalware
function Get-MerakiApplianceSecurityIntrusion() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id
    )

    Begin {
        $Headers = Get-Headers
    }

    Process {
        $Uri = "{0}/networks/{1}/appliance/security/intrusion" -f $BaseURI, $Id

        try {
            $response = Invoke-RestMethod -Method Get -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect
            return $response
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Returns all supported intrusion settings for an organization
    .PARAMETER Id
    The ID of the network.
    .OUTPUTS
    An intrusion settings object.
    #>

}

function Set-MerakiApplianceSecurityIntrusion() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id,        
        [ValidateSet('connectivity','balanced','security')]
        [string]$IdsRuleSets,
        [ValidateSet('disabled','detection','prevention' )]
        [string]$Mode,
        [Parameter(ParameterSetName = "default")]
        [switch]$UseDefault,
        [Parameter(ParameterSetName = 'non-default')]
        [string[]]$ExcludeCidr,
        [Parameter(ParameterSetName = 'non-default')]
        [string[]]$IncludeCidr
    )

    Begin {
        $_Body = @{}

        if ($IdsRuleSets) {
            $_Body.Add("idsRuleSets", $IdsRuleSets)
        }
        if ($Mode) {
            $_Body.add("mode", $Mode)
        }
        if ($UseDefault.IsPresent) {
            $_Body["protectedNetworks"].useDefault = $true
        }
        if ($ExcludeCidr) {
            $_Body["protectedNetworks"].excludeCidr = $ExcludeCidr
        }
        if ($IncludeCidr) {
            $_Body["protectedNetworks"].includeCidr = $IncludeCidr
        }

        $body = $_Body | ConvertTo-Json -Depth 4 -Compress
    }    

    Process {
        $Uri = "{0}/networks/{1}/appliance/security/intrusion" -f $BaseURI, $Id

        try {
            $response = Invoke-RestMethod -Method Put -Uri $Uri -Headers $Headers -Body $body -PreserveAuthorizationOnRedirect
            return $response
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Set the supported intrusion settings for an MX network
    .PARAMETER Id
    The ID of the Network.
    .PARAMETER IdsRuleSets
    Set the detection rule set 'connectivity','balanced','security' (optional - omitting will leave current config unchanged).
    Default value is 'balanced' if none currently saved
    .PARAMETER Mode
    Set mode to 'disabled','detection','prevention' (optional - omitting will leave current config unchanged)
    .PARAMETER UseDefault
    Whether to use special IPv4 addresses: https://tools.ietf.org/html/rfc5735 (required). Default value is true if none currently saved.
    .PARAMETER ExcludeCidr
    List of IP addresses or subnets being excluded from protection (required if 'useDefault' is false)
    .PARAMETER IncludeCidr
    list of IP addresses or subnets being protected (required if 'useDefault' is false)
    #>

}

function Get-MerakiApplianceSecurityMalwareSettings() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id
    )

    Begin {
        $Headers = Get-Headers
    }

    Process {
        $Uri = "{0}/networks/{1}/appliance/security/malware" -f $BaseURI, $Id

        try {
            $response = Invoke-RestMethod -Method Get -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect
            $number = 1
            $response.AllowedFiles | ForEach-Object {
                $_ | Add-Member -MemberType NoteProperty -Name 'Number' -Value $number
                $number += 1
            }
            $number = 1
            $response.AllowedUrls | ForEach-Object {
                $_ | Add-Member -MemberType NoteProperty -Name 'Number' -Value $number
                $number += 1
            }
            return $response
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Returns all supported malware settings for an MX network.
    .PARAMETER Id
    The ID of the network.
    .OUTPUTS
    A malware settings object
    #>

}

function Set-MerakiApplianceSecurityMalwareSettings() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [Parameter(Mandatory)]
        [bool]$Enabled,
        [PsObject[]]$AllowedFiles,
        [PsObject[]]$AllowedUrls
    )

    Begin {
        $Headers = Get-Headers

        if ($Enabled.IsPresent) {
            $_Body = @{
                mode = 'enabled'
            }

            if ($AllowedFiles) {
                $_Body.Add("allowedFiles", $AllowedFiles)
            }

            if ($AllowedUrls) {
                $_Body.Add("allowedUrls", $AllowedUrls)
            }
        } else {
            $_Body = @{
                mode = 'disabled'
                allowedFiles = @()
                allowedUrls = @()
            }
        }

        $body = $_Body | ConvertTo-Json -Depth 4 -Compress
    }

    Process {
        $Uri = "{0}/networks/{1}/appliance/security/malware" -f $BaseURI, $id

        try {
            $response = Invoke-RestMethod -Method Get -Uri $Uri -Headers $Headers -Body $body  -PreserveAuthorizationOnRedirect
            $number = 1
            $response.AllowedFiles | ForEach-Object {
                $_ | Add-Member -MemberType NoteProperty -Name 'Number' -Value $number
                $number += 1
            }
            $number = 1
            $response.AllowedUrls | ForEach-Object {
                $_ | Add-Member -MemberType NoteProperty -Name 'Number' -Value $number
                $number += 1
            }         
            return $response
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Set the supported malware settings for an MX network
    .PARAMETER Id
    The Id of the network.
    .PARAMETER Enabled
    Set the malware prevention to enabled or disabled. Setting this to false will clear the AllowedFiles and AllowedUrl settings.
    .PARAMETER AllowedFiles
    An array of allowed file Objects. Properties are:
        comment*: string Comment about the allowed entity
        sha256*: string The file sha256 hash to allow
    .PARAMETER AllowedUrls
    An array of allowed Url objects Properties are:
        comment*: string Comment about the allowed entity
        url*: string The url to allow
    #>

}

#endregion

#region Single Lan
function Get-MerakiApplianceSingleLan() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$id
    )

    Begin {
        $Headers = Get-Headers
    }

    Process {
        $Uri = "{0}/networks/{1}/appliance/singleLan" -f $BaseURI, $Id

        try {
            $response = Invoke-RestMethod -Method Get -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect
            return $response
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Return single LAN configuration.
    .PARAMETER id
    The id of the Network.
    .OUTPUTS
    A single LAN configuration object.
    #>

}

function Set-MerakiApplianceSingleLan() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param (
        [Parameter(Mandatory)]
        [string]$NetworkId,
        [string]$ApplianceIp,
        [string]$subnet,
        [Parameter(ParameterSetName = 'ipv6')]
        [switch]$Ipv6Enabled,
        [Parameter(ParameterSetName = 'ipv6')]
        [PSObject[]]$Ipv6PrefixAssignments,
        [switch]$MandatoryDHCP
    )

    $_Body = @{}

    if ($ApplianceIp) {
        $_Body.Add("applianceIp", $ApplianceIp)
    }
    if ($subnet) {
        $_.Body.Add("subnet", $subnet)
    }
    if ($Ipv6Enabled.IsPresent) {
        $_Body['ipv6'].Enabled = $true
    }
    if ($Ipv6PrefixAssignments) {
        $_Body['ipv6'].prefixAssignments = $Ipv6PrefixAssignments
    }
    if ($MandatoryDHCP.IsPresent) {
        $Body['mandatoryDhcp'].Enabled = $true
    }

    $body = $_Body | ConvertTo-Json
    
    $Uri = "{0}/networks/{networkId}/appliance/singleLan" -f $BaseURI, $Id
    
    try {
        $response = Invoke-RestMethod -Method Get -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect
        return $response
    } catch {
        throw $_
    }
    <#
    .DESCRIPTION
    Return single LAN configuration
    .PARAMETER NetworkId
    The Id of the Network.
    .PARAMETER ApplianceIp
    The local IP of the appliance on the single LAN
    .PARAMETER subnet
    The subnet of the single LAN
    .PARAMETER Ipv6Enabled
    Enable IPv6 on single LAN
    .PARAMETER Ipv6PrefixAssignments
    An array of Ipv6 prefix assignments on the single LAN.
    .PARAMETER MandatoryDHCP
    Mandatory DHCP will enforce that clients connecting to this single LAN must use the IP address assigned by the DHCP server. Clients who use a static IP address won't be able to associate.
    #>

}
#endregion

#region SSIDs
function Get-MerakiApplianceSSID() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [int]$SsidNumber
    )

    Begin {
        $Headers = Get-Headers
    }

    Process {
        $Uri = "{0}/networks/{1}/appliance/ssids" -f $BaseURI, $Id
        if ($SsidNumber) {
            $Uri = "{0}/{1}" -f $Uri, $SsidNumber
        }

        try {
            $response = Invoke-RestMethod -Method Get -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect
            return $response
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Return the SSID(s) in this network
    .PARAMETER Id
    The IDof the network
    .PARAMETER SsidNumber
    The ssid number to return a single ssid.
    .OUTPUTS
    A single or an array of SSID objects
    #>

}

Set-Alias -Name Get-MerakiApplianceSSIDs -Value Get-MerakiApplianceSSID -Option ReadOnly

function Set-MerakiApplianceSSID() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('SSIDNumber')]
        [int]$Number,
        [string]$Name,
        [bool]$Enabled,
        [int]$DefaultVlanId,
        [ValidateSet('8021x-meraki', '8021x-radius', 'open', 'psk')]
        [string]$AuthMode,
        [ValidateSet('wep', 'wpa')]
        [ValidateScript({
            $_ -and $AuthMode -eq 'psk'
        })]
        [string]$EncryptionMode,
        [ValidateScript({
            $_ -and $AuthMode -eq 'psk'
        }, ErrorMessage = "Parameter Passkey is only valid when AuthMode = 'psk'")]
        [string]$Passkey,
        [ValidateSet('WPA1 and WPA2', 'WPA2 only', 'WPA3 Transition Mode', 'WPA3 only')]
        [ValidateScript({
            ($_ -and ($AuthMode -eq 'psk' -and $EncryptionMode -eq 'wpa')) -or
            ($_ -and $AuthMode -in '8021x-meraki','8021x-radius')
        }, ErrorMessage = "The parameter WpaEncryptionMOde is only valid if (1) the AuthMode is 'psk' & the EncryptionMode is 'wpa' OR (2) the AuthMode is '8021x-meraki' OR (3) the authMode is '8021x-radius'")]
        [string]$WpaEncryptionMode,
        [switch]$Hide,
        [switch]$DhcpEnforcedDeAuthentication,
        [switch]$Dot11wEnabled,
        [switch]$Dot11wRequired,
        [ValidateScript({
            $_ -and $AuthMode -eq '8021x-radius'
        })]
        [PsObject]$RadiusServers
    )

    Begin {
        $Headers = Get-Headers

        $_Body = @{}
        if ($Name) {
            $_Body.Add("Name", $Name)
        }
        if ($Enabled) {
            $_Body.Add("enabled", $Enabled)
        }
        if ($DefaultVlanId) {
            $_Body.Add("defaultVlanId",$DefaultVlanId)
        }
        if ($AuthMode) {
            $_Body.Add("authMode", $AuthMode)
        }
        if ($Passkey) {
            $_Body.Add("Psk", $Passkey)
        }
        if ($EncryptionMode) {
            $_Body.Add("encryptionMode", $EncryptionMode)
        }
        if ($Hide.IsPresent) {
            $_Body.Add("visible", $False)
        } else {
            $_Body.Add("visible", $true)
        }
        if ($DhcpEnforcedDeAuthentication) {
            $_Body.Add("dhcpEnforceDeauthentication",@{
                enabled = $true
            })
        }
        if ($Dot11wEnabled) {
            $_Body["dot11w"].enabled = $true
        }
        if ($Dot11wRequired) {
            $_Body["dot11w"].required = $true
        }
        if ($RadiusServers) {
            $_Body.Add("radiusServers", $RadiusServers)
        }

        $body = $_Body | ConvertTo-Json -Depth 6 -Compress
    }

    Process {
        $Uri = "{0}/networks/{1}/appliance/ssids/{2}" -f $BaseURI, $Id, $Number
        try {
            $response = Invoke-RestMethod -Method Put -Uri $Uri -Headers $Headers -Body $body -PreserveAuthorizationOnRedirect
            return $response
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Update the attributes of an MX SSID
    .PARAMETER Id
    The Id of the network.
    .PARAMETER Number
    The number of the ssid.
    .PARAMETER Name
    The name of the ssid.
    .PARAMETER Enabled
    Enable or disable an ssid.
    .PARAMETER DefaultVlanId
    The default VLAN for the ssid.
    .PARAMETER AuthMode
    The association control method for the SSID ('open', 'psk', '8021x-meraki' or '8021x-radius').
    .PARAMETER EncryptionMode
    The psk encryption mode for the SSID ('wep' or 'wpa'). This param is only valid if the authMode is 'psk'.
    .PARAMETER Passkey
    The passkey for the SSID. This param is only valid if the authMode is 'psk'.
    .PARAMETER WpaEncryptionMode
    The types of WPA encryption. ('WPA1 and WPA2', 'WPA2 only', 'WPA3 Transition Mode' or 'WPA3 only'). This param is only valid if (1) the authMode is 'psk' & the encryptionMode is 'wpa' OR (2) the authMode is '8021x-meraki' OR (3) the authMode is '8021x-radius'
    .PARAMETER Hide
    Hide this SSID. Omitting this parameter will make the ssid visible.
    .PARAMETER DhcpEnforcedDeAuthentication
    DHCP Enforced Deauthentication enables the disassociation of wireless clients in addition to Mandatory DHCP. This param is only valid on firmware versions >= MX 17.0 where the associated LAN has Mandatory DHCP Enabled.
    .PARAMETER Dot11wEnabled
    Enable 802.11w
    .PARAMETER Dot11wRequired
    Require 802.11w
    .PARAMETER RadiusServers
    The RADIUS 802.1x servers to be used for authentication. This param is only valid if the authMode is '8021x-radius'.
    .OUTPUTS
    An SSID Object.
    #>

}
#endregion

#region WarmSpare
function Get-MerakiApplianceWarmSpare() {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id
    )

    Begin {
        $Headers = Get-Headers
    }

    Process {
        $Uri = "{0}/networks/{2}/appliance/warmSpare/swap" -f $BaseURI, $Id

        try {
            $response = Invoke-RestMethod -Method Get -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect
            return $response
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Return MX warm spare settings
    .PARAMETER Id
    The Id of the network.
    .OUTPUTS
    An appliance warm spare object
    #>

}

function Set-MerakiApplianceWarmSpare() {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    Param(
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [Alias('NetworkId')]
        [string]$Id,
        [bool]$Enabled,
        [string]$SpareSerial,
        [ValidateSet('virtual','public')]
        [string]$UplinkMode,
        [string]$Wan1Ip,
        [string]$Wan2Ip
    )

    Begin {
        $Headers = Get-Headers

        $_Body = @{}
        if ($Enable) {
            $_Body.Add("enabled", $Enabled)
        }
        if ($SpareSerial) {
            $_Body.Add("spareSerial", $SpareSerial)
        }
        if ($UplinkMode) {
            $_Body.Add("uplinkMOde", $UplinkMode)
        }
        if ($Wan1Ip) {
            $_Body.Add("virtualIp1", $Wan1Ip)
        }
        if ($Wan2Ip) {
            $_Body.Add("virtualIp2", $Wan2Ip)
        }

        $body = $_Body | ConvertTo-Json -Depth 3 -Compress
    }

    Process {
        $Uri = "{0}/networks/{networkId}/appliance/warmSpare" -f $BaseURI, $Id

        try {
            $response = Invoke-RestMethod -Method Put -Uri $Uri -Headers $Headers -Body $body -PreserveAuthorizationOnRedirect
            return $response
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Update MX warm spare settings.
    .PARAMETER Id
    The Id of the network.
    .PARAMETER Enabled
    Enable pr disable the warm spare.
    .PARAMETER SpareSerial
    Serial number of the warm spare appliance
    .PARAMETER UplinkMode
    Uplink mode, either virtual or public
    .PARAMETER Wan1Ip
    The WAN 1 shared IP
    .PARAMETER Wan2Ip
    The WAN 2 shared IP
    .OUTPUTS
    A warm spare object.
    #>

}

function Invoke-SwapMerakiApplianceWarmSpare() {
    [CmdletBinding(
        SupportsShouldProcess,
        ConfirmImpact = 'high'
    )]
    Param (
        [Parameter(Mandatory)]
        [Alias('NetworkId')]
        [string]$Id
    )

    $Headers = Get-Headers

    $Uri = "{0}/networks/{1}/appliance/warmSpare/swap" -f $BaseURI, $Id
    $NetworkName = (Get-MerakiNetwork -Id $Id).Name
    if ($PSCmdlet.ShouldProcess("Network $NetworkName to warm spare", "Swap")) {
        try {
            $response = Invoke-RestMethod -Method Post -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect
            return $response
        } catch {
            throw $_
        }
    }
    <#
    .DESCRIPTION
    Swap MX primary and warm spare appliances
    .PARAMETER Id
    The network Id
    .OUTPUTS
    A warm spare object.
    #>

}

#endregion